用 OpenMontage 从零生成一个中文科普动画视频
本文以《天空为什么是蓝色的?》为例,完整演示了使用 OpenMontage 从零生成中文科普动画视频的全流程。教程覆盖研究、提案、剧本、分镜到渲染发布的八个管线阶段,重点展示了本地 Piper TTS 配音与 Remotion 程序化动画的零成本组合方案,并提供了场景规划、脚本编写及技术参数等实操细节。
实战案例:30 秒中文科普短片《天空为什么是蓝色的?》(为什么天空是蓝色的) 全流程零成本:本地 Piper TTS 配音 + Remotion 程序化动画 + 无任何外部素材。 你将得到一个 1920×1080 / 30fps / 约 30 秒的 MP4 成片。
1. 总体流程
OpenMontage 是**指令驱动(Agent-First)**的视频生产系统:AI 助手(Agent)是导演,负责读管线、做创意决策、调工具;Python 工具只做执行与持久化。所有制作必须走管线,不允许跳过。
idea → research → proposal → script → scene_plan → assets → edit → compose → publish
概念 调研 提案 剧本 分镜 素材 剪辑 合成渲染 发布
本教程对应的管线:animated-explainer(动画解说),入口规则见 AGENT_GUIDE.md。
2. 从下载开始:环境准备
2.1 下载并安装 Python(3.10+)
- 打开 https://www.python.org/downloads/ 下载最新 3.10+ 安装包;
- 安装时务必勾选 Add python.exe to PATH;
- 验证:
python --version能输出版本号。
2.2 创建虚拟环境并安装项目依赖
# 进入项目根目录
cd e:/gitdown/OpenMontage
# 创建虚拟环境
python -m venv .venv
# 安装依赖(含 Piper 封装、ffmpeg 校验等工具)
.venv/bin/python.exe -m pip install -r requirements.txt
2.3 下载并安装 Piper TTS
- 从 piper1-gpl 的 GitHub Releases 页下载对应 Windows 版本(本教程使用 1.6.0);
- 解压后把
piper所在的目录加入 PATH(或把piper.exe放到任一 PATH 目录); - 验证:
piper --version。
2.4 下载中文语音模型
模型文件:zh_CN-huayan-medium.onnx(约 60 MB)。
# 放到 Piper 默认模型目录(不存在就新建)
New-Item -ItemType Directory -Force -Path $HOME/.piper/models
Move-Item zh_CN-huayan-medium.onnx $HOME/.piper/models/
2.5 下载并安装 Node.js + Remotion
- 从 https://nodejs.org/ 下载 LTS 版并安装(会自动加入 PATH);
- 验证:
node --version; - 安装 Remotion 依赖(本仓库
remotion-composer/已预装node_modules,若是全新环境则执行):
cd remotion-composer
npm install
2.6 下载并安装 ffmpeg
winget install Gyan.FFmpeg
# 或从 https://ffmpeg.org/download.html 下载后加入 PATH
ffmpeg -version # 验证
2.7 浏览器(Chrome/Edge)
Windows 一般已自带 Edge;Chrome 可从 https://www.google.com/chrome/ 下载。 Remotion 渲染会用它(详见 §9.2 坑 A)。
Windows 注意:任何 Python 脚本涉及中文文件时,先执行
$env:PYTHONUTF8="1", 否则读写中文内容会乱码或报编码错误。
3. 阶段一:研究(research)
Agent 先做主题调研,产出 research_brief(研究简报)。本案例的核心事实:
- 瑞利散射定律:散射强度 $I \propto 1/\lambda^4$,蓝光(~450nm)散射强度约为红光(~700nm)的 $(700/450)^4 \approx 5.8 \approx 6$ 倍。
- 太阳光是包含所有颜色的白光。
- 你看到的蓝天 = 空气分子把蓝光散射后涌进眼睛的蓝光。
- 数据来源:NASA Space Place、Britannica 等公开资料。
4. 阶段二:提案(proposal)
从研究简报产出 2-3 个差异化概念,让用户选择。本案例的提案:
| 概念 | 标题 | 叙事结构 |
|---|---|---|
| c1(选中) | 一束光的旅程:天空为什么是蓝色的 | journey(旅程式) |
| c2 | 为什么天空不是紫色的? | comparison(对比式) |
| c3 | 天空蓝的数学:蓝光是红光的 6 倍 | data_narrative(数据叙事) |
用户选择 c1 后,锁定关键决策并写入 decision_log.json(决策审计轨):
- 渲染引擎:Remotion(程序化 React 动画,零素材)
- 风格:custom-sky(天空蓝 #38BDF8 + 落日橙 #FB923C,扁平几何风)
- 配音:本地 Piper(免费、离线)
- 规格:30 秒 / 1920×1080 / 30fps
5. 阶段三:剧本(script)
产出 script.json,含 5 段旁白(每段带 provider_text 供 TTS 使用)与表演提示:
| 段 | 时间 | 文案 |
|---|---|---|
| s1 | 0–5s | 你有没有想过,头顶的蓝天,到底是从哪儿来的? |
| s2 | 5–10s | 太阳送来的光,看着是白色,其实藏着所有颜色。 |
| s3 | 10–19s | 光穿过空气时,空气分子会把蓝光弹向四面八方。蓝光的散射强度,是红光的六倍! |
| s4 | 19–25s | 所以,无论你往哪儿看,蓝光都在涌进你的眼睛。这也是为什么,傍晚的太阳会变红。 |
| s5 | 25–30s | 蓝天,是光的一场旅行。关注我,明天继续讲下一个为什么。 |
6. 阶段四:分镜(scene_plan)
产出 scene_plan.json,6 个场景对应 6 个 Remotion 场景类型:
| 场景 | 时间 | 类型 | 内容 |
|---|---|---|---|
| sc-01 | 0–3.8s | hero_title | 大字标题「蓝天,到底从哪儿来?」+ 副题「一束光的旅程」 |
| sc-02 | 3.8–7.8s | spectrum_scene | 太阳 + 棱镜展开七色光谱 + 波长标注(紫400nm/蓝450nm/红700nm) |
| sc-03 | 7.8–12s | scatter_scene | 空气分子把蓝光弹向四面八方,红光直行 |
| sc-04 | 12–16s | stat_card | 大字「≈6倍」+ 公式 (700/450)⁴ ≈ 5.8 |
| sc-05 | 16–23s | sunset_scene | 蓝光涌进眼睛 → 傍晚太阳变红(两阶段动画) |
| sc-06 | 23–30s | text_card | 结尾「蓝天 = 光的旅行」+ 关注 CTA |
自研场景组件:
spectrum_scene/scatter_scene/sunset_scene是仓库原有组件 之外的 3 个新 React 组件,放在remotion-composer/src/components/SkyScenes/, 并在tools/video/video_compose.py的_REMOTION_SCENE_TYPES中登记 (否则渲染器不识别这些类型为 Remotion 内容)。
7. 阶段五:素材(assets)
7.1 生成中文旁白(本地 Piper)
用 tools/audio/piper_tts.py(或直接命令行)。关键坑:中文必须通过 UTF-8 文件喂给 Piper,
直接管道传中文会被终端 locale 编码搞乱码。
# 每段单独生成(便于测时长)
piper -m C:\Users\Administrator\.piper\models\zh_CN-huayan-medium.onnx `
-i s1.txt -f assets/audio/s1.wav
# 拼接全部段落 → narration_full.wav(28.37s,22050Hz,mono)
ffmpeg -y -f concat -safe 0 -i list.txt -c copy assets/audio/narration_full.wav
实测各段时长(用于校准字幕与场景时间轴):
| 段 | 时长 | 区间 |
|---|---|---|
| s1 | 3.66s | 0.0–3.66 |
| s2 | 3.96s | 3.66–7.62 |
| s3 | 8.14s | 7.62–15.76 |
| s4 | 7.19s | 15.76–22.95 |
| s5 | 5.42s | 22.95–28.37 |
7.2 生成逐词字幕(captions.json)
本环境没有 faster_whisper,所以手工按短语分块:把每段文案切成短语,按字数比例
分配该段实测时长。脚本:projects/why-sky-blue/scripts/make_captions.py,
产出 18 个 chunk,覆盖 0–28370ms,供 Remotion 的 CaptionOverlay 逐词高亮使用。
7.3 素材清单(asset_manifest.json)
记录每个素材的来源与校验状态;本案例所有素材 source: "generate"(程序化生成),
零外部下载(Pixabay 等图库在本网络 403 不可用,属于预期降级而非缺陷)。
8. 阶段六:剪辑(edit)
产出 schema 校验通过的 edit_decisions.json:6 个 cut,每个带
in_seconds / out_seconds / transition_in / transition_out / transition_duration,
时间轴与旁白边界对齐,并配置:
audio.narration:旁白音频引用subtitles.enabled: true、style: word-by-word(逐词字幕)metadata:playbook / concept_id / delivery_promise 等
9. 阶段七:合成渲染(compose)—— 核心
9.1 渲染管线怎么选
tools/video/video_compose.py 的 _needs_remotion() 判断是否走 Remotion:
只要有 transition、动画、富场景类型或图片,就返回 True(本项目 6 个 cut 全带 transition)。
9.2 两个关键修复(本案例实际踩过的坑)
坑 A:Remotion 首次渲染挂起——Chrome Headless Shell 下载卡死
首次运行 npx remotion render 会尝试下载 113 MB 的 Headless Shell,慢网络下卡在 38MB 不动。
修复:remotion-composer/remotion.config.ts 强制用系统 Chrome:
import { Config } from "@remotion/cli/config";
Config.setBrowserExecutable(
process.env.REMOTION_BROWSER_EXECUTABLE ||
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
);
video_compose.py 的 npx 调用会自动加载此配置,无需改 Python。
(无 Chrome 的机器可删除此文件,让 Remotion 自动下载。)
坑 B:<Audio> 无法读取 file:// 路径
渲染器报错 Can only download URLs starting with http:// or https://。
Remotion 的音频 src 必须是 staticFile() 路径(public/ 目录内的裸文件名)。
修复:把 narration_full.wav 复制到 remotion-composer/public/,
props 里用裸文件名 "src": "narration_full.wav" 而不是绝对路径。
9.3 渲染脚本
projects/why-sky-blue/scripts/compose_render.py 负责:
build_props():把 edit_decisions 的时间轴 + 富场景 props(type/text/stat/chart 等)- captions + audio + 显式 themeConfig 合并成完整 ExplainerProps;
run_validator():写composition_preflight.json并跑CompositionValidator, 不通过(exit 2)就阻止渲染;- 调
VideoCompose().execute({operation: "render", ...}): 自动执行 Remotion 渲染 + 最终自审(ffprobe 校验容器/码流)。
cd e:/gitdown/OpenMontage
$env:PYTHONUTF8="1"
.venv/bin/python.exe projects/why-sky-blue/scripts/compose_render.py
9.4 先冒烟再全量(重要习惯)
全量 930 帧之前,先用 30 帧冒烟验证管线,秒级出结果:
cd remotion-composer
node node_modules/@remotion/cli/remotion-cli.js render src/index.tsx Explainer `
../projects/why-sky-blue/renders/smoke.mp4 --props=..\projects\why-sky-blue\artifacts\composition_preflight.json --frames=0-29
确认冒烟正常(画面、音频、字幕无误)再跑全量。
9.5 渲染产物验证
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate,channels,bit_rate -of default=noprint_wrappers=1 projects/why-sky-blue/renders/final.mp4
预期输出:
codec_name=h264 # 视频流
width=1920 height=1080 r_frame_rate=30/1
codec_name=aac # 音频流(48kHz 立体声)
抽帧做视觉 QA(6 个场景中点各抽一帧):
ffmpeg -y -v error -ss 1.9 -i projects/why-sky-blue/renders/final.mp4 -frames:v 1 projects/why-sky-blue/review_frames/01_hero.png
# ... 5.8 / 9.9 / 14.0 / 19.5 / 26.5 同理
本案例抽帧验证结果:6 个场景全部可见、字幕覆盖正常、日落晚段帧红色分量 > 蓝色分量(暖色正确)。
9.6 已知非关键告警(可忽略)
Could not validate delivery promise: 'promise_type'—— 校验器期望键名promise_type, 我们用的是type;不影响渲染,可改键名消告警。Subtitles expected but not found...—— 字幕由 Remotion 原生CaptionOverlay渲染, 无需 ASS 烧录,属误报。transcript_comparison skipped—— 未装 faster_whisper,跳过自动转录对比, 用人工比对脚本文案与旁白时长即可。
10. 阶段八:发布(publish)
projects/why-sky-blue/scripts/publish_bundle.py 调用 ExportBundle(离线打包器)生成:
exports/why-sky-blue/
├── video/
│ ├── output.mp4 # 成片
│ └── subtitles.srt # 标准 SRT 字幕(18 条)
├── metadata/
│ ├── metadata.json # SEO 标题/描述/标签/话题/章节
│ ├── description.txt # 可直接粘贴的描述(含章节时间戳 + CTA)
│ ├── tags.txt # 每行一个标签
│ └── chapters.txt # YouTube 章节标记
└── thumbnails/
└── concept.json # 缩略图概念「蓝光×6倍」
SRT 格式坑:导出脚本里的时间格式必须含小时位(
00:00:01,156), 写成00:01,156会不符合标准,播放器可能不认。
发布阶段是人工闸门(checkpoint_publish.json status=awaiting_human),
Agent 展示成果后必须结束回合,等用户审批,不得擅自进入下一步。
11. 一分钟速查表
# 1) 配音(Piper,中文走 UTF-8 文件)
piper -m <zh_CN-huayan-medium.onnx> -i text.txt -f out.wav
# 2) 字幕(无 whisper 时手工分块)
.venv/bin/python.exe projects/why-sky-blue/scripts/make_captions.py
# 3) 校验 + 渲染(含 CompositionValidator 前置检查)
.venv/bin/python.exe projects/why-sky-blue/scripts/compose_render.py
# 4) 冒烟测试(可选,全量前先跑)
node remotion-composer/node_modules/@remotion/cli/remotion-cli.js render `
remotion-composer/src/index.tsx Explainer smoke.mp4 --frames=0-29
# 5) 验证
ffprobe -v error -show_entries stream=codec_name -of default=noprint_wrappers=1 final.mp4
# 6) 发布打包
.venv/bin/python.exe projects/why-sky-blue/scripts/publish_bundle.py
12. 制作同类视频的 Checklist
- 主题是否适合程序化动画?(科普/数据/流程/讲解 → 合适;写实场景 → 考虑生成式管线)
- 剧本每段是否 ≤ 目标时长/段落数?文案是否可直接喂 TTS?
- 用实测旁白时长校准场景时间轴(别用估算值)
- 字幕 chunk 是否覆盖旁白全程?
- 渲染前先跑 CompositionValidator + 30 帧冒烟
- ffprobe 确认 h264 + aac 双流
- 抽帧目检每个场景与字幕
- 发布前确认 SRT 时间格式、导出包完整
附:为什么"不用 AI 生成画面"也能出片?
- 画面由 Remotion(React 代码)逐帧确定性绘制,每个像素都是计算出来的,不是模型生成的。
- 优点:零成本、精确到帧、文字/字幕完美、科学内容可严格按数据、改一行代码即可重渲。
- 生成式视频模型(Sora/Veo 等)适合写实画面,但不可控、按秒计费、文字常出错——本需求恰好相反。
- 本项目实际用了两处 AI:Agent(LLM)做创意决策 + Piper 做语音合成;画面 100% 程序化。