Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 316b8c43933406205651ab53810f75c71310c6e7
parent 90c3dfd775aceae85b87ac0fcea8fc343e86de89
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 02:46:35 -0400

common: `archilyzer transcribe` goes; `run`'s exit codes match its header (review Q2, L6, L1)

- Q2: `common/bin/transform.ts` and its `transcribe` row are deleted
  together. It ran a whisper batch with this process's own worker pool —
  what `run transcription` refuses — and nothing referenced it;
  `runWhisperBatch` stays in use by the editor.
- L6: a usage error (unknown operation or channel, a stray flag) exits 2,
  a refusal exits 1 — an external operation now exits 1, not 2. A list of
  ids none of which has a data dir is refused (exit 1) instead of starting
  an empty job (+1 test).
- L1: the header and the usage line say `run` does not see the editor's
  lanes, so not beside one on the same channel (the same videos twice:
  wasted work, not damage).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/bin/archilyzer.ts | 4+---
Mcommon/bin/run-operation.test.ts | 15+++++++++++++--
Mcommon/bin/run-operation.ts | 19++++++++++++++++---
Dcommon/bin/transform.ts | 25-------------------------
4 files changed, 30 insertions(+), 33 deletions(-)

diff --git a/common/bin/archilyzer.ts b/common/bin/archilyzer.ts @@ -196,7 +196,7 @@ export const COMMANDS: Command[] = [ { path: ["run"], usage: - "<operation> <channel> [ids…] [--lane local|remote] run one catalogued operation over a channel offline, as the editor's job does (sync, downloads and transcription are refused: they run in the editor)", + "<operation> <channel> [ids…] [--lane local|remote] run one catalogued operation over a channel offline, as the editor's job does (sync, downloads and transcription are refused: they run in the editor; it does not see the editor's lanes, so not beside one on the same channel)", flags: { lane: "string" }, maxPositionals: Number.MAX_SAFE_INTEGER, run: async ({ positionals, flags }) => { @@ -235,8 +235,6 @@ export const COMMANDS: Command[] = [ "[--channel <slug>] [--dry-run] [--verbose] rename video dirs to the canonical id layout"), script(["verify", "transcripts"], "verify-transcripts.ts", "--channel <slug> list duplicate and missing transcripts"), - script(["transcribe"], "transform.ts", - "--channel <slug> whisper over a channel's downloaded audio with THIS process's own worker pool — never beside a running editor"), script(["migrate", "channel-priority"], "migrate-channel-priority.ts", "[--dry-run] the one-shot channel-priority migration (plans/channel-priority.md, S5)"), { diff --git a/common/bin/run-operation.test.ts b/common/bin/run-operation.test.ts @@ -99,11 +99,11 @@ test("an unknown operation is refused with the list", async () => { test("sync, the scan, downloads and transcription are refused with a sentence, not half-run", async () => { for (const id of ["sync", "metadata-scan", "download"]) { const { o, out } = capture(); - assert.equal(await runOperation({ operation: id, channel: "x", ids: [] }, { out }), 2, id); + assert.equal(await runOperation({ operation: id, channel: "x", ids: [] }, { out }), 1, id); assert.match(o.stderr, /download queue inside the editor/, id); } const { o, out } = capture(); - assert.equal(await runOperation({ operation: "transcription", channel: "x", ids: [] }, { out }), 2); + assert.equal(await runOperation({ operation: "transcription", channel: "x", ids: [] }, { out }), 1); assert.match(o.stderr, /worker pool/); // Every catalogued operation has an answer, and exactly the registry's run. const runs = operationCatalog().filter((op) => offlineRefusal(op) === null).map((op) => op.id); @@ -190,3 +190,14 @@ test("ids scope the run, and ids with no data dir are named", { timeout: 60_000 assert.match(o.stderr, /1 of 2 id\(s\) have no data\/<id>\/ on disk and are skipped: gone/); assert.match(o.stdout, /Backfill scoped: 0 done, 0 already current, 0 failed; 1 still need their media re-acquired/); }); + +test("ids none of which is on disk are refused, and no job is started", { timeout: 60_000 }, async () => { + settings(); + await seed("empty-ids", ["v1"]); + const before = jobRecords(); + const { o, out } = capture(); + const code = await runOperation({ operation: "diarization", channel: "empty-ids", ids: ["gone", "also-gone"] }, { out }); + assert.equal(code, 1); + assert.match(o.stderr, /none of the 2 id\(s\) has a data\/<id>\/ on disk/); + assert.deepEqual(jobRecords(), before); +}); diff --git a/common/bin/run-operation.ts b/common/bin/run-operation.ts @@ -24,8 +24,15 @@ // - a paused lane is REFUSED up front rather than held: the batch would // idle-wait for a resume that only the editor can give. // -// Ctrl-C cancels the job the way the editor's Cancel does. Exit: 0 done, -// 1 failed or refused, 2 usage, 130 cancelled. +// - it does not see the editor's lanes either: a `run` over a channel the +// editor's lane is working on at the same moment does the same videos +// twice (the writes are atomic, so it is wasted work, not damage). Run it +// when that lane is paused or elsewhere. +// +// Ctrl-C cancels the job the way the editor's Cancel does. Exit: 0 done; +// 1 failed or refused (an operation this process will not run, one switched +// off, a paused lane, unreachable media, no given id on disk); 2 usage (an +// unknown operation or channel, a stray flag); 130 cancelled. import { existsSync } from "node:fs"; import path from "node:path"; @@ -91,7 +98,7 @@ export async function runOperation( const refusal = offlineRefusal(op); if (refusal) { out.error(`run: ${refusal}`); - return 2; + return 1; } if (args.lane && op.id !== DIGEST_OPERATION_ID) { out.error(`run: --lane is the digest engine lane; ${op.label} has none`); @@ -129,6 +136,12 @@ export async function runOperation( const ids = [...new Set(args.ids.map((s) => s.trim()).filter(Boolean))]; if (ids.length > 0) { const absent = ids.filter((id) => !existsSync(path.join(channelDir, "data", id))); + if (absent.length === ids.length) { + out.error( + `run: none of the ${ids.length} id(s) has a data/<id>/ on disk (${absent.join(", ")}) — nothing to run`, + ); + return 1; + } if (absent.length > 0) { out.error( `run: ${absent.length} of ${ids.length} id(s) have no data/<id>/ on disk and are skipped: ${absent.join(", ")}`, diff --git a/common/bin/transform.ts b/common/bin/transform.ts @@ -1,25 +0,0 @@ -#!/usr/bin/env tsx -import { getPaths } from "../lib/paths"; -import { runWhisperBatch } from "../controller/whisperBatch"; -import { parseFlags } from "./_parseFlags"; - -const flags = parseFlags(process.argv.slice(2)); -const channelSlug = flags.channel; -if (!channelSlug) { - console.error("Usage: transform.ts --channel <slug>"); - process.exit(2); -} - -runWhisperBatch({ - channelSlug, - paths: getPaths(), -}) - .then((result) => { - console.log( - `Done: ${result.succeeded} succeeded, ${result.failed} failed, ${result.skipped} skipped, ${result.attempted} attempted.`, - ); - }) - .catch((err) => { - console.error(err); - process.exit(1); - });