using System.IO; using System.Text.RegularExpressions; namespace Futonizer.Services; /// /// Pure static helpers for computing the final output filename from a source /// file path, applying release-tag prefixing and show-name injection. /// public static class FileNameHelper { // Suffix release tag: "Show Name-TTGA" → tag = "TTGA" // Must be 2-8 uppercase letters/digits at the very end, preceded by a dash. private static readonly Regex SuffixTagRegex = new(@"-([A-Z][A-Z0-9]{1,7})$", RegexOptions.Compiled); // Prefix release tag: "[KAA] Show Name" → tag = "KAA" private static readonly Regex PrefixTagRegex = new(@"^\[(.+?)\]", RegexOptions.Compiled); // Episode-only filename: starts with S01E01, S1E1, etc. (no show name present) private static readonly Regex EpisodeOnlyRegex = new(@"^S\d+E\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled); // Individual metadata-token patterns used to find where the show name ends // inside a folder name. private static readonly Regex[] MetadataPatterns = [ new(@"^S\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // S01 new(@"^S\d+E\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled), // S01E01... new(@"^E[Pp]?\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // E01, EP01 new(@"^\d{3,4}[pP]$", RegexOptions.Compiled), // 1080p, 720p new(@"^(4K|UHD|2160p)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // 4K, UHD new(@"^(BD|BluRay|Blu-Ray|WEB|WEB-DL|WEBRip|HDTV|AMZN|NF|DSNP|HULU|MAX)$",RegexOptions.IgnoreCase | RegexOptions.Compiled), // sources new(@"^(Remux|Encode)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // processing new(@"^(HEVC|AVC|x264|x265|H\.?264|H\.?265|VP9)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // video codecs new(@"^(FLAC|AAC|DTS|TrueHD|Atmos|AC3|EAC3|Opus|MP3)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // audio codecs new(@"^(HDR|SDR|10bit|8bit|HDR10|DV|DoVi)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // HDR / bit-depth ]; // ------------------------------------------------------------------------- /// /// Computes the final output filename for a given source file path, applying /// (in order): /// /// Show-name injection — if the filename starts with an episode code /// (e.g. S01E01) the show name is derived from the parent folder and /// prepended. /// Release-tag prefix — a [TAG] bracket prefix derived from /// the parent folder name, either from a suffix pattern (-TAG) /// or a prefix pattern ([TAG]). /// /// Returns the original filename unchanged when the parent folder provides /// no actionable information. /// public static string ComputeOutputFileName(string sourceFilePath) { string fileName = Path.GetFileName(sourceFilePath); string? folder = Path.GetDirectoryName(sourceFilePath); if (string.IsNullOrEmpty(folder)) return fileName; string folderName = Path.GetFileName(folder); if (string.IsNullOrEmpty(folderName)) return fileName; // 1. Inject show name when only an episode code is present. string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName); if (EpisodeOnlyRegex.IsMatch(fileNameNoExt)) { string showName = ExtractShowName(folderName); if (!string.IsNullOrEmpty(showName)) fileName = showName + " " + fileName; } // 2. Prepend release tag if not already present. string? tag = ExtractReleaseTag(folderName); if (!string.IsNullOrEmpty(tag)) { string tagPrefix = $"[{tag}]"; if (!fileName.StartsWith(tagPrefix, StringComparison.OrdinalIgnoreCase)) fileName = tagPrefix + " " + fileName; } return fileName; } /// /// Extracts the release tag string (without brackets) from a folder name. /// Suffix pattern (-TAG) takes priority over prefix pattern /// ([TAG]). Returns null when neither is present. /// public static string? ExtractReleaseTag(string folderName) { var suffixMatch = SuffixTagRegex.Match(folderName); if (suffixMatch.Success) return suffixMatch.Groups[1].Value; var prefixMatch = PrefixTagRegex.Match(folderName); if (prefixMatch.Success) return prefixMatch.Groups[1].Value; return null; } /// /// Derives the show name from a folder name by stripping any release tag /// (prefix or suffix) and then taking all words that precede the first /// recognised metadata token (season, quality, source, codec, etc.). /// public static string ExtractShowName(string folderName) { // Strip release tags first. string name = SuffixTagRegex.Replace(folderName, "").Trim(); name = PrefixTagRegex.Replace(name, "").Trim(); // Collect words up to the first metadata token. var words = name.Split(' ', StringSplitOptions.RemoveEmptyEntries); var showWords = new List(); foreach (var word in words) { if (IsMetadataToken(word)) break; showWords.Add(word); } return string.Join(" ", showWords).Trim(); } private static bool IsMetadataToken(string token) { foreach (var pattern in MetadataPatterns) if (pattern.IsMatch(token)) return true; return false; } }