mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
Added resolving of alternative files and extras for audibooks.
This commit is contained in:
parent
7b6363b09a
commit
c060ed1a18
@ -12,13 +12,16 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">Name of audiobook.</param>
|
/// <param name="name">Name of audiobook.</param>
|
||||||
/// <param name="year">Year of audiobook release.</param>
|
/// <param name="year">Year of audiobook release.</param>
|
||||||
public AudioBookInfo(string name, int? year)
|
/// <param name="files">List of files composing the actual audiobook.</param>
|
||||||
|
/// <param name="extras">List of extra files.</param>
|
||||||
|
/// <param name="alternateVersions">Alternative version of files.</param>
|
||||||
|
public AudioBookInfo(string name, int? year, List<AudioBookFileInfo>? files, List<AudioBookFileInfo>? extras, List<AudioBookFileInfo>? alternateVersions)
|
||||||
{
|
{
|
||||||
Files = new List<AudioBookFileInfo>();
|
|
||||||
Extras = new List<AudioBookFileInfo>();
|
|
||||||
AlternateVersions = new List<AudioBookFileInfo>();
|
|
||||||
Name = name;
|
Name = name;
|
||||||
Year = year;
|
Year = year;
|
||||||
|
Files = files ?? new List<AudioBookFileInfo>();
|
||||||
|
Extras = extras ?? new List<AudioBookFileInfo>();
|
||||||
|
AlternateVersions = alternateVersions ?? new List<AudioBookFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -41,12 +41,101 @@ namespace Emby.Naming.AudioBook
|
|||||||
|
|
||||||
stackFiles.Sort();
|
stackFiles.Sort();
|
||||||
|
|
||||||
var result = new AudioBookNameParser(_options).Parse(stack.Name);
|
var nameParserResult = new AudioBookNameParser(_options).Parse(stack.Name);
|
||||||
|
|
||||||
var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles };
|
FindExtraAndAlternativeFiles(ref stackFiles, out var extras, out var alternativeVersions, nameParserResult);
|
||||||
|
|
||||||
|
var info = new AudioBookInfo(
|
||||||
|
nameParserResult.Name,
|
||||||
|
nameParserResult.Year,
|
||||||
|
stackFiles,
|
||||||
|
extras,
|
||||||
|
alternativeVersions);
|
||||||
|
|
||||||
yield return info;
|
yield return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FindExtraAndAlternativeFiles(ref List<AudioBookFileInfo> stackFiles, out List<AudioBookFileInfo> extras, out List<AudioBookFileInfo> alternativeVersions, AudioBookNameParserResult nameParserResult)
|
||||||
|
{
|
||||||
|
extras = new List<AudioBookFileInfo>();
|
||||||
|
alternativeVersions = new List<AudioBookFileInfo>();
|
||||||
|
|
||||||
|
var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null);
|
||||||
|
var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber });
|
||||||
|
|
||||||
|
foreach (var group in groupedBy)
|
||||||
|
{
|
||||||
|
if (group.Key.ChapterNumber == null && group.Key.PartNumber == null)
|
||||||
|
{
|
||||||
|
if (group.Count() > 1 || haveChaptersOrPages)
|
||||||
|
{
|
||||||
|
var ex = new List<AudioBookFileInfo>();
|
||||||
|
var alt = new List<AudioBookFileInfo>();
|
||||||
|
|
||||||
|
foreach (var audioFile in group)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
|
||||||
|
if (name == "audiobook" ||
|
||||||
|
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
name.Contains(nameParserResult.Name.Replace(" ", "."), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
alt.Add(audioFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ex.Add(audioFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex.Count > 0)
|
||||||
|
{
|
||||||
|
var extra = ex
|
||||||
|
.OrderBy(x => x.Container)
|
||||||
|
.ThenBy(x => x.Path)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
stackFiles = stackFiles.Except(extra).ToList();
|
||||||
|
extras.AddRange(extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alt.Count > 0)
|
||||||
|
{
|
||||||
|
var alternatives = alt
|
||||||
|
.OrderBy(x => x.Container)
|
||||||
|
.ThenBy(x => x.Path)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var main = FindMainAudioBookFile(alternatives, nameParserResult.Name);
|
||||||
|
alternatives.Remove(main);
|
||||||
|
stackFiles = stackFiles.Except(alternatives).ToList();
|
||||||
|
alternativeVersions.AddRange(alternatives);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (group.Count() > 1)
|
||||||
|
{
|
||||||
|
var alternatives = group
|
||||||
|
.OrderBy(x => x.Container)
|
||||||
|
.ThenBy(x => x.Path)
|
||||||
|
.Skip(1)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
stackFiles = stackFiles.Except(alternatives).ToList();
|
||||||
|
alternativeVersions.AddRange(alternatives);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioBookFileInfo FindMainAudioBookFile(List<AudioBookFileInfo> files, string name)
|
||||||
|
{
|
||||||
|
var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path) == name);
|
||||||
|
main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path) == "audiobook");
|
||||||
|
main ??= files.OrderBy(x => x.Container)
|
||||||
|
.ThenBy(x => x.Path)
|
||||||
|
.First();
|
||||||
|
|
||||||
|
return main;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
|
|
||||||
public AudioBookFileInfo? Resolve(string path)
|
public AudioBookFileInfo? Resolve(string path)
|
||||||
{
|
{
|
||||||
if (path.Length == 0)
|
if (path.Length == 0 || Path.GetFileNameWithoutExtension(path).Length == 0)
|
||||||
{
|
{
|
||||||
// Return null to indicate this path will not be used, instead of stopping whole process with exception
|
// Return null to indicate this path will not be used, instead of stopping whole process with exception
|
||||||
return null;
|
return null;
|
||||||
|
@ -568,7 +568,7 @@ namespace Emby.Naming.Common
|
|||||||
// Chapter is often beginning of filename
|
// Chapter is often beginning of filename
|
||||||
"^(?<chapter>[0-9]+)",
|
"^(?<chapter>[0-9]+)",
|
||||||
// Part if often ending of filename
|
// Part if often ending of filename
|
||||||
"(?<part>[0-9]+)$",
|
@"(?<!ch(?:apter) )(?<part>[0-9]+)$",
|
||||||
// Sometimes named as 0001_005 (chapter_part)
|
// Sometimes named as 0001_005 (chapter_part)
|
||||||
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
||||||
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
||||||
@ -579,7 +579,7 @@ namespace Emby.Naming.Common
|
|||||||
{
|
{
|
||||||
// Detect year usually in brackets after name Batman (2020)
|
// Detect year usually in brackets after name Batman (2020)
|
||||||
@"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
|
@"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
|
||||||
@"^\s*(?<name>.+?)\s*$"
|
@"^\s*(?<name>[^ ].*?)\s*$"
|
||||||
};
|
};
|
||||||
|
|
||||||
var extensions = VideoFileExtensions.ToList();
|
var extensions = VideoFileExtensions.ToList();
|
||||||
|
@ -26,14 +26,16 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
"Batman/Chapter 2.mp3",
|
"Batman/Chapter 2.mp3",
|
||||||
"Batman/Chapter 3.mp3",
|
"Batman/Chapter 3.mp3",
|
||||||
|
|
||||||
"Ready Player One (2020)/Ready Player One.mp3",
|
|
||||||
"Ready Player One (2020)/extra.mp3",
|
|
||||||
|
|
||||||
"Badman/audiobook.mp3",
|
"Badman/audiobook.mp3",
|
||||||
"Badman/extra.mp3",
|
"Badman/extra.mp3",
|
||||||
|
|
||||||
"Superman (2020)/book.mp3",
|
"Superman (2020)/Part 1.mp3",
|
||||||
"Superman (2020)/extra.mp3"
|
"Superman (2020)/extra.mp3",
|
||||||
|
|
||||||
|
"Ready Player One (2020)/audiobook.mp3",
|
||||||
|
"Ready Player One (2020)/extra.mp3",
|
||||||
|
|
||||||
|
".mp3"
|
||||||
};
|
};
|
||||||
|
|
||||||
var resolver = GetResolver();
|
var resolver = GetResolver();
|
||||||
@ -44,7 +46,9 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
FullName = i
|
FullName = i
|
||||||
})).ToList();
|
})).ToList();
|
||||||
|
|
||||||
Assert.Equal(4, result[0].Files.Count);
|
Assert.Equal(5, result.Count);
|
||||||
|
|
||||||
|
Assert.Equal(2, result[0].Files.Count);
|
||||||
Assert.Single(result[0].Extras);
|
Assert.Single(result[0].Extras);
|
||||||
Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name);
|
Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name);
|
||||||
|
|
||||||
@ -52,13 +56,17 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
Assert.Empty(result[1].Extras);
|
Assert.Empty(result[1].Extras);
|
||||||
Assert.Equal("Batman", result[1].Name);
|
Assert.Equal("Batman", result[1].Name);
|
||||||
|
|
||||||
Assert.Equal(2, result[2].Files.Count);
|
Assert.Single(result[2].Files);
|
||||||
Assert.Single(result[2].Extras);
|
Assert.Single(result[2].Extras);
|
||||||
Assert.Equal("Badman", result[2].Name);
|
Assert.Equal("Badman", result[2].Name);
|
||||||
|
|
||||||
Assert.Equal(2, result[3].Files.Count);
|
Assert.Single(result[3].Files);
|
||||||
Assert.Single(result[3].Extras);
|
Assert.Single(result[3].Extras);
|
||||||
Assert.Equal("Superman", result[3].Name);
|
Assert.Equal("Superman", result[3].Name);
|
||||||
|
|
||||||
|
Assert.Single(result[4].Files);
|
||||||
|
Assert.Single(result[4].Extras);
|
||||||
|
Assert.Equal("Ready Player One", result[4].Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -69,12 +77,9 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
||||||
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
||||||
|
|
||||||
"Aqua-man/book.mp3",
|
|
||||||
|
|
||||||
"Deadpool.mp3",
|
"Deadpool.mp3",
|
||||||
"Deadpool [HQ].mp3",
|
"Deadpool [HQ].mp3",
|
||||||
|
|
||||||
"Superman/book.mp3",
|
|
||||||
"Superman/audiobook.mp3",
|
"Superman/audiobook.mp3",
|
||||||
"Superman/Superman.mp3",
|
"Superman/Superman.mp3",
|
||||||
"Superman/Superman [HQ].mp3",
|
"Superman/Superman [HQ].mp3",
|
||||||
@ -92,27 +97,24 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
FullName = i
|
FullName = i
|
||||||
})).ToList();
|
})).ToList();
|
||||||
|
|
||||||
Assert.Equal(6, result[0].Files.Count);
|
Assert.Equal(5, result.Count);
|
||||||
// HP - Same name so we don't care which file is alternative
|
// HP - Same name so we don't care which file is alternative
|
||||||
Assert.Single(result[0].AlternateVersions);
|
Assert.Single(result[0].AlternateVersions);
|
||||||
// Aqua-man
|
|
||||||
Assert.Empty(result[1].AlternateVersions);
|
|
||||||
// DP
|
// DP
|
||||||
Assert.Empty(result[2].AlternateVersions);
|
Assert.Empty(result[1].AlternateVersions);
|
||||||
// DP HQ (directory missing so we do not group deadpools together)
|
// DP HQ (directory missing so we do not group deadpools together)
|
||||||
Assert.Empty(result[3].AlternateVersions);
|
Assert.Empty(result[2].AlternateVersions);
|
||||||
// Superman
|
// Superman
|
||||||
// Priority:
|
// Priority:
|
||||||
// 1. Name
|
// 1. Name
|
||||||
// 2. audiobook
|
// 2. audiobook
|
||||||
// 3. book
|
// 3. Names with modifiers
|
||||||
// 4. Names with modifiers
|
Assert.Equal(2, result[3].AlternateVersions.Count);
|
||||||
Assert.Equal(3, result[4].AlternateVersions.Count);
|
var paths = result[3].AlternateVersions.Select(x => x.Path).ToList();
|
||||||
Assert.Equal("Superman/audiobook.mp3", result[4].AlternateVersions[0].Path);
|
Assert.Contains("Superman/audiobook.mp3", paths);
|
||||||
Assert.Equal("Superman/book.mp3", result[4].AlternateVersions[1].Path);
|
Assert.Contains("Superman/Superman [HQ].mp3", paths);
|
||||||
Assert.Equal("Superman/Superman [HQ].mp3", result[4].AlternateVersions[2].Path);
|
|
||||||
// Batman
|
// Batman
|
||||||
Assert.Single(result[5].AlternateVersions);
|
Assert.Single(result[4].AlternateVersions);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
Loading…
Reference in New Issue
Block a user