不少人拿到“某款新旗舰大模型”的第一反应,就是丢一句“帮我生成一段产品演示视频”,然后坐等一个mp4文件。实际体验过就会明白,模型压根不会直接生成视频像素,它生成的是一堆代码,真正把画面一帧帧画出来、合成出成片的,是那套代码和渲染管线。
这个认知不纠正,后面所有操作都会跑偏。把“让AI生成视频”理解成“让AI写代码来生成视频”,才是能落地的路子。这篇文章就拆一下这条生产线的完整流程,一共五步:需求拆解、技术选型、代码实现、渲染合成、迭代修正。每一步都踩过坑,也有可以直接抄的配置和命令。
1. 先搞清楚为什么是“写代码”而不是“生成视频”
1.1 生成式视频与代码驱动视频的本质差异
目前市面上确实有端到端的视频生成模型,输入文字直接输出画面。这类模型擅长“无中生有”,但有几个很要命的短板:时长做不长、物理规律经常崩、文字/字幕容易乱、修改要重新抽卡。更麻烦的是,你几乎无法精确控制某个物体在第几帧出现在哪个位置,只能靠描述和运气。
代码驱动视频走的是另一条路。模型输出的是HTML、CSS、JavaScript脚本,或者是一份FFmpeg命令行、一个Python绘图脚本。你运行这段代码,由渲染引擎把每一帧画出来。它的优势极其明确:确定性、可修改、可复用。某一帧不对,改参数重新渲染就行;某个转场不满意,调整代码再看效果,不用整个视频推翻重来。
打个比方。生成式视频就像你找画师口头描述一幅画,画成什么样全凭画师理解;代码驱动视频则像你给数控机床写G代码,机器按坐标精确走位,错了就改坐标。后者没有前者那种“惊艳的偶然性”,但有工程上最需要的可控性。
1.2 五步流水线全景图
整个流程可以用一条流水线概括:
- 需求拆解:把一句话需求拆成分镜表和时间轴
- 技术选型:根据视频类型确定渲染方案
- 代码实现:让模型产出结构化代码,人做检查
- 渲染合成:把代码渲染成帧序列,再用工具合成成片
- 迭代修正:针对问题定位原因,修改参数重新渲染
这五个环节环环相扣。实际执行中,绝大多数时间花在第一和第五步,中间三步反而是最顺畅的。很多人以为写代码是最难的,实际上在模型辅助下,把需求想清楚、把问题定位准才是最费精力的。
2. 第一步:把一句话需求拆成分镜表
2.1 为什么不能直接告诉模型“做个视频”
模型对待一个模糊指令,只能给出一个模糊的通用答案。你让它“做一个科技感的产品宣传视频”,它大概率生成一个泛泛的动画模板,跟你脑海中那个“左上角放Logo、第三秒文字弹入、背景光效从暗变亮”的预期完全对不上。
问题不在于模型笨,在于需求本身就是二维的,语言是一维的。口头描述会丢失大量坐标、时长、层次信息。
解决办法是先把视频拆成“镜头表”。分镜表是视频行业的通用工具,拍电影、拍广告都用它,做代码视频一样适用。它强制你把脑子里的概念转化成具体参数:第几秒到第几秒、画面里有什么、在做什么运动、字幕是什么。
2.2 分镜表模板与提示词写法
以下是我一直在用的分镜表结构,直接复制到模型对话里就能用:
请根据以下分镜表生成代码,每条镜头都要严格按时长执行: 视频总时长:10秒 分辨率:1920x1080 帧率:30fps 背景:深色科技感,深蓝渐变 #0a0e27 到 #12203e 分镜1:0s-3s,标题文字“年度数据报告”从透明淡入,同时背景光斑从中心向外扩散,字幕底部显示“2025年度” 分镜2:3s-6s,三个数据卡片依次从左向右滑入,每个卡片间隔0.2秒,卡片内容分别为“营收 +32%”、“用户 128万”、“增速 41%” 分镜3:6s-10s,Logo居中缩放出现,底部显示“扫码查看完整报告”,背景光效强度下降,画面定格把这张表喂给模型后,它输出的是标准的动画分解结构,而不是含糊的“史诗感开场”。这里有个关键细节:分镜表里一定要写清楚每一条的时间区间和画面类型,因为模型在生成代码时,是根据这些区间去计算动画关键帧的。时间范围模糊,代码里的延迟参数就全是猜的。
2.3 提示词里的时间轴换算技巧
视频以帧为最小单位。30fps下,1秒就是30帧,3秒就是90帧。写提示词时,我习惯同时提供秒数和帧数,让模型心里有数:
动画总帧数:300帧(10秒 x 30fps) 分镜1:第0帧到第90帧 分镜2:第90帧到第180帧 分镜3:第180帧到第300帧模型对“秒”有直觉,但代码里用的是帧索引。你帮它把换算做了,它生成的代码参数就会跟分镜表严格对应,后期几乎不用调。
3. 第二步:技术选型,五条路线对照
3.1 主流代码渲染视频方案对比
根据视频类型不同,渲染方案也有不同选择。我整理了一份对照表,基本覆盖了常见场景:
| 方案 | 适合场景 | 语言门槛 | 修改成本 | 备注 |
|---|---|---|---|---|
| HTML/CSS/JS + 浏览器录屏 | 文字动效、发布会PPT风、数据可视化 | JavaScript基础即可 | 低 | 所见即所得,改代码刷新就能看,适合快速迭代 |
| Remotion | 程序员向的精确视频,逐帧控制 | React基础 | 低 | 用代码描述每一帧,直接渲染输出mp4,帧级可控 |
| P5.js / Processing | 艺术类、生成艺术视觉 | 中等 | 中 | 适合粒子、噪波、创意图案 |
| Manim | 数学动画、教学讲解 | Python | 中 | 专为数学公式演示而生 |
| FFmpeg + 滤镜 | 素材剪辑、转场、合成、加字幕 | 命令行为主 | 高 | 不产生画面,但对已有素材做处理极其高效 |
3.2 我的选型逻辑
选型不追求“最强大”,追求“最短路径到达可用状态”。
如果你要的是产品宣传短视频、文字动效、图文混排,HTML/CSS/JS是成本最低的路线。浏览器本身就是渲染引擎,CSS动画写起来直观,调试时候打开开发者工具就能实时看效果。录屏即可得到结果,不满意就直接改代码。
如果你要的是帧级精确控制,例如某个元素必须在第几帧出现、必须在第几步移动,Remotion是首选。它直接暴露当前帧号,所有动画都是“基于帧号的计算函数”,逻辑清晰,适合程序员团队维护。
如果你要的是从零开始的艺术视觉效果,例如粒子流动、噪波干扰、图形扭曲,P5.js这类创意编程库更合适。Manim则适合数值公式向的教学视频,它对数学表达式和几何变换有专门的抽象。
FFmpeg不是画面生成工具,但凡是后期合成、裁剪、拼接、加字幕、转码,没有它效率高。我不建议用FFmpeg直接绘制复杂图形,那是跟自己的头发过不去。
注意:选型不是一锤子买卖。我见过不少项目,先用HTML+录屏快速出一版确认内容,再用Remotion重写出可动态改参数的正式版本。两个工具不互斥,互补使用反而是最高效的。
4. 第三步:代码实现与模型协作
4.1 把任务拆成组件再交给模型
模型写代码,最怕的不是代码写错,而是“一次生成完整工程”。一次性要求一个大型动画成片,模型的注意力会分散,后期改一个位置连带着出错好几个细节。
正确做法是切碎。每个分镜表里的画面,都是一块独立的组件。举个例子,如果用HTML/CSS方案,把三个分镜各自拆成CSS类:
<div class="scene scene-1"> <h1 class="title">年度数据报告</h1> <div class="light-effect"></div> </div> <div class="scene scene-2"> <div class="card">营收 +32%</div> <div class="card">用户 128万</div> <div class="card">增速 41%</div> </div> <div class="scene scene-3"> <div class="logo">LOGO</div> <div class="footer">扫码查看完整报告</div> </div>每个组件的动画逻辑互不干扰,修改其中一组,不会牵动其他画面。这种结构也为后续“改某个镜头重新渲染”留下了余地,不必为了一个细节把整条片子重跑一遍。
4.2 给模型喂参数的技巧:从秒到帧的换算
模型生成代码时,你要把时间参数直接写进请求里,不要让它自己估算。举例,30fps、10秒视频、第90帧开始数据卡片滑动,我通常这么提需求:
卡片滑入动画:从第90帧开始,到第170帧结束(持续80帧) 位移范围:从x = 100px 移动到 x = 0(最终位置) 缓动函数:ease-out,先快后慢这样模型给出的代码,参数都是明确的、可审计的。它不再需要编造一个“看起来差不多”的延迟时间,而是按你给的帧区间写过渡逻辑。
4.3 一个可复用的CSS动画实现示例
以分镜2的数据卡片滑动为例,一个标准的CSS实现长这样:
.scene-2 .card { opacity: 0; transform: translateX(100px); animation: card-in 0.8s ease-out forwards; } .scene-2 .card:nth-child(1) { animation-delay: 3s; } .scene-2 .card:nth-child(2) { animation-delay: 3.2s; } .scene-2 .card:nth-child(3) { animation-delay: 3.4s; } @keyframes card-in { to { opacity: 1; transform: translateX(0); } }看到这里,你应该意识到整个视频的总时长逻辑:分镜1占3秒,分镜2的卡片滑动从第3秒开始,所以第一个卡片的animation-delay是3s,而不是0s。这就是为什么分镜表必须严格先定好,不然所有延迟全部错位。
模型生成代码时,只要分镜表参数清晰,它可以直接给出带正确延迟的完整代码。你只需要做一件事:把分镜表的时间参数喂到位。
5. 第四步:渲染合成,把代码变成成片
5.1 渲染管线的两种路径
代码写完,接下来就是从“动画”到“视频文件”的最后一跳。根据你选的方案不同,有两条路径。
路径A:浏览器渲染 + 录屏
适合HTML/CSS/JS方案。打开页面,点击播放,用录屏工具(比如OBS或设备自带录屏)全程录制。这种方式胜在快,但有两个副作用:录屏帧率取决于显示器和录屏工具设置,容易出现跳帧;鼠标光标可能被录进去,需要隐藏。
精确的做法不是录屏,而是用无头浏览器按帧截图。用Puppeteer控制页面,一帧一帧截图,得到一个PNG序列,然后用FFmpeg合成。虽然多写几行代码,但每一帧都是干净的、可预期的。
路径B:直接渲染输出帧
Remotion这类框架内置渲染器,一行命令输出mp4:
npx remotion render src/index.ts out/output.mp4 --codec=h264如果先输出PNG序列,再合成:
npx remotion render src/index.ts out/frames --sequence5.2 FFmpeg合成命令与参数解释
无论哪条路径,最终都要借助FFmpeg把素材变成最终交付文件。最常用的命令如下:
ffmpeg -framerate 30 -i out/frames/frame_%04d.png -i audio.mp3 \ -c:v libx264 -pix_fmt yuv420p -crf 18 \ -c:a aac -shortest final.mp4逐项说下这些参数意味着什么:
-framerate 30:告诉FFmpeg,PNG序列要按照每秒30帧播放。-c:v libx264:视频编码器,H.264是目前兼容性最均衡的交付格式。-pix_fmt yuv420p:像素格式。如果不指定,FFmpeg可能输出yuv444,连电脑都放不了,更别提上传到小程序或网页。yuv420是各类播放器兼容度最高的格式。-crf 18:恒定质量参数。数值越小画质越高,18是视觉无损级别,日常够用。-c:a aac:音频编码,使用AAC,兼容性好。-shortest:以较短的输入流为准停止编码,音频比视频长时自动截断。
需要算总时长的话,公式是:总帧数 ÷ 帧率 = 总时长。300帧 ÷ 30fps = 10秒,这是最基础、也最容易被忽视的计算。
5.3 视频大小估算与码率选择
做交付之前,先估算一下文件体积,免得成片生成出来动辄几百MB没法上传。
文件大小约等于平均码率乘以时长再除以8。计算公式:
文件大小(MB) = 码率(Mbps) × 时长(秒) ÷ 81080p、30fps、静态文本动画为主的片子,4-6Mbps足够美观。如果10秒视频用5Mbps,最终文件约6.2MB。如果是高动态画面、粒子特效密集,建议升到8-10Mbps,否则压缩痕迹会很重。
注意:上传至某些社交平台时会二次转码,对H.264、AAC、yuv420p最友好。尽量自己先压好,不要提交一个1GB的中间文件去给平台转,转出来的效果不可控。
5.4 加字幕、加背景音乐、加水印
FFmpeg给视频叠加文字字幕,直接用drawtext滤镜:
ffmpeg -i final.mp4 -vf \ "drawtext=text='扫码查看完整报告':fontfile=/path/to/font.ttf:\ fontsize=36:fontcolor=white:x=(w-text_w)/2:y=h-200" \ -c:v libx264 -pix_fmt yuv420p final_with_sub.mp4这里x=(w-text_w)/2是让文字水平居中,y=h-200是距离底部200像素的位置。
加背景音乐合成:
ffmpeg -i final.mp4 -i bgm.mp3 -c:v copy -c:a aac -shortest output.mp4混音和加字幕可以合并到一个命令里,但为了排查方便,我通常是分两步做。先合成干净的视频流,再加字幕,最后混合音频。每一步出问题,都能立刻定位是哪一环节。
5.5 剪辑过程中的踩坑记录
真实操作中最常见的坑有三个。
第一个是帧序列命名不连续。FFmpeg默认按frame_%04d.png这种连续编号读取,如果中间断了一个序号,后面所有帧都会错位。解决办法是生成序列时发票编号固定为4位或更多位,比如frame_0000.png到frame_0299.png。
第二个是透明背景丢失。PNG序列本身可以带透明通道,但一旦在FFmpeg里指定libx264编码,透明通道直接失效,透明区域变成黑色。如果后续还要在剪辑软件里二次叠加,输出建议用ffv1编码的MOV或直接保留PNG序列,不要在H.264阶段就追求透明。
第三个是音画不同步。根源通常是录屏工具的采样帧率与音频采样率不一致。解决方法是渲染阶段就固定帧率,音频统一用44.1kHz送进去。
6. 第五步:迭代修正,五分钟定位问题
6.1 模型代码的错误与排查思路
模型写完代码,直接运行就一次通过的情况有,但很少。大多数时候会出各种问题,关键点在于能不能快速定位。
我把模型代码的常见错误分成三类。
第一类:无效CSS值与浏览器兼容性问题。模型偶尔会输出了一些只存在于某个草案规范里的CSS属性,或者遗漏了厂商前缀。现象是页面看起来跟预期差很远,但控制台不报错。定位方式:打开开发者工具,看样式是否全部生效。推荐做法是要求模型在生成代码时做“样式自检”,只输出浏览器稳定支持的CSS属性。
第二类:动画逻辑方向反了。比如期望“滑入”,代码写的却是translateX(-100px)作为初始值。这类问题肉眼一看就知道,改一个参数就好。
第三类:时间轴错位。这是最常见、也最隐蔽的。模型在多段动画同时存在时,delay和duration很容易多算或少算。遇到这类问题,不要整体看,而是逐镜头暂停对比分镜表。把分镜表里的“第几秒到第几秒”逐条对照代码里的延迟参数,一条条检查过去,答案自己会浮出来。
6.2 长视频动效卡顿的排查路径
渲染10秒的短视频基本没问题,一旦视频时间拉长、DOM元素增多,画面就开始掉帧。卡顿的根源往往是浏览器每一帧都在重新排版。
优化方向是按优先级排列:
- 只用
transform和opacity做动画,避免修改top、left、width这些触发layout的属性。 - 把每个独立动画元素提升为独立图层,用
will-change: transform提前告知浏览器。 - 大量粒子或光斑效果时,优先考虑Canvas绘制而不是几百个DOM节点。
- 减少阴影和滤镜的实时计算,静态的pre-render掉。
6.3 一个高效修复流程
我自己的修片流程已经固定下来,供参考:
- 停止视频播放,逐帧截图,标注出问题出现的精确帧数。
- 反查分镜表,确认这一帧应当属于哪个分镜。
- 在代码里定位到控制该分镜的时间参数和动画参数。
- 修改后只重新渲染该分镜片段,确认没问题再全片重出。
这个方法的关键在于第四步:局部重渲染。很多人的习惯是每次改完代码都把全片重新渲染一遍,10秒还行,到几分钟的视频就会明显浪费时间。做局部渲染,需要代码结构上支持“分镜独立”,也就是第三节提到的组件化。前期结构拆得好,后期修片省一半时间。
7. 实操复盘:十分钟做出一个10秒数据宣传片
7.1 需求与分镜表
以一个“年度数据报告”宣传片为案例,从零到成片走一遍。
- 分辨率:1920x1080
- 帧率:30fps
- 时长:10秒,共300帧
分镜表:
| 分镜 | 时间段 | 画面内容 | 动效 |
|---|---|---|---|
| 分镜1 | 0s-3s | 标题“年度数据报告”淡入 | 背景光斑扩散,文字透明度由0到1 |
| 分镜2 | 3s-6.7s | 三张数据卡片依次滑入 | 从左向右滑入,依次延迟0.2s |
| 分镜3 | 6.7s-10s | Logo居中,底部字幕 | Logo缩放出现,背景定格 |
7.2 模型生成与人工检查
向模型输入分镜表后,让它生成HTML/CSS/JS单文件动画。检查重点有三处:分镜1的标题是否正好从第0帧开始;分镜2的卡片延迟是否严格对应时间轴;分镜3的Logo动画是否在总时长的后三分之一启动。
实际中模型第一次给出的代码,分镜2的卡片延迟很可能给的是0.2s而不是3s,原因在于模型把“卡片之间间隔0.2秒”理解成了从第0秒开始的间隔,而忘了分镜2本身从第3秒才开始。这种情况会在第一步分镜表里写清楚“从第3秒开始”的前提下仍然发生,所以人工检查绝对不能省。
7.3 渲染与成片
用Puppeteer逐帧截图,代码如下:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); await page.goto('file:///path/to/animation.html'); for (let frame = 0; frame < 300; frame++) { await page.evaluate((f) => { window.scrollTo(0, 0); document.querySelector('.video-container').style.animationPlayState = 'paused'; // 这里控制动画进度到第 f 帧 window.animateToFrame(f); }, frame); await page.screenshot({ path: `frames/frame_${String(frame).padStart(4, '0')}.png` }); } await browser.close(); })();这里核心思想是每次截屏前,把动画进度推进到指定帧,再截图。这样得到的帧序列是严格按帧对齐的。
然后FFmpeg合成,加字幕:
ffmpeg -framerate 30 -i frames/frame_%04d.png \ -vf "drawtext=text='扫码查看完整报告':fontsize=36:fontcolor=white:x=(w-text_w)/2:y=h-200" \ -c:v libx264 -pix_fmt yuv420p -crf 18 \ -c:a aac -shortest final.mp4这一套流程走下来,从需求到成片十分钟完全够用。前提是前几步的结构工作没有偷懒。
8. 常见问题速查表与验收清单
8.1 高频问题速查
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 动画整体时间比预期短 | 帧率设置与录屏不一致 | 统一渲染帧率,录屏设置与视频帧率一致 |
| 某段画面元素位置突变 | 分镜切换时未重置状态 | 每个分镜初始状态显式定义,不依赖上一个分镜残留 |
| 中文字幕显示为方块 | 缺字体文件或编码不对 | 指定正确的fontfile路径,UTF-8编码 |
| 视频文件过大 | 码率设置过高 | 检查CRF值,静态画面使用6Mbps左右即可 |
| 透明背景变黑色 | 编码器不支持透明通道 | 保留PNG序列或使用FFV1编码的MOV |
| 音频结尾被截断 | -shortest参数生效 | 用-t明确指定输出的音频时长 |
8.2 交付前验收清单
每次出片之前,我习惯过一遍清单:
- 第一帧是否干净,没有多余元素残留
- 每一秒的画面是否与分镜表严格对应
- 字幕文本没有错别字、字体是否嵌入
- 音频高潮点是否与视觉高潮点对齐
- 全片时长与预期误差不超过一帧
- 不同分辨率下播放无损,平台转码无黑边
这个清单看起来啰嗦,但能挡住绝大多数低级返工。
最后分享一点个人实操体会
整套流水线跑熟之后,我最大的体会是:不要迷信模型一次成片,要让模型变成“一个可以随时修改的执行者”。每一次跟模型对话,都明确给参数、给分镜、给帧数,它会输出越来越符合预期的代码。
另外一个小技巧是保留每一次修改前后的代码版本。改崩了随时能回退,改好了也能对比出是哪个参数起效。很多时间轴问题在对比中会看得格外清楚。
代码视频这条路,输出的不是偶然生成的画面,而是可维护、可解释、可精确修改的工程成果。花十分钟搭好流水线,后面每一次出片都是同一套流程的重放,效率会稳定在一个很高的水平。