// `archilyzer media playable [--ids a,b] [--trackers u1,u2] [--root // ] [--dry-run]` — a channel's saved containers remuxed losslessly into // browser-playable copies, with one torrent each, offline. The work and its // rules are controller/preparePlayable.ts; the editor runs the same function // as the `prepare-playable` job (`POST /api/ops/prepare-playable`). // // Disk-bound and long (a remux reads and writes every byte): run it where the // editor's own prepare-playable is not running. import path from "node:path"; import { getPaths } from "../lib/paths"; import { assertChannelMediaReachable } from "../lib/channelMedia"; import { isValidChannelSlug, readChannelConfig } from "../controller/channels"; import { preparePlayable } from "../controller/preparePlayable"; const list = (s: string | undefined) => s === undefined ? undefined : s.split(",").map((x) => x.trim()).filter(Boolean); export async function main(opts: { slug: string; ids?: string; trackers?: string; root?: string; dryRun: boolean; signal?: AbortSignal; }): Promise { if (!isValidChannelSlug(opts.slug)) { console.error(`media playable: "${opts.slug}" is not a channel slug`); return 2; } if (opts.root !== undefined && !path.isAbsolute(opts.root)) { console.error("media playable: --root must be an absolute path"); return 2; } try { const paths = getPaths(); const channelConfig = await readChannelConfig(paths, opts.slug); if (!channelConfig) { console.error(`media playable: no channel "${opts.slug}"`); return 2; } if (!opts.dryRun) await assertChannelMediaReachable(paths, opts.slug, channelConfig); const ids = list(opts.ids); const trackers = list(opts.trackers); const result = await preparePlayable({ paths, slug: opts.slug, channelConfig, ...(opts.root ? { root: opts.root } : {}), ...(ids ? { ids } : {}), ...(trackers ? { trackers } : {}), ...(opts.dryRun ? { dryRun: true } : {}), onLog: (line) => console.log(line.replace(/\n$/, "")), ...(opts.signal ? { signal: opts.signal } : {}), }); return result.failed.length > 0 || (result.stopped && result.stopped !== "drained") ? 1 : 0; } catch (err) { console.error(`media playable: ${(err as Error).message}`); return 1; } }