German-audio step: mislabeled-tag fallback, full logging, shorter temp

- tag mode: when no German-tagged audio track is found but the release name
  says German/Dubbed, fall back to the first track (the dub is mislabeled, e.g.
  German tagged "eng") instead of skipping; non-German names still skip safely
- comprehensive logging: per-package ffmpeg/ffprobe availability, plus per-file
  detected audio languages, decision + reason, remux/rename result and the exact
  error text when a file can't be processed
- shorter same-dir temp name so a long scene path + temp suffix cannot exceed
  Windows MAX_PATH and silently fail the remux
This commit is contained in:
Sucukdeluxe
2026-06-08 14:50:34 +02:00
parent 15edfbeb74
commit 2b93f47d3a
4 changed files with 124 additions and 30 deletions
+10 -4
View File
@@ -8,7 +8,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
// download-manager's selection + .DL.-rename wiring is exercised for real.
vi.mock("../src/main/video-processor", async (importActual) => {
const actual = await importActual<typeof import("../src/main/video-processor")>();
return { ...actual, processVideoFile: vi.fn() };
return { ...actual, processVideoFile: vi.fn(), resolveVideoTooling: vi.fn() };
});
import { DownloadManager } from "../src/main/download-manager";
@@ -17,13 +17,15 @@ import { createStoragePaths, emptySession } from "../src/main/storage";
import { shutdownItemLogs } from "../src/main/item-log";
import { shutdownPackageLogs } from "../src/main/package-log";
import { shutdownRenameLog } from "../src/main/rename-log";
import { processVideoFile, type VideoProcessResult } from "../src/main/video-processor";
import { processVideoFile, resolveVideoTooling, type VideoProcessResult } from "../src/main/video-processor";
const mockedProcess = processVideoFile as unknown as ReturnType<typeof vi.fn>;
const mockedTooling = resolveVideoTooling as unknown as ReturnType<typeof vi.fn>;
const tempDirs: string[] = [];
afterEach(() => {
mockedProcess.mockReset();
mockedTooling.mockReset();
shutdownItemLogs();
shutdownPackageLogs();
shutdownRenameLog();
@@ -66,6 +68,9 @@ function setup(keepGermanAudioOnly: boolean): { extractDir: string; manager: Dow
createdAt: 0,
updatedAt: 0
};
// Default: ffmpeg/ffprobe "available" so the step proceeds to the (mocked)
// processVideoFile. Tests that need the no-tool path override this.
mockedTooling.mockResolvedValue({ ffmpeg: "ffmpeg", ffprobe: "ffprobe" });
return { extractDir, manager, pkg };
}
@@ -131,14 +136,15 @@ describe("keepGermanAudioOnly integration", () => {
expect(fs.readdirSync(extractDir)).toContain("Show.S01E01.German.720p.x264.mkv");
});
it("stops the run and leaves files untouched when ffmpeg is missing", async () => {
it("skips up front (no processVideoFile calls) and leaves files untouched when ffmpeg is missing", async () => {
const { extractDir, manager, pkg } = setup(true);
stage(extractDir);
mockedProcess.mockResolvedValue({ action: "skipped-no-tool", reason: "ffmpeg/ffprobe nicht gefunden" } as VideoProcessResult);
mockedTooling.mockResolvedValue(null); // ffmpeg/ffprobe not found
const n = await (manager as any).keepGermanAudioOnlyImpl(extractDir, pkg);
expect(n).toBe(0);
expect(mockedProcess).not.toHaveBeenCalled(); // bailed before touching any file
expect(fs.readdirSync(extractDir)).toContain(DL_MKV); // untouched
});
});
+46 -4
View File
@@ -6,6 +6,7 @@ import {
stripDualLangMarker,
hasDualLangMarker,
isRemuxableVideoFile,
looksLikeGermanRelease,
pickAudioTrack,
parseFfprobeAudioStreams,
buildFfprobeArgs,
@@ -95,6 +96,34 @@ describe("pickAudioTrack", () => {
it("tag mode, tagged but no German -> SKIP (never delete the only usable audio)", () => {
expect(pickAudioTrack([eng, { language: "fre", title: "" }], "tag")).toMatchObject({ action: "skip", reason: "no-german-track" });
});
it("tag mode, no German tag but GERMAN release -> fall back to first track (mislabeled dub)", () => {
expect(pickAudioTrack([eng, eng], "tag", true)).toMatchObject({ action: "remux", audioRelIndex: 0, reason: "fallback-first-german-release" });
});
it("tag mode, single mislabeled track on a German release -> keep it (no remux)", () => {
expect(pickAudioTrack([eng], "tag", true)).toMatchObject({ action: "single", reason: "single-german-mislabeled" });
});
it("tag mode, no German tag and NOT flagged German -> still SKIP (safety preserved)", () => {
expect(pickAudioTrack([eng, eng], "tag", false)).toMatchObject({ action: "skip", reason: "no-german-track" });
});
it("correctly tagged German still wins even on a German release (fallback not needed)", () => {
expect(pickAudioTrack([eng, ger], "tag", true)).toMatchObject({ action: "remux", audioRelIndex: 1, reason: "german-tag" });
});
});
describe("looksLikeGermanRelease", () => {
it("detects German/Dubbed release names", () => {
expect(looksLikeGermanRelease("Desperate.Housewives.S02E01.German.DD51.Dubbed.DL.720p.WEB-DL.x264.mkv")).toBe(true);
expect(looksLikeGermanRelease("1899.S01E01.German.DL.720p.WEB-x264-WvF.mkv")).toBe(true);
expect(looksLikeGermanRelease("Show.S01E01.Deutsch.1080p.mkv")).toBe(true);
});
it("does not flag a bare .DL. name without an explicit German token", () => {
expect(looksLikeGermanRelease("Show.S01E01.DL.720p.x264.mkv")).toBe(false);
expect(looksLikeGermanRelease("Show.S01E01.MULTi.1080p.mkv")).toBe(false);
});
});
describe("parseFfprobeAudioStreams", () => {
@@ -156,10 +185,10 @@ describe("processVideoFile (real fs body, fake runner)", () => {
}
});
function makeFile(content: string): string {
function makeFile(content: string, name = "Show.S01E01.German.DL.720p.mkv"): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-vp-"));
tempDirs.push(dir);
const file = path.join(dir, "Show.S01E01.German.DL.720p.mkv");
const file = path.join(dir, name);
fs.writeFileSync(file, content);
return file;
}
@@ -222,8 +251,21 @@ describe("processVideoFile (real fs body, fake runner)", () => {
expect(fs.readFileSync(file, "utf8")).toBe("ORIGINAL");
});
it("leaves the file untouched when tagged but no German track", async () => {
const file = makeFile("ORIGINAL");
it("remuxes a German-named release with MISLABELED audio tags (fallback to first track)", async () => {
// Name says German, but both audio tracks are tagged eng/fre (the dub is
// mislabeled). The fallback keeps the first track instead of skipping.
const file = makeFile("ORIGINAL"); // name contains "German"
const result = await processVideoFile(file, { mode: "tag" }, {
resolveTooling: tooling,
runProcess: fakeRunner({ probeJson: JSON.stringify({ streams: [{ tags: { language: "eng" } }, { tags: { language: "fre" } }] }) })
});
expect(result.action).toBe("remuxed");
expect(result.keptTrackIndex).toBe(0);
expect(fs.readFileSync(file, "utf8")).toBe("REMUXED-GERMAN-ONLY");
});
it("leaves a NON-German-named file untouched when tagged but no German track (safety preserved)", async () => {
const file = makeFile("ORIGINAL", "Show.S01E01.MULTi.DL.720p.mkv");
const result = await processVideoFile(file, { mode: "tag" }, {
resolveTooling: tooling,
runProcess: fakeRunner({ probeJson: JSON.stringify({ streams: [{ tags: { language: "eng" } }, { tags: { language: "fre" } }] }) })