using System; using System.Globalization; using System.IO; using System.Text.RegularExpressions; namespace Emby.Naming.TV { /// /// Class to parse season paths. /// public static partial class SeasonPathParser { [GeneratedRegex(@"^\s*((?(?>\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*(?.*)$")] 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*(?(?>\d+)(?!\s*[Ee]\d+))(?.*)$")] private static partial Regex ProcessPost(); /// /// Attempts to parse season number from path. /// /// Path to season. /// Folder name of the parent. /// Support special aliases when parsing. /// Support numeric season folders when parsing. /// Returns object. 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, parentFolderName, supportSpecialAliases, supportNumericSeasonFolders); result.SeasonNumber = seasonNumber; if (result.SeasonNumber.HasValue) { result.Success = true; result.IsSeasonFolder = isSeasonFolder; } return result; } /// /// Gets the season number from path. /// /// The path. /// The parent folder name. /// if set to true [support special aliases]. /// if set to true [support numeric season folders]. /// System.Nullable{System.Int32}. 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) { if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase)) { return (0, true); } if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase)) { return (0, true); } } if (supportNumericSeasonFolders) { if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return (val, true); } } if (filename.StartsWith('s')) { var testFilename = filename.AsSpan()[1..]; if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return (val, true); } } var preMatch = ProcessPre().Match(filename); if (preMatch.Success) { return CheckMatch(preMatch); } else { var postMatch = ProcessPost().Match(filename); return CheckMatch(postMatch); } } private static (int? SeasonNumber, bool IsSeasonFolder) CheckMatch(Match match) { var numberString = match.Groups["seasonnumber"]; if (numberString.Success) { var seasonNumber = int.Parse(numberString.Value, CultureInfo.InvariantCulture); return (seasonNumber, true); } return (null, false); } /// /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel"). /// /// The path. /// System.Nullable{System.Int32}. private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan path) { var numericStart = -1; var length = 0; var hasOpenParenthesis = false; var isSeasonFolder = true; // Find out where the numbers start, and then keep going until they end for (var i = 0; i < path.Length; i++) { if (char.IsNumber(path[i])) { if (!hasOpenParenthesis) { if (numericStart == -1) { numericStart = i; } length++; } } else if (numericStart != -1) { // There's other stuff after the season number, e.g. episode number isSeasonFolder = false; break; } var currentChar = path[i]; if (currentChar == '(') { hasOpenParenthesis = true; } else if (currentChar == ')') { hasOpenParenthesis = false; } } if (numericStart == -1) { return (null, isSeasonFolder); } return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder); } } }