diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index 353a0f4a01..adf403ab6d 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -12,13 +12,16 @@ namespace Emby.Naming.AudioBook
///
/// Name of audiobook.
/// Year of audiobook release.
- public AudioBookInfo(string name, int? year)
+ /// List of files composing the actual audiobook.
+ /// List of extra files.
+ /// Alternative version of files.
+ public AudioBookInfo(string name, int? year, List? files, List? extras, List? alternateVersions)
{
- Files = new List();
- Extras = new List();
- AlternateVersions = new List();
Name = name;
Year = year;
+ Files = files ?? new List();
+ Extras = extras ?? new List();
+ AlternateVersions = alternateVersions ?? new List();
}
///
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index 86ba2eeeaf..e8908aa37c 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 stackFiles, out List extras, out List alternativeVersions, AudioBookNameParserResult nameParserResult)
+ {
+ extras = new List();
+ alternativeVersions = new List();
+
+ 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();
+ var alt = new List();
+
+ 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 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 c48db93b37..7c86161241 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 542d6fee51..c7b3b2d2d1 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 5bf232451b..d2f07817a6 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
"^(?[0-9]+)",
// Part if often ending of filename
- "(?[0-9]+)$",
+ @"(?[0-9]+)$",
// Sometimes named as 0001_005 (chapter_part)
"(?[0-9]+)_(?[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)
@"^(?.+?)\s*\(\s*(?\d{4})\s*\)\s*$",
- @"^\s*(?.+?)\s*$"
+ @"^\s*(?[^ ].*?)\s*$"
};
var extensions = VideoFileExtensions.ToList();
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
index a246999628..e5768b6209 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]