|
@@ -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);
|
|
|
}
|
|
|
}
|
|
|
}
|