从一篇文章到 15 分钟高质量纪录片,我用 Claude Code + Remotion 完成了一次完整的视频制作——全程无需剪辑软件,纯代码驱动。本文记录这套工作流的每一个细节,以及我踩过的坑。

图:纯代码驱动的视频制作——开发者的新「剪辑台」
这次做了什么
马斯克在 2026 年 2 月完成了一次史无前例的资本重组——SpaceX 全资收购 xAI,合并实体估值冲至 1.25 万亿美元。我把一篇深度分析文章,用 remotion-video skill 制作成了一个 12 场景、15 分钟的科普纪录片。
最终视频:12 个场景、独立配音、动态字幕、Ken Burns 图片动效、数据可视化动画,渲染输出 162.6MB 的 MP4。
视频链接:https://weixin.qq.com/sph/AlO70HMPx
什么是 remotion-video skill
remotion-video skill 是一套跑在 Claude Code 上的 AI 视频制作工作流。核心理念是:
- 音频优先:先用 MiniMax TTS 生成配音,视频时长由音频驱动
- 代码即视频:每个场景是一个 React 组件,动画全部可编程
- 一键渲染:
npx remotion render输出 MP4,全程不碰剪辑软件
触发方式是在 Claude Code 中直接说"帮我把这篇文章做成视频",或者输入 /remotion-video。

图:remotion-video skill 的 7 步完整工作流,文章进去,MP4 出来
完整工作流:7 步从文章到视频
Step 0:环境准备
在开始前需要两个东西:
# 1. MiniMax API Key(生成配音)
export MINIMAX_API_KEY="your-api-key"
export MINIMAX_VOICE_ID="your-clone-voice-id" # 你的克隆声音 ID
# 2. ffprobe(检测音频时长)
brew install ffmpeg
克隆声音是关键。去 minimax.io 上传一段 20 秒左右的清晰音频,等待 1-3 分钟就能得到专属的 voice_id。克隆声音比预设音色自然很多,非常值得这一步。
创建项目目录:
mkdir -p ~/remotion-projects/my-video/{src/scenes,public/audio,public/images,scripts,out}
cd ~/remotion-projects/my-video
npx create-video@latest # 初始化 Remotion 项目
Step 1:脚本规划
告诉 Claude 你的文章和期望时长,它会帮你把文章拆解成场景化脚本。
这次 15 分钟视频被拆成了 12 个场景,每个场景对应一个叙事节点:
| 场景 | 主题 | 时长 |
|---|---|---|
| 01 | 开场钩子:水槽与1.25万亿 | ~70秒 |
| 02 | 往前推5天:特斯拉停产高端车 | ~65秒 |
| 03 | 算力撞上物理天花板 | ~90秒 |
| 04 | 热力学的诅咒:散热悖论 | ~75秒 |
| 05 | 太空数据中心假说 | ~80秒 |
| 06 | 马斯克为什么有资格做这件事 | ~85秒 |
| 07 | 三块拼图:Starlink / Optimus / xAI | ~90秒 |
| 08 | 三层楼的帝国架构 | ~80秒 |
| 09 | 货币失效:成本黑洞 | ~75秒 |
| 10 | 指数加速飞轮 | ~80秒 |
| 11 | 四面悬崖:清醒时刻 | ~85秒 |
| 12 | 尾声:燃烧时代的终结 | ~90秒 |
脚本原则:
- 每场景配音不超过 320 字(语速约 213 字/分钟)
- 每句不超过 25 字,适合 TTS 断句
- 旁白与画面描述分开写
Step 2:音频生成
用 scripts/generate_audio_minimax.py 批量生成 12 个音频文件:
# scenes.json 格式
{
"scenes": [
{
"id": "scene_01",
"narration": "三年前,当马斯克抱着那个水槽走进推特总部时..."
},
...
]
}
python3 scripts/generate_audio_minimax.py
# 输出:public/audio/scene_01.mp3, scene_02.mp3 ... scene_12.mp3
脚本支持断点续作——已生成的文件自动跳过,中途失败不会从头再来。
Step 3:时长检测,生成 audioConfig.ts
音频生成后,用 detect_durations.py 自动检测每个文件的精确时长,生成时间配置:
python3 scripts/detect_durations.py
输出 src/audioConfig.ts:
export const audioDurations: Record<string, number> = {
"scene_01": 72.4,
"scene_02": 67.8,
"scene_03": 91.2,
// ...
};
export const FPS = 30;
// 自动计算每个场景的起始帧
export const getSceneStartFrame = (sceneIndex: number): number => {
const keys = Object.keys(audioDurations);
return keys.slice(0, sceneIndex)
.reduce((sum, key) => sum + Math.ceil(audioDurations[key] * FPS), 0);
};
这是音画同步的核心:视频时长完全由真实音频驱动,不会出现声音提前结束或画面空转的问题。

图:音频驱动架构——从 MP3 文件到每帧时间戳,一条完整的依赖链
Step 4:React 场景组件
每个场景是一个独立的 React 组件。以数据可视化场景为例:
// Scene03.tsx:算力撞上物理天花板
export const Scene03: React.FC<{ durationInFrames: number }> = ({ durationInFrames }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 数字增长动画:10万 → 55万 GPU
const gpuCount = interpolate(
frame,
[30, 90],
[100000, 550000],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
// Spring 入场动画
const cardProgress = spring({ frame: frame - 20, fps, config: { damping: 18 } });
return (
<AbsoluteFill style={{ background: COLORS.gradientBg }}>
<Audio src={staticFile("audio/scene_03.mp3")} />
{/* 背景图片(氛围背景模式,opacity 0.55)*/}
<BackgroundPhoto src={staticFile("images/img_energy.jpg")} opacity={0.55} />
<AbsoluteFill style={{ background: "rgba(5,5,16,0.40)" }} />
{/* GPU 数量动态展示 */}
<div style={{ fontSize: 120, color: COLORS.gold, opacity: Math.max(0, cardProgress) }}>
{Math.floor(gpuCount).toLocaleString()}
<span style={{ fontSize: 36 }}>块 GPU</span>
</div>
<Subtitles narration={NARRATION} totalFrames={durationInFrames} />
</AbsoluteFill>
);
};
Remotion 的核心是 useCurrentFrame():返回当前渲染帧(0、1、2…),所有动画都基于它计算。帧号 × 时间 = 动画进度,纯粹的函数式思维。
Step 5:图片的正确使用方式(重要!踩坑记录)
这是本次制作最大的教训。图片不只是背景,它是视觉叙事的重要组成部分。

图:左侧三重压制导致图片消失(104MB),右侧正确用法图片清晰呈现(162MB)
踩坑:三重压制 = 图片消失
初版代码是这样写的:
// ❌ 错误:三重叠加导致图片几乎不可见
<BackgroundPhoto src={src} opacity={0.25} blur={3} />
<AbsoluteFill style={{ background: "rgba(5,5,16,0.72)" }} />
低透明度(0.25) + 深色遮罩(0.72)+ 模糊(3px),三重叠加后图片几乎完全消失,视频文件从应有的 162MB 萎缩到 104MB——机器用文件大小告诉你图片没了。
还有一个更隐蔽的 bug:
// ❌ 错误:zIndex: -1 导致图片被父元素背景色遮挡
<AbsoluteFill style={{ zIndex: -1 }}>
<Img src={src} />
</AbsoluteFill>
zIndex: -1 会把元素推到父元素的背景绘制层之后,渲染结果中图片直接消失。正确做法是利用 DOM 顺序,把 BackgroundPhoto 放在其他子元素之前即可。
正确用法一:氛围背景(opacity 0.55)
// ✅ 普通叙述场景:图片清晰可见,不遮挡文字
<BackgroundPhoto
src={staticFile("images/scene.jpg")}
opacity={0.55} // 关键:0.55 而不是 0.25
kenBurns // Ken Burns 缩放平移效果
blur={0} // 不加模糊
/>
{/* 轻量遮罩,不超过 0.42 */}
<AbsoluteFill style={{ background: "rgba(5,5,16,0.40)" }} />
正确用法二:分屏面板(视觉主角)
关键叙事场景(开篇、高潮、转折),图片占画面 40-45%,以 90% 不透明度呈现:
// SidePanelImage.tsx:图片作为视觉主角
export const SidePanelImage: React.FC<{
src: string;
side: "left" | "right";
widthPercent?: number; // 默认 42
opacity?: number; // 默认 0.9
}> = ({ src, side, widthPercent = 42, opacity = 0.9 }) => {
const frame = useCurrentFrame();
// Ken Burns:微妙的缩放 + 平移
const scale = interpolate(frame, [0, 600], [1.05, 1.18], {
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
extrapolateRight: "clamp",
});
return (
<div style={{
position: "absolute",
[side]: 0, top: 0,
width: `${widthPercent}%`, height: "100%",
overflow: "hidden",
}}>
<Img src={src} style={{
width: "100%", height: "100%",
objectFit: "cover", opacity,
transform: `scale(${scale})`,
}} />
{/* 边缘渐变,与内容区自然融合 */}
<div style={{
position: "absolute", top: 0,
[side === "left" ? "right" : "left"]: 0,
width: "50%", height: "100%",
background: `linear-gradient(to ${side === "left" ? "right" : "left"},
rgba(5,5,16,0) 0%, rgba(5,5,16,0.95) 100%)`,
}} />
</div>
);
};
使用方式:
// Scene01:开篇场景,英雄图左侧 + 标题右侧
<AbsoluteFill style={{ background: "#05050F" }}>
{/* 内容区(右侧 58%)*/}
<AbsoluteFill style={{ paddingLeft: "45%" }}>
<h1>硅基帝国</h1>
<p>$1.25 万亿</p>
</AbsoluteFill>
{/* 图片面板(左侧 42%,在 DOM 顺序最后,层叠在内容上方)*/}
<SidePanelImage
src={staticFile("images/img_hero.jpg")}
side="left"
widthPercent={42}
opacity={0.9}
/>
</AbsoluteFill>
效果对比:图片 opacity 0.25 → 0.55(背景模式),视频从 104MB → 162MB,清晰可见。
图片使用决策树
场景照片
├─ 关键叙事场景(开篇/高潮/转折)
│ └─ 分屏面板:SidePanelImage,40-45%屏宽,opacity 0.9
└─ 普通叙述场景(氛围营造)
└─ 背景层:BackgroundPhoto,全屏,opacity 0.55,遮罩 ≤ 0.42
Step 6:字幕系统
字幕是提升信息留存率的关键(研究数据:良好字幕设计可提升 40%)。
这个项目用了一个基于旁白文本自动切分的字幕组件,无需手写时间轴:
// Subtitles.tsx:按标点断句,自动分配时间
export const Subtitles: React.FC<{
narration: string;
totalFrames: number;
}> = ({ narration, totalFrames }) => {
const frame = useCurrentFrame();
// 按标点切分成字幕段
const segments = splitIntoSegments(narration);
// 均匀分配时间
const currentSegment = segments.find((seg, i) => {
const start = (i / segments.length) * totalFrames;
const end = ((i + 1) / segments.length) * totalFrames;
return frame >= start && frame < end;
});
return currentSegment ? (
<div style={{
position: "absolute", bottom: 80,
left: 60, right: 60,
textAlign: "center",
}}>
<div style={{
display: "inline-block",
background: "rgba(0,0,0,0.20)",
backdropFilter: "blur(6px)",
padding: "12px 28px", borderRadius: 8,
}}>
<span style={{
fontSize: 28, color: "#fff",
textShadow: "0 2px 10px rgba(0,0,0,1)",
}}>
{currentSegment.text}
</span>
</div>
</div>
) : null;
};
字幕背景透明度设为 0.20(而非常见的 0.7),既保证可读性,又不遮挡视频内容。
Step 7:渲染输出
# 启动预览(热重载,实时看效果)
npm run dev
# 渲染最终视频(前台执行,可看进度)
npx remotion render MuskSiliconEmpire out/video.mp4
渲染时间约 8-12 分钟(M1 MacBook Pro),进度实时显示。
注意:如果场景有 3D 内容(@remotion/three),需要加 --gl=angle 参数:
npx remotion render MyVideo out/video.mp4 --gl=angle
项目完整目录结构
musk-silicon-empire/
├── src/
│ ├── Root.tsx # 注册所有场景,计算总时长
│ ├── audioConfig.ts # 音频时长配置(自动生成)
│ ├── colors.ts # 全局配色常量
│ ├── components/
│ │ ├── BackgroundPhoto.tsx # 背景图片(Ken Burns + 色彩分级)
│ │ ├── SidePanelImage.tsx # 分屏图片面板(视觉主角模式)
│ │ ├── Particles.tsx # 粒子背景
│ │ ├── TypeWriter.tsx # 打字机 + 淡入文字
│ │ └── Subtitles.tsx # 自动字幕
│ └── scenes/
│ ├── Scene01.tsx ~ Scene12.tsx
├── public/
│ ├── audio/ # TTS 生成的 12 个 MP3
│ └── images/ # 6 张场景配图
├── scripts/
│ ├── generate_audio_minimax.py
│ ├── detect_durations.py
│ └── generate_subtitles.py
├── scenes.json # 场景数据(旁白文本)
└── out/video.mp4 # 最终输出(162.6MB,15分钟)
核心经验总结

图:制作过程中提炼的五颗「经验宝石」
1. 音频驱动,时长自动同步
手动填写 durationInFrames: 300 这种写法迟早出问题。正确姿势:用 ffprobe 检测真实音频时长,全部存入 audioConfig.ts,代码自动计算帧数。
2. 图片是叙事工具,不是背景装饰
最重要的教训:图片 opacity 0.25 + 遮罩 0.72 + blur,三重叠加 = 图片消失,视觉叙事崩塌。
- 普通场景:opacity 0.55,遮罩 0.40,无模糊
- 关键场景:用
SidePanelImage占 40-45% 屏宽,opacity 0.9
3. 场景组件独立自治
每个 Scene 组件内的 useCurrentFrame() 从 0 开始计数(<Sequence> 隔离),内部动画不需要关心全局帧号。
4. 渲染前台执行,看得到进度
不要后台渲染 npm run render &,8-12 分钟的渲染看不到进度会很焦虑。前台执行,Remotion 会实时显示百分比。
5. 先预览再渲染
npm run dev 打开 Remotion Studio,可以拖拽时间轴逐帧检查,发现问题及时改,避免浪费渲染时间。
工具链一览
| 工具 | 用途 |
|---|---|
| Claude Code + remotion-video skill | 整体工作流编排 |
| Remotion | React 视频框架,useCurrentFrame() 驱动动画 |
| MiniMax TTS | 克隆声音配音 |
| Pexels API | 高质量场景配图 |
| 豆包 Seedream | AI 生成概念配图 |
| ffprobe | 检测音频时长 |
| Python 脚本 | 批量生成音频、检测时长、生成字幕 |
获取 remotion-video skill
这套 skill 已经打包好,包含:
SKILL.md:完整工作流文档(5600+ 行)scripts/:音频生成、时长检测、字幕生成脚本templates/:audioConfig 模板
将 remotion-video/ 文件夹放到 ~/.claude/skills/ 下,在 Claude Code 中即可调用 /remotion-video 开始你的第一个 AI 视频项目。
本文记录于 2026 年 2 月,制作工具:Claude Code + Remotion 4.x + MiniMax TTS