9d65dd12be
Build and Push Docker Image / build (push) Successful in 2m10s
- clickable error count in header shows file names + error messages - inbox sort dropdown (scan time / name, asc / desc) - inbox movies no longer minimal (show available badges) - stop buttons use solid danger style, descriptive labels (Stop Scan, Stop Job, Stop Sorting) - double checkmarks overlap like WhatsApp read receipts - processInbox logs start/completion to stdout for Docker visibility - fix byTitle in language-resolver test, bump to 2026.04.21.1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
93 lines
1.9 KiB
TypeScript
93 lines
1.9 KiB
TypeScript
// ISO 639-1 (2-letter) → ISO 639-2/B (3-letter)
|
|
const ISO_1_TO_2: Record<string, string> = {
|
|
en: "eng",
|
|
de: "deu",
|
|
es: "spa",
|
|
fr: "fra",
|
|
it: "ita",
|
|
pt: "por",
|
|
ja: "jpn",
|
|
ko: "kor",
|
|
zh: "zho",
|
|
ar: "ara",
|
|
ru: "rus",
|
|
nl: "nld",
|
|
sv: "swe",
|
|
no: "nor",
|
|
da: "dan",
|
|
fi: "fin",
|
|
pl: "pol",
|
|
tr: "tur",
|
|
th: "tha",
|
|
hi: "hin",
|
|
hu: "hun",
|
|
cs: "ces",
|
|
ro: "ron",
|
|
el: "ell",
|
|
he: "heb",
|
|
fa: "fas",
|
|
uk: "ukr",
|
|
id: "ind",
|
|
ca: "cat",
|
|
nb: "nob",
|
|
nn: "nno",
|
|
is: "isl",
|
|
hr: "hrv",
|
|
sk: "slk",
|
|
bg: "bul",
|
|
sr: "srp",
|
|
sl: "slv",
|
|
lv: "lav",
|
|
lt: "lit",
|
|
et: "est",
|
|
vi: "vie",
|
|
ms: "msa",
|
|
ta: "tam",
|
|
te: "tel",
|
|
};
|
|
|
|
// ISO 639-2/T → ISO 639-2/B normalization + common aliases
|
|
const LANG_ALIASES: Record<string, string> = {
|
|
ger: "deu",
|
|
chi: "zho",
|
|
fre: "fra",
|
|
dut: "nld",
|
|
gre: "ell",
|
|
heb: "heb",
|
|
per: "fas",
|
|
rum: "ron",
|
|
may: "msa",
|
|
tib: "bod",
|
|
bur: "mya",
|
|
cze: "ces",
|
|
slo: "slk",
|
|
geo: "kat",
|
|
ice: "isl",
|
|
arm: "hye",
|
|
baq: "eus",
|
|
alb: "sqi",
|
|
mac: "mkd",
|
|
wel: "cym",
|
|
};
|
|
|
|
export function normalizeLanguage(lang: string): string {
|
|
const lower = lang.toLowerCase().trim();
|
|
if (ISO_1_TO_2[lower]) return ISO_1_TO_2[lower];
|
|
return LANG_ALIASES[lower] ?? lower;
|
|
}
|
|
|
|
const DUB_TITLE_HINTS = /(dub|dubb|synchro|commentary|director)/i;
|
|
|
|
/**
|
|
* Guess original language from audio streams by looking at the default track.
|
|
* Heuristic: prefer the default audio track, skip dubs/commentary, fall back to first.
|
|
*/
|
|
export function guessOriginalLanguage(
|
|
audioStreams: { language: string | null; title: string | null; isDefault: number }[],
|
|
): string | null {
|
|
if (audioStreams.length === 0) return null;
|
|
const notDub = (s: { title: string | null }) => !s.title || !DUB_TITLE_HINTS.test(s.title);
|
|
const pick = audioStreams.find((s) => s.isDefault && notDub(s)) ?? audioStreams.find(notDub) ?? audioStreams[0];
|
|
return pick.language ? normalizeLanguage(pick.language) : null;
|
|
}
|