/** 附属文件(字幕/外挂音轨)的语言标签,按主干名剥离后与主视频配对 */ const LANG_TAG: Record = { chs: "zh-Hans", sc: "zh-Hans", gb: "zh-Hans", chi: "zh-Hans", zho: "zh-Hans", zh: "zh-Hans", "zh-cn": "zh-Hans", "zh-hans": "zh-Hans", "zh-sg": "zh-Hans", 简体: "zh-Hans", 简中: "zh-Hans", 简: "zh-Hans", cht: "zh-Hant", tc: "zh-Hant", big5: "zh-Hant", "zh-tw": "zh-Hant", "zh-hk": "zh-Hant", "zh-hant": "zh-Hant", 繁体: "zh-Hant", 繁體: "zh-Hant", 繁中: "zh-Hant", 繁: "zh-Hant", eng: "en", en: "en", english: "en", jpn: "ja", ja: "ja", jp: "ja", kor: "ko", ko: "ko", default: "flag", forced: "flag", sdh: "flag", cc: "flag", 简英: "zh-Hans", 繁英: "zh-Hant", 中英: "zh-Hans", 双语: "zh-Hans", 简繁: "zh-Hans", }; export const basename = (p: string) => p.replace(/\\/g, "/").split("/").pop() ?? p; export const dirname = (p: string) => { const s = p.replace(/\\/g, "/"); const i = s.lastIndexOf("/"); return i <= 0 ? (i === 0 ? "/" : "") : s.slice(0, i); }; export const splitExt = (name: string): [string, string] => { const i = name.lastIndexOf("."); return i > 0 ? [name.slice(0, i), name.slice(i + 1)] : [name, ""]; }; /** 字幕主干名:去扩展名后逐级剥掉 .chs/.eng/.default 等语言标签,返回小写主干与剥下的标签 */ export function sidecarStem(name: string): { stem: string; tags: string[] } { let [stem] = splitExt(basename(name)); const tags: string[] = []; for (;;) { const i = stem.lastIndexOf("."); if (i <= 0) break; const tag = stem.slice(i + 1).toLowerCase(); if (!(tag in LANG_TAG)) break; tags.unshift(tag); stem = stem.slice(0, i); } return { stem: stem.toLowerCase(), tags }; } /** 默认字幕语言(对照 MP DEFAULT_SUB):命中的随行字幕加 .default,Jellyfin/Plex/Emby 都认 */ export type DefaultSubtitleLang = "zh-cn" | "zh-tw" | "en" | "ja" | "ko"; const DEFAULT_LANG_SUFFIX: Record = { "zh-cn": ".chi.zh-cn", "zh-tw": ".zh-tw", en: ".eng", ja: ".ja", ko: ".ko", }; /** * 目标字幕后缀(对照 MP:繁体优先判定 → .zh-tw;简体 → .chi.zh-cn;英 → .eng;日 → .ja)。 * 源文件自带 .default,或语言与 defaultLang 一致时加 .default。 */ export function sidecarLanguageSuffix(tags: readonly string[], defaultLang?: DefaultSubtitleLang): string { const langs = tags.map((t) => LANG_TAG[t]); let s = ""; if (langs.includes("zh-Hant")) s = ".zh-tw"; else if (langs.includes("zh-Hans")) s = ".chi.zh-cn"; else if (langs.includes("en")) s = ".eng"; else if (langs.includes("ja")) s = ".ja"; else if (langs.includes("ko")) s = ".ko"; if (tags.includes("default") || (defaultLang && s !== "" && s === DEFAULT_LANG_SUFFIX[defaultLang])) s += ".default"; if (tags.includes("forced")) s += ".forced"; return s; }