소스 검색

Simplify StackResolver

cvium 3 년 전
부모
커밋
220443eca1

+ 6 - 12
Emby.Naming/Common/NamingOptions.cs

@@ -124,11 +124,11 @@ namespace Emby.Naming.Common
                     token: "DSR")
             };
 
-            VideoFileStackingExpressions = new[]
+            VideoFileStackingRules = new[]
             {
-                "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
-                "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
-                "^(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
+                new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
+                new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
+                new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
             };
 
             CleanDateTimes = new[]
@@ -765,9 +765,9 @@ namespace Emby.Naming.Common
         public Format3DRule[] Format3DRules { get; set; }
 
         /// <summary>
-        /// Gets or sets list of raw video file-stacking expressions strings.
+        /// Gets the file stacking rules.
         /// </summary>
-        public string[] VideoFileStackingExpressions { get; set; }
+        public FileStackRule[] VideoFileStackingRules { get; }
 
         /// <summary>
         /// Gets or sets list of raw clean DateTimes regular expressions strings.
@@ -789,11 +789,6 @@ namespace Emby.Naming.Common
         /// </summary>
         public ExtraRule[] VideoExtraRules { get; set; }
 
-        /// <summary>
-        /// Gets list of video file-stack regular expressions.
-        /// </summary>
-        public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
-
         /// <summary>
         /// Gets list of clean datetime regular expressions.
         /// </summary>
@@ -819,7 +814,6 @@ namespace Emby.Naming.Common
         /// </summary>
         public void Compile()
         {
-            VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
             CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
             CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
             EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();

+ 14 - 14
Emby.Naming/Video/FileStack.cs

@@ -12,25 +12,30 @@ namespace Emby.Naming.Video
         /// <summary>
         /// Initializes a new instance of the <see cref="FileStack"/> class.
         /// </summary>
-        public FileStack()
+        /// <param name="name">The stack name.</param>
+        /// <param name="isDirectory">Whether the stack files are directories.</param>
+        /// <param name="files">The stack files.</param>
+        public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
         {
-            Files = new List<string>();
+            Name = name;
+            IsDirectoryStack = isDirectory;
+            Files = files;
         }
 
         /// <summary>
-        /// Gets or sets name of file stack.
+        /// Gets the name of file stack.
         /// </summary>
-        public string Name { get; set; } = string.Empty;
+        public string Name { get; }
 
         /// <summary>
-        /// Gets or sets list of paths in stack.
+        /// Gets the list of paths in stack.
         /// </summary>
-        public List<string> Files { get; set; }
+        public IReadOnlyList<string> Files { get; }
 
         /// <summary>
-        /// Gets or sets a value indicating whether stack is directory stack.
+        /// Gets a value indicating whether stack is directory stack.
         /// </summary>
-        public bool IsDirectoryStack { get; set; }
+        public bool IsDirectoryStack { get; }
 
         /// <summary>
         /// Helper function to determine if path is in the stack.
@@ -45,12 +50,7 @@ namespace Emby.Naming.Video
                 return false;
             }
 
-            if (IsDirectoryStack == isDirectory)
-            {
-                return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
-            }
-
-            return false;
+            return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparer.OrdinalIgnoreCase);
         }
     }
 }

+ 48 - 0
Emby.Naming/Video/FileStackRule.cs

@@ -0,0 +1,48 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.RegularExpressions;
+
+namespace Emby.Naming.Video;
+
+/// <summary>
+/// Regex based rule for file stacking (eg. disc1, disc2).
+/// </summary>
+public class FileStackRule
+{
+    private readonly Regex _tokenRegex;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="FileStackRule"/> class.
+    /// </summary>
+    /// <param name="token">Token.</param>
+    /// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
+    public FileStackRule(string token, bool isNumerical)
+    {
+        _tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
+        IsNumerical = isNumerical;
+    }
+
+    /// <summary>
+    /// Gets a value indicating whether the rule uses numerical or alphabetical numbering.
+    /// </summary>
+    public bool IsNumerical { get; }
+
+    /// <summary>
+    /// Match the input against the rule regex.
+    /// </summary>
+    /// <param name="input">The input.</param>
+    /// <param name="result">The part type and number or <c>null</c>.</param>
+    /// <returns>A value indicating whether the input matched the rule.</returns>
+    public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result)
+    {
+        result = null;
+        var match = _tokenRegex.Match(input);
+        if (!match.Success)
+        {
+            return false;
+        }
+
+        var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "vol";
+        result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value);
+        return true;
+    }
+}

+ 56 - 142
Emby.Naming/Video/StackResolver.cs

@@ -2,7 +2,6 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using System.Text.RegularExpressions;
 using Emby.Naming.AudioBook;
 using Emby.Naming.Common;
 using MediaBrowser.Model.IO;
@@ -51,19 +50,13 @@ namespace Emby.Naming.Video
                 {
                     foreach (var file in directory)
                     {
-                        var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
-                        stack.Files.Add(file.Path);
+                        var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { 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);
-                    }
-
+                    var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
                     yield return stack;
                 }
             }
@@ -77,166 +70,87 @@ namespace Emby.Naming.Video
         /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
         public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
         {
-            var list = files
+            var potentialFiles = files
                 .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
-                .OrderBy(i => i.FullName)
-                .Select(f => (f.IsDirectory, FileName: GetFileNameWithExtension(f), f.FullName))
-                .ToList();
+                .OrderBy(i => i.FullName);
 
-            // TODO is there a "nicer" way?
-            var cache = new Dictionary<(string, Regex, int), Match>();
-
-            var expressions = namingOptions.VideoFileStackingRegexes;
-
-            for (var i = 0; i < list.Count; i++)
+            var potentialStacks = new Dictionary<string, StackMetadata>();
+            foreach (var file in potentialFiles)
             {
-                var offset = 0;
-
-                var file1 = list[i];
-
-                var expressionIndex = 0;
-                while (expressionIndex < expressions.Length)
+                for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
                 {
-                    var exp = expressions[expressionIndex];
-                    FileStack? stack = null;
+                    var name = file.Name;
+                    if (string.IsNullOrEmpty(name))
+                    {
+                        name = Path.GetFileName(file.FullName);
+                    }
+
+                    var rule = namingOptions.VideoFileStackingRules[i];
+                    if (!rule.Match(name, out var stackParsingResult))
+                    {
+                        continue;
+                    }
 
-                    // (Title)(Volume)(Ignore)(Extension)
-                    var match1 = FindMatch(file1.FileName, exp, offset, cache);
+                    var stackName = stackParsingResult.Value.StackName;
+                    var partNumber = stackParsingResult.Value.PartNumber;
+                    var partType = stackParsingResult.Value.PartType;
 
-                    if (match1.Success)
+                    if (!potentialStacks.TryGetValue(stackName, out var stackResult))
                     {
-                        var title1 = match1.Groups[1].Value;
-                        var volume1 = match1.Groups[2].Value;
-                        var ignore1 = match1.Groups[3].Value;
-                        var extension1 = match1.Groups[4].Value;
+                        stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
+                        potentialStacks[stackName] = stackResult;
+                    }
 
-                        var j = i + 1;
-                        while (j < list.Count)
+                    if (stackResult.Parts.Count > 0)
+                    {
+                        if (stackResult.IsDirectory != file.IsDirectory
+                            || !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
+                            || stackResult.ContainsPart(partNumber))
                         {
-                            var file2 = list[j];
-
-                            if (file1.IsDirectory != file2.IsDirectory)
-                            {
-                                j++;
-                                continue;
-                            }
-
-                            // (Title)(Volume)(Ignore)(Extension)
-                            var match2 = FindMatch(file2.FileName, exp, offset, cache);
-
-                            if (match2.Success)
-                            {
-                                var title2 = match2.Groups[1].Value;
-                                var volume2 = match2.Groups[2].Value;
-                                var ignore2 = match2.Groups[3].Value;
-                                var extension2 = match2.Groups[4].Value;
-
-                                if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
-                                {
-                                    if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
-                                    {
-                                        if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
-                                            && string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
-                                        {
-                                            stack ??= new FileStack();
-                                            if (stack.Files.Count == 0)
-                                            {
-                                                stack.Name = title1 + ignore1;
-                                                stack.IsDirectoryStack = file1.IsDirectory;
-                                                stack.Files.Add(file1.FullName);
-                                            }
-
-                                            stack.Files.Add(file2.FullName);
-                                        }
-                                        else
-                                        {
-                                            // Sequel
-                                            offset = 0;
-                                            expressionIndex++;
-                                            break;
-                                        }
-                                    }
-                                    else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
-                                    {
-                                        // False positive, try again with offset
-                                        offset = match1.Groups[3].Index;
-                                        break;
-                                    }
-                                    else
-                                    {
-                                        // Extension mismatch
-                                        offset = 0;
-                                        expressionIndex++;
-                                        break;
-                                    }
-                                }
-                                else
-                                {
-                                    // Title mismatch
-                                    offset = 0;
-                                    expressionIndex++;
-                                    break;
-                                }
-                            }
-                            else
-                            {
-                                // No match 2, next expression
-                                offset = 0;
-                                expressionIndex++;
-                                break;
-                            }
-
-                            j++;
+                            continue;
                         }
 
-                        if (j == list.Count)
+                        if (rule.IsNumerical != stackResult.IsNumerical)
                         {
-                            expressionIndex = expressions.Length;
+                            break;
                         }
                     }
-                    else
-                    {
-                        // No match 1
-                        offset = 0;
-                        expressionIndex++;
-                    }
 
-                    if (stack?.Files.Count > 1)
-                    {
-                        yield return stack;
-                        i += stack.Files.Count - 1;
-                        break;
-                    }
+                    stackResult.Parts.Add(partNumber, file);
+                    break;
                 }
             }
-        }
 
-        private static string GetFileNameWithExtension(FileSystemMetadata file)
-        {
-            // For directories, dummy up an extension otherwise the expressions will fail
-            var input = file.FullName;
-            if (file.IsDirectory)
+            foreach (var (fileName, stack) in potentialStacks)
             {
-                input = Path.ChangeExtension(input, "mkv");
-            }
+                if (stack.Parts.Count < 2)
+                {
+                    continue;
+                }
 
-            return Path.GetFileName(input);
+                yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
+            }
         }
 
-        private static Match FindMatch(string input, Regex regex, int offset, Dictionary<(string, Regex, int), Match> cache)
+        private class StackMetadata
         {
-            if (offset < 0 || offset >= input.Length)
+            public StackMetadata(bool isDirectory, bool isNumerical, string partType)
             {
-                return Match.Empty;
+                Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+                IsDirectory = isDirectory;
+                IsNumerical = isNumerical;
+                PartType = partType;
             }
 
-            if (!cache.TryGetValue((input, regex, offset), out var result))
-            {
-                result = regex.Match(input, offset, input.Length - offset);
-                cache.Add((input, regex, offset), result);
-            }
+            public Dictionary<string, FileSystemMetadata> Parts { get; }
+
+            public bool IsDirectory { get; }
+
+            public bool IsNumerical { get; }
+
+            public string PartType { get; }
 
-            return result;
+            public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
         }
     }
 }

+ 0 - 1
tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs

@@ -10,7 +10,6 @@ namespace Jellyfin.Naming.Tests.Common
         {
             var options = new NamingOptions();
 
-            Assert.NotEmpty(options.VideoFileStackingRegexes);
             Assert.NotEmpty(options.CleanDateTimeRegexes);
             Assert.NotEmpty(options.CleanStringRegexes);
             Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes);

+ 3 - 4
tests/Jellyfin.Naming.Tests/Video/StackTests.cs

@@ -128,7 +128,7 @@ namespace Jellyfin.Naming.Tests.Video
         }
 
         [Fact]
-        public void TestDirtyNames()
+        public void ResolveFiles_GivenPartInMiddleOfName_ReturnsNoStack()
         {
             var files = new[]
             {
@@ -141,12 +141,11 @@ namespace Jellyfin.Naming.Tests.Video
 
             var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
 
-            Assert.Single(result);
-            TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
+            Assert.Empty(result);
         }
 
         [Fact]
-        public void TestNumberedFiles()
+        public void ResolveFiles_FileNamesWithMissingPartType_ReturnsNoStack()
         {
             var files = new[]
             {

+ 1 - 1
tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs

@@ -489,7 +489,7 @@ namespace Jellyfin.Naming.Tests.Video
         [Fact]
         public void TestDirectoryStack()
         {
-            var stack = new FileStack();
+            var stack = new FileStack(string.Empty, false, Array.Empty<string>());
             Assert.False(stack.ContainsFile("XX", true));
         }
     }