diff --git a/Futonizer.csproj b/Futonizer.csproj index 57af9dd..33739f8 100644 --- a/Futonizer.csproj +++ b/Futonizer.csproj @@ -9,8 +9,8 @@ enable Futonizer Futonizer - 1.0.3 - 1.0.3.0 + 1.0.4 + 1.0.4.0 Assets\app.ico diff --git a/Services/FileNameHelper.cs b/Services/FileNameHelper.cs index 60e4f28..ab64a16 100644 --- a/Services/FileNameHelper.cs +++ b/Services/FileNameHelper.cs @@ -9,11 +9,17 @@ namespace Futonizer.Services; /// 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 = + // Folder suffix release tag: "Show Name-TTGA" → tag = "TTGA" + // All-uppercase, 2-8 chars, at end of folder name. + private static readonly Regex FolderSuffixTagRegex = new(@"-([A-Z][A-Z0-9]{1,7})$", RegexOptions.Compiled); + // File suffix release tag: "…HEVC-DemiHuman" → tag = "DemiHuman" + // Mixed case, 3-21 chars (no dots/spaces), at end of filename stem. + // Min 3 chars avoids false-positives like "-v2" or "-HD". + private static readonly Regex FileSuffixTagRegex = + new(@"-([A-Za-z][A-Za-z0-9]{2,20})$", RegexOptions.Compiled); + // Prefix release tag: "[KAA] Show Name" → tag = "KAA" private static readonly Regex PrefixTagRegex = new(@"^\[(.+?)\]", RegexOptions.Compiled); @@ -44,36 +50,51 @@ public static class FileNameHelper /// 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]). + /// Filename suffix tag extraction — a trailing -GroupName on + /// the filename stem is stripped and promoted to a bracket prefix. + /// Takes priority over any folder-derived tag. + /// Show-name injection — if the (now tag-free) 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, sourced from + /// the filename suffix, the folder suffix, or the folder prefix, + /// in that priority order. /// - /// Returns the original filename unchanged when the parent folder provides - /// no actionable information. + /// Returns the original filename unchanged when no transformations apply. /// public static string ComputeOutputFileName(string sourceFilePath) { - string fileName = Path.GetFileName(sourceFilePath); - string? folder = Path.GetDirectoryName(sourceFilePath); - if (string.IsNullOrEmpty(folder)) return fileName; + string fileName = Path.GetFileName(sourceFilePath); + string ext = Path.GetExtension(fileName); + string stemNoExt = Path.GetFileNameWithoutExtension(fileName); + string? folder = Path.GetDirectoryName(sourceFilePath); + string folderName = string.IsNullOrEmpty(folder) ? "" : (Path.GetFileName(folder) ?? ""); - string folderName = Path.GetFileName(folder); - if (string.IsNullOrEmpty(folderName)) return fileName; + // 1. Extract in-filename suffix tag and remove it from the stem. + // e.g. "...HEVC-DemiHuman" → tag="DemiHuman", stem="...HEVC" + string? fileTag = null; + var fileSuffixMatch = FileSuffixTagRegex.Match(stemNoExt); + if (fileSuffixMatch.Success) + { + fileTag = fileSuffixMatch.Groups[1].Value; + stemNoExt = stemNoExt[..fileSuffixMatch.Index]; + fileName = stemNoExt + ext; + } - // 1. Inject show name when only an episode code is present. - string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName); - if (EpisodeOnlyRegex.IsMatch(fileNameNoExt)) + // 2. Determine the release tag: filename suffix > folder tag. + string? tag = fileTag; + if (string.IsNullOrEmpty(tag) && !string.IsNullOrEmpty(folderName)) + tag = ExtractReleaseTag(folderName); + + // 3. Inject show name when only an episode code is present. + if (EpisodeOnlyRegex.IsMatch(stemNoExt) && !string.IsNullOrEmpty(folderName)) { string showName = ExtractShowName(folderName); if (!string.IsNullOrEmpty(showName)) fileName = showName + " " + fileName; } - // 2. Prepend release tag if not already present. - string? tag = ExtractReleaseTag(folderName); + // 4. Prepend release tag if not already present. if (!string.IsNullOrEmpty(tag)) { string tagPrefix = $"[{tag}]"; @@ -91,7 +112,7 @@ public static class FileNameHelper /// public static string? ExtractReleaseTag(string folderName) { - var suffixMatch = SuffixTagRegex.Match(folderName); + var suffixMatch = FolderSuffixTagRegex.Match(folderName); if (suffixMatch.Success) return suffixMatch.Groups[1].Value; var prefixMatch = PrefixTagRegex.Match(folderName); @@ -108,7 +129,7 @@ public static class FileNameHelper public static string ExtractShowName(string folderName) { // Strip release tags first. - string name = SuffixTagRegex.Replace(folderName, "").Trim(); + string name = FolderSuffixTagRegex.Replace(folderName, "").Trim(); name = PrefixTagRegex.Replace(name, "").Trim(); // Collect words up to the first metadata token. diff --git a/publish/Futonizer-v1.0.3/Futonizer.exe b/publish/Futonizer-v1.0.3/Futonizer.exe new file mode 100644 index 0000000..3698eb7 Binary files /dev/null and b/publish/Futonizer-v1.0.3/Futonizer.exe differ diff --git a/publish/Futonizer-v1.0.3/Futonizer.pdb b/publish/Futonizer-v1.0.3/Futonizer.pdb new file mode 100644 index 0000000..829dce1 Binary files /dev/null and b/publish/Futonizer-v1.0.3/Futonizer.pdb differ