jellyfin/Emby.Naming/TV/EpisodePathParser.cs

243 lines
9.3 KiB
C#
Raw Normal View History

2018-09-12 17:26:21 +00:00
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
2019-01-13 19:17:29 +00:00
using Emby.Naming.Common;
2018-09-12 17:26:21 +00:00
namespace Emby.Naming.TV
{
2020-11-10 16:11:48 +00:00
/// <summary>
/// Used to parse information about episode from path.
/// </summary>
2018-09-12 17:26:21 +00:00
public class EpisodePathParser
{
private readonly NamingOptions _options;
2020-11-10 16:11:48 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="EpisodePathParser"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing EpisodeExpressions and MultipleEpisodeExpressions.</param>
2018-09-12 17:26:21 +00:00
public EpisodePathParser(NamingOptions options)
{
_options = options;
}
2020-11-10 16:11:48 +00:00
/// <summary>
/// Parses information about episode from path.
/// </summary>
/// <param name="path">Path.</param>
/// <param name="isDirectory">Is path for a directory or file.</param>
/// <param name="isNamed">Do we want to use IsNamed expressions.</param>
/// <param name="isOptimistic">Do we want to use Optimistic expressions.</param>
/// <param name="supportsAbsoluteNumbers">Do we want to use expressions supporting absolute episode numbers.</param>
/// <param name="fillExtendedInfo">Should we attempt to retrieve extended information.</param>
/// <returns>Returns <see cref="EpisodePathParserResult"/> object.</returns>
2020-03-25 16:53:03 +00:00
public EpisodePathParserResult Parse(
string path,
bool isDirectory,
bool? isNamed = null,
bool? isOptimistic = null,
bool? supportsAbsoluteNumbers = null,
bool fillExtendedInfo = true)
2018-09-12 17:26:21 +00:00
{
// Added to be able to use regex patterns which require a file extension.
// There were no failed tests without this block, but to be safe, we can keep it until
// the regex which require file extensions are modified so that they don't need them.
2019-05-10 18:37:42 +00:00
if (isDirectory)
{
2018-09-12 17:26:21 +00:00
path += ".mp4";
}
2018-09-12 17:26:21 +00:00
2020-01-22 21:18:56 +00:00
EpisodePathParserResult? result = null;
2018-09-12 17:26:21 +00:00
foreach (var expression in _options.EpisodeExpressions)
{
2019-05-10 18:37:42 +00:00
if (supportsAbsoluteNumbers.HasValue
&& expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
continue;
2018-09-12 17:26:21 +00:00
}
2019-05-10 18:37:42 +00:00
if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
continue;
2018-09-12 17:26:21 +00:00
}
2019-05-10 18:37:42 +00:00
if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
continue;
2018-09-12 17:26:21 +00:00
}
var currentResult = Parse(path, expression);
if (currentResult.Success)
{
result = currentResult;
break;
}
}
2022-12-05 14:01:13 +00:00
if (result is not null && fillExtendedInfo)
2018-09-12 17:26:21 +00:00
{
FillAdditional(path, result);
if (!string.IsNullOrEmpty(result.SeriesName))
{
result.SeriesName = result.SeriesName
.Trim()
2020-03-25 16:53:03 +00:00
.Trim('_', '.', '-')
2018-09-12 17:26:21 +00:00
.Trim();
}
}
return result ?? new EpisodePathParserResult();
}
private static EpisodePathParserResult Parse(string name, EpisodeExpression expression)
2018-09-12 17:26:21 +00:00
{
var result = new EpisodePathParserResult();
// This is a hack to handle wmc naming
if (expression.IsByDate)
{
name = name.Replace('_', '-');
}
var match = expression.Regex.Match(name);
// (Full)(Season)(Episode)(Extension)
if (match.Success && match.Groups.Count >= 3)
{
if (expression.IsByDate)
{
DateTime date;
if (expression.DateTimeFormats.Length > 0)
{
2019-05-10 18:37:42 +00:00
if (DateTime.TryParseExact(
2023-02-17 14:00:06 +00:00
match.Groups[0].ValueSpan,
2018-09-12 17:26:21 +00:00
expression.DateTimeFormats,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out date))
{
result.Year = date.Year;
result.Month = date.Month;
result.Day = date.Day;
result.Success = true;
}
}
2023-02-17 14:00:06 +00:00
else if (DateTime.TryParse(match.Groups[0].ValueSpan, out date))
2018-09-12 17:26:21 +00:00
{
2019-05-10 18:37:42 +00:00
result.Year = date.Year;
result.Month = date.Month;
result.Day = date.Day;
result.Success = true;
2018-09-12 17:26:21 +00:00
}
// TODO: Only consider success if date successfully parsed?
result.Success = true;
}
else if (expression.IsNamed)
{
2023-02-17 14:00:06 +00:00
if (int.TryParse(match.Groups["seasonnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
2018-09-12 17:26:21 +00:00
{
result.SeasonNumber = num;
}
2023-02-17 14:00:06 +00:00
if (int.TryParse(match.Groups["epnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
2018-09-12 17:26:21 +00:00
{
result.EpisodeNumber = num;
}
2019-01-13 20:37:13 +00:00
var endingNumberGroup = match.Groups["endingepnumber"];
2018-09-12 17:26:21 +00:00
if (endingNumberGroup.Success)
{
2020-01-18 15:18:55 +00:00
// Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers
2018-09-12 17:26:21 +00:00
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
2019-05-10 18:37:42 +00:00
if (nextIndex >= name.Length
2020-01-22 21:18:56 +00:00
|| !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
2018-09-12 17:26:21 +00:00
{
2023-02-17 14:00:06 +00:00
if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
2018-09-12 17:26:21 +00:00
{
2020-11-01 10:19:22 +00:00
result.EndingEpisodeNumber = num;
2018-09-12 17:26:21 +00:00
}
}
}
result.SeriesName = match.Groups["seriesname"].Value;
result.Success = result.EpisodeNumber.HasValue;
}
else
{
2023-02-17 14:00:06 +00:00
if (int.TryParse(match.Groups[1].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
2018-09-12 17:26:21 +00:00
{
result.SeasonNumber = num;
}
2019-05-10 18:37:42 +00:00
2023-02-17 14:00:06 +00:00
if (int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
2018-09-12 17:26:21 +00:00
{
result.EpisodeNumber = num;
}
result.Success = result.EpisodeNumber.HasValue;
}
// Invalidate match when the season is 200 through 1927 or above 2500
// because it is an error unless the TV show is intentionally using false season numbers.
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
2019-05-10 18:37:42 +00:00
if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
|| result.SeasonNumber > 2500)
{
2018-09-12 17:26:21 +00:00
result.Success = false;
2019-05-10 18:37:42 +00:00
}
2018-09-12 17:26:21 +00:00
result.IsByDate = expression.IsByDate;
}
return result;
}
private void FillAdditional(string path, EpisodePathParserResult info)
{
2020-11-07 10:02:12 +00:00
var expressions = _options.MultipleEpisodeExpressions.Where(i => i.IsNamed).ToList();
2018-09-12 17:26:21 +00:00
if (string.IsNullOrEmpty(info.SeriesName))
{
expressions.InsertRange(0, _options.EpisodeExpressions.Where(i => i.IsNamed));
}
FillAdditional(path, info, expressions);
}
private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
{
foreach (var i in expressions)
2018-09-12 17:26:21 +00:00
{
var result = Parse(path, i);
if (!result.Success)
{
continue;
}
2018-09-12 17:26:21 +00:00
if (string.IsNullOrEmpty(info.SeriesName))
{
info.SeriesName = result.SeriesName;
}
2020-11-01 10:19:22 +00:00
if (!info.EndingEpisodeNumber.HasValue && info.EpisodeNumber.HasValue)
2018-09-12 17:26:21 +00:00
{
2020-11-01 10:19:22 +00:00
info.EndingEpisodeNumber = result.EndingEpisodeNumber;
2018-09-12 17:26:21 +00:00
}
if (!string.IsNullOrEmpty(info.SeriesName)
2020-11-01 10:19:22 +00:00
&& (!info.EpisodeNumber.HasValue || info.EndingEpisodeNumber.HasValue))
2018-09-12 17:26:21 +00:00
{
break;
2018-09-12 17:26:21 +00:00
}
}
}
}
}