Sfoglia il codice sorgente

Added resolving of alternative files and extras for audibooks.

Stepan 4 anni fa
parent
commit
c060ed1a18

+ 7 - 4
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>

+ 91 - 2
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;
+        }
     }
 }

+ 0 - 1
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;
 

+ 1 - 1
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;

+ 2 - 2
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();

+ 25 - 23
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]