aboutsummaryrefslogtreecommitdiff
path: root/Emby.Naming/Video
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Naming/Video')
-rw-r--r--Emby.Naming/Video/CleanDateTimeParser.cs14
-rw-r--r--Emby.Naming/Video/CleanDateTimeResult.cs19
-rw-r--r--Emby.Naming/Video/CleanStringParser.cs21
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs114
-rw-r--r--Emby.Naming/Video/ExtraResult.cs7
-rw-r--r--Emby.Naming/Video/ExtraRule.cs17
-rw-r--r--Emby.Naming/Video/ExtraRuleType.cs7
-rw-r--r--Emby.Naming/Video/FileStack.cs25
-rw-r--r--Emby.Naming/Video/FlagParser.cs37
-rw-r--r--Emby.Naming/Video/Format3DParser.cs96
-rw-r--r--Emby.Naming/Video/Format3DResult.cs31
-rw-r--r--Emby.Naming/Video/Format3DRule.cs22
-rw-r--r--Emby.Naming/Video/StackResolver.cs70
-rw-r--r--Emby.Naming/Video/StubResolver.cs15
-rw-r--r--Emby.Naming/Video/StubResult.cs19
-rw-r--r--Emby.Naming/Video/StubTypeRule.cs16
-rw-r--r--Emby.Naming/Video/VideoFileInfo.cs47
-rw-r--r--Emby.Naming/Video/VideoInfo.cs4
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs238
-rw-r--r--Emby.Naming/Video/VideoResolver.cs125
20 files changed, 557 insertions, 387 deletions
diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs
index 579c9e91e1..0ee633dcc6 100644
--- a/Emby.Naming/Video/CleanDateTimeParser.cs
+++ b/Emby.Naming/Video/CleanDateTimeParser.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#nullable enable
-
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
@@ -12,9 +9,20 @@ namespace Emby.Naming.Video
/// </summary>
public static class CleanDateTimeParser
{
+ /// <summary>
+ /// Attempts to clean the name.
+ /// </summary>
+ /// <param name="name">Name of video.</param>
+ /// <param name="cleanDateTimeRegexes">Optional list of regexes to clean the name.</param>
+ /// <returns>Returns <see cref="CleanDateTimeResult"/> object.</returns>
public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
{
CleanDateTimeResult result = new CleanDateTimeResult(name);
+ if (string.IsNullOrEmpty(name))
+ {
+ return result;
+ }
+
var len = cleanDateTimeRegexes.Count;
for (int i = 0; i < len; i++)
{
diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs
index 57eeaa7e32..c675a19d0f 100644
--- a/Emby.Naming/Video/CleanDateTimeResult.cs
+++ b/Emby.Naming/Video/CleanDateTimeResult.cs
@@ -1,22 +1,21 @@
-#pragma warning disable CS1591
-#nullable enable
-
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Holder structure for name and year.
+ /// </summary>
public readonly struct CleanDateTimeResult
{
- public CleanDateTimeResult(string name, int? year)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CleanDateTimeResult"/> struct.
+ /// </summary>
+ /// <param name="name">Name of video.</param>
+ /// <param name="year">Year of release.</param>
+ public CleanDateTimeResult(string name, int? year = null)
{
Name = name;
Year = year;
}
- public CleanDateTimeResult(string name)
- {
- Name = name;
- Year = null;
- }
-
/// <summary>
/// Gets the name.
/// </summary>
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index 3f584d5847..4eef3ebc5e 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -1,8 +1,6 @@
-#pragma warning disable CS1591
-#nullable enable
-
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Emby.Naming.Video
@@ -12,8 +10,21 @@ namespace Emby.Naming.Video
/// </summary>
public static class CleanStringParser
{
- public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
+ /// <summary>
+ /// Attempts to extract clean name with regular expressions.
+ /// </summary>
+ /// <param name="name">Name of file.</param>
+ /// <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)
{
+ if (string.IsNullOrEmpty(name))
+ {
+ newName = ReadOnlySpan<char>.Empty;
+ return false;
+ }
+
var len = expressions.Count;
for (int i = 0; i < len; i++)
{
@@ -37,7 +48,7 @@ namespace Emby.Naming.Video
return true;
}
- newName = string.Empty;
+ newName = ReadOnlySpan<char>.Empty;
return false;
}
}
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index fc0424faab..a32af002cc 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -1,92 +1,100 @@
-#pragma warning disable CS1591
-
using System;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Resolve if file is extra for video.
+ /// </summary>
public class ExtraResolver
{
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;
}
+ /// <summary>
+ /// Attempts to resolve if file is extra.
+ /// </summary>
+ /// <param name="path">Path to file.</param>
+ /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path)
{
- return _options.VideoExtraRules
- .Select(i => GetExtraInfo(path, i))
- .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
- }
-
- private ExtraResult GetExtraInfo(string path, ExtraRule rule)
- {
var result = new ExtraResult();
- if (rule.MediaType == MediaType.Audio)
+ for (var i = 0; i < _options.VideoExtraRules.Length; i++)
{
- if (!AudioFileParser.IsAudioFile(path, _options))
+ var rule = _options.VideoExtraRules[i];
+ if (rule.MediaType == MediaType.Audio)
{
- return result;
+ if (!AudioFileParser.IsAudioFile(path, _options))
+ {
+ continue;
+ }
}
- }
- else if (rule.MediaType == MediaType.Video)
- {
- if (!new VideoResolver(_options).IsVideoFile(path))
+ else if (rule.MediaType == MediaType.Video)
{
- return result;
+ if (!VideoResolver.IsVideoFile(path, _options))
+ {
+ continue;
+ }
}
- }
- else
- {
- return result;
- }
-
- if (rule.RuleType == ExtraRuleType.Filename)
- {
- var filename = Path.GetFileNameWithoutExtension(path);
- if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
+ var pathSpan = path.AsSpan();
+ if (rule.RuleType == ExtraRuleType.Filename)
{
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
- }
- }
- else if (rule.RuleType == ExtraRuleType.Suffix)
- {
- var filename = Path.GetFileNameWithoutExtension(path);
+ var filename = Path.GetFileNameWithoutExtension(pathSpan);
- if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
+ if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+ else if (rule.RuleType == ExtraRuleType.Suffix)
{
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
+ var filename = Path.GetFileNameWithoutExtension(pathSpan);
+
+ if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
}
- }
- else if (rule.RuleType == ExtraRuleType.Regex)
- {
- var filename = Path.GetFileName(path);
+ else if (rule.RuleType == ExtraRuleType.Regex)
+ {
+ var filename = Path.GetFileName(path);
- var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
+ var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
- if (regex.IsMatch(filename))
+ if (regex.IsMatch(filename))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+ else if (rule.RuleType == ExtraRuleType.DirectoryName)
{
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
+ var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
+ if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
}
- }
- else if (rule.RuleType == ExtraRuleType.DirectoryName)
- {
- var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
- if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
+
+ if (result.ExtraType != null)
{
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
+ return result;
}
}
diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs
index 15db32e876..243fc2b415 100644
--- a/Emby.Naming/Video/ExtraResult.cs
+++ b/Emby.Naming/Video/ExtraResult.cs
@@ -1,9 +1,10 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Holder object for passing results from ExtraResolver.
+ /// </summary>
public class ExtraResult
{
/// <summary>
@@ -16,6 +17,6 @@ namespace Emby.Naming.Video
/// Gets or sets the rule.
/// </summary>
/// <value>The rule.</value>
- public ExtraRule Rule { get; set; }
+ public ExtraRule? Rule { get; set; }
}
}
diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs
index 7c9702e244..e267ac55fc 100644
--- a/Emby.Naming/Video/ExtraRule.cs
+++ b/Emby.Naming/Video/ExtraRule.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Model.Entities;
using MediaType = Emby.Naming.Common.MediaType;
@@ -11,6 +9,21 @@ namespace Emby.Naming.Video
public class ExtraRule
{
/// <summary>
+ /// Initializes a new instance of the <see cref="ExtraRule"/> class.
+ /// </summary>
+ /// <param name="extraType">Type of extra.</param>
+ /// <param name="ruleType">Type of rule.</param>
+ /// <param name="token">Token.</param>
+ /// <param name="mediaType">Media type.</param>
+ public ExtraRule(ExtraType extraType, ExtraRuleType ruleType, string token, MediaType mediaType)
+ {
+ Token = token;
+ ExtraType = extraType;
+ RuleType = ruleType;
+ MediaType = mediaType;
+ }
+
+ /// <summary>
/// Gets or sets the token to use for matching against the file path.
/// </summary>
public string Token { get; set; }
diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs
index e89876f4ae..3243195057 100644
--- a/Emby.Naming/Video/ExtraRuleType.cs
+++ b/Emby.Naming/Video/ExtraRuleType.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Extra rules type to determine against what <see cref="ExtraRule.Token"/> should be matched.
+ /// </summary>
public enum ExtraRuleType
{
/// <summary>
@@ -22,6 +23,6 @@ namespace Emby.Naming.Video
/// <summary>
/// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
/// </summary>
- DirectoryName = 3,
+ DirectoryName = 3
}
}
diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs
index 3ef190b865..6519db57c3 100644
--- a/Emby.Naming/Video/FileStack.cs
+++ b/Emby.Naming/Video/FileStack.cs
@@ -1,24 +1,43 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Object holding list of files paths with additional information.
+ /// </summary>
public class FileStack
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FileStack"/> class.
+ /// </summary>
public FileStack()
{
Files = new List<string>();
}
- public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets name of file stack.
+ /// </summary>
+ public string Name { get; set; } = string.Empty;
+ /// <summary>
+ /// Gets or sets list of paths in stack.
+ /// </summary>
public List<string> Files { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether stack is directory stack.
+ /// </summary>
public bool IsDirectoryStack { get; set; }
+ /// <summary>
+ /// Helper function to determine if path is in the stack.
+ /// </summary>
+ /// <param name="file">Path of desired file.</param>
+ /// <param name="isDirectory">Requested type of stack.</param>
+ /// <returns>True if file is in the stack.</returns>
public bool ContainsFile(string file, bool isDirectory)
{
if (IsDirectoryStack == isDirectory)
diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs
deleted file mode 100644
index a8bd9d5c5d..0000000000
--- a/Emby.Naming/Video/FlagParser.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using Emby.Naming.Common;
-
-namespace Emby.Naming.Video
-{
- public class FlagParser
- {
- private readonly NamingOptions _options;
-
- public FlagParser(NamingOptions options)
- {
- _options = options;
- }
-
- public string[] GetFlags(string path)
- {
- return GetFlags(path, _options.VideoFlagDelimiters);
- }
-
- public string[] GetFlags(string path, char[] delimeters)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
-
- var file = Path.GetFileName(path);
-
- return file.Split(delimeters, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-}
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index 51c26af863..0890899894 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -1,35 +1,37 @@
-#pragma warning disable CS1591
-
using System;
-using System.Linq;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
- public class Format3DParser
+ /// <summary>
+ /// Parse 3D format related flags.
+ /// </summary>
+ public static class Format3DParser
{
- private readonly NamingOptions _options;
-
- public Format3DParser(NamingOptions options)
+ // Static default result to save on allocation costs.
+ private static readonly Format3DResult _defaultResult = new (false, null);
+
+ /// <summary>
+ /// Parse 3D format related flags.
+ /// </summary>
+ /// <param name="path">Path to file.</param>
+ /// <param name="namingOptions">The naming options.</param>
+ /// <returns>Returns <see cref="Format3DResult"/> object.</returns>
+ public static Format3DResult Parse(ReadOnlySpan<char> path, NamingOptions namingOptions)
{
- _options = options;
- }
+ int oldLen = namingOptions.VideoFlagDelimiters.Length;
+ Span<char> delimiters = stackalloc char[oldLen + 1];
+ namingOptions.VideoFlagDelimiters.AsSpan().CopyTo(delimiters);
+ delimiters[oldLen] = ' ';
- public Format3DResult Parse(string path)
- {
- int oldLen = _options.VideoFlagDelimiters.Length;
- var delimeters = new char[oldLen + 1];
- _options.VideoFlagDelimiters.CopyTo(delimeters, 0);
- delimeters[oldLen] = ' ';
-
- return Parse(new FlagParser(_options).GetFlags(path, delimeters));
+ return Parse(path, delimiters, namingOptions);
}
- internal Format3DResult Parse(string[] videoFlags)
+ private static Format3DResult Parse(ReadOnlySpan<char> path, ReadOnlySpan<char> delimiters, NamingOptions namingOptions)
{
- foreach (var rule in _options.Format3DRules)
+ foreach (var rule in namingOptions.Format3DRules)
{
- var result = Parse(videoFlags, rule);
+ var result = Parse(path, rule, delimiters);
if (result.Is3D)
{
@@ -37,51 +39,43 @@ namespace Emby.Naming.Video
}
}
- return new Format3DResult();
+ return _defaultResult;
}
- private static Format3DResult Parse(string[] videoFlags, Format3DRule rule)
+ private static Format3DResult Parse(ReadOnlySpan<char> path, Format3DRule rule, ReadOnlySpan<char> delimiters)
{
- var result = new Format3DResult();
+ bool is3D = false;
+ string? format3D = null;
- if (string.IsNullOrEmpty(rule.PreceedingToken))
+ // If there's no preceding token we just consider it found
+ var foundPrefix = string.IsNullOrEmpty(rule.PrecedingToken);
+ while (path.Length > 0)
{
- result.Format3D = new[] { rule.Token }.FirstOrDefault(i => videoFlags.Contains(i, StringComparer.OrdinalIgnoreCase));
- result.Is3D = !string.IsNullOrEmpty(result.Format3D);
-
- if (result.Is3D)
+ var index = path.IndexOfAny(delimiters);
+ if (index == -1)
{
- result.Tokens.Add(rule.Token);
+ index = path.Length - 1;
}
- }
- else
- {
- var foundPrefix = false;
- string format = null;
- foreach (var flag in videoFlags)
- {
- if (foundPrefix)
- {
- result.Tokens.Add(rule.PreceedingToken);
+ var currentSlice = path[..index];
+ path = path[(index + 1)..];
- if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase))
- {
- format = flag;
- result.Tokens.Add(rule.Token);
- }
+ if (!foundPrefix)
+ {
+ foundPrefix = currentSlice.Equals(rule.PrecedingToken, StringComparison.OrdinalIgnoreCase);
+ continue;
+ }
- break;
- }
+ is3D = foundPrefix && currentSlice.Equals(rule.Token, StringComparison.OrdinalIgnoreCase);
- foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase);
+ if (is3D)
+ {
+ format3D = rule.Token;
+ break;
}
-
- result.Is3D = foundPrefix && !string.IsNullOrEmpty(format);
- result.Format3D = format;
}
- return result;
+ return is3D ? new Format3DResult(true, format3D) : _defaultResult;
}
}
}
diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs
index fa0e9d3b80..aac959c133 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -1,32 +1,31 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Helper object to return data from <see cref="Format3DParser"/>.
+ /// </summary>
public class Format3DResult
{
- public Format3DResult()
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Format3DResult"/> class.
+ /// </summary>
+ /// <param name="is3D">A value indicating whether the parsed string contains 3D tokens.</param>
+ /// <param name="format3D">The 3D format. Value might be null if [is3D] is <c>false</c>.</param>
+ public Format3DResult(bool is3D, string? format3D)
{
- Tokens = new List<string>();
+ Is3D = is3D;
+ Format3D = format3D;
}
/// <summary>
- /// Gets or sets a value indicating whether [is3 d].
+ /// Gets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
- public bool Is3D { get; set; }
+ public bool Is3D { get; }
/// <summary>
- /// Gets or sets the format3 d.
+ /// Gets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
- public string Format3D { get; set; }
-
- /// <summary>
- /// Gets or sets the tokens.
- /// </summary>
- /// <value>The tokens.</value>
- public List<string> Tokens { get; set; }
+ public string? Format3D { get; }
}
}
diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs
index 310ec84e8f..e562691df9 100644
--- a/Emby.Naming/Video/Format3DRule.cs
+++ b/Emby.Naming/Video/Format3DRule.cs
@@ -1,19 +1,31 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Data holder class for 3D format rule.
+ /// </summary>
public class Format3DRule
{
/// <summary>
+ /// Initializes a new instance of the <see cref="Format3DRule"/> class.
+ /// </summary>
+ /// <param name="token">Token.</param>
+ /// <param name="precedingToken">Token present before current token.</param>
+ public Format3DRule(string token, string? precedingToken = null)
+ {
+ Token = token;
+ PrecedingToken = precedingToken;
+ }
+
+ /// <summary>
/// Gets or sets the token.
/// </summary>
/// <value>The token.</value>
public string Token { get; set; }
/// <summary>
- /// Gets or sets the preceeding token.
+ /// Gets or sets the preceding token.
/// </summary>
- /// <value>The preceeding token.</value>
- public string PreceedingToken { get; set; }
+ /// <value>The preceding token.</value>
+ public string? PrecedingToken { get; set; }
}
}
diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index f733cd2620..36f65a5624 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -1,64 +1,92 @@
-#pragma warning disable CS1591
-
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;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Resolve <see cref="FileStack"/> from list of paths.
+ /// </summary>
public 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>
+ /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
}
+ /// <summary>
+ /// Resolves only files from paths.
+ /// </summary>
+ /// <param name="files">List of paths.</param>
+ /// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
}
- public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
+ /// <summary>
+ /// Resolves audiobooks from paths.
+ /// </summary>
+ /// <param name="files">List of paths.</param>
+ /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
+ public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
{
- var groupedDirectoryFiles = files.GroupBy(file =>
- file.IsDirectory
- ? file.FullName
- : Path.GetDirectoryName(file.FullName));
+ var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
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))
{
- if (file.IsDirectory)
+ foreach (var file in directory)
{
- continue;
+ var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
+ stack.Files.Add(file.Path);
+ yield return stack;
}
-
- stack.Files.Add(file.FullName);
}
+ 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;
+ }
}
}
+ /// <summary>
+ /// Resolves videos from paths.
+ /// </summary>
+ /// <param name="files">List of paths.</param>
+ /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
{
- var resolver = new VideoResolver(_options);
-
var list = files
- .Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))
+ .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
.OrderBy(i => i.FullName)
.ToList();
@@ -81,10 +109,10 @@ namespace Emby.Naming.Video
if (match1.Success)
{
- var title1 = match1.Groups[1].Value;
- var volume1 = match1.Groups[2].Value;
- var ignore1 = match1.Groups[3].Value;
- var extension1 = match1.Groups[4].Value;
+ var title1 = match1.Groups["title"].Value;
+ var volume1 = match1.Groups["volume"].Value;
+ var ignore1 = match1.Groups["ignore"].Value;
+ var extension1 = match1.Groups["extension"].Value;
var j = i + 1;
while (j < list.Count)
diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs
index f1b5d7bcca..079987fe8a 100644
--- a/Emby.Naming/Video/StubResolver.cs
+++ b/Emby.Naming/Video/StubResolver.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#nullable enable
-
using System;
using System.IO;
using System.Linq;
@@ -8,13 +5,23 @@ using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Resolve if file is stub (.disc).
+ /// </summary>
public static class StubResolver
{
+ /// <summary>
+ /// Tries to resolve if file is stub (.disc).
+ /// </summary>
+ /// <param name="path">Path to file.</param>
+ /// <param name="options">NamingOptions containing StubFileExtensions and StubTypes.</param>
+ /// <param name="stubType">Stub type.</param>
+ /// <returns>True if file is a stub.</returns>
public static bool TryResolveFile(string path, NamingOptions options, out string? stubType)
{
stubType = default;
- if (path == null)
+ if (string.IsNullOrEmpty(path))
{
return false;
}
diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs
deleted file mode 100644
index 1b8e99b0dc..0000000000
--- a/Emby.Naming/Video/StubResult.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-namespace Emby.Naming.Video
-{
- public struct StubResult
- {
- /// <summary>
- /// Gets or sets a value indicating whether this instance is stub.
- /// </summary>
- /// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
- public bool IsStub { get; set; }
-
- /// <summary>
- /// Gets or sets the type of the stub.
- /// </summary>
- /// <value>The type of the stub.</value>
- public string StubType { get; set; }
- }
-}
diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs
index 8285cb51a3..dfb3ac013d 100644
--- a/Emby.Naming/Video/StubTypeRule.cs
+++ b/Emby.Naming/Video/StubTypeRule.cs
@@ -1,10 +1,22 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// Data class holding information about Stub type rule.
+ /// </summary>
public class StubTypeRule
{
/// <summary>
+ /// Initializes a new instance of the <see cref="StubTypeRule"/> class.
+ /// </summary>
+ /// <param name="token">Token.</param>
+ /// <param name="stubType">Stub type.</param>
+ public StubTypeRule(string token, string stubType)
+ {
+ Token = token;
+ StubType = stubType;
+ }
+
+ /// <summary>
/// Gets or sets the token.
/// </summary>
/// <value>The token.</value>
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 11e789b663..481773ff60 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -1,3 +1,4 @@
+using System;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
@@ -8,6 +9,35 @@ namespace Emby.Naming.Video
public class VideoFileInfo
{
/// <summary>
+ /// Initializes a new instance of the <see cref="VideoFileInfo"/> class.
+ /// </summary>
+ /// <param name="name">Name of file.</param>
+ /// <param name="path">Path to the file.</param>
+ /// <param name="container">Container type.</param>
+ /// <param name="year">Year of release.</param>
+ /// <param name="extraType">Extra type.</param>
+ /// <param name="extraRule">Extra rule.</param>
+ /// <param name="format3D">Format 3D.</param>
+ /// <param name="is3D">Is 3D.</param>
+ /// <param name="isStub">Is Stub.</param>
+ /// <param name="stubType">Stub type.</param>
+ /// <param name="isDirectory">Is directory.</param>
+ public VideoFileInfo(string name, string path, string? container, int? year = default, ExtraType? extraType = default, ExtraRule? extraRule = default, string? format3D = default, bool is3D = default, bool isStub = default, string? stubType = default, bool isDirectory = default)
+ {
+ Path = path;
+ Container = container;
+ Name = name;
+ Year = year;
+ ExtraType = extraType;
+ ExtraRule = extraRule;
+ Format3D = format3D;
+ Is3D = is3D;
+ IsStub = isStub;
+ StubType = stubType;
+ IsDirectory = isDirectory;
+ }
+
+ /// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
@@ -17,7 +47,7 @@ namespace Emby.Naming.Video
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
- public string Container { get; set; }
+ public string? Container { get; set; }
/// <summary>
/// Gets or sets the name.
@@ -41,13 +71,13 @@ namespace Emby.Naming.Video
/// Gets or sets the extra rule.
/// </summary>
/// <value>The extra rule.</value>
- public ExtraRule ExtraRule { get; set; }
+ public ExtraRule? ExtraRule { get; set; }
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
- public string Format3D { get; set; }
+ public string? Format3D { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
@@ -65,7 +95,7 @@ namespace Emby.Naming.Video
/// Gets or sets the type of the stub.
/// </summary>
/// <value>The type of the stub.</value>
- public string StubType { get; set; }
+ public string? StubType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is a directory.
@@ -77,15 +107,14 @@ namespace Emby.Naming.Video
/// Gets the file name without extension.
/// </summary>
/// <value>The file name without extension.</value>
- public string FileNameWithoutExtension => !IsDirectory
- ? System.IO.Path.GetFileNameWithoutExtension(Path)
- : System.IO.Path.GetFileName(Path);
+ public ReadOnlySpan<char> FileNameWithoutExtension => !IsDirectory
+ ? System.IO.Path.GetFileNameWithoutExtension(Path.AsSpan())
+ : System.IO.Path.GetFileName(Path.AsSpan());
/// <inheritdoc />
public override string ToString()
{
- // Makes debugging easier
- return Name ?? base.ToString();
+ return "VideoFileInfo(Name: '" + Name + "')";
}
}
}
diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs
index ea74c40e2a..930fdb33f8 100644
--- a/Emby.Naming/Video/VideoInfo.cs
+++ b/Emby.Naming/Video/VideoInfo.cs
@@ -12,7 +12,7 @@ namespace Emby.Naming.Video
/// Initializes a new instance of the <see cref="VideoInfo" /> class.
/// </summary>
/// <param name="name">The name.</param>
- public VideoInfo(string name)
+ public VideoInfo(string? name)
{
Name = name;
@@ -25,7 +25,7 @@ namespace Emby.Naming.Video
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the year.
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 948fe037b5..ed7d511a39 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -11,22 +9,23 @@ using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
{
- public class VideoListResolver
+ /// <summary>
+ /// Resolves alternative versions and extras from list of video files.
+ /// </summary>
+ public static class VideoListResolver
{
- private readonly NamingOptions _options;
-
- public VideoListResolver(NamingOptions options)
- {
- _options = options;
- }
-
- public IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, bool supportMultiVersion = true)
+ /// <summary>
+ /// Resolves alternative versions and extras from list of video files.
+ /// </summary>
+ /// <param name="files">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>
+ /// <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)
{
- var videoResolver = new VideoResolver(_options);
-
var videoInfos = files
- .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory))
- .Where(i => i != null)
+ .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
@@ -35,11 +34,11 @@ namespace Emby.Naming.Video
.Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
- var stackResult = new StackResolver(_options)
+ var stackResult = new StackResolver(namingOptions)
.Resolve(nonExtras).ToList();
var remainingFiles = videoInfos
- .Where(i => !stackResult.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
+ .Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
.ToList();
var list = new List<VideoInfo>();
@@ -48,21 +47,17 @@ namespace Emby.Naming.Video
{
var info = new VideoInfo(stack.Name)
{
- Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList()
+ Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
+ .OfType<VideoFileInfo>()
+ .ToList()
};
info.Year = info.Files[0].Year;
- var extraBaseNames = new List<string> { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) };
-
- var extras = GetExtras(remainingFiles, extraBaseNames);
+ var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
if (extras.Count > 0)
{
- remainingFiles = remainingFiles
- .Except(extras)
- .ToList();
-
info.Extras = extras;
}
@@ -75,15 +70,12 @@ namespace Emby.Naming.Video
foreach (var media in standaloneMedia)
{
- var info = new VideoInfo(media.Name) { Files = new List<VideoFileInfo> { media } };
+ var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
- var extras = GetExtras(remainingFiles, new List<string> { media.FileNameWithoutExtension });
-
- remainingFiles = remainingFiles
- .Except(extras.Concat(new[] { media }))
- .ToList();
+ remainingFiles.Remove(media);
+ var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
info.Extras = extras;
@@ -92,8 +84,7 @@ namespace Emby.Naming.Video
if (supportMultiVersion)
{
- list = GetVideosGroupedByVersion(list)
- .ToList();
+ list = GetVideosGroupedByVersion(list, namingOptions);
}
// If there's only one resolved video, use the folder name as well to find extras
@@ -101,19 +92,14 @@ namespace Emby.Naming.Video
{
var info = list[0];
var videoPath = list[0].Files[0].Path;
- var parentPath = Path.GetDirectoryName(videoPath);
+ var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
- if (!string.IsNullOrEmpty(parentPath))
+ if (!parentPath.IsEmpty)
{
var folderName = Path.GetFileName(parentPath);
- if (!string.IsNullOrEmpty(folderName))
+ if (!folderName.IsEmpty)
{
- var extras = GetExtras(remainingFiles, new List<string> { folderName });
-
- remainingFiles = remainingFiles
- .Except(extras)
- .ToList();
-
+ var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
extras.AddRange(info.Extras);
info.Extras = extras;
}
@@ -133,7 +119,7 @@ namespace Emby.Naming.Video
}
// If there's only one video, accept all trailers
- // Be lenient because people use all kinds of mish mash conventions with trailers
+ // Be lenient because people use all kinds of mishmash conventions with trailers.
if (list.Count == 1)
{
var trailers = remainingFiles
@@ -151,86 +137,168 @@ namespace Emby.Naming.Video
// Whatever files are left, just add them
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{
- Files = new List<VideoFileInfo> { i },
+ Files = new[] { i },
Year = i.Year
}));
return list;
}
- private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
+ private static List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos, NamingOptions namingOptions)
{
if (videos.Count == 0)
{
return videos;
}
- var list = new List<VideoInfo>();
+ var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path.AsSpan()));
- var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
-
- if (!string.IsNullOrEmpty(folderName)
- && folderName.Length > 1
- && videos.All(i => i.Files.Count == 1
- && IsEligibleForMultiVersion(folderName, i.Files[0].Path))
- && HaveSameYear(videos))
+ if (folderName.Length <= 1 || !HaveSameYear(videos))
{
- var ordered = videos.OrderBy(i => i.Name).ToList();
-
- list.Add(ordered[0]);
+ return videos;
+ }
- var alternateVersionsLen = ordered.Count - 1;
- var alternateVersions = new VideoFileInfo[alternateVersionsLen];
- for (int i = 0; i < alternateVersionsLen; i++)
+ // Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if]
+ for (var i = 0; i < videos.Count; i++)
+ {
+ var video = videos[i];
+ if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
{
- alternateVersions[i] = ordered[i + 1].Files[0];
+ return videos;
}
+ }
+
+ // The list is created and overwritten in the caller, so we are allowed to do in-place sorting
+ videos.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal));
- list[0].AlternateVersions = alternateVersions;
- list[0].Name = folderName;
- var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList();
- extras.AddRange(list[0].Extras);
- list[0].Extras = extras;
+ var list = new List<VideoInfo>
+ {
+ videos[0]
+ };
- return list;
+ 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);
}
- return videos;
+ list[0].AlternateVersions = alternateVersions;
+ list[0].Name = folderName.ToString();
+ list[0].Extras = extras;
+
+ return list;
}
- private bool HaveSameYear(List<VideoInfo> videos)
+ private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos)
{
- return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2;
+ if (videos.Count == 1)
+ {
+ return true;
+ }
+
+ var firstYear = videos[0].Year ?? -1;
+ for (var i = 1; i < videos.Count; i++)
+ {
+ if ((videos[i].Year ?? -1) != firstYear)
+ {
+ return false;
+ }
+ }
+
+ return true;
}
- private bool IsEligibleForMultiVersion(string folderName, string testFilename)
+ private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, string testFilePath, NamingOptions namingOptions)
{
- testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty;
+ var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan());
+ if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
- if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ // Remove the folder name before cleaning as we don't care about cleaning that part
+ if (folderName.Length <= testFilename.Length)
{
- testFilename = testFilename.Substring(folderName.Length).Trim();
- return string.IsNullOrEmpty(testFilename)
+ testFilename = testFilename[folderName.Length..].Trim();
+ }
+
+ // There are no span overloads for regex unfortunately
+ var tmpTestFilename = testFilename.ToString();
+ if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
+ {
+ tmpTestFilename = cleanName.Trim().ToString();
+ }
+
+ // The CleanStringParser should have removed common keywords etc.
+ return string.IsNullOrEmpty(tmpTestFilename)
|| testFilename[0] == '-'
- || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
+ || 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 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);
}
- private List<VideoFileInfo> GetExtras(IEnumerable<VideoFileInfo> remainingFiles, List<string> baseNames)
+ /// <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)
{
- foreach (var name in baseNames.ToList())
+ 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 trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd();
- baseNames.Add(trimmedName);
+ 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 remainingFiles
- .Where(i => i.ExtraType != null)
- .Where(i => baseNames.Any(b =>
- i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
- .ToList();
+ return result;
}
}
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index b4aee614b0..3b1d906c64 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -1,40 +1,36 @@
-#pragma warning disable CS1591
-#nullable enable
-
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Linq;
using Emby.Naming.Common;
+using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
- public class VideoResolver
+ /// <summary>
+ /// Resolves <see cref="VideoFileInfo"/> from file path.
+ /// </summary>
+ public static class VideoResolver
{
- private readonly NamingOptions _options;
-
- public VideoResolver(NamingOptions options)
- {
- _options = options;
- }
-
/// <summary>
/// Resolves the directory.
/// </summary>
/// <param name="path">The path.</param>
+ /// <param name="namingOptions">The naming options.</param>
/// <returns>VideoFileInfo.</returns>
- public VideoFileInfo? ResolveDirectory(string path)
+ public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
{
- return Resolve(path, true);
+ return Resolve(path, true, namingOptions);
}
/// <summary>
/// Resolves the file.
/// </summary>
/// <param name="path">The path.</param>
+ /// <param name="namingOptions">The naming options.</param>
/// <returns>VideoFileInfo.</returns>
- public VideoFileInfo? ResolveFile(string path)
+ public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions)
{
- return Resolve(path, false);
+ return Resolve(path, false, namingOptions);
}
/// <summary>
@@ -42,29 +38,30 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
+ /// <param name="namingOptions">The naming options.</param>
/// <param name="parseName">Whether or not the name should be parsed for info.</param>
/// <returns>VideoFileInfo.</returns>
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
- public VideoFileInfo? Resolve(string path, bool isDirectory, bool parseName = true)
+ public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true)
{
if (string.IsNullOrEmpty(path))
{
- throw new ArgumentNullException(nameof(path));
+ return null;
}
bool isStub = false;
- string? container = null;
+ ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
string? stubType = null;
if (!isDirectory)
{
- var extension = Path.GetExtension(path);
+ var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (!namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's not supported. Check stub extensions
- if (!StubResolver.TryResolveFile(path, _options, out stubType))
+ if (!StubResolver.TryResolveFile(path, namingOptions, out stubType))
{
return null;
}
@@ -75,66 +72,86 @@ namespace Emby.Naming.Video
container = extension.TrimStart('.');
}
- var flags = new FlagParser(_options).GetFlags(path);
- var format3DResult = new Format3DParser(_options).Parse(flags);
+ var format3DResult = Format3DParser.Parse(path, namingOptions);
- var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
+ var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
- var name = isDirectory
- ? Path.GetFileName(path)
- : Path.GetFileNameWithoutExtension(path);
+ var name = Path.GetFileNameWithoutExtension(path);
int? year = null;
if (parseName)
{
- var cleanDateTimeResult = CleanDateTime(name);
+ var cleanDateTimeResult = CleanDateTime(name, namingOptions);
name = cleanDateTimeResult.Name;
year = cleanDateTimeResult.Year;
if (extraResult.ExtraType == null
- && TryCleanString(name, out ReadOnlySpan<char> newName))
+ && TryCleanString(name, namingOptions, out ReadOnlySpan<char> newName))
{
name = newName.ToString();
}
}
- return new VideoFileInfo
- {
- Path = path,
- Container = container,
- IsStub = isStub,
- Name = name,
- Year = year,
- StubType = stubType,
- Is3D = format3DResult.Is3D,
- Format3D = format3DResult.Format3D,
- ExtraType = extraResult.ExtraType,
- IsDirectory = isDirectory,
- ExtraRule = extraResult.Rule
- };
+ return new VideoFileInfo(
+ path: path,
+ container: container.IsEmpty ? null : container.ToString(),
+ isStub: isStub,
+ name: name,
+ year: year,
+ stubType: stubType,
+ is3D: format3DResult.Is3D,
+ format3D: format3DResult.Format3D,
+ extraType: extraResult.ExtraType,
+ isDirectory: isDirectory,
+ extraRule: extraResult.Rule);
}
- public bool IsVideoFile(string path)
+ /// <summary>
+ /// Determines if path is video file based on extension.
+ /// </summary>
+ /// <param name="path">Path to file.</param>
+ /// <param name="namingOptions">The naming options.</param>
+ /// <returns>True if is video file.</returns>
+ public static bool IsVideoFile(string path, NamingOptions namingOptions)
{
- var extension = Path.GetExtension(path) ?? string.Empty;
- return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path.AsSpan());
+ return namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
- public bool IsStubFile(string path)
+ /// <summary>
+ /// Determines if path is video file stub based on extension.
+ /// </summary>
+ /// <param name="path">Path to file.</param>
+ /// <param name="namingOptions">The naming options.</param>
+ /// <returns>True if is video file stub.</returns>
+ public static bool IsStubFile(string path, NamingOptions namingOptions)
{
- var extension = Path.GetExtension(path) ?? string.Empty;
- return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path.AsSpan());
+ return namingOptions.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
- public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
+ /// <summary>
+ /// Tries to clean name of clutter.
+ /// </summary>
+ /// <param name="name">Raw name.</param>
+ /// <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)
{
- return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
+ return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName);
}
- public CleanDateTimeResult CleanDateTime(string name)
+ /// <summary>
+ /// Tries to get name and year from raw name.
+ /// </summary>
+ /// <param name="name">Raw name.</param>
+ /// <param name="namingOptions">The naming options.</param>
+ /// <returns>Returns <see cref="CleanDateTimeResult"/> with name and optional year.</returns>
+ public static CleanDateTimeResult CleanDateTime(string name, NamingOptions namingOptions)
{
- return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
+ return CleanDateTimeParser.Clean(name, namingOptions.CleanDateTimeRegexes);
}
}
}