
大家好,我是人月聊IT。
今天整理和分享下基于Remoting来制作动态的可视化图表并叠加到口播MP4视频上的方法。并提供完整的skills技能包供大家参考。该方法类似B站花生AI视频的SVG图+视频的叠加效果。
我是一名持续做技术与业务知识分享的内容创作者,定期录制 16:9 横屏的真人口播视频。原始素材是单镜头出镜,我的人像位于画面正中央。
随着分享话题深入,纯口播的信息密度越来越成为瓶颈。我讲到"集成产品开发的 5 个阶段",观众听到的是音频流,没有视觉支撑结构;我说"业务流程和业务是两个不同的概念",对比关系完全靠想象。这显著拉低了观看体验和观众留存率。
但我又不想为每条视频都跑一遍剪映/AE 的人工剪辑——一来时间成本高(一段 7 分钟视频通常要花 2-3 小时手动对位),二来工作流不能沉淀,每次都从零开始。
经过梳理,核心需求可以浓缩成五条:
第 5 条是这次协作真正的关键:单次解决问题不难,难的是把"分析→生成→渲染"这条智力链路沉淀成可重复利用的资产。
针对上述需求,我和 Claude 一起评估了三条技术路线。
朴素思路。用 SVG/HTML 画图表,渲染成带 alpha 通道的透明 PNG,最后用 ffmpeg 的 overlay 滤镜按时间戳叠到视频上。
优势是链路清晰,每一步都是成熟工具,中间产物可以人工审视。劣势是动画几乎做不了——PNG 是静态的,要做淡入淡出/弹性动画就得导 PNG 序列或 ProRes 4444 MOV,复杂度陡升。更要命的是没有可视化预览,时间对位完全靠肉眼看 ffmpeg 输出,一次次重导。
用专业剪辑软件做对位、添加图表素材,所见即所得。
优势是预览方便,专业软件功能丰富,效果可控。劣势是高度依赖人工操作:处理 7 分钟视频可能要 2-3 小时;不可复用——每次新视频都要从零开始;最关键的,无法把"分析逐字稿→决定叠加什么→对齐时间戳"这部分核心智力工作沉淀下来给 AI 自动化。
这是后来发现的关键工具。原本我口误说成了 "remoting skill",Claude 反应过来后确认是 Remotion——一个用 React 组件定义视频和动画的开源框架。
它的核心思路是:视频每一帧都是 React 渲染的结果。框架内置 <Video> 组件可以把现有视频作为底层背景,上面用 <Sequence from={X} durationInFrames={Y}> 控制任何 React 组件在哪一帧出现、停留多久,最后用内置 Chromium headless 渲染所有帧、用 ffmpeg 编码成 MP4。
优势:
劣势:有学习曲线(需要 React + 一些动画概念);首次安装要下载 Chromium,国内网络可能慢;渲染算力消耗较大。

最终选定 方案 C(Remotion)。决策依据集中在四点:
interpolate 和 spring 把动画变成几行代码的事,方案 A 要做同样效果得搞一堆 PNG 序列整个准备过程分四步,每一步都遇到了真实的工程问题:
第一步:检查基础工具。 Node.js 22、Python、Playwright 已就绪;唯一缺的是 ffmpeg。
第二步:装 ffmpeg。 通过 winget install Gyan.FFmpeg 装上 8.1.1 完整版(含 x264/x265/NVENC 等全套编码器)。注意 Git Bash 当前会话不会继承 winget 改的 PATH,后续脚本里用完整路径调用即可。
第三步:创建 Remotion 项目。 在 <你的项目目录>\video-overlay\ 用 create-video --blank 起项目,带 Tailwind v4、React 19。npx create-video 是交互式 CLI,期间会问 "Add TailwindCSS?"、"Add agent skills?" 等问题,用 yes '' 持续喂回车可绕开交互。
第四步:解决渲染前的 Chrome 下载问题。 Remotion 4.x 渲染依赖 chrome-headless-shell(约 113 MB),默认从 Google Storage 拉,国内下载只有 4 MB/min。改用国内镜像 https://cdn.npmmirror.com/binaries/chrome-for-testing/<version>/win64/chrome-headless-shell-win64.zip 后 12 秒下完。手动解压到 node_modules/.remotion/chrome-headless-shell/win64/chrome-headless-shell-win64/ 并写一个 VERSION 文件即可让 Remotion 识别。
渲染过程中还遇到了 Windows 文件句柄上限(约 8000)导致的 EMFILE 错误——单次跑到 8K-9K 帧就会爆。解决方案是把视频拆分成两块(每块 < 6500 帧)分别渲染,最后用 ffmpeg 拼接。--concurrency=2 进一步降低单帧打开的临时文件数。
为让这套工作流可复用,我们把整个方案打包成了一个 Claude Code Skill,安装位置 ~/.claude/skills/video-overlay-render/。
文件结构如下:
video-overlay-render/
├── SKILL.md ← 主入口(工作流 8 步 + 检测规则)
├── scripts/probe-video.mjs ← ffprobe 探针脚本
├── templates/ ← 7 个 React 叠加组件模板
│ ├── KeywordOverlay.tsx ← 概念引入
│ ├── EmphasisOverlay.tsx ← 重点强调
│ ├── BulletListOverlay.tsx ← 并列列表
│ ├── StatCalloutOverlay.tsx ← 数据高亮
│ ├── ProcessOverlay.tsx ← 流程步骤
│ ├── ComparisonOverlay.tsx ← A/B 对比
│ └── DiagramOverlay.tsx ← 通用关系图
└── reference/
├── design-tokens.md ← 视觉规范(配色/字号/动画)
├── transcript-format.md ← 支持的逐字稿格式
└── composition-example.tsx ← 完整接线范例
技能的关键设计原则:
THIS IS YOUR JOB, NOT THE USER'S,避免 AI 偷懒去问用户第一次实战是处理我自己的视频《技术人员如何更好熟悉业务》(1920×1080,31fps,6 分 56 秒,1.25 GB)。
执行流程:
probe-video.mjs 自动获取规格[MM:SS.HH] 时间戳格式Composition.tsx(12 个叠加配置)和 Root.tsx(视频规格)out/final.mp4整个对话过程从模糊需求到最终 MP4 产出,对我的操作时间不超过 30 分钟,剩下的全是机器在跑。下次再来一条视频,只需要把 "应用 video-overlay-render 技能处理视频" 这句话扔给 Claude 就能复现整条流水线。
这种"一次性把工作流沉淀成 AI 技能"的协作模式,是我觉得最值得记录下来的部分——它把人的角色从"操作员"变成了"判断者"。
把「真人口播视频 + 带时间戳的逐字稿」变成一条最终 MP4:在合适的时刻浮现图形叠加层,为讲述者正在说的内容提供视觉支撑。底层基于 Remotion(React + ffmpeg)。
当用户出现以下情况时触发本技能:
不要用于:纯动画视频(没有真人实拍画面)、仅需烧录字幕(直接用 ffmpeg drawtext)、或 PPT 式的录屏。
设置项 | 默认值 | 原因 |
|---|---|---|
画面比例 | 16:9 横屏 | 用户所有视频都这样拍 |
人像位置 | 画面正中 | 用户已确认 |
叠加位置 | 居中,可覆盖出镜者 | 用户明确表示可以接受 |
视觉风格 | 扁平咨询风、浅色调、无 3D、无渐变、无毛玻璃 | 用户的长期偏好 |
默认帧率 | 与源视频一致(通常 30) | 避免重采样产生的伪影 |
如果用户针对某次具体任务推翻了上述任意一条,就仅在本次局部覆盖——不要去改这份 SKILL.md。
工具 | 检查命令 | 缺失时如何安装 |
|---|---|---|
Node.js ≥ 20 | node --version | 通过 winget / nvm 安装 |
ffmpeg(含 ffprobe) | ffmpeg -version,或使用完整路径 %USERPROFILE%\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_*/ffmpeg-*-full_build/bin/ffmpeg.exe | winget install Gyan.FFmpeg |
Remotion 项目 | 已存在于 <你的项目目录>\video-overlay\ | npx create-video@latest <name> --blank --pm npm(用管道喂入 yes '' 以跳过交互式提问) |
Windows + Git Bash 注意事项:刚执行完 winget install 后,当前 bash 会话不会继承新的 PATH。在用户开启新终端之前,请用 ffmpeg.exe / ffprobe.exe 的完整路径调用。
默认 Remotion 项目位置:<你的项目目录>\video-overlay\(除非用户另有说明,否则用它)。该项目已内置 Remotion 4.x + Tailwind v4 + React 19。
按顺序执行以下步骤。不要跳步——每一步都建立在上一步之上。
运行辅助脚本,获取精确的尺寸、帧率与时长:
node ~/.claude/skills/video-overlay-render/scripts/probe-video.mjs "<视频路径>"
输出为 JSON:{ width, height, fps, durationSeconds, totalFrames }。配置 Remotion 的 <Composition> 时必须原样使用这些数值。一旦不匹配,会导致黑边或播放速度异常。
用户的逐字稿会带时间戳。以下任意格式都可接受——完整语法见 reference/transcript-format.md:
[00:03] 第一段口播内容...
[00:12] 第二段...
或 SRT 格式、或 VTT 格式、或带 { start, end, text } 条目的 JSON。
将其转换为统一的内部结构:
type Segment = {
startSeconds: number;
endSeconds: number; // = 下一段的开始时间;最后一段则为视频结束时间
text: string;
};
关键:用户只会给你逐字稿和视频。不要反过来问他们哪些片段需要叠加——自动分析正是本技能的全部意义所在。逐段扫描,套用下面的检测规则,自行决断。大多数片段都将不加任何叠加——这是正确且符合预期的。
如果某段是口头填充、个人轶事、过渡、反问、泛泛之谈,或任何不明确匹配下列规则的内容,就保持其裸口播状态。一段大部分时间都能看见讲述者本人的口播视频,比一段被图形淹没的视频更能建立信任。
按以下优先级顺序套用。单个片段至多匹配一条规则。
1. 顺序流程 → ProcessOverlay
第一步 / 第二步 / 第三步、首先 / 其次 / 然后 / 最后、先 X 再 Y 最后 Z、第一阶段 / 第二阶段、step 1 / 2 / 32. A/B 对比 → ComparisonOverlay
A 和 B 的区别、相比 X / 对比 X、不同于 X、一方面 X 另一方面 Y、优点 X 缺点 Y、传统的 X,新的 Y3. 枚举 / 列表(无时间先后) → BulletListOverlay
三点 / 四个 / 五种、包括 / 分别是 / 有以下、一是 X 二是 Y 三是 Z(当各项不是顺序步骤时)4. 强调点 → EmphasisOverlay
重点、关键、核心、最重要的是、千万、一定要、记住、划重点、本质上、归根结底、说白了5. 数据 / 量化主张 → StatCalloutOverlay
60%、3 倍、1000 万、翻了一番、日活 X 亿6. 新概念 / 主题引入 → KeywordOverlay
今天聊的是 X、我们要讲的是 X、什么是 X?,外加一个清晰的单一名词短语7. 系统 / 关系图 → DiagramOverlay
生成一份内部计划,形如:
type OverlayPlan = {
startSeconds: number;
durationSeconds: number; // 钳制在 [1.5, 6.0] 区间
type: 'Keyword' | 'BulletList' | 'StatCallout' | 'Process' | 'Comparison' | 'Emphasis' | 'Diagram';
payload: Record<string, unknown>; // 传给模板的 props
rationale: string; // 一句话:为何选这一段、为何用这种类型——供用户审阅
};
在生成组件之前,把这份计划打印给用户,让他们可以否决 / 批准。
对每一个要加叠加的片段:
~/.claude/skills/video-overlay-render/templates/ 读取对应模板src/overlays/<NN>-<short-slug>.tsx(NN = 补零的序号,如 01、02)模板已内置入场 / 出场动画(15 帧淡变)以及扁平咨询风样式。除非用户要求,否则不要重新设计样式——叠加层之间的一致性比新颖更重要。
Composition.tsx替换 src/Composition.tsx,使其:
<Video src={staticFile('source.mp4')} /> 渲染源视频<Sequence from={startFrame} durationInFrames={overlayDurationFrames}><MyOverlay /></Sequence>完整可运行范例见 reference/composition-example.tsx。
帧数换算:startFrame = Math.round(startSeconds * fps)。叠加时长默认取 endFrame - startFrame,但要钳制在 1.0–6.0 秒区间——再长,叠加层就赖着不走;再短,则来不及读完。
public/将用户的视频复制(不要移动)到 <project>/public/source.mp4。Remotion 的 staticFile() 只能解析 public/ 内部的路径。
cd <project> && npm run dev
会在浏览器中打开 Remotion Studio。拖动时间轴核对时机。若有不对,直接编辑,Studio 会热重载。
渲染之前,务必让用户(或你自己,若你能查看的话)至少检查 3 个叠加时刻。在预览里修,比渲染 10 分钟之后再修便宜得多。
cd <project> && npx remotion render MyComposition out/final.mp4
(把 MyComposition 换成 Root.tsx 里的实际 id。)默认编码为 H.264,默认 CRF 即可得到广播级输出。渲染耗时约为视频时长的 0.3–1 倍,取决于叠加复杂度。
若需更高画质:
npx remotion render MyComposition out/final.mp4 --crf 18
全部模板位于 ~/.claude/skills/video-overlay-render/templates/:
KeywordOverlay.tsx —— 居中大号关键词 + 可选副标题(主题引入)EmphasisOverlay.tsx —— 超大号加粗短语,配强调下划线 + 脉冲(用于「这就是那个关键点」的时刻)BulletListOverlay.tsx —— 标题 + 逐条依次动画的要点(并列项,无时间先后)StatCalloutOverlay.tsx —— 主角数字 + 标签 + 一行小字背景说明ProcessOverlay.tsx —— 横向 3–5 步流程,带箭头(顺序步骤)ComparisonOverlay.tsx —— A | B 双栏,带滑入动画(对比 / 前后对照)DiagramOverlay.tsx —— 通用方框-箭头布局(每次按需扩展)每个模板都接受普通 props,都包含 15 帧淡入与 15 帧淡出,都会在 16:9 画面中自我居中,内容最大宽度约 70%,从而干净地落在安全区以内。
精确的配色、排版与动画曲线见 reference/design-tokens.md。要点速览:
bg-white rounded-3xl shadow-xl border border-slate-200text-slate-900(标题)、text-slate-700(正文)#2563eb(blue-600),仅在强调处少量使用interpolate(frame, [0, 15, dur-15, dur], [0, 1, 1, 0]);偶尔的强调用 spring()症状 | 可能原因 | 解决办法 |
|---|---|---|
输出有黑边 | Composition 尺寸 ≠ 源视频尺寸 | 重新探测并更新 Root.tsx |
叠加出现在错误时刻 | 探测帧率与 Composition 配置的帧率不一致 | 确保 fps 一致,且 startFrame = round(seconds * fps) |
渲染非常慢 | 大体积源视频每帧都在被解码 | 放进 public/ 之前,先把源视频按目标帧率预编码为 H.264 yuv420p |
Tailwind 类名不生效 | index.ts 中未导入 index.css | 确认 src/index.ts 里有 import './index.css' |
渲染时找不到 ffmpeg | Remotion 在 Windows 上的捆绑二进制缺失 | 安装 @remotion/compositor-win32-x64-msvc(脚手架中已含) |
EMFILE: too many open files | 长时间渲染触及 Windows 文件句柄上限(约 8000) | 用 --frames=START-END 分块渲染(每块 ≤ 6500),再用 ffmpeg 拼接 |
chrome-headless-shell 下载卡住 | Google Storage 在中国大陆访问缓慢 | 从 cdn.npmmirror.com/binaries/chrome-for-testing/<version>/win64/chrome-headless-shell-win64.zip 下载,解压到 node_modules/.remotion/chrome-headless-shell/win64/,并写入 VERSION 文件 |