Bump version to 1.0.3
- Suffix release tag support: folder name ending in -TAG prepends [TAG] to output filenames - Show name injection from parent folder for episode-only filenames (e.g. S01E01-Name.mkv) - Queue and logs display predicted output filename - Run button label reflects rename when applicable - Gray out main UI while strip/rename is in progress
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Futonizer.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Pure static helpers for computing the final output filename from a source
|
||||
/// file path, applying release-tag prefixing and show-name injection.
|
||||
/// </summary>
|
||||
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
|
||||
];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Computes the final output filename for a given source file path, applying
|
||||
/// (in order):
|
||||
/// <list type="number">
|
||||
/// <item>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.</item>
|
||||
/// <item>Release-tag prefix — a <c>[TAG]</c> bracket prefix derived from
|
||||
/// the parent folder name, either from a suffix pattern (<c>-TAG</c>)
|
||||
/// or a prefix pattern (<c>[TAG]</c>).</item>
|
||||
/// </list>
|
||||
/// Returns the original filename unchanged when the parent folder provides
|
||||
/// no actionable information.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the release tag string (without brackets) from a folder name.
|
||||
/// Suffix pattern (<c>-TAG</c>) takes priority over prefix pattern
|
||||
/// (<c>[TAG]</c>). Returns <c>null</c> when neither is present.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.).
|
||||
/// </summary>
|
||||
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<string>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user