diff options
Diffstat (limited to 'Emby.Naming/Video')
| -rw-r--r-- | Emby.Naming/Video/CleanStringParser.cs | 26 | ||||
| -rw-r--r-- | Emby.Naming/Video/ExtraRuleResolver.cs (renamed from Emby.Naming/Video/ExtraResolver.cs) | 44 | ||||
| -rw-r--r-- | Emby.Naming/Video/FileStack.cs | 29 | ||||
| -rw-r--r-- | Emby.Naming/Video/FileStackRule.cs | 48 | ||||
| -rw-r--r-- | Emby.Naming/Video/Format3DParser.cs | 2 | ||||
| -rw-r--r-- | Emby.Naming/Video/StackResolver.cs | 219 | ||||
| -rw-r--r-- | Emby.Naming/Video/StubResolver.cs | 4 | ||||
| -rw-r--r-- | Emby.Naming/Video/VideoInfo.cs | 13 | ||||
| -rw-r--r-- | Emby.Naming/Video/VideoListResolver.cs | 179 | ||||
| -rw-r--r-- | Emby.Naming/Video/VideoResolver.cs | 13 |
10 files changed, 209 insertions, 368 deletions
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 4eef3ebc5..a336f8fbd 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; @@ -17,38 +16,39 @@ namespace Emby.Naming.Video /// <param name="expressions">List of regex to parse name and year from.</param> /// <param name="newName">Parsing result string.</param> /// <returns>True if parsing was successful.</returns> - public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) + public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out string newName) { if (string.IsNullOrEmpty(name)) { - newName = ReadOnlySpan<char>.Empty; + newName = string.Empty; return false; } - var len = expressions.Count; - for (int i = 0; i < len; i++) + // Iteratively apply the regexps to clean the string. + bool cleaned = false; + for (int i = 0; i < expressions.Count; i++) { if (TryClean(name, expressions[i], out newName)) { - return true; + cleaned = true; + name = newName; } } - newName = ReadOnlySpan<char>.Empty; - return false; + newName = cleaned ? name : string.Empty; + return cleaned; } - private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName) + private static bool TryClean(string name, Regex expression, out string newName) { var match = expression.Match(name); - int index = match.Index; - if (match.Success && index != 0) + if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned)) { - newName = name.AsSpan().Slice(0, match.Index); + newName = cleaned.Value; return true; } - newName = ReadOnlySpan<char>.Empty; + newName = string.Empty; return false; } } diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs index a32af002c..0970e509a 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraRuleResolver.cs @@ -9,44 +9,27 @@ namespace Emby.Naming.Video /// <summary> /// Resolve if file is extra for video. /// </summary> - public class ExtraResolver + public static class ExtraRuleResolver { - private readonly NamingOptions _options; - - /// <summary> - /// Initializes a new instance of the <see cref="ExtraResolver"/> class. - /// </summary> - /// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param> - public ExtraResolver(NamingOptions options) - { - _options = options; - } + private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /// <summary> /// Attempts to resolve if file is extra. /// </summary> /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Returns <see cref="ExtraResult"/> object.</returns> - public ExtraResult GetExtraInfo(string path) + public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions) { var result = new ExtraResult(); - for (var i = 0; i < _options.VideoExtraRules.Length; i++) + for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++) { - var rule = _options.VideoExtraRules[i]; - if (rule.MediaType == MediaType.Audio) - { - if (!AudioFileParser.IsAudioFile(path, _options)) - { - continue; - } - } - else if (rule.MediaType == MediaType.Video) + var rule = namingOptions.VideoExtraRules[i]; + if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions)) + || (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions))) { - if (!VideoResolver.IsVideoFile(path, _options)) - { - continue; - } + continue; } var pathSpan = path.AsSpan(); @@ -62,9 +45,10 @@ namespace Emby.Naming.Video } else if (rule.RuleType == ExtraRuleType.Suffix) { - var filename = Path.GetFileNameWithoutExtension(pathSpan); + // Trim the digits from the end of the filename so we can recognize things like -trailer2 + var filename = Path.GetFileNameWithoutExtension(pathSpan).TrimEnd(_digits); - if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase)) + if (filename.EndsWith(rule.Token, StringComparison.OrdinalIgnoreCase)) { result.ExtraType = rule.ExtraType; result.Rule = rule; @@ -74,9 +58,9 @@ namespace Emby.Naming.Video { var filename = Path.GetFileName(path); - var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); + var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled); - if (regex.IsMatch(filename)) + if (isMatch) { result.ExtraType = rule.ExtraType; result.Rule = rule; diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 6519db57c..4902e6728 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Jellyfin.Extensions; namespace Emby.Naming.Video { @@ -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. @@ -40,12 +45,12 @@ namespace Emby.Naming.Video /// <returns>True if file is in the stack.</returns> public bool ContainsFile(string file, bool isDirectory) { - if (IsDirectoryStack == isDirectory) + if (string.IsNullOrEmpty(file)) { - return Files.Contains(file, StringComparer.OrdinalIgnoreCase); + return false; } - return false; + return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/Video/FileStackRule.cs b/Emby.Naming/Video/FileStackRule.cs new file mode 100644 index 000000000..76b487f42 --- /dev/null +++ b/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 : "unknown"; + result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value); + return true; + } +} diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 089089989..eb5e71d78 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -9,7 +9,7 @@ namespace Emby.Naming.Video public static class Format3DParser { // Static default result to save on allocation costs. - private static readonly Format3DResult _defaultResult = new (false, null); + private static readonly Format3DResult _defaultResult = new(false, null); /// <summary> /// Parse 3D format related flags. diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 36f65a562..8119a0267 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/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; @@ -12,37 +11,28 @@ namespace Emby.Naming.Video /// <summary> /// Resolve <see cref="FileStack"/> from list of paths. /// </summary> - public class StackResolver + public static class StackResolver { - private readonly NamingOptions _options; - - /// <summary> - /// Initializes a new instance of the <see cref="StackResolver"/> class. - /// </summary> - /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param> - public StackResolver(NamingOptions options) - { - _options = options; - } - /// <summary> /// Resolves only directories from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> - public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files) + public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions) { - return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions); } /// <summary> /// Resolves only files from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of files.</returns> - public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files) + public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions) { - return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions); } /// <summary> @@ -50,7 +40,7 @@ namespace Emby.Naming.Video /// </summary> /// <param name="files">List of paths.</param> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> - public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) + public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) { var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); @@ -60,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; } } @@ -82,158 +66,91 @@ namespace Emby.Naming.Video /// Resolves videos from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> - public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files) + public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions) { - var list = files - .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options)) - .OrderBy(i => i.FullName) - .ToList(); - - var expressions = _options.VideoFileStackingRegexes; + var potentialFiles = files + .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions)) + .OrderBy(i => i.FullName); - 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 name = file.Name; + if (string.IsNullOrEmpty(name)) + { + name = Path.GetFileName(file.FullName); + } - var expressionIndex = 0; - while (expressionIndex < expressions.Length) + for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++) { - var exp = expressions[expressionIndex]; - var stack = new FileStack(); + var rule = namingOptions.VideoFileStackingRules[i]; + if (!rule.Match(name, out var stackParsingResult)) + { + continue; + } - // (Title)(Volume)(Ignore)(Extension) - var match1 = FindMatch(file1, exp, offset); + 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["title"].Value; - var volume1 = match1.Groups["volume"].Value; - var ignore1 = match1.Groups["ignore"].Value; - var extension1 = match1.Groups["extension"].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, exp, offset); - - 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)) - { - 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 GetRegexInput(FileSystemMetadata file) - { - // For directories, dummy up an extension otherwise the expressions will fail - var input = !file.IsDirectory - ? file.FullName - : file.FullName + ".mkv"; + foreach (var (fileName, stack) in potentialStacks) + { + 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(FileSystemMetadata input, Regex regex, int offset) + private class StackMetadata { - var regexInput = GetRegexInput(input); - - if (offset < 0 || offset >= regexInput.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; } - return regex.Match(regexInput, offset); + public Dictionary<string, FileSystemMetadata> Parts { get; } + + public bool IsDirectory { get; } + + public bool IsNumerical { get; } + + public string PartType { get; } + + public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber); } } } diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index 079987fe8..f7ba606e3 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Linq; using Emby.Naming.Common; +using Jellyfin.Extensions; namespace Emby.Naming.Video { @@ -28,7 +28,7 @@ namespace Emby.Naming.Video var extension = Path.GetExtension(path); - if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs index 930fdb33f..8847ee9bc 100644 --- a/Emby.Naming/Video/VideoInfo.cs +++ b/Emby.Naming/Video/VideoInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MediaBrowser.Model.Entities; namespace Emby.Naming.Video { @@ -17,7 +18,6 @@ namespace Emby.Naming.Video Name = name; Files = Array.Empty<VideoFileInfo>(); - Extras = Array.Empty<VideoFileInfo>(); AlternateVersions = Array.Empty<VideoFileInfo>(); } @@ -40,15 +40,14 @@ namespace Emby.Naming.Video public IReadOnlyList<VideoFileInfo> Files { get; set; } /// <summary> - /// Gets or sets the extras. - /// </summary> - /// <value>The extras.</value> - public IReadOnlyList<VideoFileInfo> Extras { get; set; } - - /// <summary> /// Gets or sets the alternate versions. /// </summary> /// <value>The alternate versions.</value> public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; } + + /// <summary> + /// Gets or sets the extra type. + /// </summary> + public ExtraType? ExtraType { get; set; } } } diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index ed7d511a3..11f82525f 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace Emby.Naming.Video @@ -17,29 +16,41 @@ namespace Emby.Naming.Video /// <summary> /// Resolves alternative versions and extras from list of video files. /// </summary> - /// <param name="files">List of related video files.</param> + /// <param name="videoInfos">List of related video files.</param> /// <param name="namingOptions">The naming options.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> - public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true) + public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) { - var videoInfos = files - .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions)) - .OfType<VideoFileInfo>() - .ToList(); - // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var nonExtras = videoInfos .Where(i => i.ExtraType == null) .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); - var stackResult = new StackResolver(namingOptions) - .Resolve(nonExtras).ToList(); + var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList(); + + var remainingFiles = new List<VideoFileInfo>(); + var standaloneMedia = new List<VideoFileInfo>(); - var remainingFiles = videoInfos - .Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory))) - .ToList(); + for (var i = 0; i < videoInfos.Count; i++) + { + var current = videoInfos[i]; + if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory))) + { + continue; + } + + if (current.ExtraType == null) + { + standaloneMedia.Add(current); + } + else + { + remainingFiles.Add(current); + } + } var list = new List<VideoInfo>(); @@ -47,38 +58,20 @@ namespace Emby.Naming.Video { var info = new VideoInfo(stack.Name) { - Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions)) + Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName)) .OfType<VideoFileInfo>() .ToList() }; info.Year = info.Files[0].Year; - - var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters); - - if (extras.Count > 0) - { - info.Extras = extras; - } - list.Add(info); } - var standaloneMedia = remainingFiles - .Where(i => i.ExtraType == null) - .ToList(); - foreach (var media in standaloneMedia) { var info = new VideoInfo(media.Name) { Files = new[] { media } }; info.Year = info.Files[0].Year; - - remainingFiles.Remove(media); - var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters); - - info.Extras = extras; - list.Add(info); } @@ -87,58 +80,12 @@ namespace Emby.Naming.Video list = GetVideosGroupedByVersion(list, namingOptions); } - // If there's only one resolved video, use the folder name as well to find extras - if (list.Count == 1) - { - var info = list[0]; - var videoPath = list[0].Files[0].Path; - var parentPath = Path.GetDirectoryName(videoPath.AsSpan()); - - if (!parentPath.IsEmpty) - { - var folderName = Path.GetFileName(parentPath); - if (!folderName.IsEmpty) - { - var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters); - extras.AddRange(info.Extras); - info.Extras = extras; - } - } - - // Add the extras that are just based on file name as well - var extrasByFileName = remainingFiles - .Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename) - .ToList(); - - remainingFiles = remainingFiles - .Except(extrasByFileName) - .ToList(); - - extrasByFileName.AddRange(info.Extras); - info.Extras = extrasByFileName; - } - - // If there's only one video, accept all trailers - // Be lenient because people use all kinds of mishmash conventions with trailers. - if (list.Count == 1) - { - var trailers = remainingFiles - .Where(i => i.ExtraType == ExtraType.Trailer) - .ToList(); - - trailers.AddRange(list[0].Extras); - list[0].Extras = trailers; - - remainingFiles = remainingFiles - .Except(trailers) - .ToList(); - } - // Whatever files are left, just add them list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name) { Files = new[] { i }, - Year = i.Year + Year = i.Year, + ExtraType = i.ExtraType })); return list; @@ -162,6 +109,11 @@ namespace Emby.Naming.Video for (var i = 0; i < videos.Count; i++) { var video = videos[i]; + if (video.ExtraType != null) + { + continue; + } + if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions)) { return videos; @@ -178,17 +130,14 @@ namespace Emby.Naming.Video var alternateVersionsLen = videos.Count - 1; var alternateVersions = new VideoFileInfo[alternateVersionsLen]; - var extras = new List<VideoFileInfo>(list[0].Extras); for (int i = 0; i < alternateVersionsLen; i++) { var video = videos[i + 1]; alternateVersions[i] = video.Files[0]; - extras.AddRange(video.Extras); } list[0].AlternateVersions = alternateVersions; list[0].Name = folderName.ToString(); - list[0].Extras = extras; return list; } @@ -230,7 +179,7 @@ namespace Emby.Naming.Video var tmpTestFilename = testFilename.ToString(); if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName)) { - tmpTestFilename = cleanName.Trim().ToString(); + tmpTestFilename = cleanName.Trim(); } // The CleanStringParser should have removed common keywords etc. @@ -238,67 +187,5 @@ namespace Emby.Naming.Video || testFilename[0] == '-' || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); } - - private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters) - { - return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd(); - } - - private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName) - { - if (baseName.IsEmpty) - { - return false; - } - - return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) - || (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase)); - } - - /// <summary> - /// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles]. - /// </summary> - /// <param name="remainingFiles">The list of remaining filenames.</param> - /// <param name="baseName">The base name to use for the comparison.</param> - /// <param name="videoFlagDelimiters">The video flag delimiters.</param> - /// <returns>A list of video extras for [baseName].</returns> - private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters) - { - return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters); - } - - /// <summary> - /// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles]. - /// </summary> - /// <param name="remainingFiles">The list of remaining filenames.</param> - /// <param name="firstBaseName">The first base name to use for the comparison.</param> - /// <param name="secondBaseName">The second base name to use for the comparison.</param> - /// <param name="videoFlagDelimiters">The video flag delimiters.</param> - /// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns> - private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters) - { - var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters); - var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters); - - var result = new List<VideoFileInfo>(); - for (var pos = remainingFiles.Count - 1; pos >= 0; pos--) - { - var file = remainingFiles[pos]; - if (file.ExtraType == null) - { - continue; - } - - var filename = file.FileNameWithoutExtension; - if (StartsWith(filename, firstBaseName, trimmedFirstBaseName) - || StartsWith(filename, secondBaseName, trimmedSecondBaseName)) - { - result.Add(file); - remainingFiles.RemoveAt(pos); - } - } - - return result; - } } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 3b1d906c6..de8e177d8 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -16,10 +16,11 @@ namespace Emby.Naming.Video /// </summary> /// <param name="path">The path.</param> /// <param name="namingOptions">The naming options.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> /// <returns>VideoFileInfo.</returns> - public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions) + public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true) { - return Resolve(path, true, namingOptions); + return Resolve(path, true, namingOptions, parseName); } /// <summary> @@ -74,7 +75,7 @@ namespace Emby.Naming.Video var format3DResult = Format3DParser.Parse(path, namingOptions); - var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path); + var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions); var name = Path.GetFileNameWithoutExtension(path); @@ -87,9 +88,9 @@ namespace Emby.Naming.Video year = cleanDateTimeResult.Year; if (extraResult.ExtraType == null - && TryCleanString(name, namingOptions, out ReadOnlySpan<char> newName)) + && TryCleanString(name, namingOptions, out var newName)) { - name = newName.ToString(); + name = newName; } } @@ -138,7 +139,7 @@ namespace Emby.Naming.Video /// <param name="namingOptions">The naming options.</param> /// <param name="newName">Clean name.</param> /// <returns>True if cleaning of name was successful.</returns> - public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out ReadOnlySpan<char> newName) + public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out string newName) { return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName); } |
