Archilyzer · Source

archilyzer

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

commit 62d441921ca3b54fd5303b9c127e3a994f78ab29
parent 6651ce562afc92cc0f460b8e4a51adfd435da752
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 16:16:53 -0400

report-to-video: a muteFrom past the cut's end is noted as not heard, not as muted from there

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

Diffstat:
Mumtool/report-to-video/README.md | 2++
Mumtool/report-to-video/build-video.mjs | 17++++++++++++-----
Mumtool/report-to-video/cut-edits.test.mjs | 27+++++++++++++++++++++++++++
3 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/umtool/report-to-video/README.md b/umtool/report-to-video/README.md @@ -208,6 +208,8 @@ window: the sound is digital silence, and a hold on that clip stays silent. It is made where the cut is joined (see [The cut's edits](#the-cuts-edits-mutefrom-and-renderendfade)), so changing it rebuilds no segment: `--chrome-only` applies it under the deck. + Only the played window is in the segment, so a mark past `cutEnd` (inside the + extent, so it validates) mutes nothing; the build says it will not be heard. `render.endFade` (seconds, default 0 = off, at most 10) fades the cut's LAST segment — whatever it is — picture to `palette.bg` and sound to silence over its diff --git a/umtool/report-to-video/build-video.mjs b/umtool/report-to-video/build-video.mjs @@ -2490,12 +2490,19 @@ export async function cutJoins({ schedule = null, entries, segments, render }) { for (let i = 0; i < entries.length; i += 1) { const e = entries[i]; if (e.type !== "clip" || e.muteFrom == null) continue; - const m = muteSegmentSeconds({ - entry: e, record: await readCutRecord(segments[i]), render, - seconds: await probeDuration(segments[i], render.fps), - }); + const seconds = await probeDuration(segments[i], render.fps); + const m = muteSegmentSeconds({ entry: e, record: await readCutRecord(segments[i]), render, seconds }); if (m.note) EMIT("note", { id: e.id, message: m.note }); - EMIT("note", { id: e.id, message: `${e.id}: muted from ${m.at}s into its segment (muteFrom ${e.muteFrom}, ${m.source === "record" ? "from its cut record" : "from the unsnapped start"})` }); + const from = m.source === "record" ? "from its cut record" : "from the unsnapped start"; + // Validation allows the clip's whole extent, but only the played window is + // in the segment: a mark past the cut's end mutes nothing, and saying + // "muted from" would claim otherwise. + EMIT("note", { + id: e.id, + message: m.at >= seconds + ? `${e.id}: muteFrom ${e.muteFrom} will not be heard -- it lies ${m.at}s into a segment ${seconds}s long, past the cut's end (${from})` + : `${e.id}: muted from ${m.at}s into its segment (muteFrom ${e.muteFrom}, ${from})`, + }); mutes.set(i, m.at); } const seconds = endFadeOf(render); diff --git a/umtool/report-to-video/cut-edits.test.mjs b/umtool/report-to-video/cut-edits.test.mjs @@ -371,3 +371,30 @@ test("cutJoins: the record beside the segment places muteFrom; the end fade coun rmSync(dir, { recursive: true, force: true }); } }); + +test("cutJoins: a muteFrom past the cut's end says it will not be heard, not that the clip is muted from there", + { skip: !haveFfmpeg }, async () => { + const dir = mkdtempSync(path.join(tmpdir(), "cut-latemute-")); + const said = []; + const log = console.log; + console.log = (line) => said.push(String(line)); + try { + const segs = segments(dir, "mp4"); + // b's segment is 2 s, cut from 200; the clip's extent runs to 203, so a + // mark at 202.5 validates but lies 2.5 s into a 2 s segment. + writeFileSync(cutRecordPath(segs[1]), JSON.stringify({ version: 1, id: "b", video: "vb", start: 200, end: 202 })); + const entries = [ + { id: "a", type: "clip", video: "va", start: 100, end: 102, muteFrom: 101 }, + { id: "b", type: "clip", video: "vb", start: 200, end: 203, cutEnd: 202, muteFrom: 202.5 }, + ]; + const j = await cutJoins({ entries, segments: segs.slice(0, 2), render: R }); + assert.equal(j[1].mute, 2.5); + const b = said.filter((l) => l.startsWith("b:")); + assert.equal(b.length, 1, b.join("\n")); + assert.match(b[0], /^b: muteFrom 202\.5 will not be heard -- it lies 2\.5s into a segment 2s long, past the cut's end/); + assert.ok(said.some((l) => /^a: muted from 1s into its segment/.test(l)), said.join("\n")); + } finally { + console.log = log; + rmSync(dir, { recursive: true, force: true }); + } + });