aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Naming/AudioBook/AudioBookInfo.cs11
-rw-r--r--Emby.Naming/AudioBook/AudioBookListResolver.cs93
-rw-r--r--Emby.Naming/AudioBook/AudioBookNameParser.cs1
-rw-r--r--Emby.Naming/AudioBook/AudioBookResolver.cs2
-rw-r--r--Emby.Naming/Common/NamingOptions.cs4
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs48
6 files changed, 126 insertions, 33 deletions
diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index 353a0f4a0..adf403ab6 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -12,13 +12,16 @@ namespace Emby.Naming.AudioBook
/// </summary>
/// <param name="name">Name of audiobook.</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;
Year = year;
+ Files = files ?? new List<AudioBookFileInfo>();
+ Extras = extras ?? new List<AudioBookFileInfo>();
+ AlternateVersions = alternateVersions ?? new List<AudioBookFileInfo>();
}
/// <summary>
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index 86ba2eeea..e8908aa37 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -41,12 +41,101 @@ namespace Emby.Naming.AudioBook
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;
}
}
+
+ 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;
+ }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs
index c48db93b3..7c8616124 100644
--- a/Emby.Naming/AudioBook/AudioBookNameParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs
@@ -2,7 +2,6 @@
#pragma warning disable CS1591
using System.Globalization;
-using System.IO;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs
index 542d6fee5..c7b3b2d2d 100644
--- a/Emby.Naming/AudioBook/AudioBookResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookResolver.cs
@@ -19,7 +19,7 @@ namespace Emby.Naming.AudioBook
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;
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 5bf232451..d2f07817a 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -568,7 +568,7 @@ namespace Emby.Naming.Common
// Chapter is often beginning of filename
"^(?<chapter>[0-9]+)",
// Part if often ending of filename
- "(?<part>[0-9]+)$",
+ @"(?<!ch(?:apter) )(?<part>[0-9]+)$",
// Sometimes named as 0001_005 (chapter_part)
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
// 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)
@"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
- @"^\s*(?<name>.+?)\s*$"
+ @"^\s*(?<name>[^ ].*?)\s*$"
};
var extensions = VideoFileExtensions.ToList();
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
index a24699962..e5768b620 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
@@ -26,14 +26,16 @@ namespace Jellyfin.Naming.Tests.AudioBook
"Batman/Chapter 2.mp3",
"Batman/Chapter 3.mp3",
- "Ready Player One (2020)/Ready Player One.mp3",
- "Ready Player One (2020)/extra.mp3",
-
"Badman/audiobook.mp3",
"Badman/extra.mp3",
- "Superman (2020)/book.mp3",
- "Superman (2020)/extra.mp3"
+ "Superman (2020)/Part 1.mp3",
+ "Superman (2020)/extra.mp3",
+
+ "Ready Player One (2020)/audiobook.mp3",
+ "Ready Player One (2020)/extra.mp3",
+
+ ".mp3"
};
var resolver = GetResolver();
@@ -44,7 +46,9 @@ namespace Jellyfin.Naming.Tests.AudioBook
FullName = i
})).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.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.Equal("Batman", result[1].Name);
- Assert.Equal(2, result[2].Files.Count);
+ Assert.Single(result[2].Files);
Assert.Single(result[2].Extras);
Assert.Equal("Badman", result[2].Name);
- Assert.Equal(2, result[3].Files.Count);
+ Assert.Single(result[3].Files);
Assert.Single(result[3].Extras);
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]
@@ -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.mp3",
- "Aqua-man/book.mp3",
-
"Deadpool.mp3",
"Deadpool [HQ].mp3",
- "Superman/book.mp3",
"Superman/audiobook.mp3",
"Superman/Superman.mp3",
"Superman/Superman [HQ].mp3",
@@ -92,27 +97,24 @@ namespace Jellyfin.Naming.Tests.AudioBook
FullName = i
})).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
Assert.Single(result[0].AlternateVersions);
- // Aqua-man
- Assert.Empty(result[1].AlternateVersions);
// DP
- Assert.Empty(result[2].AlternateVersions);
+ Assert.Empty(result[1].AlternateVersions);
// DP HQ (directory missing so we do not group deadpools together)
- Assert.Empty(result[3].AlternateVersions);
+ Assert.Empty(result[2].AlternateVersions);
// Superman
// Priority:
// 1. Name
// 2. audiobook
- // 3. book
- // 4. Names with modifiers
- Assert.Equal(3, result[4].AlternateVersions.Count);
- Assert.Equal("Superman/audiobook.mp3", result[4].AlternateVersions[0].Path);
- Assert.Equal("Superman/book.mp3", result[4].AlternateVersions[1].Path);
- Assert.Equal("Superman/Superman [HQ].mp3", result[4].AlternateVersions[2].Path);
+ // 3. Names with modifiers
+ Assert.Equal(2, result[3].AlternateVersions.Count);
+ var paths = result[3].AlternateVersions.Select(x => x.Path).ToList();
+ Assert.Contains("Superman/audiobook.mp3", paths);
+ Assert.Contains("Superman/Superman [HQ].mp3", paths);
// Batman
- Assert.Single(result[5].AlternateVersions);
+ Assert.Single(result[4].AlternateVersions);
}
[Fact]