浏览代码

Add Name and Year parsing for audiobooks

Stepan 4 年之前
父节点
当前提交
1e71775688

+ 3 - 1
Emby.Naming/AudioBook/AudioBookInfo.cs

@@ -11,12 +11,14 @@ namespace Emby.Naming.AudioBook
         /// Initializes a new instance of the <see cref="AudioBookInfo" /> class.
         /// </summary>
         /// <param name="name">Name of audiobook.</param>
-        public AudioBookInfo(string name)
+        /// <param name="year">Year of audiobook release.</param>
+        public AudioBookInfo(string name, int? year)
         {
             Files = new List<AudioBookFileInfo>();
             Extras = new List<AudioBookFileInfo>();
             AlternateVersions = new List<AudioBookFileInfo>();
             Name = name;
+            Year = year;
         }
 
         /// <summary>

+ 3 - 3
Emby.Naming/AudioBook/AudioBookListResolver.cs

@@ -41,9 +41,9 @@ namespace Emby.Naming.AudioBook
 
                 stackFiles.Sort();
 
-                // stack.Name can be empty when we have file without folder, but always have some files
-                var name = string.IsNullOrEmpty(stack.Name) ? stack.Files[0] : stack.Name;
-                var info = new AudioBookInfo(name) { Files = stackFiles };
+                var result = new AudioBookNameParser(_options).Parse(stack.Name);
+
+                var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles };
 
                 yield return info;
             }

+ 59 - 0
Emby.Naming/AudioBook/AudioBookNameParser.cs

@@ -0,0 +1,59 @@
+#nullable enable
+#pragma warning disable CS1591
+
+using System.Globalization;
+using System.IO;
+using System.Text.RegularExpressions;
+using Emby.Naming.Common;
+
+namespace Emby.Naming.AudioBook
+{
+    public class AudioBookNameParser
+    {
+        private readonly NamingOptions _options;
+
+        public AudioBookNameParser(NamingOptions options)
+        {
+            _options = options;
+        }
+
+        public AudioBookNameParserResult Parse(string name)
+        {
+            AudioBookNameParserResult result = default;
+            foreach (var expression in _options.AudioBookNamesExpressions)
+            {
+                var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name);
+                if (match.Success)
+                {
+                    if (result.Name == null)
+                    {
+                        var value = match.Groups["name"];
+                        if (value.Success)
+                        {
+                            result.Name = value.Value;
+                        }
+                    }
+
+                    if (!result.Year.HasValue)
+                    {
+                        var value = match.Groups["year"];
+                        if (value.Success)
+                        {
+                            if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
+                            {
+                                result.Year = intValue;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (string.IsNullOrEmpty(result.Name))
+            {
+                result.Name = name;
+            }
+
+            return result;
+        }
+    }
+}

+ 12 - 0
Emby.Naming/AudioBook/AudioBookNameParserResult.cs

@@ -0,0 +1,12 @@
+#nullable enable
+#pragma warning disable CS1591
+
+namespace Emby.Naming.AudioBook
+{
+    public struct AudioBookNameParserResult
+    {
+        public string Name { get; set; }
+
+        public int? Year { get; set; }
+    }
+}

+ 9 - 0
Emby.Naming/Common/NamingOptions.cs

@@ -575,6 +575,13 @@ namespace Emby.Naming.Common
                 @"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
             };
 
+            AudioBookNamesExpressions = new[]
+            {
+                // Detect year usually in brackets after name Batman (2020)
+                @"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
+                @"^\s*(?<name>.+?)\s*$"
+            };
+
             var extensions = VideoFileExtensions.ToList();
 
             extensions.AddRange(new[]
@@ -658,6 +665,8 @@ namespace Emby.Naming.Common
 
         public string[] AudioBookPartsExpressions { get; set; }
 
+        public string[] AudioBookNamesExpressions { get; set; }
+
         public StubTypeRule[] StubTypes { get; set; }
 
         public char[] VideoFlagDelimiters { get; set; }

+ 16 - 4
Emby.Naming/Video/StackResolver.cs

@@ -36,13 +36,25 @@ namespace Emby.Naming.Video
 
             foreach (var directory in groupedDirectoryFiles)
             {
-                var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
-                foreach (var file in directory)
+                if (string.IsNullOrEmpty(directory.Key))
                 {
-                    stack.Files.Add(file.Path);
+                    foreach (var file in directory)
+                    {
+                        var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
+                        stack.Files.Add(file.Path);
+                        yield return stack;
+                    }
                 }
+                else
+                {
+                    var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
+                    foreach (var file in directory)
+                    {
+                        stack.Files.Add(file.Path);
+                    }
 
-                yield return stack;
+                    yield return stack;
+                }
             }
         }
 

+ 61 - 17
tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using Emby.Naming.AudioBook;
 using Emby.Naming.Common;
@@ -72,33 +73,69 @@ namespace Jellyfin.Naming.Tests.AudioBook
         }
 
         [Fact]
-        public void TestYearExtraction()
+        public void TestNameYearExtraction()
         {
-            var files = new[]
+            var data = new[]
             {
-                "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
-                "Harry Potter and the Deathly Hallows (2007)/Chapter 2.mp3",
-
-                "Batman (2020).ogg",
-
-                "Batman(2021).mp3",
-
-                "Batman.mp3"
+                new NameYearPath
+                {
+                    Name = "Harry Potter and the Deathly Hallows",
+                    Path = "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
+                    Year = 2007
+                },
+                new NameYearPath
+                {
+                    Name = "Batman",
+                    Path = "Batman (2020).ogg",
+                    Year = 2020
+                },
+                new NameYearPath
+                {
+                    Name = "Batman",
+                    Path = "Batman( 2021 ).mp3",
+                    Year = 2021
+                },
+                new NameYearPath
+                {
+                    Name = "Batman(*2021*)",
+                    Path = "Batman(*2021*).mp3",
+                    Year = null
+                },
+                new NameYearPath
+                {
+                    Name = "Batman",
+                    Path = "Batman.mp3",
+                    Year = null
+                },
+                new NameYearPath
+                {
+                    Name = "+ Batman .",
+                    Path = " + Batman . .mp3",
+                    Year = null
+                },
+                new NameYearPath
+                {
+                    Name = " ",
+                    Path = " .mp3",
+                    Year = null
+                }
             };
 
             var resolver = GetResolver();
 
-            var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+            var result = resolver.Resolve(data.Select(i => new FileSystemMetadata
             {
                 IsDirectory = false,
-                FullName = i
+                FullName = i.Path
             })).ToList();
 
-            Assert.Equal(3, result[0].Files.Count);
-            Assert.Equal(2007, result[0].Year);
-            Assert.Equal(2020, result[1].Year);
-            Assert.Equal(2021, result[2].Year);
-            Assert.Null(result[2].Year);
+            Assert.Equal(data.Length, result.Count);
+
+            for (int i = 0; i < data.Length; i++)
+            {
+                Assert.Equal(data[i].Name, result[i].Name);
+                Assert.Equal(data[i].Year, result[i].Year);
+            }
         }
 
         [Fact]
@@ -180,5 +217,12 @@ namespace Jellyfin.Naming.Tests.AudioBook
         {
             return new AudioBookListResolver(_namingOptions);
         }
+
+        internal struct NameYearPath
+        {
+            public string Name;
+            public string Path;
+            public int? Year;
+        }
     }
 }

+ 0 - 1
tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs

@@ -35,7 +35,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
             };
         }
 
-
         [Theory]
         [MemberData(nameof(GetResolveFileTestData))]
         public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)