import test from "node:test"; import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; // Run with: // pnpm -C editor exec tsx --test "app/channels/*/videos/*/whisperFromContainer.test.ts" // // The page's Transcribe — and `pnpm ops transcribe-one {slug, id}`, which // calls the same action — over a video with no audio on disk (release 23 F2): // a saved container with sound transcribed from it, nothing downloaded // (the e2e fake whisper-cli does the work); // a pointer whose file is gone refused with the media guard's sentence, // nothing downloaded; // no container the download path, as before; // a container with no sound the download path, as before. // The download path's yt-dlp is a fake that records its argv and fails, so // "downloaded" is a recorded argv, never the network. const ROOT = await mkdtemp(path.join(os.tmpdir(), "whisper-from-container-")); const SLUG = "demo-channel"; const YTDLP_LOG = path.join(ROOT, "ytdlp-argv.jsonl"); const FAKE_YTDLP = path.join(ROOT, "fake-ytdlp.mjs"); await writeFile( FAKE_YTDLP, `#!/usr/bin/env node import { appendFileSync } from "node:fs"; appendFileSync(${JSON.stringify(YTDLP_LOG)}, JSON.stringify(process.argv.slice(2)) + "\\n"); process.stderr.write("ERROR: fake yt-dlp: no network in this test\\n"); process.exit(1); `, ); await chmod(FAKE_YTDLP, 0o755); process.env.TRANSCRIPTS_DIR = ROOT; process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); process.env.YTDLP_BIN = FAKE_YTDLP; process.env.WHISPER_BIN = path.resolve("e2e/fixtures/bin/fake-whisper.mjs"); process.env.WHISPER_MODEL = "/dev/null"; delete process.env.FFPROBE_BIN; delete process.env.FFMPEG_BIN; await writeFile(process.env.SETTINGS_FILE, JSON.stringify({ minFreeDiskGB: 0 })); await mkdir(path.join(ROOT, "channels", SLUG), { recursive: true }); await writeFile( path.join(ROOT, "channels", SLUG, "config.json"), JSON.stringify({ name: "Demo", handling: "transcribe", url: "https://www.youtube.com/@demo", platform: "youtube" }), ); const ff = (args: string[]) => execFileSync("ffmpeg", ["-nostdin", "-v", "error", "-y", ...args]); const WITH_SOUND = path.join(ROOT, "with-sound.mp4"); const SILENT = path.join(ROOT, "silent.mp4"); ff([ "-f", "lavfi", "-i", "testsrc=size=160x90:rate=10:duration=1", "-f", "lavfi", "-i", "sine=frequency=440:duration=1", "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p", "-c:a", "aac", "-shortest", WITH_SOUND, ]); ff(["-f", "lavfi", "-i", "testsrc=size=160x90:rate=10:duration=1", "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p", SILENT]); // A video dir with metadata (so the download path has a URL) and, optionally, // a saved-video pointer to a container (a missing one when `file` is null). async function videoWith(id: string, container: string | null | undefined): Promise { const dir = path.join(ROOT, "channels", SLUG, "data", id); await mkdir(dir, { recursive: true }); await writeFile( path.join(dir, "metadata.info.json"), JSON.stringify({ id, title: id, webpage_url: `https://www.youtube.com/watch?v=${id}` }), ); if (container !== undefined) { const store = path.join(ROOT, "saved-videos", SLUG, id); await mkdir(store, { recursive: true }); if (container) await copyFile(container, path.join(store, "source-media.mp4")); await writeFile( path.join(dir, "saved-video.json"), JSON.stringify({ storedAt: "", dir: store, file: "source-media.mp4", bytes: 1 }), ); } return dir; } const { whisperVideoAction } = await import("./videoActions"); const { getRegistry } = await import("yt-dlp-transcript-common/jobs/registry"); test.after(() => rm(ROOT, { recursive: true, force: true })); async function ytdlpCalls(): Promise { return (await readFile(YTDLP_LOG, "utf8").catch(() => "")).split("\n").filter(Boolean).length; } // Runs the action to its end: the job's status, and its log. async function transcribe(id: string): Promise<{ status: string; log: string }> { const r = await whisperVideoAction(SLUG, id); if (!r.ok) throw new Error(r.error); // Drain the stream so the job is never held up on it. void (async () => { const reader = r.stream.getReader(); while (!(await reader.read()).done) {} })(); await r.done; const rec = getRegistry().get(r.jobId); const log = rec?.logPath ? await readFile(rec.logPath, "utf8").catch(() => "") : ""; return { status: rec?.status ?? "?", log }; } test("a saved container with sound is transcribed from, and nothing is downloaded", async () => { const dir = await videoWith("held1", WITH_SOUND); const before = await ytdlpCalls(); const r = await transcribe("held1"); assert.equal(r.status, "done", r.log); assert.match(r.log, /No audio on disk for held1; transcribing from the saved container .*source-media\.mp4 — nothing downloaded\./); assert.equal(await ytdlpCalls(), before, "no yt-dlp run"); const transcript = JSON.parse(await readFile(path.join(dir, "transcript.json"), "utf8")); assert.ok(Array.isArray(transcript.transcription)); }); test("a pointer whose container is gone is refused with the guard's sentence, and nothing is downloaded", async () => { await videoWith("gone1", null); const before = await ytdlpCalls(); const r = await transcribe("gone1"); assert.equal(r.status, "failed", r.log); assert.match(r.log, /Channel "demo-channel": media is not reachable — its saved video .*source-media\.mp4 is not there \(drive not mounted\?\)/); assert.equal(await ytdlpCalls(), before, "no yt-dlp run"); }); test("no container: the download path, as before", async () => { await videoWith("plain1", undefined); const before = await ytdlpCalls(); const r = await transcribe("plain1"); assert.equal(r.status, "failed", r.log); assert.match(r.log, /No audio on disk for plain1; downloading…/); assert.equal(await ytdlpCalls(), before + 1); }); test("a container with no sound: the download path, as before", async () => { await videoWith("silent1", SILENT); const before = await ytdlpCalls(); const r = await transcribe("silent1"); assert.equal(r.status, "failed", r.log); assert.match(r.log, /The container .*source-media\.mp4 has no sound; downloading the audio instead\./); assert.equal(await ytdlpCalls(), before + 1); });