Explorar el Código

parse more information from book filenames (#15655)

dkanada hace 1 semana
padre
commit
dd480f96cd

+ 75 - 0
Emby.Naming/Book/BookFileNameParser.cs

@@ -0,0 +1,75 @@
+using System.Text.RegularExpressions;
+
+namespace Emby.Naming.Book
+{
+    /// <summary>
+    /// Helper class to retrieve basic metadata from a book filename.
+    /// </summary>
+    public static class BookFileNameParser
+    {
+        private const string NameMatchGroup = "name";
+        private const string IndexMatchGroup = "index";
+        private const string YearMatchGroup = "year";
+        private const string SeriesNameMatchGroup = "seriesName";
+
+        private static readonly Regex[] _nameMatches =
+        [
+            // seriesName (seriesYear) #index (of count) (year) where only seriesName and index are required
+            new Regex(@"^(?<seriesName>.+?)((\s\((?<seriesYear>[0-9]{4})\))?)\s#(?<index>[0-9]+)((\s\(of\s(?<count>[0-9]+)\))?)((\s\((?<year>[0-9]{4})\))?)$"),
+            new Regex(@"^(?<name>.+?)\s\((?<seriesName>.+?),\s#(?<index>[0-9]+)\)((\s\((?<year>[0-9]{4})\))?)$"),
+            new Regex(@"^(?<index>[0-9]+)\s\-\s(?<name>.+?)((\s\((?<year>[0-9]{4})\))?)$"),
+            new Regex(@"(?<name>.*)\((?<year>[0-9]{4})\)"),
+            // last resort matches the whole string as the name
+            new Regex(@"(?<name>.*)")
+        ];
+
+        /// <summary>
+        /// Parse a filename name to retrieve the book name, series name, index, and year.
+        /// </summary>
+        /// <param name="name">Book filename to parse for information.</param>
+        /// <returns>Returns <see cref="BookFileNameParserResult"/> object.</returns>
+        public static BookFileNameParserResult Parse(string? name)
+        {
+            var result = new BookFileNameParserResult();
+
+            if (name == null)
+            {
+                return result;
+            }
+
+            foreach (var regex in _nameMatches)
+            {
+                var match = regex.Match(name);
+
+                if (!match.Success)
+                {
+                    continue;
+                }
+
+                if (match.Groups.TryGetValue(NameMatchGroup, out Group? nameGroup) && nameGroup.Success)
+                {
+                    result.Name = nameGroup.Value.Trim();
+                }
+
+                if (match.Groups.TryGetValue(IndexMatchGroup, out Group? indexGroup) && indexGroup.Success && int.TryParse(indexGroup.Value, out var index))
+                {
+                    result.Index = index;
+                }
+
+                if (match.Groups.TryGetValue(YearMatchGroup, out Group? yearGroup) && yearGroup.Success && int.TryParse(yearGroup.Value, out var year))
+                {
+                    result.Year = year;
+                }
+
+                if (match.Groups.TryGetValue(SeriesNameMatchGroup, out Group? seriesGroup) && seriesGroup.Success)
+                {
+                    result.SeriesName = seriesGroup.Value.Trim();
+                }
+
+                break;
+            }
+
+            return result;
+        }
+    }
+}

+ 41 - 0
Emby.Naming/Book/BookFileNameParserResult.cs

@@ -0,0 +1,41 @@
+using System;
+
+namespace Emby.Naming.Book
+{
+    /// <summary>
+    /// Data object used to pass metadata parsed from a book filename.
+    /// </summary>
+    public class BookFileNameParserResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BookFileNameParserResult"/> class.
+        /// </summary>
+        public BookFileNameParserResult()
+        {
+            Name = null;
+            Index = null;
+            Year = null;
+            SeriesName = null;
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the book.
+        /// </summary>
+        public string? Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the book index.
+        /// </summary>
+        public int? Index { get; set; }
+
+        /// <summary>
+        /// Gets or sets the publication year.
+        /// </summary>
+        public int? Year { get; set; }
+
+        /// <summary>
+        /// Gets or sets the series name.
+        /// </summary>
+        public string? SeriesName { get; set; }
+    }
+}

+ 23 - 11
Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs

@@ -5,12 +5,12 @@
 using System;
 using System.IO;
 using System.Linq;
+using Emby.Naming.Book;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Entities;
 
 namespace Emby.Server.Implementations.Library.Resolvers.Books
 {
@@ -35,17 +35,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
 
             var extension = Path.GetExtension(args.Path.AsSpan());
 
-            if (_validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
+            if (!_validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
             {
-                // It's a book
-                return new Book
-                {
-                    Path = args.Path,
-                    IsInMixedFolder = true
-                };
+                return null;
             }
 
-            return null;
+            var result = BookFileNameParser.Parse(Path.GetFileNameWithoutExtension(args.Path));
+
+            return new Book
+            {
+                Path = args.Path,
+                Name = result.Name ?? string.Empty,
+                IndexNumber = result.Index,
+                ProductionYear = result.Year,
+                SeriesName = result.SeriesName ?? Path.GetFileName(Path.GetDirectoryName(args.Path)),
+                IsInMixedFolder = true,
+            };
         }
 
         private Book GetBook(ItemResolveArgs args)
@@ -59,15 +64,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
                     StringComparison.OrdinalIgnoreCase);
             }).ToList();
 
-            // Don't return a Book if there is more (or less) than one document in the directory
+            // directory is only considered a book when it contains exactly one supported file
+            // other library structures with multiple books to a directory will get picked up as individual files
             if (bookFiles.Count != 1)
             {
                 return null;
             }
 
+            var result = BookFileNameParser.Parse(Path.GetFileName(args.Path));
+
             return new Book
             {
-                Path = bookFiles[0].FullName
+                Path = bookFiles[0].FullName,
+                Name = result.Name ?? string.Empty,
+                IndexNumber = result.Index,
+                ProductionYear = result.Year,
+                SeriesName = result.SeriesName ?? string.Empty,
             };
         }
     }