All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
Two bugs compounded: 1. extractOriginalLanguage() in jellyfin.ts picked the FIRST audio stream's language and called it 'original'. Files sourced from non-English regions often have a local dub as track 0, so 8 Mile with a Turkish dub first got labelled Turkish. 2. scan.ts promoted any single-source answer to confidence='high' — even the pure Jellyfin guess, as long as no second source (Radarr/Sonarr) contradicted it. Jellyfin's dub-magnet guess should never be green. Fixes: - extractOriginalLanguage now prefers the IsDefault audio track and skips tracks whose title shouts 'dub' / 'commentary' / 'director'. Still a heuristic, but much less wrong. Fallback to the first track when every candidate looks like a dub so we have *something* to flag. - scan.ts: high confidence requires an authoritative source (Radarr/Sonarr) with no conflict. A Jellyfin-only answer is always low confidence AND gets needs_review=1 so it surfaces in the pipeline for manual override. - Data migration (idempotent): downgrade existing plans backed only by the Jellyfin heuristic to low confidence and mark needs_review=1, so users don't have to rescan to benefit. - New server/services/__tests__/jellyfin.test.ts covers the default-track preference and dub-skip behavior.
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import type { JellyfinItem, JellyfinMediaStream } from "../../types";
|
|
import { extractOriginalLanguage } from "../jellyfin";
|
|
|
|
function audio(o: Partial<JellyfinMediaStream>): JellyfinMediaStream {
|
|
return { Type: "Audio", Index: 0, ...o };
|
|
}
|
|
|
|
function item(streams: JellyfinMediaStream[]): JellyfinItem {
|
|
return { Id: "x", Type: "Movie", Name: "Test", MediaStreams: streams };
|
|
}
|
|
|
|
describe("extractOriginalLanguage — Jellyfin heuristic", () => {
|
|
test("returns null when there are no audio streams", () => {
|
|
expect(extractOriginalLanguage(item([{ Type: "Video", Index: 0 }]))).toBe(null);
|
|
});
|
|
|
|
test("uses the only audio track when there is just one", () => {
|
|
expect(extractOriginalLanguage(item([audio({ Language: "eng" })]))).toBe("eng");
|
|
});
|
|
|
|
test("prefers the IsDefault audio track over position", () => {
|
|
// 8 Mile regression: Turkish dub first, English default further down.
|
|
// Old heuristic took the first track and labelled the movie Turkish.
|
|
const streams = [audio({ Index: 0, Language: "tur" }), audio({ Index: 1, Language: "eng", IsDefault: true })];
|
|
expect(extractOriginalLanguage(item(streams))).toBe("eng");
|
|
});
|
|
|
|
test("skips a dub even when it is the default", () => {
|
|
const streams = [
|
|
audio({ Index: 0, Language: "tur", IsDefault: true, Title: "Turkish Dub" }),
|
|
audio({ Index: 1, Language: "eng" }),
|
|
];
|
|
expect(extractOriginalLanguage(item(streams))).toBe("eng");
|
|
});
|
|
|
|
test("falls back to first audio track when every track looks like a dub", () => {
|
|
const streams = [
|
|
audio({ Index: 0, Language: "tur", Title: "Turkish Dub" }),
|
|
audio({ Index: 1, Language: "deu", Title: "German Dub" }),
|
|
];
|
|
// No good candidate — returns the first audio so there's *some* guess,
|
|
// but scan.ts is responsible for marking this needs_review.
|
|
expect(extractOriginalLanguage(item(streams))).toBe("tur");
|
|
});
|
|
});
|