using System.Globalization;
using System.Text.RegularExpressions;
namespace TeleWave.Application.Library;
/// Разбор сезона/серии из имени файла (SxxEyy либо NxNN). Номера в модели не хранятся —
/// вытаскиваются из имён по мере надобности (сводка в списке шоу, метка в расписании админки).
public static class EpisodeName
{
/// Потолок на один Match. Шаблоны ниже линейные и в катастрофический бэктрекинг не уходят,
/// но имя файла приходит извне (загрузка, inbox), а разбор идёт в цикле по всей библиотеке —
/// страховка стоит ноль, а её отсутствие превращает любую будущую правку шаблона в риск.
private static readonly TimeSpan MatchTimeout = TimeSpan.FromSeconds(1);
private static readonly Regex SxxEyy = new(
@"[Ss](\d{1,2})[ ._-]*[Ee](\d{1,3})",
RegexOptions.Compiled,
MatchTimeout
);
private static readonly Regex NxNN = new(
@"(?:^|[^\d])(\d{1,2})x(\d{1,3})",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
MatchTimeout
);
// Ведущий номер серии: «01. Название», «02 - Название», «03_Название» — сезон считаем первым.
private static readonly Regex LeadingNumber = new(
@"^\s*(\d{1,3})[\s._)\]-]",
RegexOptions.Compiled,
MatchTimeout
);
public static (int Season, int Episode)? Parse(string? name)
{
if (string.IsNullOrEmpty(name))
return null;
var match = SxxEyy.Match(name);
if (match.Success)
return (Int(match, 1), Int(match, 2));
match = NxNN.Match(name);
if (match.Success)
return (Int(match, 1), Int(match, 2));
match = LeadingNumber.Match(name);
if (match.Success)
return (1, Int(match, 1));
return null;
}
private static int Int(Match match, int group) =>
int.Parse(match.Groups[group].Value, CultureInfo.InvariantCulture);
public static int? ParseSeason(string? name) => Parse(name)?.Season;
/// Метка вида «S16E03», либо null если распознать не удалось.
public static string? ParseLabel(string? name) =>
Parse(name) is { } p ? $"S{p.Season:D2}E{p.Episode:D2}" : null;
}