using System.IO; namespace Futonizer.Services; /// /// Result of scanning an external subtitle's filename for language and /// display hints. /// public readonly record struct SubtitleLanguageInfo(string Language, bool Forced, bool HearingImpaired, string TrackName); /// /// Best-effort language/flag detection for external subtitle files, based on /// common filename tokens (e.g. "Show.S04E06.eng.srt", "Show.S04E06.forced.srt"). /// Never throws — falls back to an "und" (undetermined) language with no /// track name when nothing recognisable is found. /// public static class SubtitleLanguageHelper { private static readonly Dictionary TokenToIso6392 = new(StringComparer.OrdinalIgnoreCase) { ["en"] = "eng", ["eng"] = "eng", ["english"] = "eng", ["ja"] = "jpn", ["jp"] = "jpn", ["jpn"] = "jpn", ["japanese"] = "jpn", ["es"] = "spa", ["spa"] = "spa", ["spanish"] = "spa", ["esp"] = "spa", ["fr"] = "fre", ["fre"] = "fre", ["fra"] = "fre", ["french"] = "fre", ["de"] = "ger", ["ger"] = "ger", ["deu"] = "ger", ["german"] = "ger", ["it"] = "ita", ["ita"] = "ita", ["italian"] = "ita", ["pt"] = "por", ["por"] = "por", ["portuguese"] = "por", ["ptbr"] = "por", ["ru"] = "rus", ["rus"] = "rus", ["russian"] = "rus", ["zh"] = "chi", ["chi"] = "chi", ["zho"] = "chi", ["chinese"] = "chi", ["cmn"] = "chi", ["yue"] = "chi", ["ko"] = "kor", ["kor"] = "kor", ["korean"] = "kor", ["ar"] = "ara", ["ara"] = "ara", ["arabic"] = "ara", ["nl"] = "dut", ["dut"] = "dut", ["nld"] = "dut", ["dutch"] = "dut", ["sv"] = "swe", ["swe"] = "swe", ["swedish"] = "swe", ["no"] = "nor", ["nor"] = "nor", ["norwegian"] = "nor", ["da"] = "dan", ["dan"] = "dan", ["danish"] = "dan", ["fi"] = "fin", ["fin"] = "fin", ["finnish"] = "fin", ["pl"] = "pol", ["pol"] = "pol", ["polish"] = "pol", ["tr"] = "tur", ["tur"] = "tur", ["turkish"] = "tur", ["vi"] = "vie", ["vie"] = "vie", ["vietnamese"] = "vie", ["th"] = "tha", ["tha"] = "tha", ["thai"] = "tha", ["id"] = "ind", ["ind"] = "ind", ["indonesian"] = "ind", ["he"] = "heb", ["heb"] = "heb", ["hebrew"] = "heb", ["hin"] = "hin", ["hindi"] = "hin", ["cs"] = "cze", ["cze"] = "cze", ["ces"] = "cze", ["czech"] = "cze", ["el"] = "gre", ["gre"] = "gre", ["ell"] = "gre", ["greek"] = "gre", ["hu"] = "hun", ["hun"] = "hun", ["hungarian"] = "hun", ["ro"] = "rum", ["rum"] = "rum", ["ron"] = "rum", ["romanian"] = "rum", ["uk"] = "ukr", ["ukr"] = "ukr", ["ukrainian"] = "ukr", }; private static readonly Dictionary Iso6392ToDisplayName = new(StringComparer.OrdinalIgnoreCase) { ["eng"] = "English", ["jpn"] = "Japanese", ["spa"] = "Spanish", ["fre"] = "French", ["ger"] = "German", ["ita"] = "Italian", ["por"] = "Portuguese", ["rus"] = "Russian", ["chi"] = "Chinese", ["kor"] = "Korean", ["ara"] = "Arabic", ["dut"] = "Dutch", ["swe"] = "Swedish", ["nor"] = "Norwegian", ["dan"] = "Danish", ["fin"] = "Finnish", ["pol"] = "Polish", ["tur"] = "Turkish", ["vie"] = "Vietnamese", ["tha"] = "Thai", ["ind"] = "Indonesian", ["heb"] = "Hebrew", ["hin"] = "Hindi", ["cze"] = "Czech", ["gre"] = "Greek", ["hun"] = "Hungarian", ["rum"] = "Romanian", ["ukr"] = "Ukrainian", }; private static readonly char[] TokenSeparators = { '.', '_', '-', ' ', '(', ')', '[', ']' }; public static SubtitleLanguageInfo Detect(string fileName) { string stem = Path.GetFileNameWithoutExtension(fileName); var tokens = stem.Split(TokenSeparators, StringSplitOptions.RemoveEmptyEntries); string language = "und"; bool forced = false; bool hearingImpaired = false; foreach (var token in tokens) { if (string.Equals(token, "forced", StringComparison.OrdinalIgnoreCase)) { forced = true; continue; } if (string.Equals(token, "sdh", StringComparison.OrdinalIgnoreCase) || string.Equals(token, "cc", StringComparison.OrdinalIgnoreCase)) { hearingImpaired = true; continue; } if (language == "und" && TokenToIso6392.TryGetValue(token, out var iso)) language = iso; } string trackName = string.Empty; if (Iso6392ToDisplayName.TryGetValue(language, out var displayName)) trackName = displayName; if (forced || hearingImpaired) { var suffixes = new List(); if (forced) suffixes.Add("Forced"); if (hearingImpaired) suffixes.Add("SDH"); string suffix = $" ({string.Join(", ", suffixes)})"; trackName = string.IsNullOrEmpty(trackName) ? suffix.Trim(' ', '(', ')') : trackName + suffix; } return new SubtitleLanguageInfo(language, forced, hearingImpaired, trackName); } }