Remotion+Skills:让AI生成视频从抽卡走向工程化

发布时间:2026/11/4 18:04:37
Remotion+Skills:让AI生成视频从抽卡走向工程化 先说个我自己的观察。过去大半年身边做内容的朋友几乎都在试同一件事让模型帮忙出视频。试完之后大多数人得出同一个结论——像抽卡。同一段提示词今天出来的画面能看明天出来的字幕压边、配音和画面错位、关键信息被裁掉一半。改一处全盘重来。这不是模型不行是这条路本身缺少可复现的骨架。我后来换了个思路用Remotion做渲染引擎把整个怎么出片的流程封装成Skills交给 AI 助手去调用前后跑了二十多条成片之后AI 生成视频这件事才算真正从玄学落到了工程层面。这篇就讲讲我是怎么搭的、每一步为什么这么选、以及踩过的那些坑。适合两类人看一类是做过短视频但没碰过代码的内容创作者另一类是写过前端但没做过视频渲染的开发者两边都能对上号。1. 三分钟理清Remotion 与 Skills 到底是什么货色1.1 Remotion视频不是画出来的是算出来的大部分人理解的视频制作是时间轴加素材一帧一帧拖。Remotion 走的是完全相反的路子它把视频当成一个 React 组件你给它一个frame数字它返回这一帧应该长什么样。整个视频就是f(0)到f(N-1)的一次遍历渲染器逐帧截图再用 ffmpeg 编码成 mp4。这个模型一旦建立很多原本玄学的事情立刻有了确定性。因为画面是纯函数输出只要输入的 props 一样、代码版本一样出来的每一帧就完全一样一个像素都不会飘。你想改标题字号改一个 CSS 值重新渲染就行你想批量做一百条只要换一百份 props JSON。这就是工程和玄学最本质的分野——可复现。代价也很清楚你得会写一点 JSX得理解帧率、时长、插值这些概念。所以我从一开始就没打算让模型自由发挥去写代码而是把这些知识固化下来让它在固定的轨道上跑。1.2 Skills把老师傅的手感写成可加载的说明书Skills 这个概念说白了不复杂它是一个文件夹里面至少有一个SKILL.md头部用 YAML 写两三个字段名字、一句话描述什么时候该用我。Agent 启动的时候上下文里只挂这些元数据几十个 token 而已等到用户的请求真的匹配上了它才会把正文加载进来正文里再用相对路径指向references/里的详细文档和scripts/里的可执行脚本。这个渐进式披露的设计对我特别关键。Remotion 的坑不少字体没加载完就渲染会得到空白文字、staticFile()的路径规则和普通 import 不一样、durationInFrames的单位是帧不是秒、并发数开太高会 OOM。这些东西如果每次对话都从头讲一遍上下文早就爆了。写成 Skill 之后元数据常驻详细规则按需加载脚本直接跑——相当于给 AI 配了一本随身的施工手册还带工具箱。1.3 拼起来之后玄学到底被治好了哪几处我把这套组合的价值归成四条。第一是确定性同输入同输出客户要改一个字我重渲染一遍其余画面零变化不用重新抽卡。第二是可控粒度模型生成的是结构化 JSON标题、分镜、每句文案、时长、配色不是像素任何一层都能单独替换。第三是可版本管理整个项目就是一个 git 仓库改动有 diff回滚有历史。第四是可批量化一个模板配 N 份数据出 N 条片子边际成本接近零。这四条听着像宣传语但你真跑过一版就知道差别有多大。以前改一句配音文案等于推翻重来现在改 JSON 里一个字段渲染三分钟收工。2. 方案选型我为什么放弃抽卡式出片改走编程渲染这条路2.1 三条路线的横向对比在动手之前我把能走的路都捋了一遍大致三类。下面是当时做的对比表字段是我自己关心的几个维度不是通用评测。维度纯模型端到端生成模板拖拽工具编程式渲染Remotion画面可控性低提示词决定一切中受模板约束高代码级控制可复现性差同提示词结果不同较好完全确定改单点成本极高基本等于重做中受模板限制低改字段重渲染批量能力弱中需手工复制强数据驱动学习门槛最低低中需要 JSX 基础长尾定制几乎为零低无上限稳定出片周期不可预估小时级分钟级看这张表就明白我为什么选第三条。端到端生成适合做氛围素材、概念片、几秒钟的视觉冲击一旦涉及第 8 秒必须出现产品名、第 12 秒必须切到价格这种硬约束它就交不出活。模板工具能解决一部分但你的所有片子会长得一模一样而且模板不给你改底层布局的自由。Remotion 的位置刚好卡在中间偏工程门槛比拖拽高比从零写 Canvas 低得多自由度接近写代码但常用能力转场、字幕、音频、静态资源都有现成包。2.2 为什么技能层不是锦上添花而是必需品有人会问既然代码可控为什么不直接让 AI 写 Remotion 代码我试过答案是它能写但写不稳。不稳在哪它默认会按 Web 前端的习惯写动画用 CSStransition和animation——这在 Remotion 里是死路因为渲染器是逐帧截图的CSS 动画的时间基准和渲染帧不同步出来要么不动要么跳帧。它也会忽略delayRender字体没加载完就开始渲染结果标题变成一片空白。它还会把durationInFrames写成秒数导致视频只有 30 帧。这些错误的共同点是不是能力问题是知识缺口。而知识缺口正好是 Skill 该补的地方。我把 Remotion 的核心约束、常见误写、模板代码、渲染命令全部写进 Skill模型每次动手前会先读到这些出错率肉眼可见地降下来。这就是我说的技能层是必需品——它不是让 AI 变聪明是让它别在已知的坑里再摔一次。2.3 工程骨架长什么样我最终落地的目录结构大概是这样你可以直接照着建video-project/ ├── public/ │ ├── fonts/ │ └── audio/ ├── src/ │ ├── index.ts # 注册入口 │ ├── Root.tsx # Composition 注册 │ ├── Main.tsx # 主视频组件 │ ├── components/ # 可复用视觉组件 │ └── schema.ts # props 类型定义 ├── data/ │ └── ep001.json # 单条视频数据 ├── skills/ │ └── remotion-video/ │ ├── SKILL.md │ ├── references/ │ │ ├── animation.md │ │ └── rendering.md │ └── scripts/ │ └── render.sh ├── remotion.config.ts └── package.json这个结构有个好处数据、模板、技能三层彻底分离。换内容只动data/换风格只动src/components/换流程只动skills/。三者互不干扰也让 AI 每次只需要读它该读的那一层。3. 核心细节拆解把大片感拆成可以填参数的东西3.1 参数化的边界哪些进配置哪些必须写在代码里这是整个项目里我修改次数最多的一处设计。一开始我把所有东西都塞进 JSON——字体、颜色、字号、间距、动效曲线结果 JSON 比代码还长改起来更痛苦。后来定了一条线内容进配置视觉进代码只有需要批量变化的视觉才进配置。具体划分是进配置标题文案、副标题、分镜列表、每段时长、主色、强调色、背景图路径、音频路径、片尾署名。进代码字号层级、字重、行高、间距节奏、动效曲线、入场延迟、转场类型。理由很直白文案每条视频都不同必须外置字号行高这类东西一旦定下来就是一整套视觉语言每条视频都改反而会破坏一致性。主色算例外因为系列里可能按主题换色所以放配置。判断标准可以简化成一句话这条数据变了画面应该变那条数据变了画面不该变。想清楚这个参数边界自然就出来了。3.2 帧、秒、时长把所有时间换算成帧数这是我见过最多人翻车的地方。Remotion 里所有时间单位都是帧不是秒不是毫秒。人脑习惯秒所以中间必须有一次换算而且这个换算必须在写代码之前完成不能边写边算。公式很简单总帧数 时长秒 × 帧率 起始帧 起始时间秒 × 帧率举个实际例子。我做一条 45 秒的竖版短视频30fps那么durationInFrames 45 × 30 1350。分镜拆成 4 段时长分别是 6 秒、14 秒、18 秒、7 秒换算成帧就是 180、420、540、210加起来正好 1350。每段的起始帧是前面所有段累加0、180、600、1140。字幕的时长我另外有一套估算法。中文口播的舒适语速大概每秒 4 到 5 个字按 4.5 算一句 18 个字的文案大约 4 秒也就是 120 帧留 6 帧的呼吸间隙实际占 126 帧。这套估算肯定不精确但它保证了一件事没有人声的时候画面不会长时间空着有人的时候字幕不会一闪而过。真要精确就把配音的时长直接量出来填进 JSON这是最稳的做法。注意fps一旦改了所有按帧写死的数值全部失效。所以我在项目初期就把帧率定死为 30写进 Skill 的硬性约束里任何情况下不允许改。3.3 动效的三板斧插值、弹簧、序列Remotion 的动效生态其实就三个核心 API把这三个吃透90% 的画面都能做出来。interpolate是线性映射给定输入区间和输出区间自动算出当前值。基础用法是这样const opacity interpolate(frame, [0, 15], [0, 1], { extrapolateLeft: clamp, extrapolateRight: clamp, });这里clamp必须加。不加的话第 16 帧开始透明度会继续往上跑到 2、3、4浏览器会把它当 1 处理看起来没问题但一旦你把这个值拿去算位移或者缩放画面就会炸。我第一版就是因为这个标题飞出了画布。spring是物理弹簧输出 0 到 1 的自然缓动做元素入场特别合适const enter spring({ frame, fps, config: {damping: 200, stiffness: 100, mass: 1}, });damping越大越稳越小越弹。我的经验是文字用 200 左右接近临界阻尼不会有回弹图形元素用 12 到 20能出一点俏皮的过冲。这个参数没有标准答案得自己拖进度条看。Sequence负责把时间切片。它接受from和durationInFrames内部的子组件会看到一个从 0 重新开始的frame。这个时间归零的机制非常关键——它意味着每个分镜组件都可以按我从第 0 帧开始入场来写不用关心自己在整条视频里的绝对位置。分镜一多这个抽象能省掉大量心智负担。3.4 SKILL.md 的写法结构、字段与措辞Skill 的正文写得好不好直接决定 AI 的产出质量。我改了四五版才摸到点门道。文件头部是 YAML frontmatter只有两个字段值得认真对待name和description。description是唯一常驻上下文的部分它决定了技能什么时候被唤起所以要写得具体、带触发词。我一开始写的是帮助制作视频太泛模型经常在该用的时候不用。改成用 Remotion 生成或修改代码驱动的视频涉及分镜、字幕、动画、渲染命令时使用之后命中率明显上来了。正文部分我按约束 → 模板 → 检查三段来组织。约束段全是硬性规则语气要强硬比如所有时间单位必须是帧、禁止使用 CSS transition 和 animation、字体必须通过 loadFont 加载并在顶层调用。模板段给一段能直接跑的最小代码模型照着改比从零写靠谱得多。检查段是一份清单渲染前逐条过。references/里放细节文档。animation.md讲插值和弹簧的参数调法rendering.md讲渲染命令和性能优化。这些内容很长不适合常驻按需加载就好。scripts/render.sh是最实用的部分把命令封装成脚本模型只要调用就行#!/usr/bin/env bash set -euo pipefail npx remotion render src/index.ts Main out/$1.mp4 \ --propsdata/$1.json \ --concurrency4 \ --codech264 \ --crf18set -euo pipefail这三件套别省。渲染失败的时候没有它脚本会继续往下跑最后你拿到一个半截的文件还不知道错在哪。4. 实操从空目录到一条成片的完整流程4.1 环境搭建与最小可渲染工程先说版本要求Node 18 以上建议 20 LTS。低于 16 会缺一些 API报错信息还特别难懂。初始化可以直接用官方脚手架也可以手动搭。手动搭更透明我贴一下核心几个文件。src/index.ts是入口只做一件事import { registerRoot } from remotion; import { RemotionRoot } from ./Root; registerRoot(RemotionRoot);src/Root.tsx注册 Composition也就是告诉渲染器有这么一个视频它多长、多大、用什么组件import { Composition } from remotion; import { Main } from ./Main; import { defaultProps } from ./schema; export const RemotionRoot: React.FC () ( Composition idMain component{Main} durationInFrames{1350} fps{30} width{1080} height{1920} defaultProps{defaultProps} / );注意width和height。竖版短视频是 1080×1920横版是 1920×1080方版 1080×1080。分辨率一旦定了就别改因为所有字体大小、间距、安全边距都是按这个尺寸调的换一个尺寸等于重新排版。package.json里加两条脚本日常就够用了{ scripts: { dev: remotion studio src/index.ts, render: remotion render src/index.ts Main out/main.mp4 --propsdata/ep001.json } }npm run dev会起一个本地预览器带时间轴、带参数面板改代码热更新。这个预览器是我认为 Remotion 最值回票价的地方——你能像调网页一样调视频还能逐帧步进。以前做动画只能渲染完看成品现在改一像素立刻看到效果。4.2 素材、字体与静态资源的处理这一块是新手最容易卡住的地方我单独拎出来讲。Remotion 里引用本地文件必须用staticFile()文件放在public/目录下路径参数是相对于public/的import { Img, Audio, staticFile } from remotion; Img src{staticFile(images/bg-01.jpg)} / Audio src{staticFile(audio/vo-ep001.mp3)} /不能写成../public/images/bg-01.jpg也不能用import直接导入。原因在渲染机制渲染是在一个无头浏览器里跑的相对路径的基准和你本地不一样staticFile()会把它拼成渲染器能识别的 URL。我第一次不知道这个图全裂了还以为是路径写错了查了半小时。字体更麻烦有个时序问题。浏览器加载自定义字体是异步的而渲染器截帧是同步推进的。如果不做等待渲染器可能在第 0 帧就截图那时候字体还没到位结果就是标题用默认字体渲染出来或者直接空白。正确做法是在模块顶层调用loadFontRemotion 会自动接管延迟渲染import { loadFont } from remotion/fonts; import { staticFile } from remotion; loadFont({ family: TitleFont, url: staticFile(fonts/title-bold.woff2), weight: 700, });然后组件里用fontFamily: TitleFont。这套写法比手动delayRender/continueRender省心得多除非你有非常特殊的加载逻辑否则不用碰那两个 API。素材规格我总结了一个经验值供参考素材类型建议规格说明背景图尺寸等于画布 1.2 倍JPEG 质量 80留出推拉空间体积可控图标SVG 优先任意缩放不糊配音44.1kHz 单声道 mp3人声够用体积小背景音乐44.1kHz 立体声 mp3需要空间感字体woff2只打包用到的字重字重越多渲染越慢4.3 从 JSON 脚本驱动整条视频数据驱动的核心是一份结构化的 JSON。我的结构大概长这样{ title: 一个具体的话题标题, subtitle: 副标题最多十二个字, theme: { primary: #1F6FEB, accent: #F0883E }, segments: [ { type: cover, durationInFrames: 180, text: 开场 }, { type: bullet, durationInFrames: 420, heading: 第一层逻辑, points: [要点一, 要点二] }, { type: quote, durationInFrames: 540, text: 一段引述内容 }, { type: outro, durationInFrames: 210, text: 片尾 } ], audio: audio/vo-ep001.mp3 }组件侧做两件事把segments的时长累加算出总帧数用Series或手写Sequence依次排布。Series的好处是会自动累加offset不用自己记起点import { Series } from remotion; Series {data.segments.map((seg, i) ( Series.Sequence key{i} durationInFrames{seg.durationInFrames} SegmentRenderer segment{seg} theme{data.theme} / /Series.Sequence ))} /SeriesSegmentRenderer按seg.type分发到不同的分镜组件。这个分发层是整套设计的关键抽象——新增一种分镜只要加一个组件和一个case其余代码一行不动。有个细节值得说durationInFrames的总和必须和Composition上的值对齐否则最后一段会被截断。我的做法是在Root.tsx里算不写死const total data.segments.reduce((s, seg) s seg.durationInFrames, 0); Composition durationInFrames{total} ... /这样数据一改时长自动跟着走永远不会错位。4.4 预览、渲染与批量出片日常流程分三步预览、单条渲染、批量渲染。预览用npm run dev本地起一个 Studio可以拖时间轴、改参数实时看。这一步不要跳我所有的排版问题都是在这一步发现的——尤其是文字压到安全区外、元素出画、转场重叠。单条渲染npx remotion render src/index.ts Main out/ep001.mp4 \ --propsdata/ep001.json \ --concurrency4 \ --codech264 \ --crf18参数解释一下。--concurrency是并行渲染的标签页数量默认是 CPU 核数的一半左右我的机器是 8 核开到 4 最稳开到 6 反而因为内存压力变慢。--codech264兼容性最好平台通吃。--crf控制质量范围 0 到 51数字越小质量越高体积越大18 是视觉无损附近我用这个值出成片如果只是内部审片用默认值或者 23 就行快不少。批量的话我在 Skill 的scripts/里放了一个循环脚本for f in data/*.json; do name$(basename $f .json) npx remotion render src/index.ts Main out/${name}.mp4 --props$f --concurrency4 done渲染耗时我实测过一个数据1080×1920、30fps、1350 帧的片子单帧平均 70 到 90 毫秒理论总时长 1350 × 0.08 ≈ 108 秒4 并发实际跑下来 45 到 60 秒因为编码阶段是瓶颈并发收益会打折扣。也就是说一条 45 秒的片子从按下回车到成片落地差不多一分钟。这个速度意味着你可以一天迭代几十次这在拖拽工具里是不可想象的。配套还有一个静帧命令用来出封面特别好用npx remotion still src/index.ts Main out/cover.png --frame45 --propsdata/ep001.json挑一帧画面最饱满的当封面比另外做封面省事得多。5. 常见问题与排查实录5.1 高频故障速查表下面这张表里的每一条我都至少踩过一次有些踩过三次以上。现象最可能的原因处理办法成片从头到尾一片黑durationInFrames写成了秒数改成秒数 × fps字体变成默认黑体或空白字体未加载完成就渲染顶层loadFont不放组件内图片全部裂开用了相对路径或import改staticFile()文件放public/动画完全不动用了 CSStransition/animation改interpolateuseCurrentFrame元素飞到画布外interpolate未加clamp两端都加extrapolate: clamp最后一段没播完就结束总帧数与分段之和不一致用reduce动态计算总帧数渲染到一半进程被杀并发过高导致内存不足降到 2 到 4关掉其他重负载程序音画不同步音频起点未设置startFrom用Sequence或startFrom对齐中文断行位置很怪缺wordBreak或容器宽度过窄设wordBreak: break-all并留足边距渲染出来的颜色偏暗色域或压缩参数问题检查--crf必要时换--codecprores5.2 几个不常见但致命的坑第一个坑来自动画的物理参数没有归一化。spring的输出依赖fps如果你在 30fps 下调好了参数某天换到 60fps 渲染所有动画的速度会翻倍。这不是 bug是设计如此。我的处理办法是把fps从useVideoConfig()里取永远不硬编码同时在 Skill 里明确写帧率是全局常量任何情况下不修改。第二个坑是中文字体的字重缺失。很多开源中文字体只提供 Regular 和 Bold 两档你在代码里写fontWeight: 900浏览器会做伪粗体就是给字形描边加粗。这在网页上勉强能看但在视频里一旦放大到 1920 宽描边会糊成一坨边缘发毛非常明显。解决办法只有两个要么找到真正提供黑体字重的字体要么在设计上避开 900 这个档位用 Bold 加字号来拉开层级。第三个坑是音频的淡入淡出没做。背景音乐直接从第 0 帧以满音量开始第一秒会有一个非常突兀的爆音。这个在预览里不仔细听会漏掉但发布出去之后评论区一定有人提。做法是用interpolate算音量曲线喂给Audio volume{...}const volume interpolate( frame, [0, 30, total - 60, total], [0, 0.6, 0.6, 0], { extrapolateLeft: clamp, extrapolateRight: clamp } ); Audio src{staticFile(audio/bgm.mp3)} volume{volume} /30 帧淡入、60 帧淡出按 30fps 算就是 1 秒进、2 秒出听感很自然。第四个坑偏工程public/目录里堆了几百兆素材之后渲染会明显变慢。原因是渲染器启动时会把整个目录挂载进无头浏览器。我后来把素材按项目分子目录一个项目只留自己用的速度立刻回来了。5.3 我的日常检查清单每次渲染正式成片之前我会走一遍这份清单十分钟能省下两小时返工。画面层面所有文字是否都在安全区内竖版视频底部要留出约 250 像素给平台 UI 遮挡字号最小的一行在手机上看是否还能读所有元素是否有出画的情况转场处是否有重叠或黑帧。时间层面总帧数是否等于分段之和每段时长是否和配音对得上片头片尾是否有足够的停留时间让观众看清信息。资源层面字体是否只有一个来源、没有回退音频是否有淡入淡出图片是否都走了staticFile()。工程层面props是否来自外部 JSON没有硬编码在组件里fps是否只在一处定义渲染命令是否带了--props。这份清单我直接写进了 Skill 的正文末尾让 AI 每次渲染前自己过一遍。效果比我想象的好——它真的会逐条核对还会主动指出第 3 段的时长和累积起始帧对不上。6. 一点延伸这套东西还能怎么用跑顺之后我发现它的适用面比预想的宽。除了短视频我用它做过产品更新的版本播报——每周从变更日志里拉数据渲染一条 30 秒的横版视频全自动。也做过数据可视化的动态图表把一个 CSV 喂进去条形图按数据逐帧生长比静态图直观得多。还有人用它批量生成带学员名字的结课证书视频一人一条几百条跑一晚上。我个人在实际操作中的体会是真正省时间的不是AI 帮我写代码而是AI 在一个被约束好的轨道上写代码。轨道是 Skill 定的约束是 Remotion 定的AI 在这两者之间做填充和变体。缺了任何一环效率都会掉回去。所以如果你也想走这条路我的建议是先把最小可渲染工程跑通把一条片子从头到尾渲出来然后再去写 Skill——没有跑通过的项目写出来的技能文档全是纸上谈兵。最后分享一个小技巧把你每次踩坑的排查过程顺手记进references/troubleshooting.md。三个月之后你会发现这份文档比任何教程都值钱因为它记的全是你自己环境里真实发生过的事。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询