import path from "node:path"; import { readdir } from "node:fs/promises"; import type { Paths } from "../lib/paths"; import { readArchive } from "../lib/archive"; import { isVideoTranscribed, readVideoFiles } from "../lib/videoStatus"; export type VerifyTranscriptsOptions = { channelSlug: string; paths: Paths; }; export type VerifyTranscriptsResult = { duplicates: string[]; missing: string[]; }; export async function findDuplicateDirs(dataDir: string): Promise<{ dirMap: Map; duplicates: Set; }> { const dirEntries = await readdir(dataDir).catch(() => [] as string[]); const dirMap = new Map(); const duplicates = new Set(); for (const dir of dirEntries) { const id = dir; const existing = dirMap.get(id); if (existing) { duplicates.add(dir.length > existing.length ? dir : existing); } dirMap.set(id, dir); } return { dirMap, duplicates }; } // For each ID in the archive, confirm there's a matching transcript on disk // (either the YouTube auto-sub .vtt or a whisper transcript.json — channels // can have a mix). Reports missing transcripts and duplicate video directories. export async function verifyTranscripts({ channelSlug, paths, }: VerifyTranscriptsOptions): Promise { const channelDir = path.join(paths.channelsDir, channelSlug); const dataDir = path.join(channelDir, "data"); const archivePath = path.join(channelDir, "archive"); const { dirMap, duplicates } = await findDuplicateDirs(dataDir); const archive = await readArchive(archivePath); const missing = new Set(); for (const id of archive.ids) { const dir = dirMap.get(id); if (!dir) { missing.add(id); continue; } const files = await readVideoFiles(path.join(dataDir, dir)); if (!isVideoTranscribed(files)) { missing.add(id); } } return { duplicates: Array.from(duplicates), missing: Array.from(missing), }; }