产品视频即代码
将产品视频构建为代码:一个由确定性时间线驱动的HTML幻灯片、一个AI旁白、产品的真实屏幕录制,以及一个ffmpeg音频母带。一切都是可重现、可比较并可重新渲染的,因此修改只需花费几分钟,而不是重新构建。
这不是一个视频编辑器。它是一个管道。输出是一个MP4文件。
关键思路
音频优先,再确定时间,最后画面。
先生成旁白,再决定屏幕上任何内容停留多久。测量每个片段,根据这些测量结果推导场景时长,然后才渲染帧。替代方案——猜测时长,或为固定故事板编写旁白——会导致画面与语音逐渐不同步,并且每次修改措辞后都需要重新渲染。
script.json ──► TTS ──► measure ──► timings.json ──► render ──► composite ──► mux ──► MP4
▲ ▲ ▲
assemble.py render.py composite.py
对于没有旁白的视频(标题闪屏、无声循环、静音社交片段),跳过第一阶段并手动编写timings.json。下游一切保持不变。
管道
| 阶段 | 脚本 | 输出 |
|---|---|---|
| 1. 旁白 | 你选择的TTS工具 | audio/vo_NN.mp3,每个场景一个 |
| 2. 时间与语音轨道 | scripts/assemble.py | timings.json,out/voice.wav |
| 3. 画面 | scripts/render.py | out/silent.mp4 |
| 4. 屏幕素材 (可选) | scripts/capture/tour.py | capture/clips/*.mp4 |
| 5. 合成 (可选) | scripts/composite.py | out/composited.mp4 |
| 6. 音频母带 | scripts/mux.py | 最终MP4 |
请阅读references/PIPELINE.md以了解详细操作指南,references/AUDIO.md有关语音和音乐,references/SCREEN-CAPTURE.md有关产品素材,以及在进行任何调试之前阅读references/PITFALLS.md——它列出了该管道实际遇到过的失败情况。
视频类型
管道完全相同;变化的是结构、节奏以及帧中真实产品占据多少空间。在写任何一行之前,先明确你要制作的是哪种视频,因为它决定了其他一切。
| 类型 | 典型长度 | 结构 | 素材 |
|---|---|---|---|
| 产品说明 | 1-3分钟 | 问题 → 转变 → 工作原理 → 证明 → 结尾 | 少量,作为证明 |
| 功能发布 | 30-90秒 | 变化了什么 → 为什么重要 → 看到它 → 如何获得 | 中等 |
| 演示/操作指南 | 2-5分钟 | 一个真实任务,从头到尾 | 主导 |
| 教程 | 3-10分钟 | 按步骤,分章节,暂停以供跟随 | 主导 |
| 发布说明 | 30-90秒 | 每拍一个项目,密集 | 截图或短视频片段 |
| 入门引导 | 1-3分钟 | 用户首次遇到时的路径 | 主导 |
| 标题闪屏/循环 | 5-20秒 | 一个想法,通常无声 | 无 |
| 社交短剪 | 60秒以内 | 开头抓人,从长视频中派生 | 简短有力 |
根据类型得出的经验法则:
- 演示和教程以素材为先。 幻灯片是围绕产品的框架——标题卡、标注、章节分隔。不要旁白屏幕已经显示的内容。
- 说明和发布以旁白为先。 素材作为你刚刚提出的主张的证据出现;这种相邻关系使其产生效果。
- 教程需要留白。 每一步之后留一个节拍。多用
--tail;观众是在跟着做,而不是在看。 - 发布说明要密集。 屏幕文字承载信息,旁白进行总结。每拍一个项目,不要停留。
- 社交短剪是派生出来的,永远不要重复编写。 将其构建为长视频的场景子集,这样它们就不会漂移(参见PIPELINE.md)。
快速开始
一次性设置:pip install -r requirements.txt && playwright install chromium。ffmpeg和ffprobe也必须在PATH中。
先尝试无声渲染,检查幻灯片是否能正常渲染:
cp templates/deck.html .
python3 scripts/render.py --preview 2 8 18 # 静态图片
python3 scripts/render.py # -> out/silent.mp4
然后,在有了旁白后运行完整管道:
cp templates/script.example.json script.json
# 生成 audio/vo_01.mp3 ... 每个场景一个,然后:
python3 scripts/assemble.py # timings.json + out/voice.wav
python3 scripts/render.py # out/silent.mp4
python3 scripts/mux.py --final out/final.mp4 # 添加语音(+ 如果 audio/bed.mp3 存在则添加音乐)
assemble.py是唯一需要音频的阶段;没有它,render.py将使用幻灯片中声明的持续时间。
保持正常工作的规则
永远不要用挂钟时间来驱动时间线。 渲染器会寻找到显式的t并进行截图。render.py对每一帧调用window.__seek(t),因此慢速机器生成的视频文件与快速机器完全相同。使用requestAnimationFrame或CSS动画播放的任何内容在截图循环下都会撕裂或丢帧。
将每个动画绑定到一个旁白节拍上。 如果语音在4.2秒说“三件事”,那么第三张卡片就在4.2秒出现。在任何重写之后,重新检查节拍——如果某行比原来短了3秒,它的视觉元素就会错位。这是决定最终结果是看起来有导演精心编排还是显得普通的最重要因素。
以可恢复的块进行渲染。 render.py写入out/parts/part_NNN.mp4并在重新运行时跳过已完成的块。一次卡住的截图只会损失一个块,而不是整个过程。永远不要对同一个parts目录运行两个渲染进程。
测量,不要假设。 用数字验证,而不是凭感觉:
- 空白帧检测:计算暗像素数量(
(pixels < 200).mean()),而不是标准差——一个真实合法、白色为主的UI页面其标准差也偏低 - 发音:将生成的音频通过语音转文本工具回传并阅读结果
- 电平:每次都对最终文件运行
ffmpeg -af volumedetect - 几何形状:从DOM中读取
getBoundingClientRect(),而不是硬编码
一个源,多种剪辑版本。 通过查询参数(deck.html?cut=short)从单个幻灯片中选择场景子集,这样长演示和40秒社交短剪就不会产生漂移。参见references/PIPELINE.md。
从产品代码中匹配其品牌
如果产品有代码库,则从其中读取品牌信息,而不是询问十六进制颜色代码:
python3 scripts/detect_brand.py ../their-product --css
它会读取设计令牌文件、tailwind配置、CSS自定义属性、SCSS和Less变量、JS/TS主题对象以及网页清单,并报告它找到的字体、图标库和Logo文件。将其打印的:root块粘贴到deck.html中。
构建输出会被跳过,因为dist/文件夹里充满了第三方组件库的CSS,那不是品牌本身。调色板也会检查其一致性,而不是仅凭名称匹配就信任——如果一个仓库为亮色主题定义了--surface,为暗色主题定义了--bg,否则会给出白色卡片配黑色背景。拒绝的情况会被打印出来,而不会默默地应用。
在渲染之前向用户展示检测到和被拒绝的内容。
设计指导
幻灯片是HTML/CSS,因此设计完全由你决定。有两点值得注意:
社交和自动播放的上下文是静音的。屏幕上的标题必须独立承载每个节拍,且钩子必须在开头一两秒内抓住观众。将旁白视为增强,而不是传递机制。
如果被要求制作视觉上独特的视频,请将其植根于主题自身世界——它的制品、工具和语言——而不是选择带有一个亮色强调色的深色背景,这是通用输出的聚集之处。Anthropic的frontend-design技能在这方面做得很好,并且可以与此技能无缝组合。