From 2b0411bcb0139d2341cf5f8e558057eb21603288 Mon Sep 17 00:00:00 2001 From: zhanglinghao Date: Wed, 7 Oct 2026 21:48:30 +0800 Subject: [PATCH 1/3] Lessons from a narrated film with 3D: one voice in a one-request take, labels at the floor from the start, text swaps and readouts, glass in Three.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer asked that the general lessons of a 2-minute narrated canvas + Three.js film go into the docs as short guidance. - playbook/04: in a one-request gemini take every line still carries its own style, so a per-line [direction] makes that line sound different; for one voice, no per-line directions. Evening out the pace line by line on one take (waveform-refined cuts, capped inner pauses, partial per-line atempo towards one target), described, not a command. When transcription is unavailable: the take is on disk; --resume later or align locally with whisper, mapping digits back to spelled-out years. - tools/audio/README: a daily-quota 429 was once waited on and retried like the per-minute limit, which looked hung. - playbook/03 §4: write labels at the floor from the first draft; a shot's headline is a 主标题; credits go into the description. - playbook/02 layer 3, TASTE_CHECKLIST #19: strips at text swaps and rolling readouts; a readout rests only on sourced values. - engines/README: RoomEnvironment reflections and the default LatheGeometry seam on glass; a fresnel rim shell instead. --- CHANGELOG.md | 8 ++++++++ engines/README.md | 1 + playbook/02-verification.md | 1 + playbook/03-motion-design.md | 1 + playbook/04-audio.md | 11 +++++++++++ templates/TASTE_CHECKLIST.md | 2 +- tools/audio/README.md | 2 +- 7 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a662bfc..01b682e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +**Lessons from a narrated film with 3D: one voice across a one-request take, labels sized from the start, text swaps and readouts, glass in Three.js** +- Why: the maintainer asked that the general lessons from a 2-minute narrated canvas + Three.js film go into the docs as short guidance. +- playbook/04: in a one-request gemini take (`--join all`) every line still carries its own style, so one line with a `[direction]` (often the opening) sounds different from the rest; for one voice, put the whole delivery in the overall style and give no line a direction. How to even out the pace line by line on one take without synthesizing again (cuts refined from the waveform, so a misheard first syllable is not clipped; long pauses inside a line capped; per-line `atempo` part of the way towards one target pace), described, not a command yet. When transcription is unavailable (daily quota, API errors): the take is already on disk; `--resume` later, or align locally with whisper, mapping its digits back to a script that spells years out (一九五三). +- tools/audio/README: a daily-quota 429 is not always refused at once; one was waited on and retried like the per-minute limit (up to 5 times a call), which looked hung. +- playbook/03 §4: write readable labels at the floor from the first draft (44 px for `desktop`; one draft had about 40 at 24–40 px); a shot's one-line headline counts as a 主标题 (84 px); source and credit lines go into the description. +- playbook/02 layer 3 and TASTE_CHECKLIST #19: a strip at each text swap in one place (old out before new in; a reviewer found three that the author's own check missed) and at each rolling readout, whose resting values must be sourced, not interpolated. +- engines/README (Three.js): `RoomEnvironment` reflections drew a bright vertical strip down every glass object, and a full-turn `LatheGeometry` seam faces a +z camera by default; turn the seam away and give glass a fresnel rim shell instead of the environment map. + **`bin/vh tts … minimax`: MiniMax narration in one request, timed by its own subtitles** - Why: a film's narration was made with MiniMax T2A (`speech-2.8-hd`) through a project script. Sending the whole script in one request kept the voice from drifting between blocks, which the maintainer had heard and disliked with block-by-block synthesis, and the request's own subtitles gave every character's time without an ASR pass. This makes that a provider. - `minimax` posts to `https://api.minimax.io/v1/t2a_v2` (the China host `api.minimaxi.com` rejects international keys with 2049; `MINIMAX_BASE_URL` for a China-platform key). Key from `MINIMAX_TOKEN_PLAN_KEY` (Token Plan, spends its credits) first, then `MINIMAX_API_KEY` (pay-as-you-go); an error names the variable, never the key, with a hint for 1008 (no balance on that billing) and 1004/2049 (wrong host for the key). `MINIMAX_TTS_MODEL` / `_SPEED` / `_VOL` / `_PITCH` are recorded in `_run.json`, so `--resume` refuses a change; speed, volume and pitch are checked against the API's ranges before anything is deleted, and so is the 10,000-character limit of each request. `--instruct` / `[direction]` count only as one of MiniMax's nine emotion words; other text is ignored and that is said once. Default voices: zh `Chinese (Mandarin)_Reliable_Executive`, en `English_expressive_narrator`. diff --git a/engines/README.md b/engines/README.md index 7d43806..b96b00d 100644 --- a/engines/README.md +++ b/engines/README.md @@ -161,6 +161,7 @@ window.__vh = { - **把 3D 点投影到屏幕前先 `camera.updateMatrixWorld()`**:同一帧里刚 `lookAt` 过,矩阵还没更新,钉在 3D 点上的标注会错位。 - **`EdgesGeometry(geo, 阈值角)` 只画相邻面夹角大于阈值的边**:20 段的圆柱相邻面差 18°,阈值设 20° 就一条侧边线都画不出来。线框画法的圆柱少分几段(12 段是 30°),或者把阈值调低。 - **物体的位置在建的时候就设好**:只在每帧的动画函数里设 `position` 的话,不参与动画的零件永远停在原点(盒子以中心为原点,会半截埋进地面)。 + - **玻璃器皿上一道从上到下的亮条**:用 `RoomEnvironment` 做环境贴图时,它的反射会在每个玻璃件上留下一道亮竖条,一排烧瓶就是一排亮条;`LatheGeometry` 转满一圈的接缝默认也正对 +z 方向的镜头,一起转到背面(`phiStart` 设 `Math.PI`)。玻璃所在的场景不设 `scene.environment`,玻璃感改由一层菲涅尔边光壳来给:同一个几何体放大约 1.004 倍,`ShaderMaterial` 加法混合、不写深度,亮度取 `pow(1 − |N·V|, 2.6)`,只有边缘微微发亮。 - **渲染时不要依赖在线 CDN。** importmap 指向 CDN 时,只要页面加载那一刻断网,渲染就会静默挂住:日志 0 字节,也不报错。介绍片的第一次 final 渲染就卡在这里。把 three.js 装进项目,importmap 指向 `node_modules`,snapshot 和 render 都能读到这些文件: ```bash diff --git a/playbook/02-verification.md b/playbook/02-verification.md index c855e9d..b291fbc 100644 --- a/playbook/02-verification.md +++ b/playbook/02-verification.md @@ -24,6 +24,7 @@ - **strip**:看时序。按固定步长 0.1–0.15 秒出一串帧,从头到尾像观众那样读,数一数每个 read 分到几帧(24 帧 = 1 秒)。 - **crop**:局部放大,看脸、手、接触点、发光区域。 - 另外,每个转场前后各 0.5 秒都要单独看;整片再按目标屏的尺寸看一遍(BRIEF 的 `Watch on`);循环播放的交付物还要看末帧接首帧(两条命令见下文"手机测试与循环接缝")。 + - 同一位置换字、会滚动的读数,各看一条 strip:换字时旧字退完新字才进(#17;一支 2026-10 的片子作者自查漏了三处,是 reviewer 看出来的);读数每次停下的数都和 NOTES 里的出处对一遍,停在插值算出来的数上算编造(#19)。 4. **VLM 批评要给离散选项**:让模型从锚点网格里选位置(Code2Video 的做法),或者从几个渲染出来的变体里挑一个(Paper2Video 的做法)。不要让它输出"往左移 12px"这种数值微调。每轮最多列 3 个问题,每个问题都附一条可以直接执行的修改。 5. **reviewer subagent**:开一个全新上下文(`standard` 用便宜一档的模型,Claude Code 里 `model: sonnet`;`studio` 可以用最强档),只给它联系表、`TASTE_CHECKLIST.md` 和 `STORYBOARD.md`,让它逐条打分;不合格的退回给写代码的 agent。 - **人设**:严苛的导演,不是为作品自豪的作者。提示词的大意是:"这支片子不是你做的。你是一位挑剔的导演,任务是找出它为什么还不能发。"作者自己看久了会习以为常,换一个没有感情投入的人才看得出。 diff --git a/playbook/03-motion-design.md b/playbook/03-motion-design.md index 2d27ed5..d68ba3f 100644 --- a/playbook/03-motion-design.md +++ b/playbook/03-motion-design.md @@ -134,6 +134,7 @@ - `feed` 那一档的字要大得多,因为横屏片在信息流里本来就小。能另出一个竖版(重新构图,不是裁)就另出,比把横屏的字放大到这一档好。 - **4K**:照常按 1080p 写合成和字号,出片时渲成 4K(HyperFrames `render --resolution 4k`,竖屏 `portrait-4k`,方形 `square-4k`;见 `engines/README.md`),画面和字一起放大一倍,屏幕上看起来一样大。电脑上看 4K,字不需要占画面更大的比例;只有直接按 3840 宽写合成时,表里的 px 才要乘 2。 - 风格预设的 STYLE.md 里写的字号,是它 5 s 样片用过的数(不少标签是 30–42 px),不是下限;借进片子以后,标题、标签、字幕按这张表来。 + - **写场景时就从下限写起**:标签、注释、读数这些要读的辅助字,一开始就按这一档的下限写(`desktop` 是 44 px),不要先写小、等自查再放大:一支 2026-10 的横屏片初版有约 40 处辅助字是 24–40 px,事后放大要挨个重排。镜头里点题的那行大字(一句话标题)算主标题,不算辅助字(`desktop` 是 84 px)。出处、图片署名、素材来源这类不用观众读的小字,放进视频简介,不留在画面上。 - 来源:`docs/research/06-concept-first-ab.md`(同一个 44 px,竖屏手机上约 15 px,横屏放进信息流只有约 8 px);屏幕宽度是常见设备的 CSS 宽度【综合】。 - **中文字体**【综合】:思源黑体/Noto Sans CJK 用于正文和字幕,思源宋体用于标题和人文类内容;使用前确认本机已安装,或者把字体文件放进项目的 `assets/`。 - **decode / scramble 字**:字像被程序一点点解码出来,是代码感片子的常用效果。守三条规矩: diff --git a/playbook/04-audio.md b/playbook/04-audio.md index 6135edc..850df7c 100644 --- a/playbook/04-audio.md +++ b/playbook/04-audio.md @@ -70,6 +70,8 @@ bin/vh beats <任意音乐文件> # 外来音乐的节 `--align gemini` 对任何 provider 都能用:每句合成完交给 Gemini 转写,拿回带时间戳的词,再和稿子逐字对齐。结果写进 timeline:`words` 是逐词时间(文字用稿子的写法),字幕和画面逐词同步用它;`asr` 是和稿子的相似度,跑偏、或者句首句尾多出 1 s 以上声音的句子标上 `flag`,命令最后逐句列出来。转写失败也不丢配音:修好原因后,同一条命令加 `--resume` 接着跑。时间步长、费用、限额和实测见 `tools/audio/README.md` 的同名一节。 +**转写用不了时**(当天额度用完,或者接口一直报错):额度用完后,`--align gemini` 可能在 429 上一遍遍等待重试,看着像卡住。合成好的音频已经在盘上(逐句合成时是 `voiceover..wav`,`--join` 时是 `vo//_blockNN.wav`),停掉不会丢;可以等额度恢复后加 `--resume` 接着转写,也可以改用本地 whisper 拿逐字时间(还没封装;Apple Silicon 上用 mlx-whisper 的 `whisper-large-v3-turbo`,`initial_prompt` 里写上人名和术语),再按字和稿子对齐(difflib;没对上的字在前后两个对上的字之间插值)。whisper 把年份写成阿拉伯数字:稿子为了念法写成"一九五三"时,对齐前先把这些数字换回稿子的写法,否则那句的起点会算晚,剪的时候切掉第一个字。 + 有词级时间时,`bin/vh captions` 会给每条字幕加上 `words`,念得太快的短字幕会往后延到 1.8 s(`tools/audio/README.md` 的"字幕")。 **双人对话、整段合成、音色、费用**:`@speakers` 双人对话、`--join` 整段合成、音色库和设计音色(`bin/vh voices`)、Gemini 的费用和限额,都在 `tools/audio/README.md` 的同名各节。用了 Gemini 旁白的片子,在 `NOTES.md` 写明"旁白为 AI 合成(Gemini TTS,含 SynthID 水印)",发布时按平台要求标注。 @@ -188,6 +190,7 @@ bin/vh qa mix $A/stems --beats $A/music.beats.json # 只看混音报告(词 - 同一段里相邻两句的语气要有落差:问句接答句,铺垫接爆点,快接慢。 - **标签**:``、``、``、``、`` 可以直接写进句子里。只有 `gemini` 会演出来,其他 provider 和字幕都会自动去掉(规则见 `tools/audio/README.md`)。`minimax` 用的是停顿标记 `<#0.4#>`(秒),同样只有它会停。中文稿里也写英文标签,官方说这样效果最好。标签只管某一刻的动作(停顿、呼吸、笑),持续的语气写在 `[ ]` 里。 - **给 gemini 的指示要短**:一句话讲清情绪和节奏就够了。年龄、性别、口音属于音色,不要写进指示;长段的人设和导演笔记容易让音色漂移。 +- **整篇要一个声音时,不写逐句指示**:gemini 整稿一次合成(`--join all`)时,每句仍是单独的一段文字,各带自己的 style:整体语气,加上这句 `[ ]` 里的指示。只有一句带 `[ ]`(常见的是开头那句),这句就会和其余的听起来不一样。要全片一个声音,把想要的语气都写进整体语气,每句都不写 `[ ]`。 - **谁能演**: - `gemini` 表演力最好,整体和逐句指示都听; - `minimax` 只认一个情绪词(`calm`、`happy`、`whisper` 等),写别的会被忽略; @@ -215,6 +218,14 @@ bin/vh qa mix $A/stems --beats $A/music.beats.json # 只看混音报告(词 **语速的依据和用法**:表里是句内语速,不算句间停顿。普通话正常朗读、去掉 150 ms 以上的停顿后约 5.2 字/秒:七种语言的朗读语料里,汉语是 5.18 音节/秒,一个汉字一个音节(Pellegrino, Coupé & Marsico 2011,*Language* 87(3),[doi:10.1353/lan.2011.0057](https://doi.org/10.1353/lan.2011.0057))。电话闲聊按总时长算是每分钟 228–247 字,约 3.8–4.1 字/秒(Yuan, Liberman & Cieri 2006,Interspeech,[doi:10.21437/interspeech.2006-204](https://doi.org/10.21437/interspeech.2006-204))。知识科普的 4.5–5.5 围着正常朗读,原理讲解慢一档。倒推稿子字数时乘 0.85,留出句间停顿【综合】:45 s 的知识短视频约 170–210 字,3 分钟的讲解约 540–690 字。TTS 的实际语速看音色:本节第 4 小节比较 `duck_ratio` 用的那段测试旁白(0.25 s 间距,本地 Qwen3-TTS 0.6B 的 Serena,7 句 107 字)句内约 4.1 字/秒。所以字数只用来起稿,时长以 `timeline.json` 的实测为准(硬规则 2)。 +**一条 take 里快慢不一时,逐句拉齐**:整稿一次合成音色不漂,句与句的语速却可能差不少(一条 Gemini 整稿实测 5.0–7.8 字/秒)。不用重合成,在这条 take 上逐句处理就行(还没做成命令): +1. 起止按波形定:从对齐给的句首往回走,走到声音静下来为止(ASR 听错第一个字时给的起点偏晚,照着剪会切掉它),句尾同样往后走; +2. 句内过长的停顿压到约 0.25 s,切口两侧各淡几毫秒; +3. 每句用 `atempo`(不变调)拉向同一个目标语速(取人认可的那一段,比如开头),只校正一部分(例如差距的 0.8 次方,倍数限在 0.92–1.18),留一点自然的起伏; +4. 重排句间停顿,整条再对齐一次,拿最终的逐字时间。 + +一支 2026-10 的片子这样拉到 5.92 ± 0.11 字/秒,听起来还是同一条 take。 + **要做 A/B**:`studio` 档位下,同一段旁白至少试两种导演方向,把两版都给人听。实测对比见本节开头:同样三句话,只给一个"纪录片旁白"的整体语气时听起来平;逐句导演、再卡上拍之后,才有起伏和节奏。 ### 2. 旁白卡拍(动效类必做) diff --git a/templates/TASTE_CHECKLIST.md b/templates/TASTE_CHECKLIST.md index 5558653..b262825 100644 --- a/templates/TASTE_CHECKLIST.md +++ b/templates/TASTE_CHECKLIST.md @@ -43,7 +43,7 @@ 18. 超过 3s(短视频)或 8s(讲解)什么都没发生?停拍时声音和镜头同时停死(观众会以为播放卡住了;#16 的停顿要像屏息:低音垫着、镜头慢漂)?【底线】分镜里声明的卡点没对准节拍(±1 帧)?片中(首尾之外)出现数字静音或近乎静音(有声片才查;用户要的无声片不带音轨)? **内容** -19. 第 1 秒看不出这是什么、为什么要看?【底线】有占位文字、编造的数字?把不存在的界面当成真实产品的界面展示(立意本身就是一个明显虚构的界面时不算,例如把讲解装进一个仿真的 app)? +19. 第 1 秒看不出这是什么、为什么要看?【底线】有占位文字、编造的数字(在几个有出处的数之间滚动的读数,停住让人读的只能是有出处的数,不能停在插值算出来的值上)?把不存在的界面当成真实产品的界面展示(立意本身就是一个明显虚构的界面时不算,例如把讲解装进一个仿真的 app)? 20. 把这一帧换到任何别的主题也照样成立(说明它是装饰,不是设计;最常见的通用 AI 装饰是四角的小标签、取景框式的边角线、不带信息的 HUD 读数)?说不出这一镜看完观众多知道了什么? ## 结构化输出 diff --git a/tools/audio/README.md b/tools/audio/README.md index 65025c0..da55bdb 100644 --- a/tools/audio/README.md +++ b/tools/audio/README.md @@ -95,7 +95,7 @@ - **费用**(Standard 档,每百万 token):Flash TTS 输入 $0.50、输出音频 $9(约 $0.00225 / 10 s);Flash-Lite TTS 输入 $0.50、输出 $6(约 $0.0015 / 10 s)。这是 2026-12-31 之前的价格,**2027-01-01 起全部翻倍**(Flash $1 / $18,Lite $1 / $12)。Batch 和 Flex 档是一半。免费档不收费,但内容可能被用来改进 Google 的产品,有保密要求的稿子用付费档; - 单个请求最多 8,192 个输入 token; -- **转写的配额**(`--align gemini`,2026-10-01 在 Tier 1 上):`gemini-3.5-transcribe` 每分钟 10 次、每天 100 次,官方文档说按项目算,不按 key。每分钟的 429 会自动等;每天的用完会直接失败,额度恢复后用 `--resume` 接着跑,已经转写成功的句子(`vo//*.asr.json`)不会重做。每天几点恢复没有定论:文档写太平洋时间零点,429 提示里的倒计时指向 UTC 零点。AI Studio 的用量页显示的是 28 天的峰值,不是今天还剩多少; +- **转写的配额**(`--align gemini`,2026-10-01 在 Tier 1 上):`gemini-3.5-transcribe` 每分钟 10 次、每天 100 次,官方文档说按项目算,不按 key。每分钟的 429 会自动等;每天的用完时,429 要求等 90 s 以上就直接失败;但也遇到过日配额用完后它照样等了再试(那时 429 要的等待不到 90 s,或者没写、按 60 s 算),每次调用最多 5 次,看着像卡住,怎么办见 `playbook/04-audio.md` 的"转写用不了时"。额度恢复后用 `--resume` 接着跑,已经转写成功的句子(`vo//*.asr.json`)不会重做。每天几点恢复没有定论:文档写太平洋时间零点,429 提示里的倒计时指向 UTC 零点。AI Studio 的用量页显示的是 28 天的峰值,不是今天还剩多少; - **语气写短**:`--instruct` 和 `[指示]` 最后都进 `speech_metadata.style`。官方建议只写这一句的情境语气("压低声音,卖个关子");年龄、性别、口音这类身份特征不要写进 style,要换音色,或者用 `voices design` 做一个。长篇的"角色设定""导演笔记"是音色漂移最常见的原因; - **每段 Gemini 音频都带 SynthID 水印**(听不出来,但能检测到)。用了 Gemini 旁白的片子,在项目 `NOTES.md` 里写明"旁白为 AI 合成(Gemini TTS,含 SynthID 水印)",发布时按平台要求标注。 From a64f0a09426fe5813ef05666562f0e39fb150004 Mon Sep 17 00:00:00 2001 From: zhanglinghao Date: Wed, 7 Oct 2026 22:10:08 +0800 Subject: [PATCH 2/3] Fixes from the independent review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - playbook/04: whisper output goes through tts.py's align_lines (it spreads spelled-out years over a heard "1953"; a home-made character aligner can misplace the line start); the one-voice advice is for single-narrator films and asks for one short overall style; the pace numbers are re-measured on the take with the same method (4.7–6.7 → 5.6–6.1 chars/s, sd 0.45 → 0.11); the evening steps say how the factor is computed, avoid anullsrc + concat, and write the take, the timeline and the captions back. - tools/audio/README: the quota text describes what the code does (a 429 under 90 s, or without a delay, is retried up to 5 times) and points to the playbook; the joined-synthesis section points to the one-voice advice. - playbook/03 §4: credits stay on screen where a licence or the type doc wants them (a data story's source line), as labels at the floor; the headline is "the line used as the shot's title". - TASTE_CHECKLIST #6: a shot's headline counts as a 主标题. - engines/README: "a row of glass objects"; dropping scene.environment also drops it for the other materials in that scene; the list's lead-in says most pitfalls come from the intro film. - playbook/02: "旧句退完、新句才进", as in #17. - CHANGELOG: rewritten to match, with the two floor changes said explicitly. --- CHANGELOG.md | 11 ++++++----- engines/README.md | 4 ++-- playbook/02-verification.md | 2 +- playbook/03-motion-design.md | 2 +- playbook/04-audio.md | 14 +++++++------- templates/TASTE_CHECKLIST.md | 2 +- tools/audio/README.md | 6 +++--- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b682e..ba15b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,12 @@ **Lessons from a narrated film with 3D: one voice across a one-request take, labels sized from the start, text swaps and readouts, glass in Three.js** - Why: the maintainer asked that the general lessons from a 2-minute narrated canvas + Three.js film go into the docs as short guidance. -- playbook/04: in a one-request gemini take (`--join all`) every line still carries its own style, so one line with a `[direction]` (often the opening) sounds different from the rest; for one voice, put the whole delivery in the overall style and give no line a direction. How to even out the pace line by line on one take without synthesizing again (cuts refined from the waveform, so a misheard first syllable is not clipped; long pauses inside a line capped; per-line `atempo` part of the way towards one target pace), described, not a command yet. When transcription is unavailable (daily quota, API errors): the take is already on disk; `--resume` later, or align locally with whisper, mapping its digits back to a script that spells years out (一九五三). -- tools/audio/README: a daily-quota 429 is not always refused at once; one was waited on and retried like the per-minute limit (up to 5 times a call), which looked hung. -- playbook/03 §4: write readable labels at the floor from the first draft (44 px for `desktop`; one draft had about 40 at 24–40 px); a shot's one-line headline counts as a 主标题 (84 px); source and credit lines go into the description. -- playbook/02 layer 3 and TASTE_CHECKLIST #19: a strip at each text swap in one place (old out before new in; a reviewer found three that the author's own check missed) and at each rolling readout, whose resting values must be sourced, not interpolated. -- engines/README (Three.js): `RoomEnvironment` reflections drew a bright vertical strip down every glass object, and a full-turn `LatheGeometry` seam faces a +z camera by default; turn the seam away and give glass a fresnel rim shell instead of the environment map. +- playbook/04: in a one-request gemini take (`--join all`) every line still carries its own style, so one line with a `[direction]` (often the opening) tends to sound different from the rest; for one voice in a single-narrator film, write one short overall style and no per-line directions (tools/audio/README's "整段合成" points there). How to even out the pace line by line on one take without synthesizing again (cuts refined from the waveform, so a first syllable the ASR placed late is not clipped; long pauses inside a line capped; per-line `atempo` part of the way towards one target pace; the take and the timeline written back, aligned again, captions redone), described, not a command yet: the film went from 4.7–6.7 to 5.6–6.1 CJK chars/s per line. When transcription is unavailable (daily quota, API errors): the take is already on disk; `--resume` later, or take word times from a local whisper and align them with tts.py's `align_lines`, mapping digits back to a script that spells years out (一九五三). +- tools/audio/README: a 429 asking for under 90 s (or naming no delay) is retried like the per-minute limit, up to 5 times a call, so a spent daily quota can look hung; the old text said the daily quota always fails at once. +- playbook/03 §4: write readable labels at the floor from the first draft (44 px for `desktop`; one draft had about 40 at 24–40 px). Source, credit and asset lines go into the description unless a licence or the type doc wants them on screen, and then they are labels. +- playbook/02 layer 3: a strip at each text swap in one place (#17's old-out-before-new-in, unchanged; a reviewer found three that the author's own check missed) and at each rolling readout, whose resting values are checked against NOTES. +- Floors, said explicitly (CONTRIBUTING rule 7): TASTE_CHECKLIST #19 names a readout resting on an interpolated value as a fabricated number (the film's reviewer had judged it under #19); #6 and playbook/03 §4 count a shot's one-line headline as a 主标题, so it is held to 84 px (150 for `feed`), not 44. +- engines/README (Three.js): `RoomEnvironment` reflections drew a bright vertical strip down every glass object, and a full-turn `LatheGeometry` seam faces a +z camera by default; turn the seam away and give glass a fresnel rim shell instead of the environment map. The list's lead-in now says most of its pitfalls come from the intro film. **`bin/vh tts … minimax`: MiniMax narration in one request, timed by its own subtitles** - Why: a film's narration was made with MiniMax T2A (`speech-2.8-hd`) through a project script. Sending the whole script in one request kept the voice from drifting between blocks, which the maintainer had heard and disliked with block-by-block synthesis, and the request's own subtitles gave every character's time without an ASR pass. This makes that a provider. diff --git a/engines/README.md b/engines/README.md index b96b00d..fb4e78a 100644 --- a/engines/README.md +++ b/engines/README.md @@ -152,7 +152,7 @@ window.__vh = { - **等宽字体的坑**:`ui-monospace, monospace` 能通过字体 lint,快照里看起来也是等宽,但 `hyperframes render` 渲染出来却是比例字体(showcase 00 在第一版全片都中了招)。要用 `@font-face` 显式声明,例如 `@font-face{font-family:"LF Mono";src:local("SF Mono"),local("Menlo")}`。**判断字体要看 mp4 渲染出的帧,不要看快照。**`check --snapshots` 只保存对比度检查用的 PNG,不能替代 `snapshot --at`。 - `snapshot` 在 tween 刚开始的那一刻,可能和最终渲染出的帧不一致。关键帧以渲染出的 mp4 为准,逐帧 strip 的做法见 `playbook/02-verification.md`。 - 完整样板:`showcase/02-short-leo-doppler/`(竖屏科普,单个 `index.html`)。 -- **HyperFrames 加 Three.js(3D 世界、一镜到底)的坑**,来自介绍片的制作: +- **HyperFrames 加 Three.js(3D 世界、一镜到底)的坑**,多数来自介绍片的制作: - 每一帧都用 `renderAt(t)` 从 t 算出摄像机和所有物体的状态。摄像机路线用按 t 参数化的样条,不用 Three.js 的动画时钟。关卡处可以用 stop 关键帧停站,但要叠一层低幅的手持漂移,镜头不要完全停死(原因见 `playbook/08-vfx-and-motion-sources.md` 的"一镜到底"一节)。 - `VideoTexture` 必须**每帧**设置 `texture.needsUpdate = true`,否则渲染出的屏幕是全黑的。视频按 t 去 seek,而且要等 seek 完成。 - `__hf.buildReady` 要在普通 `