mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
fix(portals): treat an undetermined audio language as unknown (#1689)
* fix(portals): treat an undetermined audio language as unknown ICU 78, which Electron 43 and Node 26 ship, canonicalizes the ffprobe marker `und` to the subtag `und` instead of an empty one. The source metadata then recorded it as a stated language, so a switch between a copy tagged `und` and one tagged English raised the "dub may differ" warning. CI's older ICU hid this: the existing spec only fails on the newer runtime. Decline `und` explicitly, and cover `und-US` plus the dub comparison. * test(portals): cover undetermined language across runtimes --------- Co-authored-by: 4gray <serega05@gmail.com>
This commit is contained in:
1 parent
444ec06e94
commit
1dba959852
4 files changed
+67
-4
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
type: fix
|
||||
area: portals
|
||||
---
|
||||
|
||||
Switching to another playlist's copy of a movie no longer warns that the dub
|
||||
may differ when one copy's audio language is simply unknown (`und`).
|
||||
@@ -776,6 +776,11 @@ tag a language at all, so in practice it is usually silent — which is the
|
||||
honest state, and the same one the rest of this feature takes when it does not
|
||||
know.
|
||||
|
||||
Undetermined language tags (`und`, including regional forms such as `und-US`)
|
||||
are explicitly treated as unknown after normalization. This must hold whether
|
||||
the runtime's `Intl.Locale` returns no language or preserves `und`: neither
|
||||
case establishes a factual dub difference against a known language.
|
||||
|
||||
Switching sources through `startResolvedPlayback` closes the external session
|
||||
it LAUNCHED first — tracked separately so refreshes and overlapping handoffs
|
||||
cannot disown it within the same route session. The retained identity is ignored
|
||||
|
||||
+42
-1
@@ -180,8 +180,10 @@ describe('applyApiMetadata', () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
// ffprobe's marker for "we do not know".
|
||||
// ffprobe's marker for "we do not know", regardless of whether
|
||||
// this runtime's Intl.Locale preserves the language subtag.
|
||||
['und'],
|
||||
['und-US'],
|
||||
['Russian'],
|
||||
['en_US'],
|
||||
[''],
|
||||
@@ -360,6 +362,45 @@ describe('audioDiffersFactually', () => {
|
||||
expect(audioDiffersFactually(from, to)).toBe(false);
|
||||
});
|
||||
|
||||
it('stays silent when one side reports an undetermined language', () => {
|
||||
// `und` is ffprobe admitting it does not know; measured against a
|
||||
// stated English it is not a dub change.
|
||||
const from = candidate(
|
||||
applyApiMetadata(candidate(), { audioLanguage: 'und' })
|
||||
);
|
||||
const to = candidate(
|
||||
applyApiMetadata(candidate(), { audioLanguage: 'eng' })
|
||||
);
|
||||
|
||||
expect(audioDiffersFactually(from, to)).toBe(false);
|
||||
});
|
||||
|
||||
it.each(['und', 'und-US', 'UND'])(
|
||||
'stays silent for %s when Intl.Locale preserves und',
|
||||
(raw) => {
|
||||
const known = applyApiMetadata(candidate(), {
|
||||
audioLanguage: 'eng',
|
||||
});
|
||||
// Exercise the newer runtime behavior even on Node 22. The
|
||||
// captured Locale constructor still reads this prototype getter.
|
||||
const language = jest
|
||||
.spyOn(Intl.Locale.prototype, 'language', 'get')
|
||||
.mockReturnValue('und');
|
||||
|
||||
try {
|
||||
const unknown = applyApiMetadata(candidate(), {
|
||||
audioLanguage: raw,
|
||||
});
|
||||
|
||||
expect(unknown.audioLanguage).toBeUndefined();
|
||||
expect(audioDiffersFactually(unknown, known)).toBe(false);
|
||||
expect(audioDiffersFactually(known, unknown)).toBe(false);
|
||||
} finally {
|
||||
language.mockRestore();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
it('stays silent when both sides state the same track', () => {
|
||||
const from = candidate({
|
||||
audioLanguage: { value: 'rus', provenance: 'api' },
|
||||
|
||||
@@ -217,8 +217,11 @@ function keepFactual(field?: VodSourceField): VodSourceField | undefined {
|
||||
*
|
||||
* `Intl.Locale` does the canonicalizing: 639-2 collapses to 639-1 where one
|
||||
* exists, both German forms land on `de`, and regions drop away. `und` —
|
||||
* ffprobe's marker for undetermined — canonicalizes to nothing, which is
|
||||
* exactly right: it means the provider does not know either.
|
||||
* ffprobe's marker for undetermined — is declined explicitly: it means the
|
||||
* provider does not know either. Some runtimes return no language for this
|
||||
* tag; others (including Electron 43 and Node 26) preserve `und`. Declining
|
||||
* it in either case prevents "unknown vs. English" from becoming a dub
|
||||
* change, independently of the runtime's Intl implementation.
|
||||
*
|
||||
* Anything that survives with more than three characters is not a language
|
||||
* code (`Russian` parses as the subtag `russian`), so the comparison is
|
||||
@@ -232,7 +235,11 @@ function canonicalLanguage(raw: string | null | undefined): string | null {
|
||||
|
||||
try {
|
||||
const language = new LocaleCtor(value).language;
|
||||
return language && language.length <= 3 ? language : null;
|
||||
return language &&
|
||||
language.length <= 3 &&
|
||||
language !== UNDETERMINED_LANGUAGE
|
||||
? language
|
||||
: null;
|
||||
} catch {
|
||||
// Not a well-formed tag at all; saying nothing beats comparing junk.
|
||||
return null;
|
||||
@@ -253,6 +260,9 @@ const LocaleCtor = (
|
||||
}
|
||||
).Locale;
|
||||
|
||||
/** ISO 639-2 "undetermined"; see `canonicalLanguage`. */
|
||||
const UNDETERMINED_LANGUAGE = 'und';
|
||||
|
||||
function cleanString(raw: string | null | undefined): string | null {
|
||||
const trimmed = typeof raw === 'string' ? raw.trim() : '';
|
||||
return trimmed === '' ? null : trimmed;
|
||||
|
||||
Reference in new issue
Block a user