diff --git a/src/main/video-processor.ts b/src/main/video-processor.ts index ac9e821..bb1b9f8 100644 --- a/src/main/video-processor.ts +++ b/src/main/video-processor.ts @@ -89,10 +89,11 @@ export function isRemuxableVideoFile(fileName: string): boolean { // True when the release name explicitly marks it as a German release. Used in // tag mode to fall back to the first audio track (German-first scene convention) // when the audio language tags are wrong (a German dub mislabeled "eng"), instead -// of skipping. Deliberately requires an explicit german/deutsch/dubbed token — -// the ".DL." marker alone (present on every processed file) is not enough. +// of skipping. Deliberately requires an explicit german/deutsch token — the +// ".DL." marker alone (present on every processed file) is not enough, and a bare +// "dubbed" can mean an Italian/French dub, so it must NOT flag a German release. export function looksLikeGermanRelease(fileName: string): boolean { - return /(^|[._\s-])(german|deutsch|dubbed)([._\s-]|$)/i.test(fileName); + return /(^|[._\s-])(german|deutsch)([._\s-]|$)/i.test(fileName); } function isGermanStream(stream: ProbedAudioStream): boolean { @@ -100,8 +101,11 @@ function isGermanStream(stream: ProbedAudioStream): boolean { if (["ger", "deu", "de", "german", "deutsch"].includes(lang)) { return true; } + // Free-text title fallback (used when the language tag is missing). Full words + // only — the 2-3 letter codes ger/deu are too ambiguous in a title and would + // pick the wrong track to keep (which then deletes the real German one). const title = (stream.title || "").toLowerCase(); - return /\b(german|deutsch|ger|deu)\b/.test(title); + return /\b(german|deutsch)\b/.test(title); } // Decide which audio track to keep. Safety invariant: only ever choose to remux diff --git a/tests/video-processor.test.ts b/tests/video-processor.test.ts index ae8dd0a..ccdbcb9 100644 --- a/tests/video-processor.test.ts +++ b/tests/video-processor.test.ts @@ -85,6 +85,13 @@ describe("pickAudioTrack", () => { expect(d).toMatchObject({ action: "remux", audioRelIndex: 1 }); }); + it("tag mode does NOT treat an ambiguous 3-letter title code as German (no false-positive pick)", () => { + // Two untagged tracks whose titles are only "Ger"/"Deu" must not be mistaken + // for a German track; with no real German signal this falls back to first. + const d = pickAudioTrack([{ language: "", title: "Ger" }, { language: "", title: "Deu" }], "tag"); + expect(d).toMatchObject({ action: "remux", audioRelIndex: 0, reason: "fallback-first-untagged" }); + }); + it("tag mode with single German -> single (no remux)", () => { expect(pickAudioTrack([ger], "tag")).toMatchObject({ action: "single" }); }); @@ -125,6 +132,10 @@ describe("looksLikeGermanRelease", () => { expect(looksLikeGermanRelease("Show.S01E01.DL.720p.x264.mkv")).toBe(false); expect(looksLikeGermanRelease("Show.S01E01.MULTi.1080p.mkv")).toBe(false); }); + it("does not flag a non-German dub as a German release (bare 'Dubbed' is ambiguous)", () => { + expect(looksLikeGermanRelease("Movie.2020.ITALIAN.Dubbed.DL.1080p.mkv")).toBe(false); + expect(looksLikeGermanRelease("Movie.2020.FRENCH.DUBBED.DL.720p.mkv")).toBe(false); + }); }); describe("parseFfprobeAudioStreams", () => {