把论文笔记里的 K.explainer 分镜渲染成带中文配音和字幕的竖屏视频,可直接发微信视频号 / 抖音 / 小红书。
<paper> |
分镜 | 时长 |
|---|---|---|
ppo |
assets/js/demos/ppo.js 五幕 |
约 3 分 50 秒 |
awr |
assets/js/demos/awr.js 六幕 |
约 5 分 31 秒 |
deepmimic |
assets/js/demos/deepmimic.js 五幕 |
约 4 分 35 秒 |
amp |
assets/js/demos/amp.js 五幕 |
约 4 分 46 秒 |
phc |
assets/js/demos/phc.js 六幕 |
约 6 分 33 秒 |
add |
assets/js/demos/add.js 五幕 |
约 6 分 34 秒 |
ase |
assets/js/demos/ase.js 六幕 |
约 7 分 47 秒 |
calm |
assets/js/demos/calm.js 五幕 |
约 6 分 15 秒 |
pulse |
assets/js/demos/pulse.js 六幕 |
约 7 分 10 秒 |
diffusion_policy |
assets/js/demos/diffusion_policy.js 七幕(dp-explainer) |
约 8 分 2 秒 |
beyondmimic |
assets/js/demos/beyondmimic.js 十二幕(bm-explainer) |
约 9 分 47 秒 |
lcp |
assets/js/demos/lcp.js 十幕(lcp-explainer) |
约 9 分 56 秒 |
cosmos |
assets/js/demos/cosmos.js 五幕 |
约 2 分 55 秒 |
groot |
assets/js/demos/groot.js 七幕 |
约 5 分 38 秒 |
transformer |
assets/js/demos/transformer.js 七幕(tf-explainer) |
约 5 分 51 秒 |
pi0 |
assets/js/demos/pi0.js 七幕 |
约 6 分 14 秒 |
pi05 |
assets/js/demos/pi05.js 七幕 |
约 6 分 27 秒 |
op3soccer |
assets/js/demos/op3soccer.js 十二幕(soccer-explainer) |
约 10 分 16 秒 |
sonic |
assets/js/demos/sonic.js 七幕 |
约 5 分 32 秒 |
gmr |
assets/js/demos/gmr.js 十三幕(gmr-explainer) |
约 12 分 2 秒 |
omniretarget |
assets/js/demos/omniretarget.js 十二幕(omniretarget-explainer) |
约 12 分 56 秒 |
humanml3d |
assets/js/demos/humanml3d.js 十二幕 |
约 10 分 59 秒 |
smp |
assets/js/demos/smp.js 八幕 |
约 8 分 37 秒 |
domain_randomization |
assets/js/demos/domain_randomization.js 八幕(dr-explainer) |
约 8 分 20 秒 |
quadterrain |
assets/js/demos/quadterrain.js 九幕(qt-explainer) |
约 9 分 27 秒 |
realhumanoid |
assets/js/demos/realhumanoid.js 十幕(rh-explainer) |
约 10 分 33 秒 |
asap |
assets/js/demos/asap.js 十二幕(asap-explainer) |
约 10 分 16 秒 |
inekf |
assets/js/demos/inekf.js 十二幕(inekf-explainer) |
约 10 分 6 秒 |
berkeley_humanoid |
assets/js/demos/berkeley_humanoid.js 十二幕(bh-explainer) |
约 11 分 35 秒 |
toddlerbot |
assets/js/demos/toddlerbot.js 十二幕(tb-explainer) |
约 13 分 30 秒 |
exbody |
assets/js/demos/exbody.js 十三幕(exbody-explainer) |
约 13 分 17 秒 |
h2o |
assets/js/demos/h2o.js 十四幕(h2o-explainer) |
约 13 分 49 秒 |
omnih2o |
assets/js/demos/omnih2o.js 十七幕(omnih2o-explainer) |
约 13 分 25 秒 |
maskedmimic |
assets/js/demos/maskedmimic.js 十八幕(mm-explainer) |
约 15 分 8 秒 |
hover |
assets/js/demos/hover.js 十六幕(hover-explainer) |
约 13 分 23 秒 |
exbody2 |
assets/js/demos/exbody2.js 十八幕(exbody2-explainer) |
约 17 分钟 |
hugwbc |
assets/js/demos/hugwbc.js 十六幕(hugwbc-explainer) |
约 18 分 30 秒 |
homie |
assets/js/demos/homie.js 十七幕(homie-explainer) |
约 16 分 23 秒 |
twist |
assets/js/demos/twist.js 十六幕(twist-explainer) |
约 16 分 56 秒 |
twist2 |
assets/js/demos/twist2.js 十八幕(twist2-explainer) |
约 17 分 10 秒 |
falcon |
assets/js/demos/falcon.js 十七幕(falcon-explainer) |
约 18 分 3 秒 |
hdmi |
assets/js/demos/hdmi.js 十八幕(hdmi-explainer) |
约 20 分 15 秒 |
- 画面:直接复用
<paper>.js分镜的draw(t),数字与笔记算例一致;前后加片头、总结页(papers/<paper>.js)。 - 分镜
draw(t, clock)的第二个参数是这一幕的真实时间:旁白比动画长、画面停在to之前时它照样往前走,步态这类循环动作可以用它继续动(网页播放器只传t)。 - 配音:
edge-tts(zh-CN-YunxiNeural,需联网),旁白在papers/<paper>.py。 - 读音:「arXiv」念 archive,旁白里写
archive、DISPLAY把字幕换回arXiv(直接写 arXiv 会被逐字母或乱读);会议名 CoRL 直接写成一个词、按一个词念,不要拆成「C o R L」(ToddlerBot 第一版这样写被逐字母念出来,看片反馈要求改成整词:同句合成整词版 3.14 s、逐字母版 3.89 s);「数」单用会被念成 shǔ(「数都从图 8 读」),旁白改说「数字 / 数值」。旁白里的数字写阿拉伯数字(「75」不写「七十五」),字幕跟着显示数字;「2 + 量词」多数念 liǎng,个别(如「2 簇」念 èr)要 DTW 核对;比分式的「2 对 2」会被念成「两对两」,旁白写「二对二」;同一个量词在不同句子里也可能读法不同(真实世界人形行走里「2 项」念 liǎng,OP3 足球里「训练分 2 段」「力矩 2 项」却念成 èr),所以每一处「2 + 量词」都要单独比 DTW,念错的写成「两段」「两项」、字幕换回数字,念错的旁白保留汉字、用DISPLAY把字幕换成数字。拿不准的字用同句合成原字与各读音的同音字,比 log-mel DTW。多音字放进整句后读法可能和单独合成时不同:ToddlerBot 的「捏多重,夹爪就合多紧」短句里念 zhòng、整句里却念 chóng,旁白改写同音的「众」、字幕用DISPLAY换回「重」;「关节角的差」「策略把这个差学进去」整句里念 chà,旁白写「插」(chā)、字幕换回「差」(同篇「关节角之差」本来就念 chā,不用改),所以要拿整句比。表格的「一行 / 每一行 / 这一行」整句里常被念成 xíng(GMR 第 3 幕:「每一行」离「形」0.07、离「航」0.50),旁白写同音的「航」、字幕用DISPLAY换回「行」(按短语换,别误伤「偏航角」);「调参」「单独调」整句里会念成 diào(GMR 第 1、10、13 幕,2026-10-12 看片反馈),「参」又比不出读音,旁白写两个字都只有一个读音的「条餐」「单独条」、字幕换回(「不调奖励」念对了,不用改)。希腊字母记号念成中文名,字幕用DISPLAY换回(ASAP 的$\pi^{\Delta}$ 念「德尔塔模型」、字幕显示「Δ 模型」);DTW 两个读音只差零点几、或两组同音字结论相反时(ASAP 的「相当于」「重定向」),别硬选,换个说法。 - 希腊字母在旁白里写中文读音、
DISPLAY换回公式:ψ 写「赛」(不写「普西」,HugWBC 2026-10-21 看片反馈),σ「西格玛」,α「阿尔法」,ω「欧米伽」,τ「陶」。「赛」紧跟在名词后面会被连成一个词(「相位偏移赛」切成「偏移赛」),前面加「记作」:「相位偏移,记作赛」。 - 数字:两个数之间只隔一个逗号,会被当成千分位念成一个数(H2O 第 8 幕「66 加 72,138 维」念成「七万两千一百三十八维」),算式要写成「66 加 72 等于 138 维」。拿不准时用 edge-tts 的
boundary="WordBoundary"把每句合成一遍、看数字是不是落在同一个词里(2026-10-15 对 H2O 全篇 78 句查过,只有这一处)。 - 停顿:TTS 会把它不认识的词拆开念。「遥操作」一律被切成「遥 | 操作」,大多数句子里听不出来,但 OmniH2O 片头「还能拿遥操作数据」被读成「拿遥,操作数据」:「遥」拖长到 246 ms,后面 240 ms 没有声音(2026-10-16 看片反馈)。edge-tts 的免费接口不收
<phoneme>之类的 SSML,只能改句子:改成「还能把遥操作数据拿来训练」后间隙回到 50 ms(「操」本身送气 c 就有 40–80 ms 的清音)。查法:合成整句、用 WordBoundary 找到「遥」,对「遥」起点到「操作」中点做基频跟踪,量最长的一段清音 / 静音,超过 120 ms 就是断开了;只看 WordBoundary 的间隙不够(这句的间隙是 82 ms,别的句子都是 0)。全篇每一处都要量,因为同一个词在不同句子里断不断不一样。反过来,TTS 也会把不该连的两个字连成词:「只跟头和双手」被切成「只 | 跟头 | 和」,念成「只,跟头(翻跟头)」,改成「只跟踪头和双手」(同日看片反馈;H2O 片尾的预告也是这句,已发布未改)。用 WordBoundary 把全篇切词打印出来,按关键字(这次是「头」)逐个看上下文,比只听一遍可靠。MaskedMimic 全篇切词又找到三处:「平地上任一关节」切成「上任 | 一」、「从先验取 z」切成「从先 | 验」、「地形上照平地」切成「上照」,都是方位词「上」或介词「从」和后一个字粘成了词,改成「在平地上,任何一个关节」「用先验来取 z」「在不平地形上,也照着平地」;「残差」被切成「残 | 差」,照 ASAP 旁白写「残插」、字幕换回,「均方差」改说「均方误差」(2026-10-16)。HOVER 片头写成「一篇只跟头和双手」又被切成「跟头」,改成「跟踪」,同一句的「一篇跟关节角」被切成「跟关 | 节角」,也改成「跟踪关节角」。「重定向」在旧地址上切成「重 | 定向」(「重」单念可能读 zhòng),HOVER 改说「把人的动作搬到 H1 上」。HugWBC 全篇切词(2026-10-18):「不干预训出来的」切成「不 | 干 | 预训」(「干」单念可能读 gàn),改成「没做干预训练的」;「任务命令乘行为命令」切成「乘行 | 为」、「不直接放相位」切成「放相 | 位」、「干预组学上身」切成「学上 | 身」、「单脚跳最差」「变差」「误差热图」里的「差」被单独切出,都换了说法;「着地」一律说「触地」,「遥操作」换成「由人远程操作」「操纵」。TWIST 全篇都在讲重定向,没法绕开,旁白一律写只有一个读音的「崇定向」、字幕用DISPLAY换回(整句的 log-mel DTW 对「虫 / 众」两种读法的参考是 0.855 / 0.840,判不准、还偏 zhòng,细节见papers/twist.py的说明)。 - 单字并列:「走、跳、站是同一个策略」里的「走、跳、站」被合成得几乎没声音(音量约为正常的四分之一、每个字不到 0.1 s,整句听起来像「准确地说……同一个策略」中间空了两秒多,HugWBC 片头 2026-10-21 的看片反馈),改成双字词「走路、跳跃和站立」后正常(每个词 0.36–0.43 s)。同一篇里「人会走、跑、跳」「在走、跑、跳和单脚跳之间」音量正常,所以不是每次都吞,只是会发生;合成后按 0.1 s 窗口看一遍每句的音量,连续 1.5 s 的平均 RMS 明显低于其他句子(这句是 250,其他句子最低 337)的就是可疑的。这一句改长了 0.312 s,在句末补 0.021 s 静音凑成整 10 帧,之后的画面全部沿用上一版、按帧号往后挪 10 帧拼接,只重渲片头第 0–1,119 帧(85 s)。
- 跨词切错:「权重由期望接地概率分配」被切成「由期 | 望 | 接地 | 概率」,「期望」拆开念(HugWBC 第 7 幕,2026-10-21 看片反馈),同篇另两句「得到期望接地概率」「一过 0.5,期望触地」都切对了;前一个字能和「期」组词(由期、按期、到期)时最容易出事。改成「权重取决于期望接地概率」后是「取决于 | 期望 | 接地 | 概率」。这一句短了 0.192 s,补 0.025 s 静音凑成整 5 帧,其后的画面整体提前 5 帧沿用,只重渲这一句的 426 帧。HOMIE 全篇(2026-10-22 看片反馈「操作员、遥操作要连读」):「操作员遥操作宇树 G1」的「遥」拖到 256 ms、后面 150 ms 没声音,改成「操作员正在遥操作宇树 G1」后「遥」133 ms、清音 70 ms;同篇还有「机器人可以离操作员很远」切成「离操 | 作员」、「给遥操作留一个」切成「遥操 | 作留」、「手臂穿同构外骨骼」切成「穿同 | 构外 | 骨骼」、「取绝对值」切成「取绝 | 对值」、「视觉和 V R」切成「视 | 觉和」、「表四比的是」切成「表 | 四比」、「逆运动学」在每个句子里都切成「逆运 | 动学」(改说「逆向运动学」),共改 29 处说法。只看听感容易漏,查法是把全篇切词和一个参考切分(jieba,加上「遥操作」「外骨骼」「同构」这类术语)对照:TTS 的一个词跨过参考词的边界、又有一头切在参考词中间(「作留」「离操」「构外」),就是切错;再在每个参考词内部量最长的静音,超过 120 ms 就是断开(塞音、塞擦音本身有 60–90 ms 的闭塞,不算)。同一个改法在不同上下文里结果不同(「取绝对值」换了五种说法只有「取负的绝对值」切对),所以每句都要拿整句重新合成、再查一遍。
- 多音字被单独切出来时会读错:「速度误差 0.3 米每秒,得 1.28 分」的「得」后面直接跟数字,切成单独一个词,没有读成 dé(HugWBC 第 6 幕,2026-10-21 看片反馈),改成「得到」。查法:全篇跑一遍 WordBoundary,挑出单独成词的多音字(得、地、为、差、调、重、长、行、还、着、只、数……),逐个看上下文。补语「跟得准」「罚得越重」、量词「每只脚」、「还行」「就行」这类合成器一般读得对;这次一并改了「准确地说」(「地」单独成词)→「准确来说」、「固定为 0.5」「互为镜像」(「为」单独成词)→「固定在」「正好是一对」、「目标与实际之差」(「差」单独成词)→「差值」。五句时长变了 −0.024 到 +1.104 s,各补静音凑成整帧,只重渲这五句(每句约 1 分钟),其余画面按帧号挪动沿用。
- 图号、表号里的「一」会被当成数词变调:「论文图 1 就是…」读成 tú yí、「表一把…」的「一」短得几乎没有音高(HOMIE,2026-10-22 看片反馈「图 1 要读一声」)。改成后面紧跟停顿的说法(「再看论文的图一:」「先看表一:」)。听不出来时用
tone_cmp.py量:把那个字换成同位置的参考字(「咿」「铱」读 yī,「移」读 yí,「亿」读 yì),比单独成词那一段的基频曲线,和哪个重合就是读成了哪一声。 - 听写查读音:WordBoundary 只给切词、
tone_cmp.py只比声调,都听不出一个字念成了哪个音节。FALCON 第 5 幕「论文图 2 里」念成了「论文图两里」(同篇第 4 幕「论文图 2 的」念对了,2026-10-25 看片反馈),切词和声调都看不出来,是用 faster-whisper(pip install faster-whisper,medium或large-v3-turbo,音频先用 ffmpeg 解成 16 kHz 数组再传进去)把每句听写出来才找到的。全篇听写后,把原文和听写都转成不带声调的拼音(pypinyin)、数字按同一规则转成汉字再对齐,对不上的地方逐个看:大多是听写模型的偏好(「上身」→「上升」、「P D」→「屁地」、「位姿」→「位置」),判断办法是拿一个读音明确的同音字合成对照句(「位资」也被听成「位置」,就说明是模型的偏好、不是读错);两个模型都听错、对照句又听对的才算读错(FALCON 第 17 幕「课程只给手上加力」两个模型都听成「只得」,改说「课程只在手上加力」后听对)。图号、表号里的阿拉伯数字 2 不可靠,写成「图二」「表二」、字幕用DISPLAY换回。同音字合成的音频一般不逐采样相同(「己 / 挤 / 戟」互差很大,「咿 / 铱」却完全一样),不能拿「是不是一模一样」判读音。 - 字幕切行:
build.py的chunks()按标点切、每行约 17 字;一段没有标点的长句从中间切时,只在两个字母 / 数字之间以外的地方切,英文词和数字不会被拆到两行(OmniRetarget 第一版成片里出过「De / launay」「Laplac / ian」)。 - 字幕里的公式(2026-10-16 MaskedMimic 起):
DISPLAY可以把念法换成$…$(「e 的负 2.9 次方」→$e^{-2.9}$、「阿尔法」→$\alpha$、变量$z$),stage.html见到$就交给 KaTeX,用 KaTeX 自带的粗体字形,和 900 字重的字幕放在一起不显得细;build.py切行时整条公式留在一行(公式里的逗号不算断点),行长按字形算、不按 TeX 源码。变量按短语换(「来取 z」→「来取$z$ 」),别做全局替换:「30 Hz」里也有 z。上标里别放中文,$e^{-c\times\text{误差}}$在手机上小得看不清,改成$\exp(-c \times \text{误差})$。build.py --reuse沿用的句子连字幕一起沿用,只改DISPLAY不会刷新它们,要另外把timeline.json里对应的字幕换掉(旁白没变,音频不用重合成)。篇目里设SPACE_CJK = True,display()会在中文和字母、数字、公式之间补空格(「0.25 m就」→「0.25 m 就」;默认关着,旧篇目的字幕不变);没有标点的长句从中间切时,也不在数字和单位之间、不在一个小数中间断(MaskedMimic 第一版有一行以「cm涨到」开头;按新规则重建时 GMR、ExBody 各有一行会跟着变好)。 - 同步:每句旁白对应分镜的一段
[from, to],旁白比动画长时画面停在to之前一刻。[from, to]最好对齐分镜 cue 的at,「要点」框才会和旁白同步。 - 输出:
out/<paper>/<paper>_video.mp4(H.264 + AAC,30 fps,-crf 19),不入库。在对话里交付给用户的就是这一版高画质原片;只有放进笔记media/的才按下面「放进笔记」再压小。对话附件上限 30 MiB,十分钟以上的原片会超:画面-c:v copy不动,只把音频降到-b:a 96k -ac 1(旁白本来就是 24 kHz 单声道),真实世界人形行走从 30.3 降到 27.5 MiB,OP3 足球从 30.4 降到 27.7 MiB。还是超就分段交付,用python3 split.py <paper> [段数](输出在out/<paper>/parts/):它在旁白里找至少 0.5 s 的静音、里面又刚好有关键帧的位置当切点,画面-c copy在那个关键帧切开,音频从narration.wav按采样点切、各段重新编码。别用-f segment -c copy连音频一起切:AAC 帧(1024 个采样)和关键帧对不齐,每个接缝会丢掉或多出几十毫秒,切点落在句子中间就听得出跳音(HOMIE 2026-10-22 的看片反馈:320.87 s、641.73 s 两处正好在说话,接回去少了 56 ms 和 27 ms,第一段开头还多了 42.7 ms 没去掉的编码填充)。切点离前后两句旁白都至少留 0.4 s(MARGIN,找不到才退到 0.25、0.08 s,脚本会打印每个切点实际留了多少):HDMI 2026-10-27 原先一个切点离「第 68 次摔倒了」的句尾只有 0.1 s,手机上连着放会吞掉最后一个字。build.py还会给整条旁白铺一层约 −72 dBFS 的底噪(room_tone()):同日看片反馈第 13、14 幕让项目页视频放完后,八九秒纯静音之后的下一句开头被切,文件里那两句是完整的(faster-whisper 听写过),我们推测是蓝牙耳机或手机遇到长静音让音频输出休眠,没有在设备上证实。
9:16 画面发到视频号,分享页、主页会被裁成居中的 6:7(1080×1260,即 y 330–1590),播放页的上下两端还压着作者、标题、转赞评收藏与浮评。所以 stage.html 把所有要读的东西放在 y 330–1440 之内:分幕标题 330 起,动画区 504–1012,要点框 1026 起(放不下自动缩字号),字幕 1318 起(一行放不下缩字号,底不过 1440);1440 以下 480px 不放内容,顶部只留品牌角标。片头 / 片尾用 V.stack() 按实际高度排进 V.SAFE_TOP–V.contentBottom()(让出字幕);封面另有规则,见下节。
左右:视频号在长屏手机上把 9:16 铺满播放区,两侧各裁掉约 47px。动画区是 58–1022,分镜的 viewBox 按这一幕实际画到的范围缩放(render.mjs 渲染前调 fitStages(),只放大不缩小),svg 再内缩 12px,画面内容离左右边都 ≥ 72px。
渲染后用 node check_layout.mjs <paper> 逐帧检查:可见块互不重叠、不压字幕、都在安全区里,片头 / 片尾上下相邻的块间距不小于 10px(V.stack 放不下时会压缩间距,压到几 px 画面上就像贴在一起,SMP 片尾「下一篇」折成两行时出过这事),输出 no overlap 才算过。
分镜内部另跑 node check_layout.mjs <paper> story:每 0.25 s 抽一帧,量每段可见文字(svg <text> 与 KaTeX / svgRich 的字形)的外框,查三件事——文字压文字、文字压图(论文图、真机图;3D 机器人贴图按当前帧的不透明像素算,机身旁的空白不算)、文字越出有描边的框(面板、胶囊);clip-path 裁掉的部分不算,不透明度 < 0.15(正在淡入淡出)的不算。两层字交叉淡入淡出也会被抓到,改成先淡出再淡入。GMR 第 3 幕「×2」压住「张匹配表」就是这一步查出来的(2026-10-12 看片反馈),同一轮还修了第 1、4、6、10 幕的五处。2026-10-17 起还查第四件事:文字伸出 svg 画面(fitStory 把 viewBox 缩到分镜画的东西上,伸出去的字会被裁掉一截),记为 clipped by frame edge;MaskedMimic 第 11 幕右边的「个部位露过位置」伸出 27 px,前三件都查不出来。文字被实心的形状(流程图里跑的小点、采样点)盖住仍然查不到,改完分镜还是要抽每段末尾的帧看一遍。
node check_layout.mjs <paper> story near 再严一档:没压上、但只隔不到 3px 的也记下(文字和文字、文字和 3D 贴图的不透明像素、框外的字贴着框线)。OmniH2O 第 5 幕的「120」「40」贴着右栏的框(0px)、第 4 幕图例贴着顶上的横幅、第 6 幕错开两行的条形图标签上下只隔 1px,都是这一档查出来的(2026-10-16 看片反馈)。两行字的字框隔 3–4px 是正常行距,所以门槛定在 3px。只改了几幕时在末尾加时间段(秒)只查那几幕:node check_layout.mjs omnih2o story near 378,418。嵌在分镜里的真机片段(svgClip 的 <video>)按整块图算。
check_layout 不查线和移动的点压字(2026-10-21 HugWBC 看片反馈「第 5 幕文字和画面重叠」):第 5 幕表示数据流向的小圆点沿路径直穿方框,把「历史编码器」「估计器」「critic」等框里的字挨个压过去;一根竖向连线压在「上一步动作 19」那行小字上;第 6 幕的命令虚线一升到「实际 −0.14」标签的高度就从字中间穿过;第 14 幕条形图的竖向虚线网格从数值后面穿过。当时用一个临时脚本(out/audit/s5check.mjs,不入库)每 0.1 s 取一帧,拿可见圆点的外框、path / line 上每隔 2 px 的点去碰文字外框,按幕归并后再逐处看图:KaTeX 的 foreignObject 外框比字形高,贴近坐标框、地面刻度的会误报;换段淡入淡出的 0.2–0.3 s 里两块画面叠在一起也会报,这两类都不用改。修法:流向点进方框就隐藏、只在连线上画;跟着线动的标签放在自己那条线的左边同一高度(右对齐),不放在线上方;网格后面的数值垫一块和底色相同的矩形。
字压矩形条、线和箭头也查不到(FALCON 2026-10-25:第 2 幕的「手臂」被往右伸的噪声条盖住)。当时的临时脚本 out/shape_overlap.mjs 每 0.5 s 取一帧,文字外框和可见的 rect(文字整段在框里的底色不算)、line / path / circle(沿几何每 2 px 取点)比;改字号、挪位置这类会牵动全片的修改,前后各跑一遍、只看新增的。同日按「过小的字体适当放大」把 FALCON 分镜里 9–10.5 号的字放到 11–11.5(画面上约 13 px 起):在 bundle 的 svgText / svgRich 外包一层取下限,比逐处改字号少漏;放大后行距不够的地方 check_layout story near 会报,逐处拉开。
项目页有真机视频时,剪成小段嵌进分镜(K.svgClip,规则见 AGENTS.md「真机效果用项目页的视频片段」)。网页上放的是 H.264;渲染用的 Playwright Chromium 是开源版,解不了 H.264(canPlayType('video/mp4; codecs="avc1…"') 为空,能放 VP9 / AV1),所以 render.mjs 启动时把 assets/img/papers/<paper>/clips/*.mp4 转成全关键帧 VP9(-g 1 -crf 12)放进 out/<paper>/clips/,stage.html 的 window.__clipSrc 把地址换过去。片段在第几秒由 window.__wallSince(cue) 按 timeline.json 算(这一幕从 cue 开始已经过了多少秒视频时间),和分镜停没停无关,所以并行分段、只重渲几幕都得到同一帧;每帧 renderAt 之后等 window.__clipWait()(所有片段 seeked 且下一帧画出来)再截图。OmniH2O 十段片段第一次转码连同预热、出 12 张抽帧共 2 分 48 秒,之后按修改时间复用。网页上的播放 / 暂停逻辑在 Playwright 里测过(把 mp4 请求换成 VP9):只播当前要点的那一段、换幕或暂停就停;没加载完之前不追进度,否则每帧重设 currentTime 会一直卡在第一帧;测试服务器要支持 Range,不然片段没法 seek(GitHub Pages 支持)。
封面保持最初的片头设计,和 PPO / AWR / DeepMimic / AMP 的封面一致(不跟着视频片头的安全区缩小):由 stage.html 的 classicCover() 在封面帧上重新排片头的四块,内容不用改。
- 整张 1080×1920、不缩放;顶上是品牌角标「论文精读 · 机器人学习」和 arXiv 胶囊,没有字幕。
- 英文大标题:250px、字距 16px、主题蓝;名字长到放不下就改 4px 字距、等比缩小到宽 ≤ 980(DeepMimic 约 170px),在 190–440 这一带里垂直居中。
- 中文名 70px 粗体 + 一句话 40px 灰字,从 470 起,各占一行(过长就缩字号,不折行)。
- 论文信息框从 680 起(34px,内边距 34 / 40),标题可以折行。
- 目录紧跟信息框(间距 24),40px;行距随条数收紧:≤5 条 1.9、6 条 1.75、7 条 1.55、8 条及以上 1.5 / 38px;视频号「设置封面」按 3:4 置顶裁剪(只留 y 0–1440),所以最后一行目录的字底不过 1360(离裁剪线 80px;原先按 AMP 留 35px,手机上看像压在裁剪线上,2026-10-12 GMR 封面的看片反馈后放宽),超了先把行距压到 1.35、再缩字号(PHC / ASE / PULSE 6 条为 1.5,7–8 条的会缩到 36 / 32px)。
- 每块都排在上一块底下,内容比原设计高就整体往下推,永不重叠。
- 目录超过十条时单列放不下(OP3 足球十二幕:字号缩到 32px、行距 1.35 仍会越过 1405):在
papers/<paper>.js里把.toc设成两列网格(display: grid; grid-template-columns: 1fr 1fr),左列 01–06、右列 07–12,每条目录要短到一列放得下;classicCover()只改字号和行距,不动网格。
生成封面不需要先跑 build.py(没有 timeline 时 render.mjs cover 自己补一段片头)。生成后必须跑 node check_layout.mjs <paper> cover:四块互不重叠、都在画面里、大标题 / 中文名 / 一句话 / 每条目录各占一行、目录字底不过 1360,输出 no overlap 才算过。海报由 cover.png 缩到 540×960 的 jpg。
cd scripts/paper_video
pip3 install edge-tts imageio-ffmpeg
npm install
python3 build.py awr # 旁白 TTS -> out/awr/timeline.json + narration.wav
node render.mjs awr stills 14,125,200 # 先抽几帧看排版 -> out/awr/still_<t>.png
node render.mjs awr video 30 # 逐帧截图 -> ffmpeg -> out/awr/awr_video.mp4
node render.mjs awr clip 120,180 30 # 只渲染 120–180 s 这一段(配同一段旁白)-> out/awr/clip_120-180.mp4,改了某一幕先给人看
node render.mjs awr cover # 封面(最初的片头设计、无字幕)-> out/awr/cover.png
node check_layout.mjs awr cover # 封面排版检查,输出 no overlap 才算过- Chromium:设置
CHROME=/path/to/chrome,否则用 playwright-core 默认路径。几篇可以同时渲染(各自写stage.<paper>.built.html)。 - 走 TLS 代理时设置
SSL_CERT_FILE,build.py会让 edge-tts 用这份 CA。 - 2026-10 起旧的合成地址
speech.platform.bing.com一律回 403(语音列表照常能取),edge-tts ≤ 7.2.8 还连那里;build.py会把它改到新版 Edge 用的api.msedgeservices.com(同一个令牌作Ocp-Apim-Subscription-Key,见 rany2/edge-tts PR #491),设EDGE_TTS_LEGACY=1可保留旧地址。新地址合成的同一句话和旧地址的音频不完全一样(log-mel DTW 0.6,新地址自己连合成两次是 0.09),换引擎后要整篇重合成、按整句重新核对多音字(GMR 的「不调奖励」在新地址上仍念 tiáo)。 - 2026-10-16 反过来了:新地址连续回 429(Too Many Requests,几分钟才放行几句,一篇 86 句要几个小时),旧地址用 edge-tts 7.3.1 又能合成、1 秒一句不限流,HOVER 整篇用
EDGE_TTS_LEGACY=1合成(同一篇不混用两个地址)。build.py遇到 429 会退避重试,TTS_PACE=4让每次请求前先等几秒;每句的 mp3 和句子边界按「地址 + 音色 + 语速 + 文字」的哈希缓存在out/<paper>/tts_cache/,被打断后重跑只补没合成的句子。 - 新增一篇:写
papers/<paper>.py(SCRIPT/DISPLAY)和papers/<paper>.js(window.PaperVideo的arxiv/intro/outro;没有 arXiv 版本的论文改写badge,如 HumanML3D 的'CVPR 2022',角标就显示会议名),<paper>是assets/js/demos/<paper>.js的 bundle 名;讲解动画的演示 id 不必叫<paper>-explainer(diffusion_policy.js里是dp-explainer、beyondmimic.js里是bm-explainer),render.mjs会从 bundle 里读出'…-explainer'那个 id。 - 放进笔记:网页版再压一次(
-crf 27 -tune stillimage -b:a 64k -ac 1 -movflags +faststart;PPO 5.2 MB,更长的 AWR 用-crf 30压到 7.1 MB,DeepMimic 用-crf 29压到 6.2 MB;AMP 用-crf 31压到 6.4 MB,六分多钟的 PHC 用-crf 32压到 8.5 MB,ADD 加-preset slow同-crf 32压到 8.3 MB,七分多钟的 ASE 用-preset slow -crf 33压到 9.5 MB;六七分钟的 CALM / PULSE 用-preset slow -crf 31压到 7.9 / 9.4 MB,八分钟的 Diffusion Policy 用-preset slow -crf 33压到 9.9 MB;约三分钟的 Cosmos 用-preset slow -crf 30压到 4.3 MB,GR00T N1 用-preset slow -crf 31压到 7.4 MB;六分钟上下的 Transformer / π₀ / π₀.₅ 同参数压到 7.7 / 8.2 / 8.7 MB,SONIC / OmniRetarget 同参数压到 7.3 / 6.7 MB;八分半的 SMP 用-preset slow -crf 34、音频降到-b:a 48k压到 9.7 MB,八分多钟的域随机化同样 48k 音频、-preset slow -crf 35压到 9.9 MB(-crf 34是 10.2 MB),九分半的四足野外盲走同样 48k 音频、-preset slow -crf 36 -tune stillimage压到 10.4 MB(-crf 35是 10.8 MB),十分半的真实世界人形行走同参数改-crf 37压到 11.1 MB(-crf 36是 11.4 MB;对话里交付的是-crf 19原片),十分钟出头的 OP3 足球(十二幕)同样 48k 音频、-preset slow -crf 36 -tune stillimage压到 11.3 MB(-crf 35是 11.8 MB),将近十分钟的 LCP(十幕,第二版改成以动图为主,画面一直在动,体积比同长的静帧多)用-crf 37压到 11.3 MB(渲染原片 35 MiB 超了 30 MiB,对话交付改用-crf 21+ 96k 单声道音频重编码,27.8 MiB),十分钟出头的 ASAP(十二幕,同样以动图为主)同参数-crf 37压到 11.3 MB(-crf 36是 11.7 MB;原片 34.5 MB,只降音频的-c:v copy仍有 30.2 MiB,对话交付同 LCP 用-crf 21+ 96k 单声道,26.6 MiB),九分 47 秒的 BeyondMimic(十二幕,第二版,片尾改预告 InEKF)同参数-crf 37压到 11.3 MB(原片 34.0 MiB,对话交付用-crf 21+ 96k 单声道,27.1 MiB),十分 6 秒的接触辅助 InEKF(十二幕)同样 48k 音频、-preset slow -crf 36 -tune stillimage压到 11.1 MB(-crf 37是 10.7 MB;-crf 19原片 29.5 MiB,没超 30 MiB,对话里直接交付原片),11 分 16 秒的 Berkeley Humanoid(十二幕)同样 48k 音频、-preset slow -crf 37 -tune stillimage压到 12.6 MB(-crf 36是 12.6 MB;-crf 19原片 35.8 MiB,四段并行渲染 20,295 帧,对话交付同 LCP 用-crf 21+ 96k 单声道,28.5 MiB),13 分 30 秒的 ToddlerBot(十二幕,旁白第一版 15 分钟、压短两轮后仍是全系列最长;2026-10-10 片头 CoRL 改成整词念后重渲,2026-10-11 改用论文原图、重画锥齿轮、补 DDPM 一句后再重渲)同参数-crf 37压到 14.5 MB(-crf 19原片 41.4 MiB,四段并行渲染 24,303 帧;对话交付用-crf 23+ 96k 单声道,29.8 MiB),10 分 59 秒的 HumanML3D(十二幕,2026-10-11 从九幕重做)同参数-crf 37压到 11.8 MB(-crf 36是 12.1 MB;-crf 19原片 35.0 MiB,四段并行渲染 19,548 帧;对话交付用-crf 21+ 96k 单声道,28.5 MiB),12 分 2 秒的 GMR(十三幕,2026-10-11 从七幕重做;10-12 第 3–8、12 幕的 G1 换成 3D 贴图、旧合成地址 403 后改走新地址整篇重合成,再整片重渲)同参数-crf 37压到 13.2 MB(-crf 19原片 41.9 MiB,四段并行渲染 21,659 帧;3D 机身纹理多,对话交付-crf 22+ 96k 单声道会超 30 MiB,改用-crf 23,28.8 MiB),12 分 56 秒的 OmniRetarget(十二幕,2026-10-12 从七幕重做,G1 是复现实验的 3D 贴图;10-13 按看片反馈补了 SOCP、Delaunay 等几句、做过一轮全局检查,整片重渲)同参数-crf 37压到 13.4 MB(-crf 19原片 37.7 MiB,四段并行渲染 23,271 帧,音视频时长 775.70 / 775.67 s;对话交付-crf 22+ 96k 单声道,28.9 MiB),13 分 17 秒的 ExBody(十三幕,2026-10-12 新做,H1 是照官方步骤重定向的 CMU 动作的 3D 贴图;2026-10-13 全局检查后重渲,同日挥手、击掌换成腿部动捕正常的14_25、20_11后再整片重渲;2026-10-14 按看片反馈重做第 5、10 幕的结尾,只重渲这两幕、其余沿用上一版的分段按帧号拼接,见下文「只改了几幕」)同参数-crf 37压到 14.2 MB(-crf 19原片 41.0 MiB,23,919 帧,音视频时长 797.30 / 797.28 s;对话交付-crf 23+ 96k 单声道,29.6 MiB,贴着 30 MiB 的附件上限),13 分 49 秒的 H2O(十四幕,2026-10-14 新做,H1 是照官方grad_fit_h1.py重算的 CMU 动作的 3D 贴图;2026-10-15 第 4 幕把人体骨架叠到 H1 上、第 5 幕补 Adam / Adadelta 各一句话,build.py --reuse只合成新加的一句,后面整体顺延 22.5 s 即 675 帧,只重渲第 4、5 幕共 4,045 帧,其余按帧号拼接上一版的分段,两个接缝的新旧帧平均差 0.03–0.11 灰度级;同日第 8 幕「66 加 72,138 维」被念成「七万两千一百三十八维」,改成「等于」后--reuse只重合成这一句,新句长了 0.384 s,补 16 ms 静音让后面整好顺延 12 帧,只重渲这一句到下一幕起点后 0.5 s 共 680 帧,全片从第一代的分段一次拼接编码;发布前又整片四段并行重渲一遍,和拼接版抽帧比对平均差 0.07 灰度级)同参数-crf 37压到 15.5 MB(-crf 19原片 47.2 MiB,四段并行渲染 24,897 帧,音视频时长 829.90 / 829.87 s;对话交付-crf 26+ 96k 单声道,28.7 MiB),13 分 25 秒的 OmniH2O(十七幕,2026-10-15 新做,H1 是官方样例stable_punch.pkl的 3D 贴图;2026-10-16 改片头两处读音、修快压住的排版、第 10、14–17 幕嵌入项目页真机片段后,用render.mjs chunk四段并行整片重渲 24,136 帧,每段 6,034 帧、891–996 s)同参数-crf 37压到 15.1 MB(初版 14.5 MB;-crf 19原片 48.7 MiB,真机片段让原片比初版的 42.0 MiB 大,音视频时长 804.53 / 804.53 s;对话交付-crf 26+ 96k 单声道,28.0 MiB),14 分 2 秒的 MaskedMimic(十八幕,2026-10-16 新做,角色是 ProtoMotions 官方样例坐扶手椅的 SMPL 3D 贴图,第 1、8、14–18 幕嵌了项目页的仿真片段;2026-10-17 按看片反馈把 2 × 2 拼图改成一次放一个、放大图 1 / 3 / 5a、第 2 幕最后一句拆成两句后整片重渲)同参数-crf 37压到 16.8 MB(第一版 16.7 MB;-crf 19原片 55.3 MiB,render.mjs chunk四段并行渲染 25,287 帧,音视频时长 842.900 / 842.900 s;对话交付-crf 26+ 96k 单声道是 32.2 MiB,超了,改用-crf 28,28.9 MiB;重渲后 28.6 MiB),12 分 17 秒的 HOVER(十六幕,2026-10-16 新做,H1 沿用 H2O 的07_01走路贴图,第 6、11、12、14、15 幕嵌入 8 段项目页片段;旁白整集走EDGE_TTS_LEGACY=1,用render.mjs chunk四段并行渲染 22,107 帧,每段 5,526–5,527 帧、598–674 s)同参数-crf 37压到 15.6 MB(-crf 19原片 60.5 MiB,音视频时长 736.90 / 736.87 s;对话交付-crf 28+ 96k 单声道,28.7 MiB,-crf 26是 32.5 MiB;2026-10-17 片头改接 MaskedMimic、全局核对旁白与分镜后整片重渲:build.py先--reuse只合成改过的句子,再不带--reuse从tts_cache原样取音频、按新的DISPLAY/SPACE_CJK重切字幕——--reuse会连旧字幕一起沿用;四段并行 23,733 帧、每段 5,933–5,934 帧、约 640 s,音视频 791.10 / 791.08 s,-crf 37压到 16.7 MB,-crf 19原片 64.0 MiB 超过附件上限,对话里按关键帧-c copy切成三段 22.1 / 26.0 / 15.8 MiB 交付;2026-10-18 用 SMPL 在 AMASS 上照官方脚本重算走路、第 5 幕换成真实的 SMPL 人体后,整篇用旧地址重新合成旁白(上一版的tts_cache不在新容器里),13 分 17 秒,四段并行 23,923 帧、每段 5,980–5,981 帧、723–796 s,音视频 797.43 / 797.42 s,-crf 37压到 16.7 MB,-crf 19原片 63.9 MiB,按关键帧-segment_times 266,532无损切成 21.0 / 25.7 / 17.3 MiB 三段交付;同日这段走路换成官方脚本在 Python 3.10 环境里直接跑出的结果(原先是照脚本移植的版本,关节角对浮点舍入敏感、和官方差到 0.13 rad),旁白不变、沿用timeline.json,四段并行再渲 23,923 帧、778–855 s,-crf 37仍是 16.7 MB,原片 63.8 MiB,切成 20.9 / 25.7 / 17.3 MiB;合并 main 后kit.js的 svgClip 把负的片段时间截到 0(换幕淡入时循环片段不再闪片尾),bundle 的switchTag照改,只重渲包含第 15 幕开头的第 17,942–23,923 帧再按帧号拼接,和上一版逐帧比只有第 20,687–20,705 帧不同,第 1、2 段的音视频数据包与上一版相同,只重发第 3 段;同日看片反馈第 1、2 幕 H1 的脚有跳变——human2humanoid 的输出本身膝盖顶限位、髋偏航来回翻,40 帧整段循环首尾也接不上——改用 PHC 的fit_smpl_shape.py、fit_smpl_motion.py重算、循环只放第 17–32 帧,第 5 幕三句旁白--reuse只重合成这三句,全片 802.65 s,四段并行 24,080 帧、738–819 s,-crf 37压到 16.8 MB,原片 63.8 MiB,按关键帧切成 21.5 / 25.6 / 16.8 MiB;2026-10-19 第 2 幕「专才容易过拟合」被念成「容易过、拟合」,改成「对……过拟合」,--reuse只重合成这一句,后面整体顺延 0.336 s,四段并行再渲 24,090 帧,音视频 803.00 / 802.99 s,和上一版顺延 10 帧后逐帧比平均差 0.04–0.06 灰度级,-crf 37仍是 16.8 MB,原片 63.6 MiB,切成 20.7 / 25.5 / 17.4 MiB;同日第 5 幕右栏加两行不配音的标注(拟合用的站姿下两踝、两髋的距离),只重渲第 6,599–8,497 帧再按帧号拼接,接缝两侧新旧帧平均差 0.03–0.13 灰度级,网页版 16.8 MB;同日用户要求实验贴合原作,走路退回 HOVERretarget_h1.sh的原流程(--motions-file只列07_01,输出和 10-18 的官方版逐字节相同,逐帧独立的跳变照原样放),并按看片反馈改了「挑」(一律写「挑选」,单用会念成 tiǎo)、「数值」、expressive 的译法(「表达性」,不是「表情」)和第 14 幕的换掩码时间轴,--reuse只重合成改过的句子,全片 824.85 s,四段并行 24,746 帧,-crf 37压到 17.1 MB,原片 65.5 MiB,按关键帧切成 21.9 / 26.2 / 17.4 MiB;看片后用户要求走路画面换回没有跳变的 PHC 版,第 5 幕三句旁白恢复(--reuse),其余改动保留,全片 818.28 s,四段并行 24,549 帧,-crf 37压到 17.1 MB,原片 64.7 MiB,切成 21.7 / 25.8 / 17.2 MiB;同日全篇看片检查:audit_res(逐段量图片与片段的显示尺寸和原始分辨率,没有被放大的)、audit_orphan(要点框和字幕里单独掉一行的标点)两个临时脚本,加上对照 v2 LaTeX 与官方代码逐条核对旁白与分镜,改了第 2、8、11 幕论文图的尺寸、第 15 幕的换掩码时间轴、第 10 幕标签的出场、第 5 幕 PHC 做法的出处、第 6 幕「教师是上限」、第 16 幕的局限与专才项数,以及「4 只」「切换模式」「还得」几处读音,全片 835.72 s,四段并行 25,072 帧,-crf 37压到 17.6 MB,原片 66.5 MiB,切成 21.8 / 26.8 / 17.9 MiB),16 分 27 秒的 ExBody2(十八幕,2026-10-18 新做,G1 是用 ASAP 的脚本以 SMPL 在 AMASS 的 CMU 上重定向的动作的 3D 贴图,第 1、3、5、8、13、15、17 幕嵌了项目页的 8 段片段;旁白整篇走EDGE_TTS_LEGACY=1,按 WordBoundary 切词改掉了几处会被听岔的句子)同参数-crf 37压到 19.3 MB(-crf 19原片 64.4 MiB,render.mjs chunk四段并行渲染 29,624 帧、每段 7,406 帧、805–845 s,音视频 987.47 / 987.46 s;对话里按关键帧-segment_times 330,660无损切成 24.0 / 19.9 / 20.5 MiB 三段交付;2026-10-19 按看片反馈改了贴地、关键点推测、图例、读音与译法:大步走、Macarena、扭摆舞、弯腰下蹲每帧把网格最低点挪到地面,旁白--reuse只重合成改过的二十多句,全片 1019.62 s,四段并行 30,589 帧、每段 7,647–7,648 帧,-crf 37压到 20.0 MB,原片 66.8 MiB,按关键帧-segment_times 340,680切成 25.3 / 20.5 / 21.0 MiB;同日加了两个临时检查脚本:要点框和字幕里单独掉一行的字或行首标点、贴图 / 论文图 / 片段有没有被放大到超过原始分辨率,十处要点改了措辞;2026-10-20 第 18 幕改成逐条讲清「没说清的地方」、去掉和片尾重复的三句话总结,第 5 幕的关键点推测补上 ExBody 代码依据,--reuse只重合成四句,全片 1063.18 s,四段并行 31,896 帧、每段 7,974 帧、1196–1261 s,音视频 1063.200 / 1063.184 s,-crf 37压到 20.7 MB,原片 68.8 MiB,按关键帧-segment_times 355,710切成 25.9 / 22.3 / 20.6 MiB;同日第 10 幕的弯腰下蹲、扭摆舞换成踝关节放平的左右扭腰、小鸡舞(第 2、18 幕的 Macarena、跳舞专才跟着换),旁白和时间线不变,只重渲第 2、10、18 幕前后各多 0.5 s 的第 2,557–4,137、15,911–17,672、28,130–30,926 帧(四路并行约 2.5 分钟),接缝两侧新旧帧平均差 0.02–0.19 灰度级,按帧号拼接时同一个分段文件不能在 filter 里用两次(ffmpeg 会把中间解出的帧全缓存下来,内存爆掉),每段单独-i一次;音视频 1063.200 / 1063.184 s,-crf 37压到 20.6 MB,原片 67.1 MiB,切成 25.1 / 22.0 / 20.1 MiB),16 分 8 秒的 HugWBC(十六幕,2026-10-18 新做,H1 是按命令摆的运动学示意贴图,第 1、13、15 幕嵌了 5 段项目页片段;旁白整集走EDGE_TTS_LEGACY=1,build.py --reuse改句后再不带--reuse重切字幕;初版 15 分 46 秒、17.5 MB;2026-10-20 合并 ExBody2 后片头改接它的片尾预告,照 HOVER / ExBody2 的看片反馈全篇核对:表号念中文数字、换掉「挑」「数值」「还得」「切换模式」等说法,要点框与字幕逐行查单字成行和行首标点,第 1 幕放整张图 2 并按旁白框出模块、第 4 幕加图 3、第 14 幕放大雷达图、图 8 走路图从矢量图按 1.5 倍重新导出,第 16 幕不再重复片尾总结;整片四段并行 29,042 帧、每段 7,260 帧、772–856 s,之后发现片头信息框行首落了「·」、封面拆开了「上海人工智能实验室」、片尾「遥 / 操作」分在两行,只重渲片头第 0–1,109 帧与片尾第 27,880–29,041 帧(81 s / 137 s),接缝两侧新旧帧平均差 0.09–0.12 灰度级,按帧号拼回;音视频 968.06 / 968.07 s)同参数-crf 37压到 17.7 MB(-crf 19原片 55.3 MiB,按关键帧-segment_times 330,660无损切成 21.5 / 16.6 / 17.2 MiB 三段交付;2026-10-21 片头「走、跳、站」几乎没声音,改说「走路、跳跃和站立」,只重渲片头第 0–1,119 帧、其后的画面整体后移 10 帧拼回,29,052 帧、968.39 s,网页版仍是 17.7 MB;同日又修了第 5、6、14 幕的线和圆点压字与第 7 幕「由期 | 望」的切词,只重渲这几段,29,047 帧、968.23 s;同日按「讲图表先讲怎么读」的看片反馈,第 3、4、6、7、10–15 幕每张图补了一行读法、旁白改 18 句,又修了第 5 幕 critic 连线、第 13 幕换成三段片段、第 14 幕「踩地」的说法,整片四段并行重渲 33,237 帧、每段 1225–1306 s,音视频 1107.88 s,-crf 37压到 20.2 MB,原片 63.2 MiB,按关键帧-segment_times 370,740切成 24.0 / 18.2 / 21.0 MiB 三段交付),15 分 59 秒的 HOMIE(十七幕,2026-10-18 新做,G1 是官方deploy.onnx在 MuJoCo 里回放的 3D 贴图,第 2、10–12、14–16 幕嵌了 13 段项目页片段;旁白整篇用旧地址合成,render.mjs chunk四段并行渲染 26,672 帧、每段 6,668 帧、834–954 s;渲染开始后改了片头作者那一行(封面上原先折成两行、目录压到 3:4 裁剪线以下),只重渲前 965 帧、按帧号接上第 1 段其余部分,接缝处新旧帧差 0.09 灰度级,整片一次编码;音视频 889.07 / 889.03 s,初版 14 分 49 秒、网页版 17.1 MB;2026-10-21 照 HugWBC 的看片反馈自检、91 段旁白改了 34 段,整片四段并行重渲 28,789 帧、每段 7,197 帧、736–859 s,音视频 959.62 / 959.63 s)同参数-crf 37压到 18.4 MB(-crf 19原片 57.7 MiB,对话里按关键帧-segment_times 320,640无损切成 18.2 / 23.7 / 15.8 MiB 三段交付),14 分 54 秒的 TWIST(十六幕,2026-10-19 新做,G1 是官方学生模型在 MuJoCo 里跟自录动捕的复现贴图,第 1、10–14、16 幕嵌了 13 段项目页片段;旁白整篇走EDGE_TTS_LEGACY=1;渲染途中改了四句旁白,build.py --reuse只重合成这几句,前 573 s 沿用已渲好的前两段,后面按ranges2.txt四段并行重渲,共 26,833 帧;注意ranges.txt最后一行要有换行,否则while read读不到最后一段)同参数-crf 37压到 18.9 MB(-crf 19原片 72.1 MiB,音视频 894.43 / 894.43 s,按关键帧-segment_times 320,625无损切成 22.6 / 24.0 / 25.5 MiB 三段交付;298,596时第三段 29.5 MiB 贴着上限;2026-10-22 合并 main 后照 HOMIE / HugWBC 的看片反馈全篇自检:十六幕以文字为主的面板换成会动的图、每张图表补读法、从 cue 起算的动画改用K.clipSince,片头改接 HOMIE 的片尾预告,旁白按 WordBoundary 切词对照 jieba 改了二十多处说法(out/twist/audit/pron_audit.py,另用每句 1.5 s 窗口的音量查出「照着人做」几乎没声音),用临时脚本out/twist/audit/linecheck.mjs每 0.1 s 查线和移动的点压字;88 段旁白 1011.7 s,四段并行重渲 30,352 帧、每段 7,588 帧、525–718 s,音视频 1011.73 / 1011.72 s,-crf 37压到 20.6 MB,原片 78.9 MiB,先按关键帧-segment_times 389,739切成 26.7 / 26.0 / 26.2 MiB 三段交付,合并 main 后发现两处切点都在说话,改用split.py twist 4在静音里切成 17.5 / 16.1 / 17.6 / 22.9 MiB 四段重发(分三段时最后一段 30.5 MiB 超限);2026-10-23 按看片反馈改了四句旁白(「局部量」「根部量」的量没读成 liàng,改说「是局部的」「根部 8 项」;「教师会」并成一个词、教读成一声,改说「教师掌握的本事」;片尾最后一句和以前的视频一致,说「都在 Robot Learning Paper Notebooks。我们下期见」)、第 5 幕关节角柱的基线下移,先只渲这几段的片段给人看,确认后四段并行整片重渲 30,481 帧、508–703 s,音视频 1016.03 / 1016.01 s,-crf 37压到 20.7 MB,原片 79.0 MiB,split.py twist 4切成 23.4 / 8.1 / 19.1 / 23.7 MiB 四段交付),17 分 10 秒的 TWIST2(十八幕,2026-10-19 新做,G1 是官方 ONNX 权重在 MuJoCo 里跟官方 PICO 样例的复现渲染,第 1、4、5、8、11、13、14、17 幕嵌了 15 段项目页片段;旁白整篇走EDGE_TTS_LEGACY=1;初版 14 分 10 秒、17.9 MB;2026-10-23 照 HugWBC / HOMIE / TWIST 的看片反馈全篇自检——多动效少文字、每张图表补读法、片头接 TWIST、旁白按 WordBoundary 对照 jieba 改了四十多处——后四段并行整片重渲 30,925 帧、每段 7,731–7,732 帧、826–936 s,音视频 1030.833 / 1030.832 s,压到 21.7 MB;2026-10-24 第 4 幕站定、走路只循环一个步态周期、蹲下来回放、捏到指尖碰上就停后,旁白不变、四段并行整片重渲 30,925 帧、每段 761–880 s,音视频 1030.833 / 1030.832 s)同参数-crf 37压到 21.6 MB(-crf 19原片 78.4 MiB,split.py twist2 4切成 20.0 / 12.8 / 23.6 / 17.2 MiB 四段交付;同日第 5 幕 Vive 对比换成动图,只重渲第 8731–9231 帧、按关键帧整文件拼回,网页版 21.7 MB,第 1 段 20.2 MiB;再按同样做法换第 10、11 幕的第 16462–16962、17712–19712 帧,网页版 21.6 MB,第 3 段 23.4 MiB;之后整片四段并行从头重渲一遍,30,925 帧、每段 784–889 s,和拼接版抽帧比只有第 10 幕那段有编码噪声级的差(第 16,700 帧最大差 35 灰度级、超过 20 的只有 42 个像素,那段原先是单独编码的),网页版 21.6 MB,原片 78.5 MiB,split.py twist2 4切成 20.3 / 12.8 / 23.5 / 17.2 MiB;2026-10-25 看片反馈「网站上可视化」的「上」单独断开,第 18 幕这一句改说「网站可视化」,--reuse只重合成这一句,短了 0.384 s,不是整帧,所以从这一句之前的关键帧第 28,944 帧重渲到片尾(四路并行约 45 s),前面按关键帧整文件沿用,30,914 帧、音视频 1030.467 / 1030.448 s,切点不变,前三段和上一版逐字节相同,只重发第 4 段 17.1 MiB,网页版 21.6 MB),17 分 38 秒的十八幕 HDMI(2026-10-23 新做,G1 和行李箱是官方搬行李箱参考与仓库里录下的回放的 3D 贴图,第 1、12–14 幕嵌了 7 段项目页片段;旁白整篇走EDGE_TTS_LEGACY=1,用 WordBoundary 切词对照 jieba 改了十几处说法后--reuse重合成;render.mjs chunk四段并行 31,738 帧、每段 7,933–7,935 帧、723–896 s;渲染开始后改了第 9 幕表 II 的一处措辞,只重渲第 15,240–15,750 帧(37 s),接缝两侧新旧帧平均差 0.06 灰度级,按帧号替换进第 2 段再拼接;音视频 1057.93 / 1057.90 s)同参数-crf 37压到 19.8 MB(-crf 19原片 63.5 MiB,split.py hdmi 3在静音里切成 19.3 / 24.4 / 14.9 MiB 三段交付;2026-10-26 合并 main 后照 TWIST2 / FALCON 的看片反馈自检——片头接 FALCON、小字放大、循环来回放、多动效少文字、faster-whisper 全篇听写改读音——build.py --reuse只重合成改过的句子、再不带--reuse重切字幕,17 分 50 秒,四段并行 32,090 帧、每段 650–806 s,音视频 1069.67 / 1069.66 s,-crf 37压到 20.0 MB,原片 65.2 MiB,split.py hdmi 3切成 19.9 / 25.9 / 14.4 MiB;同日看片反馈第 3、4 幕的箱子相对手上下飘——官方参考里人工标注的箱子本来就不贴手——这两幕改放仓库录下的仿真回放,两句旁白--reuse重合成,后面整体顺延 7.92 s、不是整帧,四段并行整片重渲 32,328 帧、每段 726–887 s,音视频 1077.60 / 1077.58 s,-crf 37压到 20.2 MB,原片 65.1 MiB,只交付第 3–4 幕的片段(第 142.0–255.4 s 从原片剪出、重编码 CRF 19);同日又按四条看片反馈改了第 5、10、13、16 幕(第 13 幕的分镜窗口拉长到两段项目页视频放完,旁白讲完画面接着放),两句旁白重合成,四段并行整片重渲 32,941 帧、每段 788–959 s,音视频 1098.03 / 1098.00 s,-crf 37压到 20.4 MB,原片 66.5 MiB,只交付改动的四个片段;同日第 5–7 幕也改放回放、第 8 幕加参考与回放的对比(新加一段旁白、那一幕 16 → 19.4 s),三句旁白重合成,四段并行整片重渲 33,775 帧、每段 835–1009 s,音视频 1125.83 / 1125.81 s,-crf 37压到 21.0 MB,原片 68.4 MiB,split.py hdmi 3切成 20.8 / 24.8 / 17.6 MiB;同日第 2 幕右栏改画「动作 + 参考关节角 = 关节目标」,旁白不变,只重渲第 3250–3750 帧、在第 1 段的两个关键帧处切开换掉,接缝两侧新旧帧平均差 0.04 / 0.11 灰度级,网页版仍是 21.0 MB;同日第 10 幕加三句旁白展开 615 维、补全几张流程图的连线、第 1 和 11 幕的火柴人换成 G1、第 14 幕拉长到 12 个仿真任务放完,build.py --reuse只合成新加的三句,四段并行整片重渲 35,342 帧、每段 817–991 s,音视频 1178.07 / 1178.07 s,-crf 37压到 22.0 MB,原片 66.2 MiB,split.py hdmi 3切成 26.2 / 19.2 / 20.8 MiB;2026-10-27 第 4 幕补箱子原点的示意(两句新旁白)后,第 4 幕之前的 6,000 帧沿用上一版(第 200.0 s 的关键帧),其余四段并行重渲,36,449 帧、1214.94 s,-crf 37压到 23.5 MB,原片 69.9 MiB,split.py hdmi 4切成 20.3 / 18.6 / 17.8 / 13.0 MiB),海报由cover.png缩到 540×960 的 jpg,与海报一起放到笔记目录的media/,接线方式见AGENTS.md「配音讲解视频(K.video)」。
逐帧截图的串行速度受浏览器限制;CPU / 内存有余量时,可以尝试 2–4 个独立 Chrome 进程分段渲染。2026-10-08 的 BeyondMimic 使用四段并行,观察到约三倍总吞吐量,最终 18,282 帧全部保留,音视频时长分别为 609.400 / 609.380 秒。这个收益是本次机器上的观察值,不保证其他机器或分镜也一样。2026-10-09 的 Berkeley Humanoid 在 4 核机器上同样四段并行,每段约 5,074 帧、约 9 分钟渲完,共 20,295 帧,音视频时长 676.484 / 676.500 秒。同日 ToddlerBot 四段并行(每段约 6,012 帧,同时跑着 Jekyll 构建与页面截图,约 15 分钟渲完),共 24,049 帧,音视频时长 801.633 / 801.620 秒;2026-10-10 片头改读音后在刚重启的容器里四段重渲,每段 6,007 帧、约 10 分钟,共 24,028 帧,音视频时长 800.933 / 800.924 秒;2026-10-11 第 2、5、6、8、10、12 幕改版后四段重渲,每段约 6,076 帧,共 24,303 帧,音视频时长 810.100 / 810.092 秒;worker 脚本必须放在 scripts/paper_video/ 之下(如被忽略的 out/),放在仓库外会找不到 node_modules/playwright-core。
render.mjs video 仍是串行;分段用 node render.mjs <paper> chunk <起始帧>,<结束帧>(2026-10-16 起内置,之前各篇用的是放在 out/ 里的临时 worker 脚本):渲染全片第 [起始帧, 结束帧) 帧的画面,CRF 19、无声,输出 out/<paper>/chunk_<起>-<止>.mp4,每个进程用自己的 stage.<paper>.<起>.built.html、渲完删掉,所以同一篇可以同时开几个。四路并行示例:for r in "0 6036" "6036 12072" "12072 18108" "18108 24143"; do set -- $r; node render.mjs omnih2o chunk $1,$2 > out/omnih2o/w_$1.log 2>&1 & done; wait。clip 模式带分段音频且使用 CRF 21,不能当作全片的分段。
- 先完成
build.py,所有 worker 共用同一份timeline.json和narration.wav。并行只用于画面渲染。 - 以总帧数
ceil(timeline.total × fps)划分连续、不重叠的整数帧区间[startFrame, endFrame)。每段渲染endFrame - startFrame帧,传给renderAt的是全片时间(startFrame + i) / fps,不要从零重新播放分镜。 - 每个 worker 使用独立浏览器、独立生成的
stage.*.built.html、日志和输出文件;完整执行字体等待、分镜预热与fitStages()。所有分段使用相同的分辨率、帧率、H.264 / yuv420p 与 CRF 19,仅输出画面(-an);可以限制每个编码器的线程数,避免争抢 CPU。 - 检查每个 worker 的退出码和分段实际帧数,确认都与预期一致后,用 ffmpeg concat 按帧号顺序拼接画面(
-c:v copy),再从完整的narration.wav一次性编码 AAC。这样避免分段 AAC 编码延迟造成接缝停顿。 - 合并时不要依赖
-shortest来裁尾:本次它截掉了最后 4 帧,移除后才保留全部 18,282 帧。用ffprobe核对总帧数、帧率、分辨率和音视频时长,再做全片解码检查;音视频时长差应不超过一帧。排版与封面仍按上文运行check_layout.mjs。
2026-10-10 的 Berkeley Humanoid 最新版在 16 个逻辑核、31 GiB 内存的本机上使用 8 路并行,拆成 24 个连续整数帧区间、分三批完成,每批约 125–130 秒,三批共约 6 分 21 秒。先尝试的八个长片段被运行环境中断,未完成的 MP4 没有复用;短片段方式完成了全部 20,854 帧(1080×1920、30 fps、CRF 19),音视频时长 695.132 / 695.133 秒。原片 37.8 MB,供笔记页面使用的压缩版保持 -preset slow -crf 37 -tune stillimage 与 48k 单声道音频。Chrome 回放曾因全范围标记而偏暗,整片统一转成有限范围 yuv420p 后修复;render.mjs 的整片与片段输出也显式转换范围并设置 -color_range tv,避免后续重渲再次偏暗。逐段帧数、全片解码、Chrome 播放、排版与封面检查均通过。后续遇到运行时限时,可保留并行数量、缩短每个片段并分批执行;只拼接已完整封装且通过帧数检查的片段。
分段都完成后,concat.txt 按顺序列出相对路径,如 file 'chunk_0-4570.mp4'。合并示例(以下为已有四段视频的合并,不是启动 worker 的命令):
ffmpeg -y -f concat -safe 0 -i out/beyondmimic/concat.txt \
-i out/beyondmimic/narration.wav -map 0:v:0 -map 1:a:0 \
-c:v copy -c:a aac -b:a 160k -ar 48000 -movflags +faststart \
out/beyondmimic/beyondmimic_video.mp42026-10-14 ExBody 第 5、10 幕的结尾改版后没有整片重渲。stage.html 的每一帧只取决于当前这一幕的局部时间(local、st、幕内进度)和当时的字幕,没有全片进度条,所以没改的幕即使起始时刻变了,画面也逐帧相同。做法:
- 重跑
build.py(只改了几句旁白时加--reuse:文字没变的句子直接沿用上一版的segNN.wav和字幕,只合成改过或新加的句子,没改的部分音频逐位相同;timeline.json的每段记着text,下一次才认得出哪些没变),在新旧timeline.json里找出改动幕的起止时刻;改动幕之后的部分整体平移 Δ(这次是 19.67 s 与 55.52 s,即 590.1 与 1665.5 帧,按整帧取,最多差半帧)。 - 只渲染改动的幕:从这一幕的起点渲到下一幕起点之后 0.5 s。上一句字幕会在下一段多留 0.15 s,改了字幕就得连这几帧一起重渲。这次两段共 4,230 帧,四路并行约 3 分钟;整片 23,919 帧要 15 分钟左右。
- 旧画面取上一版的分段文件(
chunk_*.mp4,帧号与时间线一一对应),不取合成后的成片。上一版合成时用了-shortest,末尾少了 3 帧;接缝处逐帧比对过,前面的帧没有错位。 - 拼接前在每个接缝两侧比对新旧帧(缩成灰度后取平均绝对差:同一画面约 0.1,错一帧约 0.5)。确认后用
trim=start_frame=…:end_frame=…加concat滤镜按帧号拼接,一次编码成 CRF 19,配上完整的narration.wav,不加-shortest。
2026-10-18 MaskedMimic 第 1、2 幕只换了项目页片段、旁白没动,时间线不变,帧号直接对上:只重渲第 1 幕和第 2 幕最后一段(到下一幕起点后 0.5 s,共 1,722 帧,两路并行约 2.5 分钟),接缝两侧新旧帧的灰度平均差 0.07–0.15。把五段 trim 放进一条 concat 滤镜同时解两遍同一个分段文件,内存不够被杀掉;改成每段旧画面单独 trim 编成 CRF 19、新渲的分段直接用,再和后面三个没动的分段一起走 concat demuxer(-c copy),只有第一个分段重编码一次。
2026-10-24 TWIST2 只改了第 5 幕一段 7 s 的示意图、旁白没动,用不着重编码:重渲的区间取在旧分段的关键帧上(ffprobe -skip_frame nokey 列出关键帧,x264 默认 250 帧一个、闭合 GOP),这次是第 8731–9231 帧(render.mjs twist2 chunk 8731,9231,51 s);旧分段用 ffmpeg -i chunk_7731-15462.mp4 -map 0:v -c copy -f segment -segment_frames 1000,1500 -reset_timestamps 1 oldseg_%d.mp4 在同样两个关键帧处切成整文件,再把整文件按顺序交给 concat demuxer(-c copy)拼起来、配上完整旁白,逐位不变:接缝两侧抽帧和来源的差都是 0,帧数 30,925、时间步全是 1/30 s。别用 concat 列表里的 inpoint / outpoint 去切:有 B 帧时按解码时间戳切,接缝会多出或少掉一两帧、时间戳乱掉。另外,被 clip-path 裁掉的部分照样算进分镜的外框:新画的示意图里滚动的刻度跑出了框,整幕的 fitStages() 缩放从 1.216 变成 1.211,那一幕所有帧都跟着变了,局部重渲接不上;先看日志里的 scene N: viewBox … zoom … 和上一版一样,再渲。
所以下一次整片重渲之前,先别删上一版的 chunk_*.mp4。
并行方案应在开始渲染前决定。不要中途终止现有串行进程后直接复用它正在写的普通 MP4:未完成封装时可能没有 moov,已编码的前半段也无法直接拼接。渲染产物和临时脚本继续放在被忽略的 out/ 等临时位置,不提交进仓库。