import { readFile, rm, writeFile } from "node:fs/promises"; import { test, expect } from "@playwright/test"; import { pathExists, resetData, resolvePath } from "./helpers"; test("one-click whisper downloads audio then transcribes a YouTube video", async ({ page, }) => { await resetData("youtube-with-playlist"); // fake00000001 is the seeded "downloaded" entry. Drop its auto-subs so the // page treats it as untranscribed. await rm( resolvePath( "test-transcripts/channels/test-youtube/data/fake00000001/transcript.en.vtt", ), ); await page.goto("/channels/test-youtube/videos/fake00000001"); await page.getByLabel("download mode").selectOption("whisper"); await page.getByRole("button", { name: /^Audio \+ Whisper$/ }).click(); const log = page.getByLabel("Audio + Whisper for fake00000001 output"); await expect(log).toContainText("downloading", { timeout: 30_000 }); await expect(log).toContainText("Transcribe fake00000001 done", { timeout: 30_000, }); expect( await pathExists( "test-transcripts/channels/test-youtube/data/fake00000001/audio.mp3", ), ).toBe(true); expect( await pathExists( "test-transcripts/channels/test-youtube/data/fake00000001/transcript.json", ), ).toBe(true); }); test("one-click whisper skips download when audio is already on disk", async ({ page, }) => { await resetData("one-transcribe-channel-with-audio"); await page.goto("/channels/test-transcribe/videos/vidA"); await page.getByLabel("download mode").selectOption("whisper"); await page.getByRole("button", { name: /^Audio \+ Whisper$/ }).click(); const log = page.getByLabel("Audio + Whisper for vidA output"); await expect(log).toContainText("skipping download", { timeout: 30_000 }); await expect(log).toContainText("Transcribe vidA done", { timeout: 30_000 }); expect( await pathExists( "test-transcripts/channels/test-transcribe/data/vidA/transcript.json", ), ).toBe(true); // The fake-ytdlp script appends each invocation to fake-ytdlp.invocations // in the channel root. Confirm no download-one entry was written for vidA. const invocationsPath = resolvePath( "test-transcripts/channels/test-transcribe/fake-ytdlp.invocations", ); const invocations = await readFile(invocationsPath, "utf8").catch(() => ""); expect(invocations).not.toContain("download-one:"); }); // yt-dlp writes the live-chat track under the default audio.%(ext)s output // template (live_chat ignores the `subtitle:` prefix), so an interrupted // download leaves an `audio.live_chat.json.part` sidecar. A naive // startsWith("audio.") prefilter mistook it for real audio, skipped the // download, then crashed in whisper with "no audio file found". test("one-click whisper ignores a leftover live-chat .part sidecar", async ({ page, }) => { await resetData("youtube-with-playlist"); await rm( resolvePath( "test-transcripts/channels/test-youtube/data/fake00000001/transcript.en.vtt", ), ); await writeFile( resolvePath( "test-transcripts/channels/test-youtube/data/fake00000001/audio.live_chat.json.part", ), "[]", ); await page.goto("/channels/test-youtube/videos/fake00000001"); await page.getByLabel("download mode").selectOption("whisper"); await page.getByRole("button", { name: /^Audio \+ Whisper$/ }).click(); const log = page.getByLabel("Audio + Whisper for fake00000001 output"); await expect(log).toContainText("downloading", { timeout: 30_000 }); await expect(log).toContainText("Transcribe fake00000001 done", { timeout: 30_000, }); await expect(log).not.toContainText("skipping download"); await expect(log).not.toContainText("no audio file found"); expect( await pathExists( "test-transcripts/channels/test-youtube/data/fake00000001/audio.mp3", ), ).toBe(true); expect( await pathExists( "test-transcripts/channels/test-youtube/data/fake00000001/transcript.json", ), ).toBe(true); }); // The same blind spot for a completed live-chat sidecar: a finalized // audio.live_chat.json (no .part) must not be treated as audio either, or it // would be handed to whisper as if it were the audio file. test("one-click whisper ignores a completed live-chat sidecar", async ({ page, }) => { await resetData("youtube-with-playlist"); await rm( resolvePath( "test-transcripts/channels/test-youtube/data/fake00000001/transcript.en.vtt", ), ); await writeFile( resolvePath( "test-transcripts/channels/test-youtube/data/fake00000001/audio.live_chat.json", ), "[]", ); await page.goto("/channels/test-youtube/videos/fake00000001"); await page.getByLabel("download mode").selectOption("whisper"); await page.getByRole("button", { name: /^Audio \+ Whisper$/ }).click(); const log = page.getByLabel("Audio + Whisper for fake00000001 output"); await expect(log).toContainText("downloading", { timeout: 30_000 }); await expect(log).toContainText("Transcribe fake00000001 done", { timeout: 30_000, }); await expect(log).not.toContainText("skipping download"); await expect(log).not.toContainText("no audio file found"); });