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