aboutsummaryrefslogtreecommitdiff
path: root/Emby.Naming
diff options
context:
space:
mode:
authorTim Eisele <Ghost_of_Stone@web.de>2025-03-23 17:05:40 +0100
committerGitHub <noreply@github.com>2025-03-23 10:05:40 -0600
commitdfb485d1f205c8eda95bc3b79d341f3c3aef7ec4 (patch)
tree584a53baf9fba5321cd655ae3298bbd7d622f0a9 /Emby.Naming
parent8db6a39e92acfd76689e77c71b00ac96e60c515b (diff)
Rework season folder parsing (#11748)
Diffstat (limited to 'Emby.Naming')
-rw-r--r--Emby.Naming/TV/SeasonPathParser.cs90
1 files changed, 38 insertions, 52 deletions
diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs
index 45b91971b..98ee1e4b8 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -1,43 +1,35 @@
using System;
using System.Globalization;
using System.IO;
+using System.Text.RegularExpressions;
namespace Emby.Naming.TV
{
/// <summary>
/// Class to parse season paths.
/// </summary>
- public static class SeasonPathParser
+ public static partial class SeasonPathParser
{
- /// <summary>
- /// A season folder must contain one of these somewhere in the name.
- /// </summary>
- private static readonly string[] _seasonFolderNames =
- {
- "season",
- "sæson",
- "temporada",
- "saison",
- "staffel",
- "series",
- "сезон",
- "stagione"
- };
-
- private static readonly char[] _splitChars = ['.', '_', ' ', '-'];
+ [GeneratedRegex(@"^\s*((?<seasonnumber>(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<rightpart>.*)$")]
+ private static partial Regex ProcessPre();
+
+ [GeneratedRegex(@"^\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<seasonnumber>(?>\d+)(?!\s*[Ee]\d+))(?<rightpart>.*)$")]
+ private static partial Regex ProcessPost();
/// <summary>
/// Attempts to parse season number from path.
/// </summary>
/// <param name="path">Path to season.</param>
+ /// <param name="parentPath">Folder name of the parent.</param>
/// <param name="supportSpecialAliases">Support special aliases when parsing.</param>
/// <param name="supportNumericSeasonFolders">Support numeric season folders when parsing.</param>
/// <returns>Returns <see cref="SeasonPathParserResult"/> object.</returns>
- public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
+ public static SeasonPathParserResult Parse(string path, string? parentPath, bool supportSpecialAliases, bool supportNumericSeasonFolders)
{
var result = new SeasonPathParserResult();
+ var parentFolderName = parentPath is null ? null : new DirectoryInfo(parentPath).Name;
- var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
+ var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, parentFolderName, supportSpecialAliases, supportNumericSeasonFolders);
result.SeasonNumber = seasonNumber;
@@ -54,15 +46,24 @@ namespace Emby.Naming.TV
/// Gets the season number from path.
/// </summary>
/// <param name="path">The path.</param>
+ /// <param name="parentFolderName">The parent folder name.</param>
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
string path,
+ string? parentFolderName,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
{
string filename = Path.GetFileName(path);
+ filename = Regex.Replace(filename, "[ ._-]", string.Empty);
+
+ if (parentFolderName is not null)
+ {
+ parentFolderName = Regex.Replace(parentFolderName, "[ ._-]", string.Empty);
+ filename = filename.Replace(parentFolderName, string.Empty, StringComparison.OrdinalIgnoreCase);
+ }
if (supportSpecialAliases)
{
@@ -85,53 +86,38 @@ namespace Emby.Naming.TV
}
}
- if (TryGetSeasonNumberFromPart(filename, out int seasonNumber))
+ if (filename.StartsWith('s'))
{
- return (seasonNumber, true);
- }
+ var testFilename = filename.AsSpan()[1..];
- // Look for one of the season folder names
- foreach (var name in _seasonFolderNames)
- {
- if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
+ if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
- var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
- if (result.SeasonNumber.HasValue)
- {
- return result;
- }
-
- break;
+ return (val, true);
}
}
- var parts = filename.Split(_splitChars, StringSplitOptions.RemoveEmptyEntries);
- foreach (var part in parts)
+ var preMatch = ProcessPre().Match(filename);
+ if (preMatch.Success)
{
- if (TryGetSeasonNumberFromPart(part, out seasonNumber))
- {
- return (seasonNumber, true);
- }
+ return CheckMatch(preMatch);
}
-
- return (null, true);
- }
-
- private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber)
- {
- seasonNumber = 0;
- if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
+ else
{
- return false;
+ var postMatch = ProcessPost().Match(filename);
+ return CheckMatch(postMatch);
}
+ }
- if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ private static (int? SeasonNumber, bool IsSeasonFolder) CheckMatch(Match match)
+ {
+ var numberString = match.Groups["seasonnumber"];
+ if (numberString.Success)
{
- seasonNumber = value;
- return true;
+ var seasonNumber = int.Parse(numberString.Value, CultureInfo.InvariantCulture);
+ return (seasonNumber, true);
}
- return false;
+ return (null, false);
}
/// <summary>