Manim 1080p60 技術解説ビデオ&同期ナレーションシステム
概要とバンドルリソース
pdflatexやMacTeXを必要とせず、純粋なPythonとManim Communityを使用して、高精細で決定論的な 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: 出力バンドル検証ツール。ffprobeによるH.264yuv420p+ 音声ストリーム、WebVTTヘッダー/キューの単調増加性、およびオフラインHTML5プレーヤーのCSP/ゼロCDN/ゼロ絵文字インバリアントをチェックします。references/manim-layout-and-choreography.md: マルチノードトポロジ、直交バス/エルボールーティング、動的カウンター、双方向矢印、またはマルチシーンffmpeg連結を設計する際に参照してください。references/voiceover-and-commentary.md: ナレーションマニフェスト、自然なナレーターの韻律指示、発音用語集、EBU R128正規化、またはバイリンガルナレーショントラックを作成する際に参照してください。
1. ストーリーボードとマルチアクトナラティブデザイン
Manimコードを書く前に、説明を個別のアクトとビート単位のストーリーボードテーブルに構造化します。
1つの Scene クラスをアクトごとに保持することで、アクト2の編集がアクト1やアクト3の再レンダリングを必要としなくなります。
- アクト1 — セットアップとリクエスト受付: トポロジ、インバリアント、およびエントリーガードを紹介します(例:
Client -> API Gateway -> Token-Bucket Rate Limiter)。 - アクト2 — 定常状態の実行: プライマリデータパスと状態遷移をトレースします(例:
Read-Through Cacheシングルフライトルックアップ ->Primary DBハイドレーション -> TTLライトバック)。 - アクト3 — 障害モードとリカバリ: 制限に達したときやノードが障害を起こしたときの動作をデモします(例:
HTTP 429バースト拒否またはDistributed Lock & Write-Ahead Log Replicationクォーラム昇格)。
コーディング前にすべてのビートを明示的にマッピングします。
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を使用します。 - 黄金の模式図ルール(ポートアンカー直交配線): アーキテクチャブロック図を作成する際に、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)でワイヤージオメトリを検証します。 - セーフフレーム座標グリッド: 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
マルチティア座標テンプレート、動的な ValueTracker カウンター、および衝突防止クリーンアップルールについては、references/manim-layout-and-choreography.md を参照してください。
3. オプションのビート同期ナレーション解説(mux_narration.py)
黄金のナレーションルール
画面上のボックスタイトル、矢印、バッジ文字列をそのまま読み上げないでください。
視聴者は "API Gateway -> Rate Limiter" を即座に目にします。各遷移が なぜ 発生するのか、どのような不変条件 がシステムを保護するのか、そして 何が 拒否または回復を引き起こすのかを説明する解説的なプロセスコメントを書きましょう。
ビートファーストの2パスワークフロー
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を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. Webエンコーディング、マルチシーン連結、およびオフラインHTML5プレーヤー
Web互換エンコーディングとマスターリール連結
サイレントウォークスルー(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シネマプレーヤー
マルチチャプターのウォークスルーやバイリンガル解説パッケージを配信する場合:
assets/video_player_template.htmlをoutput/index.htmlにコピーします。PLAYER_CONFIG(chapters、sources、およびbeat_timings.jsonのビートタイムスタンプに一致するmarkers)をカスタマイズします。単一言語で配信する場合は、#langGroupのセカンダリ言語ボタンを非表示にするか省略します。- すべてのスタイル、モノクロSVGアイコン、スクリプトが100%インラインになるようにして、バンドルが外部CDNなしでオフラインで動作するようにします。
- HTTP
206 Partial Content提供に関する注意: ブラウザが<video>タイムラインをシークしたり<track>.vttファイルを正常に読み込んだりするには、HTTPバイトレンジ(206)レスポンスが必要です(file://はCORSを介してWebVTTトラックをブロックします)。output/ディレクトリは、バイトレンジをサポートする任意のHTTPサーバー(nginx、Caddy、Node.jshttp-server、Pythonhttp.serverは不完全)で提供するか、統合するアプリケーションに静的ファイルとしてバンドルしてください。
5. 検証とCIパイプライン
バンドル検証(lint.py)
チェックするには以下を実行:
python3 .agents/skills/manim-video-explainer/lint.py --bundle output/
これは以下を検証します:
ffprobeを使用したH.264yuv420pビデオストリームの存在。- AACまたはオプションのマルチプレクスされた音声ストリーム。
- WebVTTヘッダーとキューのタイムスタンプが単調増加であること。
- オンラインリソース(CDN、フォント、トラッカー)への参照がないこと。
- オフラインモードで壊れる可能性のある絵文字やその他のUnicode文字が含まれていないこと。
- 出力ディレクトリへの安全なパス。
CI用クイックサニティチェック
完全なレンダリング前にジオメトリを確認するには、-ql(低品質、854x480 @ 15fps)Manimプリセットを使用して、mux_narration.py と check_scene_bounds.py のロジックを検証します。
./venv/bin/manim -ql scenes.py Act1RateLimiter
./venv/bin/python .agents/skills/manim-video-explainer/check_scene_bounds.py --json tmp/act1_bounds.json
ルール違反の自動修正
直交コネクタが assert_all_orthogonal の検証に失敗した場合、違反したエッジは再ルーティングが必要です。connect_h または connect_v を使用し、開始/終了ノードのポートのY/X位置が一致していることを確認し、複雑な配線パスには connect_elbow を使用してください。
トラブルシューティングガイド
LaTeXの欠落エラー
RuntimeError: Latex error converting to dvi. See log file.
原因: Tex または MathTex が誤って使用され、システムにpdflatexがインストールされていません。
修正: すべての Tex() / MathTex() 呼び出しを Text() または MarkupText() に置き換え、DecimalNumber の使用を確認してください。
斜め矢印 / 直交違反
症状: connect_h/connect_v の代わりに Arrow(card_a, card_b) が使用された場合、Manimはボックスセンター間に斜めの矢印を生成します。
修正: assert_all_orthogonal(connectors, cards) を呼び出してすべてのワイヤーを検証し、違反したエッジを connect_h、connect_v、または connect_elbow を使用するように書き換えてください。
音声の途切れ / クリッピング
症状: 最終的なナレーション付き動画で最後の単語が途切れる。
修正: mux_narration.py の --loudnorm-lufs パラメータが設定されていることを確認し、最終的な音声が動画時間を超える場合は、mux_narration.py が tpad フィルタを自動的に追加することを確認してください。narration_manifest.json の min_visual_s 値を見直してください。
WebVTTが表示されない(file://)
症状: HTML5プレーヤーを file:// で開いた場合、字幕トラックが読み込まれない。
修正: ブラウザのCORSポリシーにより、file:// からのWebVTT読み込みがブロックされます。ファイルはローカルのHTTPサーバー(npx http-server output/)またはプロダクション環境で提供してください。
レンダリングが遅い
- フル
1080p60出力の前に-qlフラグを使用してプレビューします。 ValueTrackerカウンターのフレームレートを制限します。毎フレーム更新する必要はありません。- 大きなテクスチャがある場合は、
--write_to_movieの代わりに--format=pngを検討してください。
追記事項
環境変数
| 変数名 | 説明 | デフォルト |
|---|---|---|
OPENAI_API_KEY | --http-tts-key-env で使用するOpenAI APIキー | なし |
PIPER_VOICE_CACHE | Piper ONNXモデルのキャッシュディレクトリ | ~/.cache/piper-voices/ |
ファイルパス
- コアアセット:
.agents/skills/manim-video-explainer/assets/ - 参照ドキュメント:
.agents/skills/manim-video-explainer/references/ - 出力ディレクトリ:
output/(mux_narration.pyによって作成されます) - 一時ファイル:
tmp/(beat_timings.json、チェックポイント)