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; }