Author: ericksun(孙自翔)
本技能使用 Manim Community 生成数学/教育动画,并通过 manim-voiceover 插件集成 TTS 语音旁白和同步字幕。所有处理均在本地运行,无需付费 API。
核心能力:
TTS 引擎(优先 gTTS):
首次使用前,运行环境检查脚本确认所有依赖已就绪:
python3 {SKILL_DIR}/scripts/check_environment.py
该脚本检查:
manim 命令)pip install manimlibx264 编码器渲染视频,字幕烧录需要 libassbrew install ffmpeg(默认包含 x264 和 libass)conda install x264 -c conda-forge(⚠️ conda 的 ffmpeg 默认不含 libx264)sudo apt install ffmpeg libx264-dev libass-dev# Core
pip install manim
# Voiceover + TTS
pip install "manim-voiceover[gtts]"
pip install "manim-voiceover[pyttsx3]")pip install manim "manim-voiceover[gtts]"
# macOS (Homebrew) — 推荐,自带 libx264 + libass
brew install ffmpeg
# macOS (Conda) — 需额外安装 x264,否则 Manim 渲染会报错 UnknownCodecError: libx264
conda install x264 -c conda-forge
# 验证 ffmpeg 支持 libx264 和 libass
ffmpeg -codecs 2>&1 | grep libx264 # 应显示 encoders: libx264
ffmpeg -filters 2>&1 | grep subtitles # 应显示 subtitles filter
用户描述需求后,使用流水线脚本一键完成:
python3 {SKILL_DIR}/scripts/run_pipeline.py \
--scene_file <场景文件.py> \
--scene_name <SceneName> \
--quality high \
--burn_subtitles
常用选项:
| 选项 | 默认值 | 说明 |
|---|---|---|
| ------ | -------- | ------ |
--scene_file | 必需 | Manim 场景 Python 文件 |
--scene_name | 必需 | 场景类名 |
--quality | high | 渲染质量:low/medium/high/production |
--burn_subtitles | False | 是否用 ffmpeg 烧录 SRT 字幕 |
--speed | 1.35 | 播放倍速(如 1.35 表示加速到 1.35 倍,设为 1.0 则不加速) |
--preview | False | 渲染后自动打开预览 |
--output_dir | ./output | 输出目录 |
根据用户描述,生成 Manim 场景 Python 文件。场景代码需遵循以下模式:
无配音模式(纯动画):
from manim import *
class MyScene(Scene):
def construct(self):
title = Text("标题", font_size=48, color=BLUE)
self.play(Write(title))
self.wait(1)
配音模式(动画 + 语音 + 字幕):
from manim import *
from manim_voiceover import VoiceoverScene
from manim_voiceover.services.gtts import GTTSService
class MyScene(VoiceoverScene):
def _make_subtitle(self, text_str):
"""Create subtitle with dark background at bottom of screen."""
sub = Text(text_str, font_size=22, color=WHITE, weight=BOLD)
# Prevent subtitle from overflowing left/right edges
max_width = config.frame_width - 1.0 # 0.5 margin each side
if sub.width > max_width:
sub.scale_to_fit_width(max_width)
sub.to_edge(DOWN, buff=0.4)
bg = BackgroundRectangle(sub, color=BLACK, fill_opacity=0.6, buff=0.15)
return VGroup(bg, sub)
def construct(self):
self.set_speech_service(GTTSService(lang="zh"))
sub_text = "欢迎来到演示"
with self.voiceover(text=sub_text) as tracker:
sub = self._make_subtitle(sub_text)
title = Text("演示", font_size=48)
self.play(Write(title), FadeIn(sub), run_time=tracker.duration)
self.play(FadeOut(sub))
self.wait(0.3)
关键模式 — voiceover 上下文管理器:
with self.voiceover(text="语音文本") as tracker:
# tracker.duration = TTS 语音时长(秒)
# 在此块内的动画会与语音自动同步
self.play(SomeAnimation(), run_time=tracker.duration)
with self.voiceover(text=...) as tracker 做了三件事:
tracker.duration 让动画与语音同步字幕最佳实践:
_make_subtitle() 辅助函数,在屏幕底部显示带暗背景的白色粗体文字_make_subtitle() 会自动检测字幕宽度,超出画面时等比缩放(scale_to_fit_width),使用 font_size=22 以适配长文本self.play() 中就 FadeIn(sub),确保字幕与声音同步出现,不要延后⚠️ 避免双字幕: 如果场景代码中已使用 _make_subtitle() 渲染了画面内字幕,则 不要 再用 --burn_subtitles 烧录 SRT 字幕,否则画面上会出现两层重叠的字幕。两种字幕方案只能选其一:
_make_subtitle() 渲染字幕,不烧录 SRT--burn_subtitles 烧录 SRT在场景文件同目录创建 manim.cfg:
[CLI]
quality = high_quality
preview = False
[ffmpeg]
video_codec = h264
质量对照表:
| 质量 | 标志 | 分辨率 | FPS | manim.cfg 值 |
|---|---|---|---|---|
| ------ | ------ | -------- | ----- | ------------- |
| Low | -ql | 480p | 15 | low_quality |
| Medium | -qm | 720p | 30 | medium_quality |
| High | -qh | 1080p | 60 | high_quality |
| Production | -qp | 2160p | 60 | production_quality |
manim render <scene_file.py> <SceneName>
输出路径模式:media/videos/
manim-voiceover 会自动在视频同目录生成 .srt 字幕文件。使用 ffmpeg 烧录:
ffmpeg -y -i <video.mp4> \
-vf "subtitles=<subtitle.srt>:force_style='FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,BackColour=&H80000000,BorderStyle=4,MarginV=30'" \
-c:a copy \
<output_subtitled.mp4>
⚠️ 双字幕陷阱:如果场景 Python 代码中已经用 _make_subtitle() 渲染了画面内字幕,就 不要 再烧录 SRT 字幕,否则会出现两层重叠字幕。
注意:ffmpeg 需要 libass 支持。macOS 用 brew install ffmpeg 通常已包含。Conda 环境需额外安装 conda install x264 -c conda-forge。
使用 ffmpeg 对视频进行倍速处理,默认加速到 1.35 倍:
SPEED=1.35
ffmpeg -y -i <input.mp4> \
-filter_complex "[0:v]setpts=PTS/${SPEED}[v];[0:a]atempo=${SPEED}[a]" \
-map "[v]" -map "[a]" \
<output_fast.mp4>
注意:加速应在最终输出步骤执行。如果场景代码已有画面内字幕(_make_subtitle),加速输入应使用原始视频(非 SRT 烧录版),避免双字幕。run_pipeline.py 的 --speed 参数已自动处理此逻辑。
Write(text) — 书写文字Create(mobject) — 绘制形状FadeIn(mobject) / FadeOut(mobject) — 淡入淡出DrawBorderThenFill(mobject) — 先画边框再填充Transform(source, target) — 变形ReplacementTransform(source, target) — 替换变形TransformMatchingShapes(source, target) — 形状匹配变形mobject.animate.to_edge(UP) — 移动到边缘mobject.animate.shift(RIGHT * 2) — 平移mobject.animate.scale(2) — 缩放Rotate(mobject, angle=PI) — 旋转Text("文字", font_size=48, color=BLUE) — 文字MathTex(r"e^{i\pi}+1=0") — LaTeX 公式Circle(radius=1, color=RED) — 圆Square(side_length=2, color=GREEN) — 正方形Arrow(start, end) — 箭头NumberPlane() — 坐标平面Axes(x_range, y_range) — 坐标轴VGroup(obj1, obj2) — 垂直分组group.arrange(RIGHT, buff=0.5) — 水平排列BackgroundRectangle(obj, color=BLACK, fill_opacity=0.6) — 背景矩形症状:UnknownCodecError: libx264
根因:Manim 在 scene_file_writer.py 中硬编码使用 libx264 编码器(无法通过 config/cfg 覆盖),但 conda 环境的 ffmpeg 编译时 --disable-gpl,不包含 GPL 许可的 libx264。
解决:
# Conda 环境(最常见场景)
conda install x264 -c conda-forge
# 安装后 conda-forge 的 ffmpeg 会自动重新链接 x264 库
# 验证
ffmpeg -codecs 2>&1 | grep libx264
# 应输出包含: encoders: libx264 libx264rgb
注意:brew install ffmpeg 安装的 ffmpeg 自带 x264,但 conda 环境优先使用自己安装的 ffmpeg,不会使用 Homebrew 版本。
manim-voiceover 依赖 pkg_resources,Python 3.12+ 可能报错:
pip install "setuptools>=69.0,<72.0"
SRT 字幕烧录需要 libass。macOS:
brew install ffmpeg
# 验证
ffmpeg -filters 2>&1 | grep subtitles
Linux:
sudo apt install libass-dev
# 可能需要重新编译 ffmpeg
gTTS 需要访问 Google TTS 服务,如网络不通可切换为 pyttsx3 离线引擎:
from manim_voiceover.services.pyttsx3 import Pyttsx3Service
self.set_speech_service(Pyttsx3Service())
Manim 使用系统字体渲染 Text 对象,确保系统有中文字体:
sudo apt install fonts-noto-cjkText("文字", font="PingFang SC")references/manim_guide.md — 详细的 Manim + voiceover + 字幕技术文档scripts/check_environment.py — 一键检查所有依赖scripts/run_pipeline.py — 一键渲染 + 字幕烧录共 1 个版本