From 3903fd475a632afa5948a98f5390bbd006f30b9c Mon Sep 17 00:00:00 2001 From: l4kr Date: Tue, 14 Jul 2026 08:33:11 +0200 Subject: [PATCH] Fix double-tagging when file already has a release prefix Fansub descriptor brackets like [Multiple Subtitle] or [Dual Audio] were being misread as release group names because their individual words didn't match any known metadata pattern. This caused a bogus tag to be stacked in front of files that already carried their own [Group] prefix (e.g. Erai-raws releases). - Recognize common multi-word fansub descriptors (Multi(ple) Sub(s), Dual/Multi Audio, Hard/Soft sub, Uncensored/Batch/Complete, etc.) as metadata via whole-phrase matching in IsMetadataBracket. - Never prepend a derived release tag when the file's own filename already starts with a different bracket prefix, preventing any tag from being stacked on top of an existing one. --- Futonizer.csproj | 4 ++-- Services/FileNameHelper.cs | 40 +++++++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Futonizer.csproj b/Futonizer.csproj index 89413cc..bb34256 100644 --- a/Futonizer.csproj +++ b/Futonizer.csproj @@ -9,8 +9,8 @@ enable Futonizer Futonizer - 1.0.9 - 1.0.9.0 + 1.0.10 + 1.0.10.0 Assets\app.ico diff --git a/Services/FileNameHelper.cs b/Services/FileNameHelper.cs index 48edc75..965ef8c 100644 --- a/Services/FileNameHelper.cs +++ b/Services/FileNameHelper.cs @@ -67,6 +67,19 @@ public static class FileNameHelper new(@"^(HDR|SDR|10bit|8bit|HDR10|DV|DoVi)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), // HDR / bit-depth ]; + // Whole-phrase fansub/release descriptor patterns, checked against an + // entire bracket's contents rather than word-by-word. Catches common + // multi-word descriptors (e.g. "Multiple Subtitle", "Dual Audio") whose + // individual words wouldn't otherwise match any MetadataPatterns entry, + // so they'd be mistaken for a release group name. + private static readonly Regex[] DescriptorPhrasePatterns = + [ + new(@"^Multi(ple)?[\s-]?Sub(s|title|titles)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled), + new(@"^(Dual|Multi)[\s-]?Audio$", RegexOptions.IgnoreCase | RegexOptions.Compiled), + new(@"^(Hard|Soft)[\s-]?sub(s)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled), + new(@"^(Uncensored|Censored|Batch|Complete)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), + ]; + // ------------------------------------------------------------------------- /// @@ -95,6 +108,12 @@ public static class FileNameHelper string? folder = Path.GetDirectoryName(sourceFilePath); string folderName = string.IsNullOrEmpty(folder) ? "" : (Path.GetFileName(folder) ?? ""); + // Capture the file's own pre-existing bracket prefix (if any) before any + // transformation below, so we never stack a second/different tag on top + // of a file that already carries its own release-group prefix. + var originalPrefixMatch = PrefixTagRegex.Match(stemNoExt); + string? originalPrefixTag = originalPrefixMatch.Success ? originalPrefixMatch.Groups[1].Value : null; + // 1. Extract in-filename suffix tag and remove it from the stem. // e.g. "...x264-DemiHuman" → tag="DemiHuman", stem="...x264" // Only fires when the word right before the dash is itself a @@ -172,12 +191,20 @@ public static class FileNameHelper } } - // 4. Prepend release tag if not already present. + // 4. Prepend release tag if not already present. Skip when the file + // already carries its own (different) bracket prefix — never stack + // a second tag on top of an existing one. if (!string.IsNullOrEmpty(tag)) { - string tagPrefix = $"[{tag}]"; - if (!fileName.StartsWith(tagPrefix, StringComparison.OrdinalIgnoreCase)) - fileName = tagPrefix + " " + fileName; + bool differsFromOriginalTag = originalPrefixTag != null + && !string.Equals(originalPrefixTag, tag, StringComparison.OrdinalIgnoreCase); + + if (!differsFromOriginalTag) + { + string tagPrefix = $"[{tag}]"; + if (!fileName.StartsWith(tagPrefix, StringComparison.OrdinalIgnoreCase)) + fileName = tagPrefix + " " + fileName; + } } return fileName; @@ -258,7 +285,10 @@ public static class FileNameHelper /// private static bool IsMetadataBracket(string content) { - var parts = content.Split(new[] { ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); + string trimmed = content.Trim(); + if (DescriptorPhrasePatterns.Any(p => p.IsMatch(trimmed))) return true; + + var parts = trimmed.Split(new[] { ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); return parts.Length > 0 && parts.All(IsMetadataToken); } }