"use server"; import path from "node:path"; import { safeRevalidate } from "../../lib/safeRevalidate"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { readChannelConfig } from "yt-dlp-transcript-common/controller/channels"; import { preparePlayable } from "yt-dlp-transcript-common/controller/preparePlayable"; import { runManagedFunction, type StreamActionResult, } from "yt-dlp-transcript-common/jobs/streamCommand"; // PLAYABLE COPIES AND THEIR TORRENTS (release 21 D3): the `prepare-playable` // job, as `pnpm ops prepare-playable` posts it (`/api/ops/prepare-playable`). // The work and its rules are common/controller/preparePlayable.ts. // // ONE QUEUE MACHINE-WIDE: a remux reads and writes gigabytes on the archive // drive, and two channels' worth at once only thrash it. const PLAYABLE_QUEUE = "playable"; export async function preparePlayableAction( slug: string, opts: { ids?: string[]; trackers?: string[]; root?: string; dryRun?: boolean }, ): Promise { const paths = getPaths(); const channelConfig = await readChannelConfig(paths, slug); if (!channelConfig) { return { ok: false, error: `Channel "${slug}" not found` }; } if (opts.root !== undefined && !path.isAbsolute(opts.root)) { return { ok: false, error: `"root" must be an absolute path` }; } for (const tr of opts.trackers ?? []) { if (!/^(wss?|https?|udp):\/\/[^\s]+$/i.test(tr)) { return { ok: false, error: `"${tr}" is not a tracker URL (wss://, ws://, https://, http:// or udp://)` }; } } return runManagedFunction({ kind: "prepare-playable", queueKey: PLAYABLE_QUEUE, paths, channelSlug: slug, fn: async (onLog, signal, _setProgress, ctx) => { const result = await preparePlayable({ paths, slug, channelConfig, ...(opts.root ? { root: opts.root } : {}), ...(opts.ids ? { ids: opts.ids } : {}), ...(opts.trackers ? { trackers: opts.trackers } : {}), ...(opts.dryRun ? { dryRun: true } : {}), onLog, signal, drainSignal: ctx.drainSignal, }); if (!opts.dryRun && result.prepared.length > 0) { safeRevalidate([`/channels/${slug}`]); } if (result.failed.length > 0 && result.prepared.length === 0) { throw new Error(`${result.failed.length} failed, none prepared`); } if (result.stopped && result.stopped !== "drained" && result.stopped !== "cancelled") { throw new Error(`stopped: ${result.stopped}`); } }, }); }