Manim 1080p60 技术视频讲解与同步画外音系统
概述与捆绑资源
使用纯 Python 和 Manim Community(无需 pdflatex 或 MacTeX)创作清晰、确定性的 1920x1080 @ 60fps 技术讲解视频和架构演示。可选配采样级精确的神经画外音(离线 Piper ONNX、任何兼容 OpenAI 的 /v1/audio/speech HTTP 端点或自定义 CLI TTS)、EBU R128 响度归一化、同步的 .vtt WebVTT 字幕以及零 CDN 的离线 HTML5 章节播放器。
assets/nord_manim_theme.py:Nord Polar/Frost/Aurora 调色板令牌、安全框布局常量、BeatTimer同步辅助工具、无 LaTeX 原语(make_header_banner、make_node_card、make_badge、animate_blocked_pulse、safe_fit_text)以及严格的端口锚定正交示意图连接器(connect_h、connect_v、connect_elbow、animate_packet_along、assert_all_orthogonal)。assets/video_player_template.html:独立的、零 CDN 的 Nord 深色/浅色 HTML5 影院播放器,具有章节选项卡、可点击的节拍标记、播放速率(0.75x..2.0x)以及可选的切换音频/字幕功能。check_scene_bounds.py:预渲染布局验证器,检查 Manim 场景或 JSON 检查点是否存在[-6.8, 6.8] x [-3.75, 3.75]帧溢出以及同级文本标签碰撞。mux_narration.py:基于 Stdlib Python 的 CLI,通过 Piper、兼容 OpenAI 的 HTTP/v1/audio/speech端点或自定义 TTS 命令合成逐节拍 WAV,输出beat_timings.json,组装采样级精确的主 WAV 轨道,克隆填充尾随视频帧(tpad=stop_mode=clone)以防止语音被截断,可选地归一化响度(--loudnorm-lufs -18.0),合成 H.264 + AAC(+faststart)并写入.vtt字幕。lint.py:输出包验证器,检查ffprobeH.264yuv420p+ 音频流、WebVTT 头部/提示单调性以及离线 HTML5 播放器 CSP/零 CDN/零表情符号不变性。references/manim-layout-and-choreography.md:在设计多节点拓扑、正交总线/弯头路由、动态计数器、双向箭头或多场景ffmpeg拼接时阅读。references/voiceover-and-commentary.md:在创作旁白清单、自然叙述者韵律指令、音标发音词汇表、EBU R128 归一化或双语画外音轨道时阅读。
1. 故事板与多幕叙事设计
在编写 Manim 代码之前,将讲解内容结构化为离散的幕和逐节拍的故事板表格。每个“幕”保留一个 Scene 类,这样对第 2 幕的编辑就永远不需要重新渲染第 1 幕或第 3 幕:
- 第 1 幕 —— 设置与请求准入:介绍拓扑结构、不变量和入口守卫(例如,
客户端 -> API 网关 -> 令牌桶速率限制器)。 - 第 2 幕 —— 稳态执行:跟踪主要数据路径和状态转换(例如,
读穿透缓存单次飞行查找 ->主数据库持久化 -> TTL 写回)。 - 第 3 幕 —— 故障模式与恢复:演示当限制触发或节点失败时会发生什么(例如,
HTTP 429突发拒绝或分布式锁与预写日志复制法定人数提升)。
在编码前明确映射每一个节拍:
beat_id | visual_action | min_visual_sec | commentary_script (如果配有旁白) |
|---|---|---|---|
act1_ingress | 渲染 4 张 Nord 拓扑卡片 + 标题;将请求包动画化为进入网关 | 3.2 | 入站写入在接触有状态存储之前要经过边缘网关。 |
act1_bucket | 将令牌计数器从 100 递减到 99;闪烁绿色 ADMITTED 徽章 | 3.8 | 原子令牌桶检查在吸收流量突发的同时限制了下游并发。 |
act1_reject | 当令牌达到 0 时触发 animate_blocked_pulse(429 RATE LIMITED) | 3.5 | 一旦突发容量耗尽,多余的调用者将使用 HTTP 429 快速失败,而不是无限排队。 |
2. 无 LaTeX 的 Manim Community 创作
隔离环境设置
在隔离的虚拟环境中配置 Manim(和可选的 piper-tts);确保 ffmpeg 和 ffprobe 在 PATH 中:
uv venv venv
uv pip install --python venv/bin/python manim piper-tts
严格的无 LaTeX 与正交示意图规则
- 零 TeX 子进程:永远不要使用
Tex、MathTex、Title或DecimalNumber(它们会调用pdflatex/dvisvgm)。请使用Text、MarkupText(<b>、<i>、<sub>、<sup>、<span foreground="...">)、Code、RoundedRectangle、Arrow、DashedVMobject和MoveAlongPath。 - 黄金示意图规则(端口锚定正交线):在构建架构框图时,永远不要直接在两个
Mobject上调用Arrow(card_a, card_b)——Manim 会在边界框中心之间绘制射线,并且每当卡片宽度或 Y 位置不同时,就会产生歪斜的对角线。请通过connect_h(card_from, card_to, y=...)、connect_v(card_from, card_to, x=...)或connect_elbow(points)路由每条边,使用animate_packet_along(self, edge, ...)为数据包添加动画,并使用assert_all_orthogonal(connectors, cards)验证连线几何形状。 - 安全框坐标网格:Manim 的 16:9 画布覆盖
[-7.11, 7.11] x [-4.0, 4.0]。将所有创作内容保持在[-6.6, 6.6] x [-3.5, 3.5]内部,并且在显示 WebVTT 字幕时,保持y < -3.05区域为空。 - 导入
assets/nord_manim_theme.py:设置self.camera.background_color = BG_DARK,并一致地构建卡片、正交连接器、数据包动画和节拍保持:
from pathlib import Path
import sys
SKILL_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SKILL_DIR))
sys.path.insert(0, str(SKILL_DIR / "assets"))
from manim import Create, FadeIn, Scene, VGroup
from check_scene_bounds import dump_checkpoints
from nord_manim_theme import (
ACCENT_BLUE, ACCENT_CYAN, BG_DARK, STATUS_ERR, STATUS_OK,
BeatTimer, animate_blocked_pulse, animate_packet_along,
assert_all_orthogonal, connect_h, make_header_banner, make_node_card,
)
class Act1RateLimiter(Scene):
def construct(self) -> None:
self.camera.background_color = BG_DARK
timer = BeatTimer("tmp/beat_timings.json")
checkpoints: list[dict] = []
header = make_header_banner(
"Act 1 / Request Admission",
"API Gateway & Token-Bucket Rate Limiter",
)
gw = make_node_card("API Gateway", "TLS + Route Match", width=2.5, accent=ACCENT_CYAN).move_to([-4.5, 0.2, 0])
limiter = make_node_card("Rate Limiter", "Bucket: 100 tok", width=2.5, accent=ACCENT_BLUE).move_to([-1.5, 0.2, 0])
cache = make_node_card("Read Cache", "Single-Flight TTL", width=2.5, accent=ACCENT_CYAN).move_to([1.5, 0.2, 0])
db = make_node_card("Primary DB", "WAL Quorum", width=2.5, accent=STATUS_OK).move_to([4.5, 0.2, 0])
e_gw_lim = connect_h(gw, limiter)
e_lim_cache = connect_h(limiter, cache)
e_cache_db = connect_h(cache, db, dashed=True)
assert_all_orthogonal([e_gw_lim, e_lim_cache, e_cache_db], [gw, limiter, cache, db])
edges = VGroup(e_gw_lim, e_lim_cache, e_cache_db)
self.play(FadeIn(header), FadeIn(gw), FadeIn(limiter), FadeIn(cache), FadeIn(db), Create(edges), run_time=1.1)
spent = animate_packet_along(self, e_gw_lim, label="POST /orders", run_time=0.7)
timer.hold(self, "act1_ingress", consumed_anim_s=1.1 + spent, fallback_s=3.2, checkpoints=checkpoints)
spent = animate_packet_along(self, e_lim_cache, label="token=99", color=STATUS_OK, run_time=0.7)
timer.hold(self, "act1_bucket", consumed_anim_s=spent, fallback_s=3.8, checkpoints=checkpoints)
spent = animate_blocked_pulse(self, limiter, reason_label="429 RATE LIMITED", color=STATUS_ERR, run_time=0.95)
timer.hold(self, "act1_reject", consumed_anim_s=spent, fallback_s=3.5, checkpoints=checkpoints)
dump_checkpoints(checkpoints, "tmp/act1_bounds.json")
在 1080p60 渲染前验证边界与零碰撞
在启动完整的 1080p60 渲染之前,务必在几秒钟内验证布局几何形状:
# 选项 A:对每个 Scene.wait() 调用进行实时空运行钩子(< 3 秒,零 MP4 编码)
./venv/bin/python .agents/skills/manim-video-explainer/check_scene_bounds.py \
--module scenes.py --scene Act1RateLimiter
# 选项 B:在快速 -ql 渲染后检查发出的检查点 JSON
python3 .agents/skills/manim-video-explainer/check_scene_bounds.py \
--json tmp/act1_bounds.json
阅读 references/manim-layout-and-choreography.md 了解多层坐标模板、动态 ValueTracker 计数器和防碰撞清理规则。
3. 可选的节拍同步画外音评论(mux_narration.py)
黄金旁白规则
切勿逐字阅读屏幕上的框标题、箭头或徽章字符串。
观众立即就能看到 "API Gateway -> Rate Limiter"。请编写解释性过程评论,说明为什么每个转换会发生、什么不变量保护系统以及什么原因导致拒绝或恢复。
先节拍后声音的两遍工作流
- 编写
narration_manifest.json,将干净的字幕text与音标覆盖项(tts_phonetic_text或phonetic_glossary)分开,这样技术缩写(WAL、gRPC、TTL、p99、HTTP 429)就能自然地发音,而不会污染.vtt字幕:
{
"lead_in_s": 0.20,
"inter_beat_gap_s": 0.25,
"phonetic_glossary": {
"WAL": "write-ahead log",
"TTL": "T T L",
"HTTP 429": "H T T P four twenty-nine"
},
"scenes": [
{
"id": "act1",
"beats": [
{
"id": "act1_ingress",
"min_visual_s": 3.2,
"text": "Inbound writes terminate at the gateway before touching stateful storage."
},
{
"id": "act1_bucket",
"min_visual_s": 3.8,
"text": "An atomic token-bucket check absorbs burst traffic while bounding database concurrency."
},
{
"id": "act1_reject",
"min_visual_s": 3.5,
"text": "When burst tokens drain to zero, excess callers fail fast with HTTP 429."
}
]
}
]
}
- 第 1 遍 —— 合成逐节拍 WAV 并输出精确时序:
# 选项 A:离线 Piper ONNX 神经 TTS(零网络调用)
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_en \
--piper-bin ./venv/bin/piper \
--model ~/.cache/piper-voices/en_US-lessac-medium.onnx \
--emit-timings tmp/beat_timings.json
# 选项 B:兼容 OpenAI 的 HTTP 神经 TTS (/v1/audio/speech, stdlib urllib)
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_en \
--http-tts-url https://api.openai.com/v1/audio/speech \
--http-tts-key-env OPENAI_API_KEY \
--http-tts-model gpt-4o-mini-tts \
--http-tts-voice cedar \
--http-tts-instructions "Calm, clear technical documentary narrator; brief pauses at clauses." \
--emit-timings tmp/beat_timings.json
(任何自定义 CLI 合成器也可以通过 --tts-cmd "my-tts --out {wav} --text {text}" 使用,或者预渲染的 .wav 文件可以直接放置在 --wav-dir 中。)
- 渲染由
tmp/beat_timings.json驱动的 1080p60 Manim 视频:
./venv/bin/manim -qh --fps 60 scenes.py Act1RateLimiter
- 第 2 遍 —— 采样级精确主轨道组装、响度归一化、尾部保护与 AAC 混流:
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_en \
--video media/videos/scenes/1080p60/Act1RateLimiter.mp4 \
--loudnorm-lufs -18.0 \
--output output/act1_en.mp4 \
--vtt output/act1_en.vtt
mux_narration.py 将每个节拍 WAV 放置在 round(start_s * sample_rate) 处的 16 位 PCM 主轨道上,每当最终语音超过视频时长时,通过 tpad=stop_mode=clone 扩展最后一帧视频(零单词截断),可选地应用 EBU R128 响度归一化(--loudnorm-lufs -18.0)以使音量在章节间保持一致,编码 -c:a aac -b:a 160k -movflags +faststart,并写入 output/act1_en.vtt。
阅读 references/voiceover-and-commentary.md 了解不良与良好评论的转换、非英语音标覆盖项以及双语音频工作流程。
4. 网络编码、多场景拼接与离线 HTML5 播放器
兼容网络的编码与主卷拼接
对于无声演示(不使用 mux_narration.py),确保通用浏览器/QuickTime 兼容性(yuv420p + 移动的 moov atoms):
ffmpeg -y -i media/videos/scenes/1080p60/Act1RateLimiter.mp4 \
-c:v libx264 -pix_fmt yuv420p -crf 23 -movflags +faststart \
output/act1_silent.mp4
要将多个带有画外音的 MP4 片段组装成一个连续的主卷:
printf "file 'act1_en.mp4'\nfile 'act2_en.mp4'\nfile 'act3_en.mp4'\n" > output/concat.txt
ffmpeg -y -f concat -safe 0 -i output/concat.txt \
-c:v libx264 -pix_fmt yuv420p -crf 21 -c:a aac -b:a 160k -movflags +faststart \
output/master_en.mp4
可选的独立 HTML5 影院播放器
在交付多章节演示或双语讲解包时:
- 将
assets/video_player_template.html复制到output/index.html。 - 自定义
PLAYER_CONFIG(chapters、sources和匹配beat_timings.json中节拍时间戳的markers)。如果只交付单一语言,隐藏或省略#langGroup中的次要语言按钮。 - 保持所有样式、单色 SVG 图标和脚本 100% 内联,以便该包无需外部 CDN 即可离线工作。
- HTTP
206 Partial Content服务说明:浏览器需要 HTTP 字节范围(206)响应才能正常搜索<video>时间线并加载<track>.vtt文件(file://会通过 CORS 阻止 WebVTT 轨道,而 Python 的基本http.server在没有额外配置的情况下也不支持字节范围请求)。可以将播放器部署到任何支持静态文件的 Web 服务器,或使用npm包http-server或serve等工具进行测试。