+
29
-

回答

用 OpenAI 生成分镜脚本与配音,再用 Manim 的 LaggedStart 批量、有节奏地呈现字幕/公式,实现讲解视频的自动生成。

流程概览

生成脚本与分镜:OpenAI 产出 JSON(每个场景包含标题、要点、公式、朗读文本、动画节奏参数)

文本转语音:OpenAI TTS(gpt-4o-mini-tts)生成每个场景的配音

Manim 渲染:用 LaggedStart 让每个场景的元素依次出现,并与配音对齐

合成导出:直接在 Manim 中 add_sound 对上时间轴,或用 moviepy 后期合成

依赖安装

Manim(建议 conda):conda install -c conda-forge manim ffmpeg

PIP 依赖:pip install openai pydub

字体/LaTeX:中文用 Noto Sans CJK/思源黑体;若用 MathTex 渲染公式,需要系统装 TeX(如 TeX Live)

一、脚本与配音生成(ai_gen.py)

作用:给一个主题,得到结构化分镜 JSON,并为每个场景生成独立配音音频,自动记录时长用于对齐

运行前:导出 OPENAI_API_KEY 环境变量

# ai_gen.py
import os, json, sys
from pathlib import Path
from pydub import AudioSegment
from openai import OpenAI

client = OpenAI()
OUT_DIR = Path("ai_out")
AUDIO_DIR = OUT_DIR / "audio"
OUT_DIR.mkdir(exist_ok=True, parents=True)
AUDIO_DIR.mkdir(exist_ok=True, parents=True)

SYSTEM = "你是一个面向科普视频的分镜脚本生成器,输出严格的JSON。"

SCHEMA_HINT = """
请仅输出 JSON(不要加解释、不要代码块)。结构:
{
  "title": "视频总标题",
  "scenes": [
    {
      "title": "场景标题",
      "lag_ratio": 0.15,
      "anim_time": 3.0,
      "elements": [
        {"type": "text", "content": "要点1", "pos": "left"},
        {"type": "formula", "content": "E=mc^2"}
      ],
      "voiceover": "本场景的配音全文,口语化,10-20秒。"
    }
  ]
}
要求:
- 场景数 2-4 个,每场景 2-5 个 elements,适合 30-90 秒短视频。
- 文字简洁、直观;尽量避免生僻字。
- formula 用 LaTeX 可选(如 \\frac, ^2 等),不含中文。
"""

def gen_script(topic: str):
    prompt = f"主题:{topic}\n{SCHEMA_HINT}"
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0.7,
        messages=[
            {"role":"system","content": SYSTEM},
            {"role":"user","content": prompt}
        ],
        response_format={"type": "json_object"}
    )
    data = resp.choices[0].message.content
    return json.loads(data)

def tts_to_file(text: str, out_path: Path, voice="alloy"):
    out_path.parent.mkdir(parents=True, exist_ok=True)
    # 使用 OpenAI TTS(gpt-4o-mini-tts)
    with client.audio.speech.with_streaming_response.create(
        model="gpt-4o-mini-tts",
        voice=voice,
        input=text
    ) as response:
        response.stream_to_file(out_path)

def main():
    topic = "为什么天空是蓝色的"
    if len(sys.argv) > 1:
        topic = " ".join(sys.argv[1:])
    script = gen_script(topic)

    # 为每个场景生成配音并记录时长
    for i, sc in enumerate(script["scenes"], start=1):
        voice_path = AUDIO_DIR / f"scene_{i:02d}.mp3"
        tts_to_file(sc["voiceover"], voice_path)
        dur_s = len(AudioSegment.from_file(voice_path)) / 1000.0
        sc["voice_path"] = str(voice_path.as_posix())
        sc["voice_duration"] = round(dur_s, 2)
        # 缺省动画参数兜底
        sc["lag_ratio"] = sc.get("lag_ratio", 0.15)
        sc["anim_time"] = sc.get("anim_time", 3.0)

    out_json = OUT_DIR / "script.json"
    with open(out_json, "w", encoding="utf-8") as f:
        json.dump(script, f, ensure_ascii=False, indent=2)

    print("Saved:", out_json)
    for sc in script["scenes"]:
        print(f"- {sc['title']} | voice {sc['voice_duration']}s | {sc['voice_path']}")

if __name__ == "__main__":
    main()

二、Manim 场景渲染(使用 LaggedStart)(manim_scene.py)

作用:读取上一步的 script.json,逐场景播放配音并用 LaggedStart 依次展示元素

要点:LaggedStart 控制“错峰出现”,anim_time 控制整体时长;根据配音时长自动补 Wait 避免声音被截断

# manim_scene.py
from manim import *
import json, math, os
from pathlib import Path

JSON_PATH = Path("ai_out/script.json")

# 你机器上的中文字体名,如未安装请改为可用字体
CN_FONT = "Noto Sans CJK SC"  # 或 "SimHei", "Source Han Sans SC"

class AIExplainer(Scene):
    def construct(self):
        assert JSON_PATH.exists(), f"not found: {JSON_PATH}"
        data = json.loads(JSON_PATH.read_text(encoding="utf-8"))

        for idx, sc in enumerate(data["scenes"], start=1):
            self.next_section(sc["title"])

            # 标题
            title = Text(sc["title"], font=CN_FONT, weight=BOLD).scale(0.8).to_edge(UP)
            self.play(FadeIn(title, shift=0.3*UP), run_time=0.6)

            # 元素构建
            mobjects = []
            y_cursor = 1.0
            for el in sc["elements"]:
                t = el.get("type", "text")
                if t == "text":
                    m = Text(el["content"], font=CN_FONT).scale(0.6)
                    pos = el.get("pos","left")
                    if pos == "left":
                        m.to_edge(LEFT).shift(DOWN * y_cursor)
                    elif pos == "right":
                        m.to_edge(RIGHT).shift(DOWN * y_cursor)
                    else:
                        m.next_to(title, DOWN, buff=0.5).shift(DOWN * (y_cursor-1))
                    y_cursor += 0.8
                    mobjects.append(m)
                elif t == "formula":
                    # 需要系统安装 LaTeX
                    m = MathTex(el["content"]).scale(0.9)
                    m.next_to(title, DOWN, buff=0.6)
                    mobjects.append(m)
                # 可扩展:image、code、graph 等

            # 配音加入(从当前时间开始)
            if "voice_path" in sc and os.path.exists(sc["voice_path"]):
                self.add_sound(sc["voice_path"])

            # 依次出现:LaggedStart
            anims = []
            for mo in mobjects:
                if isinstance(mo, MathTex):
                    anims.append(Write(mo))
                else:
                    anims.append(FadeIn(mo, shift=0.2*DOWN))
            if anims:
                self.play(LaggedStart(*anims, lag_ratio=sc.get("lag_ratio", 0.15),
                                      run_time=sc.get("anim_time", 3.0)))

            # 让画面至少持续到配音结束
            voice_dur = sc.get("voice_duration", 0)
            anim_time = sc.get("anim_time", 3.0)
            pad = max(0.3, voice_dur - anim_time)
            if pad > 0:
                self.wait(pad)

            # 淡出本场景
            self.play(FadeOut(VGroup(title, *mobjects)), run_time=0.6)
            self.wait(0.2)  # 轻微过场

# 便于快速测试 LaggedStart 的最小例子
class LaggedStartDemo(Scene):
    def construct(self):
        a, b, c = Square(), Circle(), Triangle()
        VGroup(a,b,c).arrange(RIGHT, buff=1)
        self.play(LaggedStart(Create(a), Create(b), Create(c), lag_ratio=0.2, run_time=2))
        self.wait(0.5)

三、如何运行1) 生成分镜与配音

export OPENAI_API_KEY=你的key

python ai_gen.py "你想讲解的主题(例如:光的折射)"

产出:ai_out/script.json + 每个场景的音频文件

2) 渲染 Manim

manim -pqh manim_scene.py AIExplainer

输出视频位于 media/videos/.../AIExplainer.mp4(-pqh 表示预览/低画质/720p,发布用 -pqh 改成 -p -qh 或 -p -qk)

四、可扩展点

更强的对齐:把 elements 的关键语句切成“句级”并逐句 TTS,逐句 LaggedStart + time_offset 对齐

高级镜头语言:结合 Transform、ReplacementTransform、Indicate 等提升教育效果

图像/图表:让 LLM 产出绘图参数,Manim 中用 Axes/NumberPlane/ParametricFunction 渲染

BGM/音效:self.add_sound("bgm.mp3", time_offset=0) 并降低音量;或后期用 moviepy 合成

风格控制:在提示词中固定配色、图层布局、每场景时长范围、是否带公式等

字体与排版:为中文选用合适字体;公式需要 LaTeX;可通过 config.media_width, background_color 自定义主题

常见坑

中文显示乱码:换成安装好的中文字体(CN_FONT)

MathTex 报错:系统未装 LaTeX;或公式语法不合法

音频不同步:配音过长时增加 wait,或将 anim_time 设得更短并添加额外 wait

生成 JSON 解析失败:把 response_format 设置为 json_object,或在系统提示里强调“严格 JSON 输出”

网友回复

我知道答案,我要回答