aboutsummaryrefslogtreecommitdiff
path: root/Emby.Naming
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Naming')
-rw-r--r--Emby.Naming/TV/EpisodeResolver.cs5
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs2
-rw-r--r--Emby.Naming/Video/FlagParser.cs53
-rw-r--r--Emby.Naming/Video/Format3DParser.cs88
-rw-r--r--Emby.Naming/Video/Format3DResult.cs23
-rw-r--r--Emby.Naming/Video/StackResolver.cs4
-rw-r--r--Emby.Naming/Video/VideoFileInfo.cs7
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs233
-rw-r--r--Emby.Naming/Video/VideoResolver.cs60
9 files changed, 219 insertions, 256 deletions
diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs
index c63aec64e..5e952e47b 100644
--- a/Emby.Naming/TV/EpisodeResolver.cs
+++ b/Emby.Naming/TV/EpisodeResolver.cs
@@ -16,7 +16,7 @@ namespace Emby.Naming.TV
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
- /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileExtensions and passed to <see cref="StubResolver"/>, <see cref="FlagParser"/>, <see cref="Format3DParser"/> and <see cref="EpisodePathParser"/>.</param>
+ /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileExtensions and passed to <see cref="StubResolver"/>, <see cref="Format3DParser"/> and <see cref="EpisodePathParser"/>.</param>
public EpisodeResolver(NamingOptions options)
{
_options = options;
@@ -62,8 +62,7 @@ namespace Emby.Naming.TV
container = extension.TrimStart('.');
}
- var flags = new FlagParser(_options).GetFlags(path);
- var format3DResult = new Format3DParser(_options).Parse(flags);
+ var format3DResult = Format3DParser.Parse(path, _options);
var parsingResult = new EpisodePathParser(_options)
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index f9d06c09b..1fade985b 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -44,7 +44,7 @@ namespace Emby.Naming.Video
}
else if (rule.MediaType == MediaType.Video)
{
- if (!new VideoResolver(_options).IsVideoFile(path))
+ if (!VideoResolver.IsVideoFile(path, _options))
{
continue;
}
diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs
deleted file mode 100644
index 439de1813..000000000
--- a/Emby.Naming/Video/FlagParser.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.IO;
-using Emby.Naming.Common;
-
-namespace Emby.Naming.Video
-{
- /// <summary>
- /// Parses list of flags from filename based on delimiters.
- /// </summary>
- public class FlagParser
- {
- private readonly NamingOptions _options;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FlagParser"/> class.
- /// </summary>
- /// <param name="options"><see cref="NamingOptions"/> object containing VideoFlagDelimiters.</param>
- public FlagParser(NamingOptions options)
- {
- _options = options;
- }
-
- /// <summary>
- /// Calls GetFlags function with _options.VideoFlagDelimiters parameter.
- /// </summary>
- /// <param name="path">Path to file.</param>
- /// <returns>List of found flags.</returns>
- public string[] GetFlags(string path)
- {
- return GetFlags(path, _options.VideoFlagDelimiters);
- }
-
- /// <summary>
- /// Parses flags from filename based on delimiters.
- /// </summary>
- /// <param name="path">Path to file.</param>
- /// <param name="delimiters">Delimiters used to extract flags.</param>
- /// <returns>List of found flags.</returns>
- public string[] GetFlags(string path, char[] delimiters)
- {
- if (string.IsNullOrEmpty(path))
- {
- return Array.Empty<string>();
- }
-
- // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
-
- var file = Path.GetFileName(path);
-
- return file.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-}
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index 4fd5d78ba..089089989 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -1,45 +1,37 @@
using System;
-using System.Linq;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
/// <summary>
- /// Parste 3D format related flags.
+ /// Parse 3D format related flags.
/// </summary>
- public class Format3DParser
+ public static class Format3DParser
{
- private readonly NamingOptions _options;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Format3DParser"/> class.
- /// </summary>
- /// <param name="options"><see cref="NamingOptions"/> object containing VideoFlagDelimiters and passes options to <see cref="FlagParser"/>.</param>
- public Format3DParser(NamingOptions options)
- {
- _options = 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 Format3DResult Parse(string path)
+ public static Format3DResult Parse(ReadOnlySpan<char> path, NamingOptions namingOptions)
{
- int oldLen = _options.VideoFlagDelimiters.Length;
- var delimiters = new char[oldLen + 1];
- _options.VideoFlagDelimiters.CopyTo(delimiters, 0);
+ int oldLen = namingOptions.VideoFlagDelimiters.Length;
+ Span<char> delimiters = stackalloc char[oldLen + 1];
+ namingOptions.VideoFlagDelimiters.AsSpan().CopyTo(delimiters);
delimiters[oldLen] = ' ';
- return Parse(new FlagParser(_options).GetFlags(path, delimiters));
+ 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)
{
@@ -47,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.PrecedingToken))
+ // 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.PrecedingToken);
+ 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.PrecedingToken, 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 ac935f203..aac959c13 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-
namespace Emby.Naming.Video
{
/// <summary>
@@ -10,27 +8,24 @@ namespace Emby.Naming.Video
/// <summary>
/// Initializes a new instance of the <see cref="Format3DResult"/> class.
/// </summary>
- public Format3DResult()
+ /// <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/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index 550c42961..36f65a562 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -85,10 +85,8 @@ namespace Emby.Naming.Video
/// <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();
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 1457db737..481773ff6 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
@@ -106,9 +107,9 @@ 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()
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 7b6a1705b..7da2dcd7a 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -12,31 +12,19 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolves alternative versions and extras from list of video files.
/// </summary>
- public class VideoListResolver
+ public static class VideoListResolver
{
- private readonly NamingOptions _options;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="VideoListResolver"/> class.
- /// </summary>
- /// <param name="options"><see cref="NamingOptions"/> object containing CleanStringRegexes and VideoFlagDelimiters and passes options to <see cref="StackResolver"/> and <see cref="VideoResolver"/>.</param>
- public VideoListResolver(NamingOptions options)
- {
- _options = options;
- }
-
/// <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 IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, bool supportMultiVersion = true)
+ public static IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
{
- var videoResolver = new VideoResolver(_options);
-
var videoInfos = files
- .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory))
+ .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
.OfType<VideoFileInfo>()
.ToList();
@@ -46,7 +34,7 @@ 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
@@ -59,23 +47,17 @@ namespace Emby.Naming.Video
{
var info = new VideoInfo(stack.Name)
{
- Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack))
+ 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;
}
@@ -88,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;
@@ -105,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
@@ -114,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;
}
@@ -164,96 +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));
+ var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path.AsSpan()));
- 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;
- private bool HaveSameYear(List<VideoInfo> videos)
- {
- return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2;
+ return list;
}
- private bool IsEligibleForMultiVersion(string folderName, string testFilePath)
+ private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos)
{
- string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
- if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ if (videos.Count == 1)
{
- // 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 true;
+ }
- if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
+ var firstYear = videos[0].Year ?? -1;
+ for (var i = 1; i < videos.Count; i++)
+ {
+ if ((videos[i].Year ?? -1) != firstYear)
{
- testFilename = cleanName.Trim().ToString();
+ return false;
}
+ }
- // The CleanStringParser should have removed common keywords etc.
- return string.IsNullOrEmpty(testFilename)
- || testFilename[0] == '-'
- || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
+ return true;
+ }
+
+ private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, string testFilePath, NamingOptions namingOptions)
+ {
+ var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan());
+ if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
}
- return false;
+ // Remove the folder name before cleaning as we don't care about cleaning that part
+ if (folderName.Length <= testFilename.Length)
+ {
+ 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] == '-'
+ || 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 List<VideoFileInfo> GetExtras(IEnumerable<VideoFileInfo> remainingFiles, List<string> baseNames)
+ private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
{
- foreach (var name in baseNames.ToList())
+ if (baseName.IsEmpty)
{
- var trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd();
- baseNames.Add(trimmedName);
+ return false;
}
- return remainingFiles
- .Where(i => i.ExtraType != null)
- .Where(i => baseNames.Any(b =>
- i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
- .ToList();
+ 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 27e73208c..c4ac5fdc6 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -9,38 +9,28 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolves <see cref="VideoFileInfo"/> from file path.
/// </summary>
- public class VideoResolver
+ public static class VideoResolver
{
- private readonly NamingOptions _options;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="VideoResolver"/> class.
- /// </summary>
- /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileExtensions, StubFileExtensions, CleanStringRegexes and CleanDateTimeRegexes
- /// and passes options in <see cref="StubResolver"/>, <see cref="FlagParser"/>, <see cref="Format3DParser"/> and <see cref="ExtraResolver"/>.</param>
- 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>
@@ -48,10 +38,11 @@ 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))
{
@@ -67,10 +58,10 @@ namespace Emby.Naming.Video
var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparison.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;
}
@@ -81,10 +72,9 @@ 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 = Path.GetFileNameWithoutExtension(path);
@@ -92,12 +82,12 @@ namespace Emby.Naming.Video
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();
}
@@ -121,43 +111,47 @@ namespace Emby.Naming.Video
/// 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 bool IsVideoFile(string path)
+ public static bool IsVideoFile(string path, NamingOptions namingOptions)
{
var extension = Path.GetExtension(path.AsSpan());
- return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
+ return namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
/// <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 bool IsStubFile(string path)
+ public static bool IsStubFile(string path, NamingOptions namingOptions)
{
var extension = Path.GetExtension(path.AsSpan());
- return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
+ return namingOptions.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
/// <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 bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName)
+ 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);
}
/// <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 CleanDateTimeResult CleanDateTime(string name)
+ public static CleanDateTimeResult CleanDateTime(string name, NamingOptions namingOptions)
{
- return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
+ return CleanDateTimeParser.Clean(name, namingOptions.CleanDateTimeRegexes);
}
}
}