jellyfin-server/Emby.Naming/TV/SeasonPathParser.cs

195 lines
6.5 KiB
C#
Raw Normal View History

using System;
2018-09-12 17:26:21 +00:00
using System.Globalization;
using System.IO;
namespace Emby.Naming.TV
{
2020-11-10 18:23:10 +00:00
/// <summary>
/// Class to parse season paths.
/// </summary>
2020-01-22 21:18:56 +00:00
public static class SeasonPathParser
2018-09-12 17:26:21 +00:00
{
2020-01-22 21:18:56 +00:00
/// <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"
};
2020-11-10 18:23:10 +00:00
/// <summary>
/// Attempts to parse season number from path.
/// </summary>
/// <param name="path">Path to season.</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>
2020-01-22 21:18:56 +00:00
public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
2018-09-12 17:26:21 +00:00
{
var result = new SeasonPathParserResult();
2020-02-19 20:56:35 +00:00
var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
2018-09-12 17:26:21 +00:00
2020-02-19 20:56:35 +00:00
result.SeasonNumber = seasonNumber;
2018-09-12 17:26:21 +00:00
if (result.SeasonNumber.HasValue)
{
result.Success = true;
2020-02-19 20:56:35 +00:00
result.IsSeasonFolder = isSeasonFolder;
2018-09-12 17:26:21 +00:00
}
return result;
}
/// <summary>
/// Gets the season number from path.
/// </summary>
/// <param name="path">The path.</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>
2019-05-10 18:37:42 +00:00
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
string path,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
var filename = Path.GetFileName(path) ?? string.Empty;
2018-09-12 17:26:21 +00:00
if (supportSpecialAliases)
{
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
{
2019-05-10 18:37:42 +00:00
return (0, true);
2018-09-12 17:26:21 +00:00
}
2019-05-10 18:37:42 +00:00
2018-09-12 17:26:21 +00:00
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
{
2019-05-10 18:37:42 +00:00
return (0, true);
2018-09-12 17:26:21 +00:00
}
}
if (supportNumericSeasonFolders)
{
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
return (val, true);
2018-09-12 17:26:21 +00:00
}
}
if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase))
{
var testFilename = filename.AsSpan().Slice(1);
2018-09-12 17:26:21 +00:00
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
return (val, true);
2018-09-12 17:26:21 +00:00
}
}
// Look for one of the season folder names
2019-05-10 18:37:42 +00:00
foreach (var name in _seasonFolderNames)
2018-09-12 17:26:21 +00:00
{
2020-02-19 20:56:35 +00:00
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
2018-09-12 17:26:21 +00:00
{
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
2020-02-19 20:56:35 +00:00
if (result.seasonNumber.HasValue)
2018-09-12 17:26:21 +00:00
{
return result;
}
break;
}
}
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
2020-11-01 10:19:22 +00:00
foreach (var part in parts)
2020-02-19 20:56:35 +00:00
{
2020-11-01 10:19:22 +00:00
if (TryGetSeasonNumberFromPart(part, out int seasonNumber))
2020-02-19 20:56:35 +00:00
{
return (seasonNumber, true);
}
}
return (null, true);
2018-09-12 17:26:21 +00:00
}
2020-02-19 20:56:35 +00:00
private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber)
2018-09-12 17:26:21 +00:00
{
2020-02-19 20:56:35 +00:00
seasonNumber = 0;
2018-09-12 17:26:21 +00:00
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
{
2020-02-19 20:56:35 +00:00
return false;
2018-09-12 17:26:21 +00:00
}
2020-02-19 20:56:35 +00:00
if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
2018-09-12 17:26:21 +00:00
{
2020-02-19 20:56:35 +00:00
seasonNumber = value;
return true;
2018-09-12 17:26:21 +00:00
}
2020-02-19 20:56:35 +00:00
return false;
2018-09-12 17:26:21 +00:00
}
/// <summary>
2019-10-25 10:47:20 +00:00
/// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel").
2018-09-12 17:26:21 +00:00
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
2020-02-19 20:56:35 +00:00
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
2018-09-12 17:26:21 +00:00
{
var numericStart = -1;
var length = 0;
2020-11-01 10:19:22 +00:00
var hasOpenParenthesis = false;
2018-09-12 17:26:21 +00:00
var isSeasonFolder = true;
// Find out where the numbers start, and then keep going until they end
for (var i = 0; i < path.Length; i++)
{
2020-02-19 20:56:35 +00:00
if (char.IsNumber(path[i]))
2018-09-12 17:26:21 +00:00
{
2020-11-01 10:19:22 +00:00
if (!hasOpenParenthesis)
2018-09-12 17:26:21 +00:00
{
if (numericStart == -1)
{
numericStart = i;
}
2020-01-22 21:18:56 +00:00
2018-09-12 17:26:21 +00:00
length++;
}
}
else if (numericStart != -1)
{
// There's other stuff after the season number, e.g. episode number
isSeasonFolder = false;
break;
}
var currentChar = path[i];
2020-01-22 21:18:56 +00:00
if (currentChar == '(')
2018-09-12 17:26:21 +00:00
{
2020-11-01 10:19:22 +00:00
hasOpenParenthesis = true;
2018-09-12 17:26:21 +00:00
}
2020-01-22 21:18:56 +00:00
else if (currentChar == ')')
2018-09-12 17:26:21 +00:00
{
2020-11-01 10:19:22 +00:00
hasOpenParenthesis = false;
2018-09-12 17:26:21 +00:00
}
}
if (numericStart == -1)
{
2019-05-10 18:37:42 +00:00
return (null, isSeasonFolder);
2018-09-12 17:26:21 +00:00
}
2020-02-19 20:56:35 +00:00
return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder);
2018-09-12 17:26:21 +00:00
}
}
}