Archilyzer · Source

archilyzer

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

commit 68be357ee77942281f6926fb510a07e079b994b6
parent 52750646db8dab46ac911582ba1df13f78b64beb
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 21:17:25 -0400

report-to-video: --chrome-only and --chrome-preview check that the segments are on disk and framed for the cut's layout before any teaser is rendered (a teaser's own segment is built after the refusals, so it is not required beforehand); a test drives buildVideo over deck-framed segments under a feed and asserts the refusal leaves the renderer unrun

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

Diffstat:
Mumtool/report-to-video/build-video.mjs | 22+++++++++++++---------
Mumtool/report-to-video/chrome-feed.test.mjs | 38++++++++++++++++++++++++++++++++++++--
2 files changed, 49 insertions(+), 11 deletions(-)

diff --git a/umtool/report-to-video/build-video.mjs b/umtool/report-to-video/build-video.mjs @@ -3534,26 +3534,30 @@ export async function buildVideo({ manifestPath, opts = {}, out, only, fetchOnly if (!deck) throw new Error(`${what} needs the deck: render.chrome is not set in the manifest`); if (opts.noChrome) throw new Error(`${what} and --no-chrome contradict each other`); if (only) throw new Error(`${what} lays the deck over the whole cut; --only does not apply`); - // A teaser's segment is chrome too -- graphics made from the manifest's - // words, nothing fetched -- so it is (re)built here: re-rendered and - // re-encoded only when its words, motion or sound changed. - for (const e of entries) { - if (e.type !== "teaser") continue; - EMIT("card", { id: e.id, i: entries.indexOf(e), n: entries.length }); - await buildTeaserSegment(e, { manifestPath, render, outDir, variant, transition: D }); - } const segs = entries.map((e) => path.join(outDir, "segments", `${e.id}.mp4`)); - for (const seg of segs) { + // Every refusal before any render: a teaser's segment is built below, so + // only the others must be on disk already. + for (const [i, seg] of segs.entries()) { + if (entries[i].type === "teaser") continue; if (!(await exists(seg))) throw new Error(`${what} needs ${seg}, which is missing — run a full build first`); } // Segments framed for another layout (the posts feed switched on or off // since they were built) cannot take this cut's chrome: refused, by name. + // A teaser is full frame, so its record is not read for this. { const records = await Promise.all(segs.map((sg) => readCutRecord(sg))); const problems = framingProblems({ entries, records, render, want: framing }); if (problems.length) throw new Error(`${what}: ${problems.join("; ")}`); } + // A teaser's segment is chrome too -- graphics made from the manifest's + // words, nothing fetched -- so it is (re)built here: re-rendered and + // re-encoded only when its words, motion or sound changed. + for (const e of entries) { + if (e.type !== "teaser") continue; + EMIT("card", { id: e.id, i: entries.indexOf(e), n: entries.length }); + await buildTeaserSegment(e, { manifestPath, render, outDir, variant, transition: D }); + } const schedule = await writeChromeSchedule({ manifest, entries, segments: segs, D, outDir }); EMIT("chrome", { phase: "schedule", total: schedule.total, segments: schedule.segments.length }); // The holds, the moves, the mutes and the end fade, joined on their diff --git a/umtool/report-to-video/chrome-feed.test.mjs b/umtool/report-to-video/chrome-feed.test.mjs @@ -6,7 +6,7 @@ // // Run with: pnpm test:scripts import assert from "node:assert/strict"; -import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; @@ -17,7 +17,7 @@ import { postsGeometry, postWindows, validateChrome, validatePosts, } from "./deck.mjs"; import { - chromeOverlayChain, chromeRegions, deckFraming, deckFramingFilter, feedRegion, framedUnderDeck, framingProblems, + buildVideo, chromeOverlayChain, chromeRegions, deckFraming, deckFramingFilter, feedRegion, framedUnderDeck, framingProblems, postsRegions, segmentFraming, segmentJoins, } from "./build-video.mjs"; @@ -320,6 +320,40 @@ test("framingProblems: segments framed for another layout are named; a record-le assert.deepEqual(framingProblems({ entries, records: entries.map(() => null), render: RENDER, want: deck }), []); }); +test("--chrome-only and --chrome-preview refuse a framing mismatch before rendering a teaser", async () => { + const dir = mkdtempSync(path.join(tmpdir(), "feed-refuse-")); + const env = process.env.HYPERFRAMES_BIN; + try { + // A renderer that only leaves a mark: the refusal must come first. + const mark = path.join(dir, "rendered"); + const stub = path.join(dir, "hf-stub.sh"); + writeFileSync(stub, `#!/bin/sh\ntouch '${mark}'\nexit 1\n`); + chmodSync(stub, 0o755); + process.env.HYPERFRAMES_BIN = stub; + const manifestPath = path.join(dir, "m.json"); + writeFileSync(manifestPath, JSON.stringify({ + slug: "refuse", render: FEED, provenance: PROV, posts: POSTS, + timeline: [...CLIPS, { id: "t", type: "teaser", lines: ["Soon"] }], + })); + // Every footage segment on disk, framed for the deck: the cut is now a feed. + const segDir = path.join(dir, "out", "sourced", "segments"); + mkdirSync(segDir, { recursive: true }); + for (const e of CLIPS) { + writeFileSync(path.join(segDir, `${e.id}.mp4`), ""); + writeFileSync(path.join(segDir, `${e.id}.cut.json`), JSON.stringify({ version: 1, framing: segmentFraming(FEED, false) })); + } + for (const opts of [{ chromeOnly: true }, { chromePreview: { at: 1, dur: 2 } }]) { + await assert.rejects(buildVideo({ manifestPath, opts }), /framed for another layout than this cut's posts feed/); + } + assert.equal(existsSync(mark), false, "a teaser was rendered before the refusal"); + assert.equal(existsSync(path.join(segDir, "t.mp4")), false); + } finally { + if (env === undefined) delete process.env.HYPERFRAMES_BIN; + else process.env.HYPERFRAMES_BIN = env; + rmSync(dir, { recursive: true, force: true }); + } +}); + test("the feed's overlay is laid like the deck's: whole cut, reinit off, rgba, shortest", () => { const regions = [...chromeRegions(FEED, "/o"), feedRegion(FEED, "/o/chrome/feed-frames")]; assert.deepEqual(regions[1], { name: "feed", frames: "/o/chrome/feed-frames", x: 1320, y: 0, width: 600, height: 890 });