本文由 莫潇羽@复源AI社区(bbs.fuyuan7.com)撰写,转载请注明出处。
快速摘要
Claude Opus 5.5 并不会直接"吐"出一个视频文件。它做的事情是写一段网页动画程序,再由无头浏览器按时间一帧一帧截图,最后交给 FFmpeg 压成 MP4。所以它擅长的是讲解片、数据动画、动态图形这类"画出来"的内容,不擅长真人实拍质感。想让这条链路稳定出片,要抓住四件事:画面只由时间决定,同一时刻渲染两次必须一模一样;先配音,再按音频的实际时长排画面;整片渲染要能中断续跑,并且等到校验通过才算完成;智能体每次开工都从零开始,经验必须写进工作目录的规则文件。在国内平台发布,还要做好 AI 生成内容声明、声音克隆授权和素材正版授权这三项。
这篇文章我按"新手会卡在哪"的顺序来讲,不按制作步骤流水账。前半部分讲原理,后半部分讲翻车点和人机分工,最后给一份国内发布前可以逐条对照的合规清单。文中的代码都是最小可用的示意,能跑通,但你需要按自己的项目改。
|
你关心的问题 |
对应章节 |
|
它到底是不是"生成视频" |
第一章 |
|
为什么画面要写成"时间的函数" |
第二章 |
|
先做画面还是先配音 |
第三章 |
|
二维、三维、原件上屏怎么选 |
第四章 |
|
渲染到一半断了怎么办 |
第五章 |
|
声音忽大忽小、破音怎么查 |
第六章 |
|
智能体不记事,经验怎么沉淀 |
第七章 |
|
哪些环节必须人来把关 |
第八章 |
|
国内平台发布要注意什么 |
第九章 |
想看完整拆解,往下翻。
一、先把误会说清:它做的不是"生成视频",而是"写一段会画画的程序"
1.1 一条被误读的热点
2026年9月22日,Anthropic 发布了 Claude Opus 5.5,这是 5.5 系列的首发型号,同一天就能在它的命令行编程智能体 Claude Code 里使用。发布之后的一段时间,社交平台上冒出了很多"用一句话做出来"的宣传片、讲解片、音乐短片,看得人心里发痒。
我当时的第一反应也是:文本模型什么时候能出视频了?
翻了几份公开的技术拆解之后才弄明白,这是个误读。多方的结论是一致的:模型输出的仍然是文本,只不过这段文本是一份动画程序。程序交给浏览器去执行,浏览器把画面画出来,再被一张张截下来拼成视频。模型自己从头到尾没有产生过一个像素。
这里有三个词,新手第一次见可能会懵,我先用白话解释一下:
- 帧:视频里的一张静止画面。每秒播放 30 张,就叫 30 帧每秒。
- 无头浏览器:没有窗口的浏览器,由命令行或脚本驱动,网页照常渲染,只是你看不见它。
- FFmpeg:一个开源的音视频处理工具,可以把一堆图片和一条音轨压成一个 MP4。
整条链路画出来是这样的:
一句题目或一页说明
│
▼
编程智能体(读资料、写稿、写动画代码、调用工具)
│ 产出:scene.html,里面有一个 seek(t) 函数
▼
无头浏览器(逐帧调用 seek,逐帧截图)
│ 产出:000000.png、000001.png、000002.png ……
▼
FFmpeg(图片序列 + 混好的音轨 → 编码)
│
▼
out.mp4(再跑一遍校验)
理解了这张图,后面所有的坑都好解释。因为每一个坑,几乎都出在某两个环节的交接处。
1.2 它适合拍什么,不适合拍什么
既然画面是代码画出来的,它的长处和短处就很清楚了。代码没有摄像机,也没有从海量影像里学来的"真实世界长什么样"的直觉,它只会精确地执行你描述的几何、颜色和运动。
|
内容类型 |
是否适合用代码画 |
原因 |
|
带旁白的科普讲解片 |
很适合 |
文字、图表、示意图可以精确控制,改一处只需改一行 |
|
数据动画、流程演示 |
很适合 |
数据驱动,图表与数字天然一致 |
|
产品功能演示、片头动效 |
适合 |
排版和节奏可复现 |
|
低多边形风格的三维场景 |
可以,但更费时间 |
几何简单时可控,复杂了渲染会变慢 |
|
真人出镜、自然口型 |
不适合 |
需要视频生成模型或实拍 |
|
要求真实光影和物理质感的镜头 |
不适合 |
代码画不出"拍出来"的感觉 |
业内也有混合做法:真实感镜头交给视频生成模型,字幕、图表、标注这些要求精确的部分用代码画,再合成到一起。这篇文章只讲纯代码画帧这一条路,因为它成本最可控,结果也最稳定。
1.3 "一句话出片"为什么是错觉
很多演示视频配的文案是"一个提示词搞定",但背后的工作量被藏起来了。有创作者公开记录过自己的过程:一部水彩风格的短片,前后调用模型一百六十多次,耗时接近七个小时,开工前口述的说明转成文字有好几千字,里面包含角色设定、逐场脚本、视觉参考和预算。
这才是真实的工作量分布。我自己折腾下来的体会是,一部几分钟的讲解片,真正花时间的地方按顺序是这几块:
- 说明文档:题目、要讲清楚的几个问题、硬性约束。
- 资料核实:每个日期、人名、数字从哪来。
- 逐字稿:写给耳朵听的稿子。
- 配音:切段、合成、核对。
- 画面代码:最长的一段。
- 渲染与校验:机器时间为主,但最容易出事故。
所谓"一句话",只是把第一项压缩成了一句话,后面五项一个都没少,只是由智能体代劳了。代劳得好不好,取决于你给它定的规则清不清楚。这一点第七章会展开。
1.4 动手前需要准备什么
|
组件 |
作用 |
说明 |
|
编程智能体 |
读写文件、执行命令、根据结果决定下一步 |
Claude Code 属于这一类;国内可用的同类命令行工具有 Qwen Code、Kimi Code CLI、CodeBuddy Code,以及 Trae 这样的 AI 编程环境 |
|
Node.js |
运行抓帧脚本 |
用长期支持版本即可 |
|
Playwright |
驱动无头浏览器的自动化库 |
微软开源 |
|
FFmpeg |
编码、混音、校验 |
开源 |
|
语音合成服务 |
旁白配音 |
第三章细讲,国内有多家可选 |
|
中文字体 |
画面里的汉字 |
开源字体可用,商用字体需取得正版授权 |
这里要插一句合规提示:Claude Code 和 Opus 5.5 属于境外服务,本文对它们只做原理层面的科普,境外模型使用需遵守国内网络与内容管理相关规定。从工程角度看,"写动画代码、调浏览器、调 FFmpeg"这套方法并不依赖某一家的模型,国内的编程智能体同样可以按这个思路搭建,差别主要在长任务的稳定性和代码质量上。
至于费用,写稿时公开报道的 Opus 5.5 标准定价大约是每百万输入 token 4 美元、每百万输出 token 20 美元,比上一代有所下调。模型定价变动非常快,这只是写稿时的近似行情,请以官方定价页面的实时数据为准。
二、整条链路的地基:让每一帧只由"时间"决定
2.1 一个约定:给我一个时刻,我还你一张画面
如果整篇文章只能记住一句话,我希望是这一句:动画页面要对外暴露一个函数,输入一个时间,输出那一刻完整的画面。
这个函数通常叫 seek(t)。你告诉它"第 12.34 秒",它就把第 12.34 秒应该出现的所有东西画出来,不管上一次画的是第几秒,也不管电脑上的钟走到了哪里。
业内对这种做法有个很形象的概括,大意是"只定位,不播放"。老牌的编程式视频框架 Remotion 走的也是同一个思路:渲染时按帧号逐帧定位,截图,再交给 FFmpeg。有独立测试观察到,Opus 5.5 在没有特别要求时,会自己倾向于最朴素的方案,也就是一个 HTML 文件加一个 seek 函数加 Playwright 截图,哪怕项目里装着更完整的框架。朴素有朴素的好处,出了问题容易查。
2.2 为什么不能"边播边录"
新手最自然的想法是:让动画在浏览器里正常播放,同时录屏。这条路走不通,原因有两个。
一个原因是截图本身很慢。截一张 1920×1080 的图可能要几十到几百毫秒,三维场景更久。如果动画跟着真实时钟往前走,等你截完这一张,动画已经跑出去好几帧了,结果就是掉帧、卡顿。
另一个原因是不可复现。同一段动画录两次,每次掉的帧都不一样,你没法做"只重做第三段"这种局部修改,也没法验收。
换成逐帧定位之后,时间由脚本说了算。先算好要多少帧,再一帧一帧地问:
总帧数 = 片长(秒)× 帧率
第 n 帧对应的时刻 t = n ÷ 帧率
举例:一部 8 分钟的片子,帧率 30
总帧数 = 8 × 60 × 30 = 14400 帧
第 600 帧的时刻 = 600 ÷ 30 = 20 秒
截一张图花 0.1 秒还是 3 秒都无所谓,因为画面里的"时间"是脚本喂进去的数字,跟墙上的钟没有关系。
2.3 哪些东西会悄悄破坏确定性
约定听起来简单,执行起来处处是陷阱。下面这张表是我整理的常见破坏项,让智能体写画面代码之前,最好把它们写进规则里。
|
破坏项 |
为什么有问题 |
对策 |
|
|
读的是真实时钟,每次渲染都不同 |
画面代码里禁用,时间只从 seek 的参数来 |
|
|
按真实时间调度 |
不用它们驱动动画,或用虚拟时钟接管 |
|
CSS 的 |
浏览器按自己的时钟播放 |
被截取的元素不使用,改为按 t 计算样式 |
|
|
每次结果不同 |
换成带种子的伪随机函数 |
|
字体、图片尚未加载完 |
第一次截图和第二次截图不一样 |
开始抓帧前等待资源就绪 |
|
|
无头环境下解码不稳定,容易丢帧 |
事先用 FFmpeg 把视频拆成图片序列再引用 |
|
依赖上一帧的状态 |
跳着渲染时结果会错 |
每一帧都从 t 重新算,不累加 |
最后一条最隐蔽。比如粒子效果,常见写法是"每帧让位置加上速度",这就依赖了上一帧。改成"位置 = 初始位置 + 速度 × t",才是只依赖时间的写法。
下面是一个最小的画面文件,演示怎么把标题淡入和背景星点都写成 t 的函数:
<!-- scene.html:最小示例,所有画面只由 t 决定 -->
<canvas id="c" width="1920" height="1080"></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
// 带种子的伪随机:同一个 seed 永远返回同一个数
function rand(seed) {
const x = Math.sin(seed * 12.9898) * 43758.5453;
return x - Math.floor(x);
}
const clamp01 = (v) => Math.min(1, Math.max(0, v));
window.seek = function (t) {
// 1. 每一帧都从空白画起,不依赖上一帧
ctx.globalAlpha = 1;
ctx.fillStyle = '#10141f';
ctx.fillRect(0, 0, 1920, 1080);
// 2. 背景星点:位置由种子决定,闪烁由 t 决定
for (let i = 0; i < 60; i++) {
const x = rand(i) * 1920;
const y = rand(i + 500) * 1080;
const phase = rand(i + 900) * 6.283;
ctx.globalAlpha = 0.3 + 0.3 * Math.sin(t * 2 + phase);
ctx.fillStyle = '#ffffff';
ctx.fillRect(x, y, 3, 3);
}
// 3. 标题:第 1.0 秒开始,用 0.8 秒淡入并上移
const p = clamp01((t - 1.0) / 0.8);
const ease = 1 - Math.pow(1 - p, 3);
ctx.globalAlpha = ease;
ctx.fillStyle = '#f2e9d8';
ctx.font = '72px "Noto Sans CJK SC", sans-serif';
ctx.fillText('二十四节气是怎么定下来的', 240, 560 - 40 * (1 - ease));
};
window.seek(0);
</script>
注意看标题那一段:没有"开始播放"这个动作,只有"在 t 这一刻,淡入进度是多少"。t 小于 1 秒时进度是 0,大于 1.8 秒时进度是 1,中间按缓动曲线插值。整部片子的所有元素都这么写。
2.4 两种落地方式:自己写 seek,或者接管浏览器的时钟
实现确定性有两条路,各有适用场景。
自己写 seek。 就是上面的写法,所有动画逻辑显式依赖 t。优点是简单、透明,出错了一眼能看出来;缺点是不能直接用现成的 CSS 动画和很多动画库。
注入虚拟时钟。 一些开源渲染项目的做法是,在页面加载前注入一段脚本,把 Date、performance.now、requestAnimationFrame、定时器全部替换成受控版本,再配合浏览器调试协议里的逐帧控制能力来推进时间。这样普通的网页动画也能被确定性地截取。优点是兼容性好,缺点是黑盒多,边角情况不少,比如预热阶段时间倒流会让合成器出错。
我的建议是新手从第一条路开始。等你把一部片子完整跑通,理解了"时间是喂进去的"这件事,再考虑要不要上虚拟时钟。莫潇羽@复源AI社区 在这里多说一句:工具越复杂,智能体写错的地方你越难发现,简单方案带来的可查性,比省下的那点代码量值钱得多。
2.5 这个约定换来了什么
守住"画面只由时间决定",会一次性换来四个好处,后面几章全都建立在它们之上。
- 可以拆开并行:第 0 到 3600 帧和第 3600 到 7200 帧互不依赖,开几个进程同时跑。
- 可以中断续跑:已经截好的帧不用重来,脚本从缺的地方接着截。
- 可以局部重做:只改了第三段的画面,就只重渲染第三段对应的帧。
- 可以验收:同一帧渲染两次,文件应该逐字节相同。
最后一条可以直接写成自检命令。渲染同一帧两次,比对哈希值:
# 把第 600 帧分别渲染到两个目录,再比对
node render.mjs scene.html check_a 30 600 601
node render.mjs scene.html check_b 30 600 601
sha256sum check_a/000600.png check_b/000600.png
# 两行哈希一致 → 通过;不一致 → 画面代码里还藏着不确定因素
这个检查成本极低,却能提前拦住一大类问题。抓帧脚本 render.mjs 的完整写法放在第五章。
三、声音先行:先把旁白配出来,再让画面跟着走
3.1 为什么顺序要反过来
做过剪辑的朋友,习惯是先把画面剪好,再录旁白去贴。用代码做讲解片,我的做法正好相反:先有声音,量出每一句话从第几秒开始、到第几秒结束,画面再按这张时间表去排。
原因很实际。合成语音的时长没法提前精确预估,同一句话,这次念 4.2 秒,换个音色或者换个模型版本可能就是 5.1 秒。如果先把画面写死,再去配音,结果必然是画面等声音或者声音追画面,到处都要手工对齐。
而代码画面最不怕的就是改时间。把"标题在第 1.0 秒出现"改成"标题在第 3 段旁白的起点出现",时间表一变,全片自动跟着变。所以正确的依赖方向是:音频决定时间轴,时间轴决定画面。
逐字稿 ──切段──▶ 逐段合成 ──量时长──▶ 时间轴(每段的起止秒数)
│
▼
画面代码读取时间轴,按段落编排
3.2 逐字稿是写给耳朵的
稿子有两种,写给眼睛看的和写给耳朵听的。讲解片要的是后一种。区别在哪?我拿一句话举例:
|
写法 |
例句 |
|
书面写法 |
该历法体系以太阳在黄道上的视位置为划分依据,共计二十四个划分点。 |
|
口语写法 |
古人盯着太阳看了一整年。太阳每走一小段,就记一个名字,一共记了二十四个。 |
第二种才念得出口。让智能体写稿时,我会给三条硬要求:句子要能当面对人说出来;开头说人话,别一上来就背定义;结尾要回到开头提的那个问题。
事实核对是另一条线。写稿之前,先让智能体把素材读完、把不确定的地方上网查证,整理成一份资料清单,每一个要讲的日期、人名、数字都标明出处。写稿时只允许使用清单里有出处的内容。下面是一份虚构的示意,用来说明格式:
|
编号 |
要讲的事实 |
出处类型 |
核对状态 |
|
F01 |
节气划分依据太阳在黄道上的位置 |
天文科普读物、权威百科条目 |
两处一致 |
|
F02 |
某部古代历法首次完整记载二十四节气名称 |
史料原文、学术综述 |
待补第二来源 |
|
F03 |
相邻两个节气之间大约相隔半个月 |
历法常识,可用日历验证 |
已验证 |
"待补第二来源"的条目,要么查到了再写,要么稿子里干脆不讲。宁可少讲一条,也不要讲一条站不住的。
3.3 切段、合成、量时长
稿子定下来之后,按意群切成几十段,一段一般是一到三句话。切段有两个好处:某一段出问题时只需要重配那一段;段与段之间可以人为插入停顿,控制节奏。
每一段单独送去合成,回来之后立刻量它的实际时长,然后累加成时间轴:
第 k 段的起点 = 第 1 段到第 k−1 段的时长之和 + 它们之间的停顿之和
第 k 段的终点 = 第 k 段的起点 + 第 k 段的时长
量时长用 FFmpeg 自带的 ffprobe 就行:
ffprobe -v error -show_entries format=duration -of csv=p=0 seg_007.wav
累加出来的时间轴存成一个文件,画面代码直接读它:
{
"fps": 30,
"segments": [
{ "id": "s01", "start": 0.80, "end": 5.36, "text": "古人盯着太阳看了一整年。" },
{ "id": "s02", "start": 5.76, "end": 11.02, "text": "太阳每走一小段,就记一个名字。" },
{ "id": "s03", "start": 11.62, "end": 17.48, "text": "一共记了二十四个。" }
]
}
有的语音合成服务还能返回更细的对齐信息。比如境外的 ElevenLabs,官方文档里有一个带时间戳的合成接口,会在返回音频的同时给出每一个字符的起止时间,这样就能做到"念到哪个词,哪个词高亮"。国内这边,阿里云百炼平台的声音复刻文档写的是提供一段 10 到 20 秒的音频样本即可创建定制音色,平台上有 CosyVoice、MiniMax、Qwen-TTS 等多个模型系列可选;MiniMax 的异步长文本合成文档里也写明支持返回句级时间戳。具体支持到哪个粒度,各家各版本不一样,请以官方文档的实时说明为准。
就算服务不给时间戳也没关系。讲解片绝大多数画面只需要对齐到"句",自己量每段时长就够用了。
合规提示:声音克隆只能用于本人的声音,或者已经取得声音主体明确授权的声音。发布含合成配音的作品时,要按平台要求做 AI 生成内容声明。详细依据放在第九章。
3.4 稿子干净,不等于声音干净
这是我认为最容易被忽略的一关。很多人检查配音只看一样东西:送出去的文字有没有错。可是文字没错,回来的音频照样可能出问题。
基于大模型的语音合成会出现类似"幻觉"的现象,学术论文里对此有记录:生成的语音里可能混入文本中没有的内容,可能重复,也可能漏掉。我碰到过的典型情况是,一句话明明只送了一遍,回来的音频却把它念了两遍,时长直接翻倍。
所以交付之前要核对三样东西,而不是一样:
|
核对对象 |
它是什么 |
可能出的问题 |
|
定稿 |
最终确认的逐字稿 |
无 |
|
实际送出的文本 |
脚本真正发给合成服务的字符串 |
夹带了标注、提示语、多余符号 |
|
返回的音频 |
合成结果 |
重复、漏读、读错、多出内容 |
前两样做字符串比对就行。第三样的办法是让语音识别把音频再转回文字,逐句和定稿对比。除此之外还有一个更便宜的信号:语速。每段算一下"每秒念了多少个字",明显偏离正常区间的,大概率有问题。
# 伪代码:配音质检的两道筛子
for seg in segments:
chars = count_chinese_chars(seg.text)
rate = chars / seg.duration # 每秒字数
# 第一道:语速异常(正常区间自己先用几段样本测出来)
if not (RATE_MIN <= rate <= RATE_MAX):
flag(seg, "时长异常,疑似重复或漏读")
# 第二道:识别回来的文字和定稿差太多
heard = asr(seg.audio)
if similarity(normalize(heard), normalize(seg.text)) < THRESHOLD:
flag(seg, "内容不一致,需要人工听一遍")
# 处理原则:只重配被标记的那几段;同一段重配达到上限仍不通过,
# 就停下来,把情况如实写进交付说明,交给人判断。
这里有个细节要提醒:语音识别对同音字没有分辨力,"权力"和"权利"它分不出来,数字的写法也常常和定稿不同。所以相似度比对之前要先做归一化,而且识别结果只能当线索用,不能当结论。真正判断念没念错,还得人去听。
处理原则我写在了伪代码末尾,再强调一遍:哪句有问题就只重配哪句,不要因为一句不满意就把整篇打散重来。重配次数要设上限,到了上限还不行,就停手并如实记录。无休止地重试,既烧额度,也不解决问题。
3.5 两个容易自作聪明的地方
风格要求不要混进朗读文本。 我们对配音有很多期待,比如"开头要从容一点""这句要有停顿"。这些是验收标准,是你和智能体在规划、试听时的目标,它们不应该被原样塞进送给合成模型的文字里。塞进去的后果,轻则没效果,重则被当成正文念出来。想控制语气,应该用服务官方提供的参数(语速、情感、稳定度之类),或者通过切段和段间停顿来实现。
多音字不要抢着标注。 另一个常见冲动是,在容易读错的字后面加上拼音或注音,想提醒模型怎么读。结果往往是模型把标注也念了出来。更稳的做法反而简单:先发原来的汉字,等真的听到或识别到读错了,再只处理那一个字,处理方式用服务官方支持的发音标注或发音词典,不要自己发明格式。
这两条背后是同一个道理:下游模型只会处理它收到的东西,它不知道哪些是"说明",哪些是"正文"。把给人看的意图和给模型吃的输入分开放,是整条流水线里反复要用到的原则。
四、画法怎么选:按内容定,不按习惯定
4.1 三种基本画法
代码能画的东西很多,归拢起来是三类。每一类都有它最合适的内容,用错了地方就会显得别扭。
|
画法 |
适合讲什么 |
优点 |
常见风险 |
|
二维插画与数据图 |
概念、流程、对比、论文里的数据 |
清晰、渲染快、文字好排 |
容易做成幻灯片翻页 |
|
低多边形三维场景 |
空间关系、结构、有地理纵深的历史叙事 |
有镜头感,能表现体量和位置 |
渲染慢,容易粗糙 |
|
原件上屏 |
有真实出处的材料:论文页、古籍影印、公有领域画作 |
观众直接看到证据 |
涉及版权和授权 |
一个容易犯的错是"手里有锤子,看什么都是钉子"。三维看起来高级,就全片都上三维。其实讲一篇论文,观众要看的是图表和数据,二维反而更清楚,三维只在需要解释某个立体结构时出场一小段就够了。反过来,讲一段有山川地势的历史,三维场景能让人一眼看懂"谁在哪、往哪走",这时二维地图就显得单薄。
比例不需要事先规定死。让智能体逐段判断"这一段在讲什么",再决定用哪种画法,比一刀切的风格要求效果好。
4.2 把选择写成一张分镜决策表
判断可以交给智能体,但判断的结果必须落到纸面上。我要求每部片子在动手写画面代码之前,先产出一张决策表:每一段讲什么、用哪种画法、为什么、用了什么外部素材。
还是用那部虚构的节气讲解片举例:
|
段落 |
讲什么 |
画法 |
选择理由 |
外部素材与授权 |
|
开场 |
抛出问题:节气是谁定的 |
二维插画 |
需要快速进入,文字为主 |
无 |
|
第二段 |
太阳在一年里的位置变化 |
三维场景 |
空间关系,二维说不清 |
无,纯代码建模 |
|
第三段 |
古籍中的最早记载 |
原件上屏 |
观众应当看到原文 |
古籍影印件,需确认馆藏方的使用条款 |
|
第四段 |
相邻节气间隔的规律 |
数据图 |
数字对比 |
数据来自资料清单 F03 |
|
结尾 |
回到开头的问题 |
二维插画 |
与开场呼应 |
无 |
这张表有三个用处。写代码时不会跑偏;返工时能快速定位"哪一段要改";最后一列逼着你在制作前就把授权问题想清楚,而不是发布后被投诉了才想起来。
除了分段的决定,全片还要定一个统一的风格档位:主色和辅色、标题字体和正文字体、字幕的位置和样式。定了就从头用到尾,不要中途换。风格不统一是"看起来像拼凑的"最主要的原因。
4.3 证据能上屏就上屏
示意图干净好看,但它有一个天然的弱点:观众看不到证据。你说"某篇论文指出",屏幕上是你自己画的一张图,观众只能选择信或者不信。
所以我的原则是,只要手里有真实的原件,并且有权使用,就把原件请上屏幕,旁边写清楚出处。讲论文就放论文页,高亮你正在讲的那一段;讲一幅画,就放那幅画本身。
这里必须提版权。公有领域的作品可以直接用,但"公有领域"要查证,不能凭感觉;论文页面、新闻截图这类材料,要注明来源并控制在合理范围内;图片、字体、音乐只要涉及商用,都需要取得正版授权。拿不准的,就退回到自己画示意图,并在画面上标注"示意"。
4.4 三维场景最常见的两个毛病
让智能体写三维场景,默认产出往往有两个问题,我每次都要在规则里专门压一压。
全程远景。 一台相机远远地挂在天上,下面一堆小方块在动,从头拍到尾。这样的画面有信息,没有情绪。要求远景、中景、近景混着用:交代环境用远景,讲关系用中景,关键动作和关键人物要推近。
白模感。 所有物体一个材质一个颜色,看着像没上色的模型。几何可以简单,低多边形本来就是风格,但木头、金属、布料至少要看得出区别;场景里要有一盏明确的主光,要有柔和的阴影。有了光影,简单的几何也立得住。
这两条我会写成可检查的要求,比如"任意连续 20 秒内至少出现两种景别""场景中至少区分三种材质"。模糊的要求("画面要有电影感")对智能体几乎没用,能数出来的要求才管用。
4.5 历史与严肃题材的分寸
讲历史、讲灾害、讲战争,会遇到一个两难:画得太干净,像沙盘推演,旁白在说伤亡惨重,画面上却一尘不染,这是失真;画得太用力,又会变成渲染暴力。
我给自己定的尺度是两句话。该有的代价要如实呈现,旁白讲到了,画面就不能假装没发生;但绝不把它当成刺激观众的卖点去夸大,不做特写式的渲染。低多边形的抽象风格在这里反而是优势,它能表达"发生了什么",又不会过于具象。
另外要留意平台的治理方向。公开报道显示,国内视频平台在整治 AI 技术滥用时,把 AI 生成的虚假信息、低俗猎奇内容,以及虚构历史人物言行的"穿越"类视频列为重点。做历史题材,史实必须有出处,演绎和推测要明确标注,这既是对观众负责,也是对自己的账号负责。
五、渲染这一关,最容易前功尽弃
5.1 先跑十几秒,别急着跑全片
整片渲染是个大工程。一部几分钟的片子有上万帧,简单的二维画面可能十几分钟跑完,复杂的三维场景跑上一个小时也不稀奇。如果跑完才发现汉字全是方框,那一个小时就白费了。
所以正式渲染之前,一定先做冒烟测试。这是软件测试里的老办法,意思是先通个电,看看冒不冒烟,再谈别的。具体到这里,就是从全片里挑出十几秒最难的片段先渲染一遍,重点看四件事:
|
检查项 |
怎么看 |
不通过说明什么 |
|
汉字显示 |
抽几帧肉眼看 |
字体没装或没加载完 |
|
三维材质和光影 |
抽关键帧看 |
材质或灯光设置有问题 |
|
同一帧渲染两次是否相同 |
比对哈希值(第二章的命令) |
画面代码里有不确定因素 |
|
单帧耗时 |
记录平均每帧多少秒 |
用来估算全片要跑多久 |
最后一项直接决定你怎么安排时间。估算方法很简单:
全片渲染耗时 ≈ 总帧数 × 平均单帧耗时 ÷ 并行路数
示意:14400 帧 × 0.4 秒 ÷ 4 路 = 1440 秒,也就是 24 分钟
(单帧耗时因画面复杂度和机器配置差别很大,这里的数字只是演示算法)
可以参考一个公开的数据点:有开源的短片渲染项目自述,简单画面下的渲染速度接近实时,30 秒的片子大约渲染 30 到 40 秒。画面一旦复杂,这个比例会成倍拉长。所以别拿别人的速度当预期,自己测一遍最可靠。
5.2 中文字体:容器里的"豆腐块"
汉字变方框,是在服务器或容器里做渲染时最常见的翻车。原因并不神秘:精简版的 Linux 镜像几乎不带中日韩字体,浏览器找不到能画这个字的字体,就只能画一个空心方框。圈内管这个叫"豆腐块"。
还有一种更隐蔽的情况:字能显示,但用的是系统随便找来的替补字体,字形和字宽都不对,标题本来一行,变成了两行,下面的排版全乱。
解决分三步。安装字体,确认浏览器真的能找到它,抓帧之前等字体加载完。
# 以 Debian / Ubuntu 系为例:安装开源的思源系列(Noto CJK)并刷新缓存
sudo apt-get install -y fonts-noto-cjk fontconfig
fc-cache -fv
# 确认系统里确实有中文字体,而不只是"装过了"
fc-list :lang=zh | head
第三步在抓帧脚本里做,下一节的代码里有一行 document.fonts.ready,就是在等字体就绪。
顺带一提字体授权。思源系列这类开源字体可以放心用;很多好看的商业字体,个人学习没问题,但用在公开发布、尤其是商业用途的视频里,需要取得正版授权。字体侵权是内容创作者被索赔的高发区,别在这上面省事。
5.3 抓帧脚本要能"从断点接着跑"
抓帧脚本的核心逻辑只有三行:定位到某个时刻,截图,存盘。但要让它在实战里可靠,还得补上两个能力:支持指定帧范围,这样可以分段并行;已经存在的帧直接跳过,这样中断之后可以续跑。
// render.mjs
// 用法:node render.mjs <画面文件> <输出目录> <帧率> <起始帧> <结束帧(不含)>
// 例子:node render.mjs scene.html frames 30 0 14400
import { chromium } from 'playwright';
import fs from 'node:fs';
import path from 'node:path';
const [scene, outDir, fpsArg, startArg, endArg] = process.argv.slice(2);
const fps = Number(fpsArg);
const start = Number(startArg);
const end = Number(endArg);
fs.mkdirSync(outDir, { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1920, height: 1080 },
deviceScaleFactor: 1,
});
await page.goto('file://' + path.resolve(scene));
await page.evaluate(() => document.fonts.ready); // 等字体就绪再开始
let done = 0;
for (let n = start; n < end; n++) {
const file = path.join(outDir, String(n).padStart(6, '0') + '.png');
if (fs.existsSync(file)) continue; // 已有的帧跳过,实现续跑
await page.evaluate((t) => window.seek(t), n / fps);
const buf = await page.screenshot({ type: 'png' });
// 先写临时文件再改名:避免进程被杀时留下半张图,却被当成"已完成"
fs.writeFileSync(file + '.tmp', buf);
fs.renameSync(file + '.tmp', file);
if (++done % 300 === 0) console.log(`已完成 ${n + 1 - start} / ${end - start}`);
}
await browser.close();
console.log('本段抓帧结束');
第一次使用 Playwright 时,它会提示你安装与当前版本配套的浏览器内核,按提示装好再运行即可。
代码里"先写临时文件再改名"那两行,是从教训里来的。如果直接往最终文件名里写,进程恰好在写到一半时被终止,磁盘上就会留下一张残缺的图片。下次续跑时脚本看到文件存在,会跳过它,于是成片里就多了一帧花屏。改名是一个瞬间完成的操作,要么成功要么没发生,用它来标记"这一帧真的写完了"才可靠。
并行也很直接,把帧范围切成几段,各开一个进程:
# 四路并行:每一路负责 3600 帧
node render.mjs scene.html frames 30 0 3600 &
node render.mjs scene.html frames 30 3600 7200 &
node render.mjs scene.html frames 30 7200 10800 &
node render.mjs scene.html frames 30 10800 14400 &
wait # 关键:等所有子进程都结束,再往下走
最后那个 wait 不是可有可无的,下一节专门讲它。
5.4 长任务不要托付给"后台"
这是我认为整条流水线里最值得警惕的一个坑,而且它不是代码问题,是智能体的工作方式带来的。
编程智能体是一轮一轮工作的:你给任务,它执行一串操作,然后结束这一轮,把结果汇报给你。问题出在"结束这一轮"上。如果它把整片渲染丢到后台,自己随即宣布"已启动渲染"并结束回合,那么会话一旦关闭,后台进程很可能跟着被清理掉。
这不是我的猜测。Claude Code 的代码仓库里有多条公开的问题反馈,描述的都是类似现象:通过智能体启动并被它跟踪的后台任务,在会话结束、上下文压缩或者界面长时间闲置之后被终止,有时任务列表里还显示"运行中"。不同版本、不同运行环境下的具体表现不一样,但结论是一致的:不要假设后台任务能活过当前会话。
落到视频制作上,典型的事故现场是这样的:智能体汇报"渲染已在后台进行",你第二天来看,上万帧只截出来一小部分,没有 MP4,也没有任何报错。
我的应对是把"什么叫完成"在规则里写死:
|
条件 |
说明 |
|
最后一帧已写入磁盘 |
帧文件数量等于总帧数 |
|
MP4 编码完成 |
编码命令正常退出 |
|
校验全部通过 |
第六章的检查清单逐项通过 |
|
以上三条在同一轮内确认 |
不允许"已启动,稍后完成"这种汇报 |
也就是说,整片渲染必须在前台等到底。上一节脚本里的 wait,作用就是让当前命令一直等到所有并行进程结束。如果单次命令有时长上限,就分批跑,每一批都在前台等完,靠"已有帧跳过"的续跑能力把几批接起来。
还有一个更省心的办法:超长的渲染干脆由你自己在一个独立终端里启动,智能体只负责在它之前写好脚本、在它之后做校验。人和智能体各管一段,谁也不用赌。
5.5 返工时只重做受影响的那一段
渲染好的片子被打回来修改是常态。这时候"画面只由时间决定"和"声音先行"两个约定的价值就体现出来了。
如果问题只出在画面上,旁白本身没讲错,那声音一秒都不用动。声音不动,时间轴就不变;时间轴不变,其他段落的帧就全部有效。你只需要删掉受影响那几段对应的帧文件,重新运行抓帧脚本,它会自动只补缺的部分,然后重新编码。
返工判断:
旁白讲错了? ──是──▶ 重配相关段落 → 时间轴变化 → 该段之后的画面全部重渲染
│
否
▼
只是画面问题 ──▶ 声音不动 → 只删除并重渲染受影响的帧区间 → 重新编码
反过来,如果动了旁白,哪怕只改了一句,那一句之后的所有时间点都会平移,后面的帧都得重来。所以稿子和配音的把关要尽量放在前面做足,画面是最后才定的东西。
六、混音与出片:听得舒服,查得过关
6.1 响度和峰值是两回事
声音问题有两种,新手常常混在一起。一种是"整体太小声或太大声",另一种是"某个瞬间爆了,听着刺耳"。它们对应两个不同的指标。
|
指标 |
单位 |
衡量什么 |
白话解释 |
|
综合响度 |
LUFS |
整段音频的平均响度 |
听起来总体有多响 |
|
真峰值 |
dBTP |
瞬间最高电平 |
最响的那一下冲到了多高 |
|
响度范围 |
LU |
最轻和最响段落的差距 |
音量起伏有多大 |
克隆出来的人声有一个容易被忽略的特点:平均响度可能并不高,但个别音节的瞬时峰值很冲。如果只看平均响度,觉得偏小就整体放大,峰值就会冲过上限,结果就是破音。真峰值一旦超过 0 dBTP,失真基本就听得出来了。
标准可以参考欧洲广播联盟的 EBU R128 建议书:广播场景的目标响度是 -23 LUFS,真峰值不超过 -1 dBTP。网络视频普遍做得更响一些,业内常见的目标落在 -16 到 -14 LUFS 之间,真峰值留 1 到 2 个 dB 的余量。各平台会按自己的规则再做一次响度归一,所以不必追求某个精确数字,守住"峰值不爆、响度别离谱"就行。
6.2 用两遍 loudnorm 把人声调到位
FFmp