From 46d2c65374363c863ea3063faa6b2c4062ce37cd Mon Sep 17 00:00:00 2001 From: zhanglinghao Date: Tue, 6 Oct 2026 23:12:57 +0800 Subject: [PATCH 1/2] Lessons from a long 4K canvas film: disk space, canvas density, 8-bit voice padding, a stale-mix warning in qa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - engines/README "出 4K": at 4K every capture is a screenshot, and HyperFrames 0.8.82's multi-worker screenshot capture stores every frame as a JPEG first (about 4 MB a frame; it refuses to start when its estimate exceeds 90 % of the free space, as a 457 s film did). HF_CAPTURE_PARALLEL_STREAM=true streams the frames into the encoder, or use --workers 1. Canvas: reset the transform with setTransform(dpr, 0, 0, dpr, 0, 0), not (1, 0, 0, 1, 0, 0); a WebGL layer drawn into the canvas sizes by the same dpr. - tools/audio/qa.py: warn, never fail, when the mix may be stale: an input written more than 2 s after it, an SFX event landing after its end, or narration ending after it. bin/vh mix writes nothing when it stops on an error (a ding given a pitch), and qa used to report on the previous mix.wav without a hint. - tools/audio/README, playbook/04: t is a built-in's landing point (a riser ends on it); which shaping keys each built-in takes (the signals take none); padding a voice with -f lavfi -i anullsrc and the concat filter quantises it to 8 bits (reproduced with ffmpeg 8.0.1): use tts --lead, adelay and apad, numpy, or aformat=sample_fmts=s32 on every concat input. - playbook/02: how to measure canvas text that is covered or low in contrast, which hyperframes check cannot see. --- CHANGELOG.md | 7 ++++++ engines/README.md | 14 ++++++++++- playbook/02-verification.md | 1 + playbook/04-audio.md | 6 +++-- tools/audio/README.md | 13 ++++++++++- tools/audio/qa.py | 46 +++++++++++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6059b..5ce8f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +**Long 4K renders and disk space, canvas density, a voice padded to 8 bits, and qa warns about a stale mix** +- Why: lessons from a 457 s HyperFrames canvas film with narration. Its 4K render refused to start (about 57 GB of temporary frames, 53 GB free); a per-frame `setTransform(1, 0, 0, 1, 0, 0)` dropped the 2× scale; padding the voice with `anullsrc` and the `concat` filter quantised it to 8 bits (2,000+ qa click warnings); `bin/vh mix` stopped on a `ding` given a `pitch`, and `bin/vh qa` then reported on the previous mix.wav without a hint. +- engines/README "出 4K": at 4K every capture is a screenshot (drawElement and Linux BeginFrame capture do not supersample), and multi-worker screenshot capture stores every frame as a JPEG first, about 4 MB a frame; HyperFrames 0.8.82 refuses to start when its estimate is over 90 % of the free space. `HF_CAPTURE_PARALLEL_STREAM=true` streams the frames into the encoder (the 13,710 frames took about 11 minutes with 4 workers on an M3 Max), or `--workers 1`; `NODE_OPTIONS=--max-old-space-size=8192` is HyperFrames' own advice when its worker count may outgrow the heap. In the table: reset a canvas transform with `setTransform(dpr, 0, 0, dpr, 0, 0)`, not `(1, 0, 0, 1, 0, 0)` or `resetTransform()`; a WebGL layer drawn into the canvas sizes its canvas and viewport by the same dpr and computes positions in CSS pixels rather than from `gl_FragCoord`. +- `bin/vh qa` warns, never fails, when the mix may be stale: an input written more than 2 s after it (events.json, the beat map, `--events`, `--voice`, `--timeline`, `--words`, the inputs the stems' meta.json names; for `qa mix`, after meta.json), an SFX event landing after its end, or narration ending after it. The warning heads the report and rides on the last line, which the showcase and swatch scripts print. +- tools/audio/README: `t` is where a built-in lands (riser, swish_rev and tape end on it, so a riser starts `dur` earlier); which shaping keys each built-in takes (the four signals take none, nor a `variant`); `bin/vh mix` writes nothing when it stops on an error. Padding a voice with `-f lavfi -i anullsrc` and the `concat` filter makes it 8-bit: the lavfi input decodes as `pcm_u8` and concat converts the voice to match, and neither a `pcm_s24le` output nor an `aformat` after concat helps (ffmpeg 8.0.1). Use `bin/vh tts --lead`, `adelay` and `apad`, or numpy; with concat, `aformat=sample_fmts=s32` on every input. `bin/vh tts`'s own gaps are unaffected (checked sample for sample). playbook/04 points to `--lead` (it works without `--beats`), the riser's landing point and the stale warning. +- playbook/02, layer 2: text drawn in a canvas is outside `hyperframes check`. Measure it by drawing each frame with and without text and each text alone as a mask (the share of the text's pixels that show, and its contrast with the picture under it), and how bright the picture is under DOM captions. It stays a method rather than a `bin/vh` command: it needs a frame function that can skip text and a registry of the reads, which each project wires up itself. + **Lessons from four films go into the docs** - Why: the maintainer asked that the general lessons kept in the agent's memory notes and in project LESSONS.md files move into the docs, leaving out what is personal to one channel. They come from 《发送之后》, a news film (FZ1073), the 3-minute tech-history film and the review rounds of 2026-10-04/05. - playbook/09 §10 (new): how narration, title cards and labels read as written by a person. Let the fact carry the heaviest beat; vary sentence length; the sentence shapes models reach for (parallelism, triplets, "不是……而是……"); spoken words; explanations go into labels; one common way to end (from this case to this kind of case, back to the person) in plain words; a caption-only script goes out with the gate ② storyboard (or stops on its own when the human owns `script`). A default, overridden by a concept that wants another voice; any film with narration or cards reads it. `templates/SCRIPT.md` points there from both of its comments; the concept card's 首尾 line goes down to the last line or last picture (a 3-minute film changed its ending three times at gate ③). diff --git a/engines/README.md b/engines/README.md index 03d11fb..2f595ae 100644 --- a/engines/README.md +++ b/engines/README.md @@ -56,9 +56,10 @@ HYPERFRAMES_RENDER_DETACHED=1 nohup caffeinate -i npx hyperframes render --quali | 自己就对,不用改代码 | 要写代码 | |---|---| -| DOM 文字、CSS 的线和边框、SVG | 2D ``:宽高乘 `devicePixelRatio`,CSS 尺寸仍是合成尺寸,再 `ctx.scale(dpr, dpr)`,之后坐标照旧按 1080p 写(只开大画布、不 scale,画面会挤在左上角四分之一) | +| DOM 文字、CSS 的线和边框、SVG | 2D ``:宽高乘 `devicePixelRatio`,CSS 尺寸仍是合成尺寸,再 `ctx.scale(dpr, dpr)`,之后坐标照旧按 1080p 写(只开大画布、不 scale,画面会挤在左上角四分之一)。每帧开头重置变换也要带上 dpr:`ctx.setTransform(dpr, 0, 0, dpr, 0, 0)`;写成 `setTransform(1, 0, 0, 1, 0, 0)` 或 `resetTransform()` 会清掉这个 2 倍,1080p 下看不出来,4K 下又挤回左上角 | | `ctx.scale` 之后画的 canvas 内容,包括 `lineWidth`、`measureText` | 拿 `canvas.width`、`image.width`、`getImageData` 去算布局的地方:这些是物理像素,要除以 dpr | | 4K 的 `