Experiment 5-5: Paper Lecture Video / 实验 5-5:论文讲解视频的自动生成 ★★¶
Companion lab for AI Agents in Depth, Chapter 5 — spoken narration per slide, TTS, ffmpeg page-synced lecture video.
《深入理解 AI Agent》第 5 章:每页口语讲解词 + TTS + ffmpeg 逐页同步合成带旁白的讲解视频。
English¶
Overview¶
On top of “paper → PPT”, the Agent generates spoken lecture scripts per slide (guiding narration, not bullet recitation), calls TTS for audio, then uses ffmpeg to mux each slide PNG with its audio into a narrated video.
Canonical manuscript campaign¶
campaign.py is the formal Experiment 5-5 runner. It consumes twelve real
Slidev screenshots and source from the pinned Experiment 5-4 paper run, calls
Kimi K3 for narration, independently checks every narration against the actual
slide pixels with Qwen-VL-Max, synthesizes every accepted page with Fish Audio
S1, and produces a 5–15 minute H.264/AAC video. It checkpoints every provider
call so an interrupted run resumes without replacing missing pages with
silence or generated placeholders.
The formal gate requires 12 distinct rendered pages, live receipts for all
three providers, per-page A/V drift at most 0.2 seconds, final-duration drift at
most 0.75 seconds, and a 300–900 second final video. experiment_protocol.json
pins the source pages, models, thresholds, and authorized voice manifest.
The completed canonical run is
validation/runs/exp5-5-kimi-fish-qwen-20260730-v1/manifest.json
(SHA-256 93bb69a916a76d12de56270928971f6e39f47755214f7a135817d7effd8b3f09).
All formal gates passed. The H.264/AAC result is 513.010 seconds (8.55
minutes), summed page audio is 512.913 seconds, and the maximum measured page
drift is 0.024 seconds. The rejected real malformed-JSON response for page 12
is retained beside the successful retry instead of being hidden.
The demo.py flow below is retained as a fast teaching/compatibility path. Its
built-in five-page PIL deck and offline silent audio do not satisfy the
formal manuscript campaign.
Legacy quick-demo pipeline¶
Paper bullets (built-in sample)
│ PIL render
▼
Per-page PNG slides ──► gpt-5.6-luna spoken script ──► OpenAI tts-1 → mp3
│ │
└──────────────── ffmpeg: each PNG + that page's audio ──┘
│ (page duration = audio duration)
▼
ffmpeg concat
▼
output/lecture.mp4
- Self-contained; does not depend on experiment 5-4: built-in Attention Is All You Need outline rendered to 5 slide PNGs via PIL (or replace with 5-4 Slidev screenshots).
- Scripts from
gpt-5.6-luna; audio from OpenAItts-1(voice=alloy). - Video via ffmpeg: one mp4 segment per page with duration = that page’s audio, then concat → display time matches speech exactly.
Run¶
# From the repository root: use the shared Chapter 5 environment
uv sync --locked --python 3.12 --extra ch5
# Activate it before changing directories:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
# Windows cmd: .venv\Scripts\activate.bat
# pip fallback when uv is not installed:
# python -m pip install -e ".[ch5]"
cd chapter5/paper-to-video
# Single-project compatibility path, still supported during migration:
# python -m pip install -r requirements.txt
cp env.example .env # OPENAI_API_KEY (or OPENROUTER_API_KEY for script fallback; TTS degrades offline)
python demo.py # full 5-page lecture video
Common flags (python demo.py --help for all):
python demo.py --check # env check: ffmpeg/ffprobe/fonts/config; no API
python demo.py --quick # smoke: page 1 only (same as --limit 1)
python demo.py --limit 2 # first 2 pages only
python demo.py --offline # no API: silent placeholder audio; validates ffmpeg pipeline
Full flags:
| Flag | Description |
|---|---|
--slides FILE |
Slide content JSON ([{title, subtitle, bullets}, ...]); replaces built-in sample |
--script FILE |
Ready narration JSON (list of strings, one per page); skips LLM script gen |
-o, --output FILE |
Final video path (default output/lecture.mp4) |
--tts-provider {openai,offline} |
TTS provider; offline = ffmpeg silent placeholder (no API) |
--offline |
Fully offline: same as --tts-provider offline + bullet placeholder scripts (zero API) |
--text-model / --tts-model / --tts-voice |
Override model/voice (defaults from same-named env vars) |
--limit N / --quick / --check |
First N pages / page 1 only / check only |
Offline validation:
--offlineneeds no key or network; usesffmpeg anullsrcwith duration estimated from script length—runs “render → estimate duration → per-page mux → concat” to verify per-page time alignment (silent placeholders, not real voice).
Artifacts:
output/slides/slide_*.png— slidesoutput/audio/audio_*.mp3— per-page audiooutput/segments/seg_*.mp4— per-page segmentsoutput/narration.json— scripts + audio durationsoutput/lecture.mp4— final video
Probe metadata:
Sample outputs¶
Built-in 5-page Attention Is All You Need full run (real artifacts):
output/lecture.mp4: ~2.8 MB, 166.97s (≈2m 47s), 1280×720, H.264 + AAC.output/audio/audio_01.mp3 … audio_05.mp3: ~28.6s / 33.3s / 37.2s / 37.9s / 29.9s (sum ≈166.9s = video duration).output/narration.json: e.g. page 1 spoken intro about “Attention Is All You Need” and attention without recurrence/convolutions.
Logs print per-page “slide → script → audio duration”; end summary compares total audio duration to final video (should match closely).
Dependencies¶
- ffmpeg / ffprobe (validated on 8.x). macOS:
brew install ffmpeg. - Python: root
ch5extra (openai,Pillow,python-dotenv) or the compatibilityrequirements.txtpath. - CJK fonts: script falls back over common macOS fonts (PingFang / STHeiti / Hiragino / Arial Unicode).
- Env:
OPENAI_API_KEYfor official OpenAI; without it,OPENROUTER_API_KEYcan fall back for scripts (TTS not on OpenRouter → offline silent). Seeenv.example.
Adapt / extend¶
- Model / provider (env or CLI; no code change):
TEXT_MODEL/--text-model(defaultgpt-5.6-luna).TTS_MODEL/TTS_VOICE(or--tts-model/--tts-voice; defaulttts-1/alloy; voices e.g.nova/shimmer/echo).--tts-provider offlinefor local validation.OPENAI_BASE_URL+ matchingOPENAI_API_KEYfor compatible endpoints.- Input:
--slides my.jsonor editSLIDESindemo.py; with real PDF, use 5-4 “paper → PPT” then feed bullets/screenshots here. - Own scripts:
--script narr.json(string list) skips LLM and goes TTS → mux. - Longer video: more
SLIDESpages or longer scripts (often 5–15 min). - Fast tuning:
--quick/--limit Nbefore full runs.
Limitations¶
- Script + TTS call real OpenAI APIs (
TEXT_MODEL,TTS_MODEL) and bill; full 5 pages ≈ 2–3 min video. Prefer--checkthen--quick. - Slides are static PIL (no animation/transitions); non-macOS may need
FONT_CANDIDATEStweaks. - Page duration = audio only; no silence pads or BGM. For richer layout/transitions, prefer 5-4 Slidev screenshots as input.
中文¶
概述¶
在“论文 → PPT”的基础上,Agent 为每一页幻灯片生成口语化讲解词(引导性叙述, 而非逐条复述要点),调用 TTS 合成语音,再用 ffmpeg 把 PPT 截图与音频 逐页同步合成为一段带旁白的讲解视频。
正式实验活动¶
campaign.py 是实验 5-5 的正式运行器:它读取实验 5-4 固定真实论文活动中的 12 张
Slidev 截图与源码,用 Kimi K3 生成讲解词,再让 Qwen-VL-Max 对照真实页面像素逐页独立
审核;通过后调用 Fish Audio S1 合成每页语音,最终用 ffmpeg 生成 5–15 分钟的 H.264/AAC
视频。所有供应商调用都可续跑缓存;中断后不会用静音或占位内容冒充缺失页面。
正式门禁要求:12 张互不相同的真实渲染页、三个真实供应商的完整收据、逐页音画误差不超过
0.2 秒、总时长误差不超过 0.75 秒、最终视频时长 300–900 秒。固定来源页、模型、阈值与
授权音色清单记录在 experiment_protocol.json。
已完成的正式证据是
validation/runs/exp5-5-kimi-fish-qwen-20260730-v1/manifest.json
(SHA-256 93bb69a916a76d12de56270928971f6e39f47755214f7a135817d7effd8b3f09)。
所有门禁均通过;H.264/AAC 成片长 513.010 秒(8.55 分钟),逐页音频合计
512.913 秒,最大逐页漂移 0.024 秒。第 12 页真实供应商返回的非法 JSON
作为失败尝试与成功重试一并保留,没有被隐藏。
下述 demo.py 流程保留为快速教学/兼容入口。其内置 5 页 PIL 幻灯片和离线静音模式
不满足正式实验门禁。
旧版快速演示流程¶
论文要点(内置示例)
│ PIL 渲染
▼
每页 PNG 幻灯片 ──► gpt-5.6-luna 生成口语化讲解词 ──► OpenAI tts-1 合成 mp3
│ │
└──────────────── ffmpeg:每页 PNG + 该页音频 ───────────┘
│ (每页时长 = 该页音频时长)
▼
ffmpeg concat 拼接
▼
output/lecture.mp4
- 本项目自包含,不依赖实验 5-4:内置一份《Attention Is All You Need》的论文要点, 用 PIL 直接渲染出 5 页幻灯片 PNG(也可替换为 5-4 的 Slidev 截图)。
- 讲解词由
gpt-5.6-luna生成;语音由 OpenAItts-1(voice=alloy)合成。 - 视频由
ffmpeg合成:每页做一段 mp4,段时长等于该页音频时长,最后 concat 拼接, 因此每页展示时间与语音时长精确匹配。
运行命令¶
# 在仓库根目录使用统一的第 5 章环境
uv sync --locked --python 3.12 --extra ch5
# 切换目录前先激活环境:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell:.\.venv\Scripts\Activate.ps1
# Windows cmd:.venv\Scripts\activate.bat
# 未安装 uv 时可用 pip 兜底:
# python -m pip install -e ".[ch5]"
cd chapter5/paper-to-video
# 迁移期间仍支持单项目兼容路径:
# python -m pip install -r requirements.txt
cp env.example .env # 填入 OPENAI_API_KEY(未配置时设 OPENROUTER_API_KEY 兜底讲解词,TTS 降级为离线占位)
python demo.py # 生成全部 5 页的完整讲解视频
常用参数(python demo.py --help 查看全部):
python demo.py --check # 环境自检:检查 ffmpeg/ffprobe/字体/配置,不调用任何 API
python demo.py --quick # 快速冒烟:只跑第 1 页(等价 --limit 1),省时省钱
python demo.py --limit 2 # 只处理前 2 页
python demo.py --offline # 无需 API:占位静音音轨,验证整条 ffmpeg 合成流水线
完整参数:
| 参数 | 说明 |
|---|---|
--slides FILE |
幻灯片内容 JSON([{title, subtitle, bullets}, ...]),替换内置示例 |
--script FILE |
现成讲解词 JSON(字符串列表,每页一段),提供后跳过 LLM 生成 |
-o, --output FILE |
最终视频输出路径(默认 output/lecture.mp4) |
--tts-provider {openai,offline} |
TTS 供应商;offline 用 ffmpeg 生成占位静音音轨(无需 API) |
--offline |
完全离线:等价 --tts-provider offline,并用要点占位讲解词(零 API 调用) |
--text-model / --tts-model / --tts-voice |
覆盖模型/音色(默认取同名环境变量) |
--limit N / --quick / --check |
只跑前 N 页 / 只跑第 1 页 / 仅自检 |
离线验证:
--offline不需要任何 API Key 或网络,用ffmpeg anullsrc按讲解词字数 估算时长合成静音占位音轨,跑通「渲染 → 估时 → 逐页合成 → concat 拼接」全链路, 专门用于验证 ffmpeg 的逐页时长对齐是否正确(音轨为静音占位,非真实配音)。
产物:
- output/slides/slide_*.png 每页幻灯片
- output/audio/audio_*.mp3 每页讲解音频
- output/segments/seg_*.mp4 每页分段视频
- output/narration.json 每页讲解词与音频时长清单
- output/lecture.mp4 最终讲解视频
查看视频元信息:
预期输出示例¶
以内置的 5 页《Attention Is All You Need》为例,一次完整运行的真实产物:
output/lecture.mp4:约 2.8 MB,时长 166.97s(≈2 分 47 秒), 分辨率 1280×720,视频 H.264 + 音频 AAC。output/audio/audio_01.mp3 … audio_05.mp3:每页一段旁白, 单页时长约 28.6s / 33.3s / 37.2s / 37.9s / 29.9s(总计 ≈166.9s,与视频时长一致)。output/narration.json:每页的口语化讲解词与音频时长清单,例如第 1 页:
今天,我们将一起探讨一个改变了自然语言处理领域的重要研究——"Attention Is All You Need"…… 它完全依赖于注意力机制,摒弃了传统的循环和卷积结构。
运行日志会逐页打印「幻灯片 → 讲解词 → 音频时长」,末尾汇总各页音频总时长与最终视频时长 (二者应基本一致,说明每页展示时间与语音精确对齐)。
依赖¶
- ffmpeg / ffprobe:命令行工具(本项目用 8.x 验证)。macOS 可
brew install ffmpeg。 - Python 包:根目录
ch5extra(openai、Pillow、python-dotenv),或兼容requirements.txt路径。 - 中文字体:渲染幻灯片需系统中文字体,脚本已按 macOS 常见字体 (PingFang / STHeiti / Hiragino / Arial Unicode)自动回退。
- 环境变量:需
OPENAI_API_KEY(走官方 OpenAI);未配置时可用OPENROUTER_API_KEY兜底讲解词生成(此时 TTS 因不在 OpenRouter 上而降级为离线静音占位)。可选项见env.example。
如何适配 / 扩展¶
- 换模型 / 换供应商:环境变量或命令行均可,无需改代码:
TEXT_MODEL/--text-model:讲解词生成模型(默认gpt-5.6-luna,可换其它)。TTS_MODEL/TTS_VOICE(或--tts-model/--tts-voice):语音模型与音色 (默认tts-1/alloy,音色可选nova/shimmer/echo等)。--tts-provider offline:切到离线占位音轨(不产生任何 API 调用),用于本地验证。OPENAI_BASE_URL:指向任何兼容 OpenAI 协议的自定义端点(自建网关、代理或 第三方供应商);配合对应的OPENAI_API_KEY即可切换后端。- 换输入(换论文 / PDF):用
--slides my.json传入外部幻灯片内容,或直接编辑demo.py中的SLIDES列表(标题 / 副标题 / 要点);若已有真实 PDF,可先用 5-4 的 「论文 → PPT」流程产出要点或 Slidev 截图,再喂给本脚本,其余流程不变。 - 自带讲解词:用
--script narr.json(每页一段的字符串列表)跳过 LLM 生成, 直接进入「TTS → 合成」,便于人工润色脚本后重跑。 - 更长视频:增加
SLIDES页数或加长每页讲解词即可(单次 5~15 分钟)。 - 快速调参:先用
--quick/--limit N只渲染少量页,确认音色/风格满意后再跑全量。
局限¶
- 讲解词与 TTS 都会产生真实的 OpenAI API 调用(
TEXT_MODEL与TTS_MODEL),会计费; 全量 5 页约生成 2~3 分钟视频。建议先--check自检、再--quick冒烟。 - 幻灯片为 PIL 纯静态渲染(无动画/转场),中文字体依赖系统字体,非 macOS 需自行调整
FONT_CANDIDATES。 - 每页时长严格等于该页音频时长,不做静音停顿或背景音乐;如需更精细的排版与转场, 建议改用 5-4 的 Slidev 截图作为输入。
Notes / 说明¶
- Use
--check/--offlinebefore spending API budget. / 花钱前先--check/--offline。 - Commands/code/paths/env vars are identical in both language sections. / 命令、代码、路径与环境变量在中英文两侧保持一致。