Inject show name for bare-number episode filenames

Previously, only S01E01-style episode codes triggered show-name
injection from the folder name. Plain numbered filenames (e.g.
"11 - Title.mkv") with a release tag sourced only from the folder
(e.g. "[DmonHiro] Digimon Tamers (BD, 1080p)") were left untouched
apart from the tag prefix.

Also fixes ExtractShowName to strip trailing parenthetical metadata
blocks like "(BD, 1080p)", which previously leaked into the derived
show name.
This commit is contained in:
2026-07-24 14:30:34 +02:00
parent 05fbe34e20
commit 7aa8b44f65
2 changed files with 37 additions and 4 deletions
+2 -2
View File
@@ -9,8 +9,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>Futonizer</AssemblyName>
<RootNamespace>Futonizer</RootNamespace>
<Version>1.0.13</Version>
<FileVersion>1.0.13.0</FileVersion>
<Version>1.0.14</Version>
<FileVersion>1.0.14.0</FileVersion>
<ApplicationIcon>Assets\app.ico</ApplicationIcon>
</PropertyGroup>
+35 -2
View File
@@ -60,6 +60,14 @@ public static class FileNameHelper
private static readonly Regex EpisodeStartRegex =
new(@"^(\d+|S\d+E\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
// Bare leading episode number with no tag of its own on the file (the
// release tag, if any, comes only from the folder), e.g. "11 - Title.mkv",
// "06_Title.mkv", "06.Title.mkv". Requires a separator right after the
// digits so resolution/quality tokens glued to more characters (e.g.
// "1080p") aren't mistaken for an episode number lead-in.
private static readonly Regex BareLeadingEpisodeNumberRegex =
new(@"^\d{1,3}(?=[\s._-])", RegexOptions.Compiled);
// Individual metadata-token patterns used to find where the show name ends
// inside a folder name.
private static readonly Regex[] MetadataPatterns =
@@ -104,8 +112,9 @@ public static class FileNameHelper
/// stripped and promoted to a bracket prefix. Takes priority over
/// any folder-derived tag.</item>
/// <item>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.</item>
/// an episode code (e.g. S01E01), or with a bare episode number
/// (e.g. "11 - Title") once a release tag has been resolved, the
/// show name is derived from the parent folder and prepended.</item>
/// <item>Release-tag prefix — a <c>[TAG]</c> bracket prefix, sourced from
/// the filename suffix, the folder suffix, or the folder prefix,
/// in that priority order.</item>
@@ -211,6 +220,17 @@ public static class FileNameHelper
}
}
}
// Pattern C: file has no tag of its own — the tag came only from
// the folder — but the filename still starts with a bare
// episode number, e.g. "11 - Title.mkv" in a
// "[DmonHiro] Show Name (BD, 1080p)" folder. Inject the show
// name here; the tag is prepended separately in step 4.
else if (BareLeadingEpisodeNumberRegex.IsMatch(stemNoExt))
{
string showName = ExtractShowName(folderName);
if (!string.IsNullOrEmpty(showName))
fileName = showName + " " + fileName;
}
}
}
@@ -273,6 +293,19 @@ public static class FileNameHelper
name = FolderSuffixBracketTagRegex.Replace(name, "").Trim();
name = PrefixTagRegex.Replace(name, "").Trim();
// Strip a trailing parenthesised metadata block, e.g. "(BD, 1080p)",
// when every comma/space-separated word inside it is recognisable
// quality/source metadata — so a real release group or descriptive
// parenthetical left at the end of the name isn't mistakenly dropped.
var trailingParenMatch = TrailingParenGroupRegex.Match(name);
if (trailingParenMatch.Success)
{
var parenWords = trailingParenMatch.Groups[1].Value
.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parenWords.Length > 0 && parenWords.All(IsMetadataToken))
name = name[..trailingParenMatch.Index].Trim();
}
// Nothing left, or what remains immediately starts with a bracket — no show name.
if (string.IsNullOrEmpty(name) || name[0] == '[') return string.Empty;