Communitygithub.com

manim-video-explainer

使用 Manim Community(無需 LaTeX)渲染高畫質 1080p60 技術解說影片、逐步系統導覽與多幕架構動畫,並可選配節拍同步的神經網路 TTS 旁白、EBU R128 響度正規化、WebVTT 字幕與獨立 HTML5 章節播放器。

manim-video-explainer 是什麼?

這是一個專為開發者與 AI 代理設計的 Manim 技能,能將複雜的系統架構與流程轉化為高畫質 1080p60 技術解說影片。它支援多幕敘事設計,讓使用者能將解釋拆分為「設定與請求准入」、「穩定狀態執行」與「故障模式與復原」等獨立場景,便於獨立編輯與重複使用。其核心優勢在於強制使用正交示意圖連線(避免 Manim 預設的對角線箭頭)、無需 LaTeX 的純文字排版,以及內建的佈局碰撞檢查工具,確保影片元件精準排列。技能提供靈活的語音旁白選項,可離線使用 Piper ONNX、任何相容 OpenAI 的 HTTP 端點或自訂 CLI TTS,並能自動生成節拍時間碼、進行 EBU R128 響度正規化、合併音訊與影片、以及產生 WebVTT 字幕。最終成果可封裝成一個零 CDN 依賴、可離線運作的 HTML5 章節播放器,非常適合製作教學影片、架構導覽或逐步視覺解說。

相容平台~Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/iuriimedvedev-dev/manim-video-explainer-skill/tree/HEAD

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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 Cinema 播放器,包含章節標籤、可點擊的節拍標記、播放速率(0.75x..2.0x),以及可選的雙語音訊/字幕切換。
  • check_scene_bounds.py:預渲染佈局驗證器,檢查 Manim 場景或 JSON 檢查點是否存在 [-6.8, 6.8] x [-3.75, 3.75] 框架溢出以及同級文字標籤碰撞。
  • mux_narration.py:基於 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:輸出套件驗證器,檢查 ffprobe 的 H.264 yuv420p 與音訊串流、WebVTT 標頭/提示的單調性,以及離線 HTML5 播放器的 CSP/零 CDN/零表情符號不變量。
  • references/manim-layout-and-choreography.md:在設計多節點拓撲、正交匯流排/肘節路由、動態計數器、雙向箭頭或多場景 ffmpeg 串接時閱讀。
  • references/voiceover-and-commentary.md:在編寫旁白清單、自然敘述者語調指示、音標發音詞彙表、EBU R128 正規化或雙語旁白音軌時閱讀。

1. 分鏡與多幕敘事設計

在編寫 Manim 程式碼之前,將解釋結構化為離散的幕與逐個節拍的分鏡表。每個「幕」保留一個 Scene 類別,這樣編輯第二幕永遠不需要重新渲染第一幕或第三幕:

  • 第一幕 — 設定與請求准入:介紹拓撲、不變量與入口守衛(例如 客戶端 -> API 閘道 -> 令牌桶速率限制器)。
  • 第二幕 — 穩定狀態執行:追蹤主要資料路徑與狀態轉換(例如 讀取快取 單次飛行查找 -> 主資料庫 填充 -> TTL 寫回)。
  • 第三幕 — 故障模式與復原:展示當限制觸發或節點故障時會發生什麼(例如 HTTP 429 突發拒絕或 分散式鎖定與預寫式日誌複製 法定人數提升)。

在編碼前明確映射每個節拍:

beat_idvisual_actionmin_visual_seccommentary_script (若需旁白)
act1_ingress渲染 4 個 Nord 拓撲卡片 + 標題;將請求封包動畫化進入閘道3.2入站寫入在接觸有狀態儲存之前,會先通過邊緣閘道。
act1_bucket令牌計數器遞減 100 -> 99;閃爍綠色 已准入 徽章3.8一個原子化的令牌桶檢查可吸收流量突發,同時限制下游並發量。
act1_reject當令牌歸 0 時,觸發 animate_blocked_pulse(429 已限流)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 與正交示意圖規則

  1. 零 TeX 子程序:永遠不要使用 Tex、MathTex、Title 或 DecimalNumber(這些會呼叫 pdflatex/dvisvgm)。請使用 Text、MarkupText(<b>、<i>、<sub>、<sup>、<span foreground="...">)、Code、RoundedRectangle、Arrow、DashedVMobject 和 MoveAlongPath。
  2. 黃金示意圖規則(連接埠錨定的正交導線):在建構架構方塊圖時,絕對不要直接在兩個 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) 驗證導線幾何形狀。
  3. 安全框座標網格: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 為空白。
  4. 匯入 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(
            "第一幕 / 請求准入",
            "API 閘道與令牌桶速率限制器",
        )
        gw = make_node_card("API 閘道", "TLS + 路由匹配", width=2.5, accent=ACCENT_CYAN).move_to([-4.5, 0.2, 0])
        limiter = make_node_card("速率限制器", "桶: 100 tok", width=2.5, accent=ACCENT_BLUE).move_to([-1.5, 0.2, 0])
        cache = make_node_card("讀取快取", "單次飛行 TTL", width=2.5, accent=ACCENT_CYAN).move_to([1.5, 0.2, 0])
        db = make_node_card("主資料庫", "WAL 法定人數", 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 已限流", 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 閘道 -> 速率限制器"。請編寫解釋性的流程註解,說明為什麼每個轉換會發生、哪個不變量保護了系統,以及什麼原因導致了拒絕或復原。

節拍優先的兩遍工作流程

  1. 編寫 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": "預寫式日誌",
    "TTL": "T T L",
    "HTTP 429": "H T T P 四二九"
  },
  "scenes": [
    {
      "id": "act1",
      "beats": [
        {
          "id": "act1_ingress",
          "min_visual_s": 3.2,
          "text": "入站寫入在接觸有狀態儲存之前,會先終止於閘道。"
        },
        {
          "id": "act1_bucket",
          "min_visual_s": 3.8,
          "text": "一個原子化的令牌桶檢查可吸收突發流量,同時限制資料庫並發量。"
        },
        {
          "id": "act1_reject",
          "min_visual_s": 3.5,
          "text": "當突發令牌耗盡至零時,多餘的呼叫者會快速失敗並回傳 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 "冷靜、清晰的技術紀錄片式敘述者;在子句處短暫停頓。" \
  --emit-timings tmp/beat_timings.json

(也可以透過 --tts-cmd "my-tts --out {wav} --text {text}" 使用任何自訂 CLI 合成器,或直接將預渲染的 .wav 檔案放入 --wav-dir。)

  1. 根據 tmp/beat_timings.json 驅動的 1080p60 Manim 影片渲染:
./venv/bin/manim -qh --fps 60 scenes.py Act1RateLimiter
  1. 第二遍 — 樣本精確的主音軌組裝、響度正規化、尾部保護與 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 放置在 16 位元 PCM 主音軌的 round(start_s * sample_rate) 位置,每當最終語音超過影片持續時間時,便透過 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 原子):

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 Cinema 播放器

在交付多章節導覽或雙語解說套件時:

  1. 將 assets/video_player_template.html 複製到 output/index.html。
  2. 自訂 PLAYER_CONFIG(chapters、sources 和 markers,使其與 beat_timings.json 中的節拍時間戳記匹配)。如果只交付單一語言,請隱藏或省略 #langGroup 中的次要語言按鈕。
  3. 將所有樣式、單色 SVG 圖示和腳本 100% 內聯,以便套件無需外部 CDN 即可離線運作。
  4. HTTP 206 Partial Content 提供注意事項:瀏覽器需要 HTTP 位元組範圍(206)回應才能擦洗 <video> 時間軸並乾淨地載入 <track> .vtt 檔案(file:// 會透過 CORS 封鎖 WebVTT 軌道,而 Python 的基本 http.server 可能無法正確提供範圍請求)。

輸出套件驗證

交付前驗證所有成品:

python3 .agents/skills/manim-video-explainer/lint.py --bundle output/

lint.py 確認每個 MP4 串流符合 H.264 yuv420p 規範並包含預期的音訊串流、WebVTT 標頭與提示順序單調,以及 HTML5 播放器 index.html 零 CDN、零表情符號且符合 CSP 規範。


5. 常見問題與專案結構

  • 每個場景的最大持續時間? mux_narration.py 對影片長度沒有限制。每秒 60 幀的 5 分鐘場景在磁碟上約為 1.5–2 GB(取決於複雜度)。對於較長的敘述,請透過多個 Scene 子類別拆分成多個幕,並在最後使用 ffmpeg concat 串接。
  • Piper 模型從哪裡獲得? 從 rhasspy/piper-voices 下載您的語言和語音的 .onnx 和 .json 檔案,放入 ~/.cache/piper-voices/。
  • 可以對同一影片使用多個語音嗎? 可以。對同一個場景以不同語音執行兩次 mux_narration.py,並混合 --wav-dir 目錄。
  • 如何建立雙語字幕? 為每種語言運行 mux_narration.py 以產生第二個 .vtt,然後手動編輯 assets/video_player_template.html 以包含 <track kind="subtitles" src="..." srclang="...">。

建議的專案結構

project/
├── venv/                     # 隔離的 Python 虛擬環境
├── scenes.py                 # 多個 Act 類別(Act1, Act2, ...)
├── tmp/
│   ├── narration_manifest.json # 旁白清單
│   ├── beat_timings.json       # 從第一遍輸出
│   └── act1_bounds.json        # 檢查點佈局轉儲
├── cache/
│   └── beats_en/               # 第一遍 WAV 檔案
├── media/videos/scenes/1080p60/ # Manim 渲染輸出
├── output/
│   ├── act1_en.mp4             # 第二遍混合 MP4
│   ├── act1_en.vtt             # WebVTT 字幕
│   ├── master_en.mp4           # 串接的主捲帶
│   └── index.html              # 自訂播放器
└── .agents/skills/manim-video-explainer/  # 此技能(符號連結或複製)

相關技能