// Tests for the deck's overlay in the build (slice S3): the overlay chain the // deck's frames go through, the hard-cut pass that lays it (applyChrome), the // preview window, and the CLI flags. All pure -- argv and filtergraph strings, // no ffmpeg run. // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import test from "node:test"; import { applyChromeArgs, chromeFlags, chromeOverlayChain, chromeRegions, previewFromSegmentsArgs, windowSegments, } from "./build-video.mjs"; const PALETTE = { bg: "#15121c", fg: "#ece8f4", muted: "#9a93ad", accent: "#7c5cff", amber: "#f2b84b" }; const BASE = { width: 1920, height: 1080, fps: 30, palette: PALETTE, crf: 21, preset: "slow" }; const DECK = { ...BASE, chrome: { engine: "hyperframes", layout: "deck", deck: {} } }; const CHART = { ...BASE, chromeEngine: "hyperframes" }; test("chromeOverlayChain: the chart band's chain and inputs are as they shipped", () => { const hf = chromeOverlayChain(CHART, chromeRegions(CHART, "/o"), "[v3]", 4, { outLabel: "[hfout]", final: false }); assert.deepEqual(hf.inputs, ["-framerate", "30", "-start_number", "1", "-i", "/o/chrome/chart-frames/frame_%06d.png"]); assert.equal(hf.chain, "[v3][4:v]overlay=x=0:y=880:format=yuv444:shortest=1[hfout]"); const fin = chromeOverlayChain(CHART, chromeRegions(CHART, "/o"), "[v3]", 4); assert.equal(fin.chain, "[v3][4:v]overlay=x=0:y=880:format=yuv444:shortest=1[hf0];[hf0]format=yuv420p[vout]"); assert.equal(fin.outLabel, "[vout]"); }); test("chromeOverlayChain: the deck's input keeps the graph (-reinit_filter 0) and is pinned to rgba", () => { const hf = chromeOverlayChain(DECK, chromeRegions(DECK, "/o/sourced"), "[v16]", 17); assert.deepEqual(hf.inputs, [ "-reinit_filter", "0", "-framerate", "30", "-start_number", "1", "-i", "/o/sourced/chrome/deck-frames/frame_%06d.png", ]); assert.equal( hf.chain, "[17:v]format=rgba[hfa0];[v16][hfa0]overlay=x=0:y=890:format=yuv444:shortest=1[hf0];[hf0]format=yuv420p[vout]", ); // The reinit option is an INPUT option: it has to precede its own -i. assert.ok(hf.inputs.indexOf("-reinit_filter") < hf.inputs.indexOf("-i")); }); test("applyChromeArgs: one video encode over the concat, audio copied, deck frames second input", () => { const plan = { regions: chromeRegions(DECK, "/o/sourced"), outLabel: "[hfout]" }; const args = applyChromeArgs("/o/sourced/x.prerail-hardcut.mp4", "/o/x.mp4", DECK, plan); assert.deepEqual(args.slice(0, 6), ["-nostdin", "-v", "error", "-y", "-i", "/o/sourced/x.prerail-hardcut.mp4"]); const fc = args[args.indexOf("-filter_complex") + 1]; assert.equal(fc, "[1:v]format=rgba[hfa0];[0:v][hfa0]overlay=x=0:y=890:format=yuv444:shortest=1[hf0];[hf0]format=yuv420p[vout]"); assert.deepEqual(args.slice(args.indexOf("-map"), args.indexOf("-map") + 4), ["-map", "[vout]", "-map", "0:a"]); assert.equal(args[args.indexOf("-c:a") + 1], "copy"); assert.equal(args[args.indexOf("-crf") + 1], "21"); assert.equal(args.at(-1), "/o/x.mp4"); assert.ok(!args.includes("-ss")); }); test("applyChromeArgs: a preview seeks the base and needs no timestamp shift", () => { const plan = { regions: chromeRegions(DECK, "/o").map((r) => ({ ...r, frames: "/o/chrome/deck-from20-frames" })), outLabel: "[hfout]" }; const args = applyChromeArgs("/in.mp4", "/out.mp4", DECK, plan, { start: 20, dur: 8 }); assert.deepEqual(args.slice(4, 10), ["-ss", "20", "-t", "8", "-i", "/in.mp4"]); assert.ok(args.includes("/o/chrome/deck-from20-frames/frame_%06d.png")); assert.ok(!args[args.indexOf("-filter_complex") + 1].includes("setpts")); }); test("windowSegments: every segment the window touches, and the window's offset into the first", () => { // Three segments of 10 s crossfaded by 0.5: starts 0, 9.5, 19. const starts = [0, 9.5, 19]; const durs = [10, 10, 10]; assert.deepEqual(windowSegments(starts, durs, 2, 3), { first: 0, last: 0, offset: 2 }); // Inside the dissolve both neighbours are on screen. assert.deepEqual(windowSegments(starts, durs, 9.7, 1), { first: 0, last: 1, offset: 9.7 }); assert.deepEqual(windowSegments(starts, durs, 12, 10), { first: 1, last: 2, offset: 2.5 }); assert.throws(() => windowSegments(starts, durs, 40, 2), /no segment covers/); }); test("previewFromSegmentsArgs: the window's segments crossfaded as the full concat does, trimmed, deck over", () => { const plan = { regions: chromeRegions(DECK, "/o").map((r) => ({ ...r, frames: "/o/chrome/deck-from12-frames" })), outLabel: "[hfout]" }; const args = previewFromSegmentsArgs({ segments: ["/s/a.mp4", "/s/b.mp4", "/s/c.mp4"], durs: [10, 10, 10], starts: [0, 9.5, 19], D: 0.5, at: 12, dur: 10, render: DECK, chromePlan: plan, outPath: "/o/x.preview.mp4", }); // Only b and c are inputs; the deck frames are the third. assert.deepEqual(args.filter((_, i) => args[i - 1] === "-i"), [ "/s/b.mp4", "/s/c.mp4", "/o/chrome/deck-from12-frames/frame_%06d.png", ]); assert.equal( args[args.indexOf("-filter_complex") + 1], [ "[0:a]apad=whole_dur=10.000000,atrim=end=10.000000,asetpts=PTS-STARTPTS[p0a]", "[1:a]apad=whole_dur=10.000000,atrim=end=10.000000,asetpts=PTS-STARTPTS[p1a]", "[0:v][1:v]xfade=transition=fade:duration=0.5:offset=9.500[v1]", "[p0a][p1a]acrossfade=d=0.5:c1=tri:c2=tri[a1]", "[v1]trim=start=2.500:duration=10.000,setpts=PTS-STARTPTS[vw]", "[a1]atrim=start=2.500:duration=10.000,asetpts=PTS-STARTPTS[aw]", "[2:v]format=rgba[hfa0];[vw][hfa0]overlay=x=0:y=890:format=yuv444:shortest=1[hf0];[hf0]format=yuv420p[vout]", ].join(";"), ); assert.equal(args.at(-1), "/o/x.preview.mp4"); }); test("previewFromSegmentsArgs: hard cuts concatenate; one segment needs neither", () => { const plan = { regions: chromeRegions(DECK, "/o"), outLabel: "[hfout]" }; const two = previewFromSegmentsArgs({ segments: ["/s/a.mp4", "/s/b.mp4"], durs: [10, 10], starts: [0, 10], D: 0, at: 8, dur: 4, render: DECK, chromePlan: plan, outPath: "/p.mp4", }); const fc2 = two[two.indexOf("-filter_complex") + 1]; assert.match(fc2, /^\[0:v\]\[0:a\]\[1:v\]\[1:a\]concat=n=2:v=1:a=1\[vc\]\[ac\];\[vc\]trim=start=8\.000:duration=4\.000/); const one = previewFromSegmentsArgs({ segments: ["/s/a.mp4", "/s/b.mp4"], durs: [10, 10], starts: [0, 10], D: 0, at: 1, dur: 4, render: DECK, chromePlan: plan, outPath: "/p.mp4", }); assert.match(one[one.indexOf("-filter_complex") + 1], /^\[0:v\]trim=start=1\.000:duration=4\.000/); }); test("chromeFlags: the three deck flags, one at a time", () => { assert.deepEqual(chromeFlags(["m.json", "--skip-fetch"]), { chromeOnly: false, noChrome: false, chromePreview: null }); assert.deepEqual(chromeFlags(["m.json", "--chrome-only"]), { chromeOnly: true, noChrome: false, chromePreview: null }); assert.deepEqual(chromeFlags(["m.json", "--no-chrome"]), { chromeOnly: false, noChrome: true, chromePreview: null }); assert.deepEqual(chromeFlags(["m.json", "--chrome-preview", "20", "8", "--out", "o"]), { chromeOnly: false, noChrome: false, chromePreview: { at: 20, dur: 8 }, }); assert.throws(() => chromeFlags(["m.json", "--chrome-preview", "20"]), / /); assert.throws(() => chromeFlags(["m.json", "--chrome-preview", "x", "8"]), / /); assert.throws(() => chromeFlags(["m.json", "--chrome-preview", "5", "0"]), / /); assert.throws(() => chromeFlags(["m.json", "--chrome-only", "--no-chrome"]), /pick one/); }); test("the driver's chromeOnly produces the flag this parser reads", async () => { const { buildSteps } = await import("../lib/report/driver.mjs"); const steps = buildSteps({ id: "p", dir: "/p" }, { preset: "final", options: { chromeOnly: true } }); const build = steps.find((s) => s.argv.some((a) => a.endsWith("build-video.mjs"))); assert.equal(chromeFlags(build.argv).chromeOnly, true); });