import { test } from "node:test"; import { SUB_FILE_RE } from "./videoStatus"; import assert from "node:assert/strict"; import path from "node:path"; import os from "node:os"; import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; import { DIARIZATION_FILENAME, diarizationSpeechSeconds, type DiarizationRecord, } from "./diarization"; import { hasDiarization, loadDiarization, writeDiarization, } from "./diarization-server"; import { readVideoFiles, DIARIZATION_FILENAME as STATUS_FILENAME } from "./videoStatus"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test common/lib/diarization.test.ts async function tmpDir() { return mkdtemp(path.join(os.tmpdir(), "diarization-test-")); } function record(over: Partial = {}): DiarizationRecord { return { videoId: "vid1", generatedAt: "2026-08-07T00:00:00.000Z", speakers: 2, turns: [ { start: 0, end: 4, speaker: 0 }, { start: 4, end: 10, speaker: 1 }, ], engine: { engine: "sherpa-onnx", threshold: 0.5 }, ...over, }; } test("the sidecar filename is not claimed by SUB_FILE_RE as a subtitle track", () => { // A name like transcript.diarization.json would be indexed as a // `diarization`-language subtitle track — which is exactly the trap this // constant exists to avoid. (sidecar() also refuses such a name at // declaration; see sidecar-server.test.ts.) assert.equal(DIARIZATION_FILENAME, "diarization.json"); assert.equal(STATUS_FILENAME, DIARIZATION_FILENAME); assert.ok(!SUB_FILE_RE.test(DIARIZATION_FILENAME)); }); test("write then load round-trips a record", async () => { const dir = await tmpDir(); try { await writeDiarization(dir, record()); const loaded = await loadDiarization(dir); assert.ok(loaded); assert.equal(loaded.videoId, "vid1"); assert.equal(loaded.turns.length, 2); assert.equal(loaded.engine.engine, "sherpa-onnx"); assert.equal(await hasDiarization(dir), true); } finally { await rm(dir, { recursive: true, force: true }); } }); test("a missing sidecar reads as absent, not as an error", async () => { const dir = await tmpDir(); try { assert.equal(await loadDiarization(dir), null); assert.equal(await hasDiarization(dir), false); } finally { await rm(dir, { recursive: true, force: true }); } }); // THE ONE THAT MATTERS. hasDiarization gates deletion of the audio, and audio // cannot be regenerated. A truncated or malformed file must read as "not // diarized" so the sweep keeps the audio — the failure mode to avoid is a // half-written file convincing cleanup that the work is done. test("a malformed or truncated sidecar reads as absent so the audio is kept", async () => { const dir = await tmpDir(); try { await writeFile(path.join(dir, DIARIZATION_FILENAME), "{ not json"); assert.equal(await hasDiarization(dir), false); // Parseable JSON, but missing the fields that make it a record. await writeFile(path.join(dir, DIARIZATION_FILENAME), '{"videoId":"vid1"}'); assert.equal(await hasDiarization(dir), false); // Parseable, has ids, but no turns array. await writeFile( path.join(dir, DIARIZATION_FILENAME), '{"videoId":"vid1","generatedAt":"2026-08-07T00:00:00.000Z"}', ); assert.equal(await hasDiarization(dir), false); } finally { await rm(dir, { recursive: true, force: true }); } }); test("readVideoFiles reports the sidecar's presence", async () => { const dir = await tmpDir(); try { await mkdir(dir, { recursive: true }); let files = await readVideoFiles(dir); assert.equal(files.hasDiarization, false); await writeDiarization(dir, record()); files = await readVideoFiles(dir); assert.equal(files.hasDiarization, true); } finally { await rm(dir, { recursive: true, force: true }); } }); test("speech seconds sums turns, and overlapping turns are not clamped", () => { assert.equal(diarizationSpeechSeconds(record()), 10); // Two people talking at once is real; reporting 12s of speech over 10s of // audio is the honest answer, not a bug to hide. const overlapping = record({ turns: [ { start: 0, end: 10, speaker: 0 }, { start: 8, end: 10, speaker: 1 }, ], }); assert.equal(diarizationSpeechSeconds(overlapping), 12); });