commit dacea8bc1e3af286de25babd6225055c1c76bf79
parent 62d441921ca3b54fd5303b9c127e3a994f78ab29
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 16:17:38 -0400
report-to-video: the teaser segment's key carries the encode's parameters, so a crf, preset or audio change re-encodes it with the clips
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/umtool/report-to-video/README.md b/umtool/report-to-video/README.md
@@ -488,9 +488,10 @@ cut: the cut measures −17.7 LUFS integrated, the teaser −19.6. `hits: false`
digital silence, as a card's is.
**When it is rebuilt.** The segment is re-encoded only when its key — the
-frames' render key and the whole sound graph — differs from the one recorded
-beside it (`<id>.teaser.json`). Any build that reaches a teaser rebuilds it
-when its words, motion or sound changed: a full build, `--only <id>`, and
+frames' render key, the whole sound graph and the encode's parameters (`crf`,
+`preset`, `audioBitrate`, `audioRate`, `audioChannels`) — differs from the one
+recorded beside it (`<id>.teaser.json`). Any build that reaches a teaser
+rebuilds it when its words, motion, sound or encoding changed: a full build, `--only <id>`, and
**`--chrome-only`**, which builds teaser segments (they are chrome — graphics
made from the manifest, nothing fetched) while still rebuilding no clip.
`verify-build` checks each teaser's frame count and that its segment was
diff --git a/umtool/report-to-video/build-video.mjs b/umtool/report-to-video/build-video.mjs
@@ -1116,12 +1116,17 @@ export function teaserEncodeArgs({ framesDir, seconds, render, audio, outPath })
}
/**
- * A teaser segment's key: its frames' render key and its sound's whole graph
- * (every hit's time and parameters, `hits: false`'s silence, the level). A
- * segment whose recorded key differs is re-encoded.
+ * A teaser segment's key: its frames' render key, its sound's whole graph
+ * (every hit's time and parameters, `hits: false`'s silence, the level) and
+ * the encode's parameters (`encodeArgs`: crf, preset, the audio's bitrate,
+ * rate and channels -- the graph says "stereo" whatever `audioChannels` is).
+ * A segment whose recorded key differs is re-encoded, as every clip is when
+ * one of those changes.
*/
-export const teaserSegmentKey = (framesKey, audioGraph) =>
- createHash("sha256").update(JSON.stringify({ v: 1, frames: framesKey, audio: audioGraph })).digest("hex");
+export const teaserSegmentKey = (framesKey, audioGraph, render) =>
+ createHash("sha256")
+ .update(JSON.stringify({ v: 2, frames: framesKey, audio: audioGraph, encode: encodeArgs(render) }))
+ .digest("hex");
/**
* Compose and render the teaser (cached by compose-chrome's key), then encode
@@ -1142,7 +1147,7 @@ async function buildTeaserSegment(entry, { manifestPath, render, outDir, variant
});
const seconds = Number(entry.seconds);
const audio = teaserAudioGraph(teaserHits(entry), { seconds, render });
- const key = teaserSegmentKey(r.key, audio);
+ const key = teaserSegmentKey(r.key, audio, render);
const seg = path.join(outDir, "segments", `${entry.id}.mp4`);
const recPath = teaserRecordPath(seg);
const rec = await readFile(recPath, "utf8").then(JSON.parse, () => null);
diff --git a/umtool/report-to-video/chrome-teaser.test.mjs b/umtool/report-to-video/chrome-teaser.test.mjs
@@ -225,9 +225,16 @@ test("the cache key: the composed page changes with the words, the segment key w
// The sound is in the segment's key: hits on and off are different segments.
const on = teaserAudioGraph(teaserHits(FERRET), { seconds: 7, render: RENDER });
const off = teaserAudioGraph(teaserHits({ ...FERRET, hits: false }), { seconds: 7, render: RENDER });
- assert.notEqual(teaserSegmentKey(a.key, on), teaserSegmentKey(a.key, off));
- assert.notEqual(teaserSegmentKey(a.key, on), teaserSegmentKey(b.key, on));
- assert.equal(teaserSegmentKey(a.key, on), teaserSegmentKey(again.key, on));
+ const key = (k, g, r = RENDER) => teaserSegmentKey(k, g, r);
+ assert.notEqual(key(a.key, on), key(a.key, off));
+ assert.notEqual(key(a.key, on), key(b.key, on));
+ assert.equal(key(a.key, on), key(again.key, on));
+ // So are the encode's parameters: a rebuild that re-encodes every clip
+ // re-encodes the teaser too.
+ assert.equal(key(a.key, on), key(a.key, on, { ...RENDER }));
+ for (const change of [{ crf: 30 }, { preset: "veryslow" }, { audioBitrate: "96k" }, { audioChannels: 6 }, { audioRate: 44100 }]) {
+ assert.notEqual(key(a.key, on), key(a.key, on, { ...RENDER, ...change }), JSON.stringify(change));
+ }
} finally {
rmSync(dir, { recursive: true, force: true });
}