Système de vidéo explicative technique Manim 1080p60 et voix off synchronisée
Présentation et ressources intégrées
Créez des vidéos explicatives techniques nettes et déterministes en 1920x1080 @ 60 ips en Python pur avec Manim Community, sans nécessiter pdflatex ou MacTeX.
Associez optionnellement les animations à une voix off neuronale précise à l'échantillon (Piper ONNX hors ligne, tout point d'accès HTTP /v1/audio/speech compatible OpenAI, ou TTS CLI personnalisé), une normalisation de volume EBU R128, des sous-titres .vtt WebVTT synchronisés, et un lecteur HTML5 hors ligne sans CDN.
assets/nord_manim_theme.py: Jetons de palette Nord Polar/Frost/Aurora, constantes de mise en page du cadre de sécurité, assistant de synchronisationBeatTimer, primitives sans LaTeX (make_header_banner,make_node_card,make_badge,animate_blocked_pulse,safe_fit_text), et connecteurs schématiques orthogonaux stricts à ancrage de port (connect_h,connect_v,connect_elbow,animate_packet_along,assert_all_orthogonal).assets/video_player_template.html: Lecteur cinéma HTML5 autonome, sans CDN, thème Nord clair/sombre, avec onglets de chapitres, marqueurs de temps cliquables, vitesse de lecture (0.75x..2.0x), et commutation optionnelle de l'audio/sous-titres bilingue.check_scene_bounds.py: Vérificateur de mise en page avant rendu qui contrôle les scènes Manim ou les points de contrôle JSON pour les débordements du cadre[-6.8, 6.8] x [-3.75, 3.75]et les collisions de texte entre éléments frères.mux_narration.py: CLI Python avec la bibliothèque standard qui synthétise des WAV par temps via Piper, un point d'accès HTTP/v1/audio/speechcompatible OpenAI, ou une commande TTS personnalisée, génèrebeat_timings.json, assemble une piste WAV maître précise à l'échantillon, clone les images vidéo de fin (tpad=stop_mode=clone) pour que la parole ne soit jamais coupée, normalise optionnellement le volume (--loudnorm-lufs -18.0), multiplexe H.264 + AAC (+faststart), et écrit des sous-titres.vtt.lint.py: Vérificateur du bundle de sortie qui contrôle les flux H.264yuv420p+ audio parffprobe, la monotonie des en-têtes/timbres WebVTT, et les invariants CSP/sans CDN/sans émoji du lecteur HTML5 hors ligne.references/manim-layout-and-choreography.md: À lire lors de la conception de topologies multi-nœuds, de routage orthogonal bus/coude, de compteurs dynamiques, de flèches bidirectionnelles, ou de concaténation multi-scènes avecffmpeg.references/voiceover-and-commentary.md: À lire lors de la rédaction de manifestes de narration, d'instructions de prosodie pour narrateur naturel, de glossaires de prononciation phonétique, de normalisation EBU R128, ou de pistes vocales bilingues.
1. Storyboard et conception narrative multi-acte
Avant d'écrire du code Manim, structurez l'explication en actes distincts et une table de storyboard temps par temps.
Gardez une classe Scene par Acte afin que les modifications de l'Acte 2 ne nécessitent jamais de re-rendu de l'Acte 1 ou 3 :
- Acte 1 — Configuration et admission de la requête : Présentez la topologie, les invariants et le garde d'entrée (par ex.,
Client -> API Gateway -> Limiteur de Débit à Seau). - Acte 2 — Exécution en régime permanent : Tracez le chemin de données principal et les transitions d'état (par ex., lecture
Read-Through Cacheen simple vol -> hydratationBase de données primaire-> réécriture TTL). - Acte 3 — Modes de défaillance et reprise : Montrez ce qui se passe lorsque les limites sont atteintes ou que des nœuds tombent en panne (par ex., rejet de rafale
HTTP 429ou promotion de quorumVerrou distribué et réplication du journal d'écriture anticipée (WAL)).
Planifiez chaque temps explicitement avant de coder :
beat_id | visual_action | min_visual_sec | commentary_script (si narré) |
|---|---|---|---|
act1_ingress | Afficher 4 cartes de topologie Nord + en-tête ; animer le paquet de requête dans la passerelle | 3.2 | Les écritures entrantes passent par une passerelle périphérique avant de toucher le stockage avec état. |
act1_bucket | Décrémenter le compteur de jetons 100 -> 99 ; faire clignoter le badge vert ADMIS | 3.8 | Une vérification atomique du seau à jetons absorbe les rafales de trafic tout en limitant la concurrence en aval. |
act1_reject | Déclencher animate_blocked_pulse (429 LIMITATION DE DÉBIT) lorsque les jetons atteignent 0 | 3.5 | Une fois la capacité de rafale épuisée, les appelants en excès échouent rapidement avec HTTP 429 au lieu de faire la queue indéfiniment. |
2. Création sans LaTeX avec Manim Community
Configuration de l'environnement isolé
Approvisionnez Manim (et optionnellement piper-tts) dans un environnement virtuel isolé ; vérifiez que ffmpeg et ffprobe sont dans le PATH :
uv venv venv
uv pip install --python venv/bin/python manim piper-tts
Règles strictes sans LaTeX et schémas orthogonaux
- Zéro sous-processus TeX : N'utilisez jamais
Tex,MathTex,Title, ouDecimalNumber(qui invoquentpdflatex/dvisvgm). UtilisezText,MarkupText(<b>,<i>,<sub>,<sup>,<span foreground="...">),Code,RoundedRectangle,Arrow,DashedVMobject, etMoveAlongPath. - Règle d'or des schémas (Fils orthogonaux ancrés aux ports) : N'appelez JAMAIS
Arrow(card_a, card_b)directement sur deuxMobjectlors de la construction de diagrammes d'architecture en blocs — Manim vise des rayons entre les centres des boîtes englobantes et produit des flèches diagonales tordues chaque fois que les largeurs de carte ou les positions Y diffèrent. Routez chaque arête viaconnect_h(card_from, card_to, y=...),connect_v(card_from, card_to, x=...), ouconnect_elbow(points), animez les paquets avecanimate_packet_along(self, edge, ...), et validez la géométrie des fils avecassert_all_orthogonal(connectors, cards). - Grille de coordonnées du cadre de sécurité : Le canevas 16:9 de Manim s'étend sur
[-7.11, 7.11] x [-4.0, 4.0]. Gardez tout le contenu créé à l'intérieur de[-6.6, 6.6] x [-3.5, 3.5], et laissezy < -3.05vide à chaque fois que des sous-titres WebVTT sont affichés. - Importez
assets/nord_manim_theme.py: Définissezself.camera.background_color = BG_DARKet construisez les cartes, connecteurs orthogonaux, animations de paquets, et temps de pause de manière cohérente :
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(
"Acte 1 / Admission des requêtes",
"Passerelle API & Limiteur de débit à seau",
)
gw = make_node_card("Passerelle API", "TLS + Routage", width=2.5, accent=ACCENT_CYAN).move_to([-4.5, 0.2, 0])
limiter = make_node_card("Limiteur", "Seau : 100 jetons", width=2.5, accent=ACCENT_BLUE).move_to([-1.5, 0.2, 0])
cache = make_node_card("Cache de lecture", "TTL simple vol", width=2.5, accent=ACCENT_CYAN).move_to([1.5, 0.2, 0])
db = make_node_card("BD primaire", "Quorum 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="jeton=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 LIMITATION DE DÉBIT", 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")
Vérifiez les limites et collisions nulles avant le rendu 1080p60
Vérifiez toujours la géométrie de la mise en page en quelques secondes avant de lancer un rendu complet 1080p60 :
# Option A : Accrochage de simulation en direct sur chaque appel Scene.wait() (< 3s, zéro encodage MP4)
./venv/bin/python .agents/skills/manim-video-explainer/check_scene_bounds.py \
--module scenes.py --scene Act1RateLimiter
# Option B : Inspectez le JSON de points de contrôle émis après un rendu rapide -ql
python3 .agents/skills/manim-video-explainer/check_scene_bounds.py \
--json tmp/act1_bounds.json
Lisez references/manim-layout-and-choreography.md pour les modèles de coordonnées multi-niveaux, les compteurs dynamiques ValueTracker, et les règles de nettoyage anti-collision.
3. Commentaire vocal optionnel synchronisé au temps (mux_narration.py)
La règle d'or de la narration
Ne lisez JAMAIS textuellement les titres des boîtes, flèches ou badges à l'écran.
Les spectateurs voient déjà "Passerelle API -> Limiteur de débit" immédiatement. Écrivez un commentaire de processus explicatif qui indique pourquoi chaque transition se produit, quel invariant protège le système, et ce qui cause le rejet ou la reprise.
Workflow en deux passes avec priorité au temps
- Écrivez
narration_manifest.jsonen séparant letextpropre des sous-titres des remplacements phonétiques (tts_phonetic_textouphonetic_glossary) afin que les acronymes techniques (WAL,gRPC,TTL,p99,HTTP 429) soient prononcés naturellement sans polluer les sous-titres.vtt:
{
"lead_in_s": 0.20,
"inter_beat_gap_s": 0.25,
"phonetic_glossary": {
"WAL": "journal d'écriture anticipée",
"TTL": "T T L",
"HTTP 429": "H T T P quatre vingt vingt-neuf"
},
"scenes": [
{
"id": "act1",
"beats": [
{
"id": "act1_ingress",
"min_visual_s": 3.2,
"text": "Les écritures entrantes se terminent à la passerelle avant de toucher le stockage avec état."
},
{
"id": "act1_bucket",
"min_visual_s": 3.8,
"text": "Une vérification atomique du seau à jetons absorbe le trafic de rafale tout en limitant la concurrence de la base de données."
},
{
"id": "act1_reject",
"min_visual_s": 3.5,
"text": "Lorsque les jetons de rafale tombent à zéro, les appelants en excès échouent rapidement avec HTTP 429."
}
]
}
]
}
- Passe 1 — Synthétisez les WAV par temps et émettez les timings exacts :
# Option A : TTS neuronal Piper ONNX hors ligne (zéro appel réseau)
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_fr \
--piper-bin ./venv/bin/piper \
--model ~/.cache/piper-voices/fr_FR-mls-medium.onnx \
--emit-timings tmp/beat_timings.json
# Option B : TTS HTTP neuronal compatible OpenAI (/v1/audio/speech, urllib stdlib)
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_fr \
--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 "Narrateur calme et clair de documentaire technique ; brèves pauses aux propositions." \
--emit-timings tmp/beat_timings.json
(Tout synthétiseur CLI personnalisé peut également être utilisé via --tts-cmd "my-tts --out {wav} --text {text}", ou des fichiers .wav pré-rendus peuvent être placés directement dans --wav-dir.)
- Rendu de la vidéo Manim 1080p60 piloté par
tmp/beat_timings.json:
./venv/bin/manim -qh --fps 60 scenes.py Act1RateLimiter
- Passe 2 — Assemblage de la piste maître précise à l'échantillon, normalisation du volume, garde de fin et multiplexage AAC :
python3 .agents/skills/manim-video-explainer/mux_narration.py \
--manifest tmp/narration_manifest.json \
--scene-id act1 \
--wav-dir cache/beats_fr \
--video media/videos/scenes/1080p60/Act1RateLimiter.mp4 \
--loudnorm-lufs -18.0 \
--output output/act1_fr.mp4 \
--vtt output/act1_fr.vtt
mux_narration.py place chaque WAV de temps à round(start_s * sample_rate) sur une piste maître PCM 16 bits, étend la dernière image vidéo via tpad=stop_mode=clone chaque fois que la parole finale dépasse la durée de la vidéo (zéro mot coupé), applique optionnellement la normalisation du volume EBU R128 (--loudnorm-lufs -18.0) pour que le volume reste cohérent entre les chapitres, encode -c:a aac -b:a 160k -movflags +faststart, et écrit output/act1_fr.vtt.
Lisez references/voiceover-and-commentary.md pour les transformations de commentaire Mauvais vs Bon, les remplacements phonétiques en langues non anglaises, et les workflows audio bilingues.
4. Encodage Web, concaténation multi-scène et lecteur HTML5 hors ligne
Encodage compatible Web et concaténation de bobine maître
Pour les présentations silencieuses (sans mux_narration.py), assurez la compatibilité universelle navigateur/QuickTime (yuv420p + atome moov déplacé) :
ffmpeg -y -i media/videos/scenes/1080p60/Act1RateLimiter.mp4 \
-c:v libx264 -pix_fmt yuv420p -crf 23 -movflags +faststart \
output/act1_silent.mp4
Pour assembler plusieurs MP4 d'actes narrés en une bobine maître continue :
printf "file 'act1_fr.mp4'\nfile 'act2_fr.mp4'\nfile 'act3_fr.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_fr.mp4
Lecteur HTML5 autonome optionnel
Lors de la livraison d'une présentation multi-chapitres ou d'un pack explicatif bilingue :
- Copiez
assets/video_player_template.htmlversoutput/index.html. - Personnalisez
PLAYER_CONFIG(chapters,sources, etmarkerscorrespondant aux timestamps des temps debeat_timings.json). Si vous livrez une seule langue, masquez ou omettez le bouton de langue secondaire dans#langGroup. - Gardez tous les styles, icônes SVG monochromes et scripts 100% en ligne afin que le bundle fonctionne hors ligne sans CDN externe.
- Note sur le service HTTP
206 Partial Content: Les navigateurs nécessitent des réponses HTTP Byte-Range (206) pour parcourir les timelines<video>et charger les fichiers<track>.vttproprement (file://bloque les pistes WebVTT via CORS, et le serveurhttp.serverde base de Python ne gère que200 OK). Pour les tests locaux, utilisez un serveur léger commenginxoupython3 -m http.server 8080.
5. Vérification du bundle de sortie (lint.py)
Exécutez lint.py avant de livrer pour vous assurer que la sortie est prête pour le web :
python3 .agents/skills/manim-video-explainer/lint.py output/master_fr.mp4 --vtt output/act1_fr.vtt --player output/index.html
lint.py vérifie :
- Format vidéo –
ffprobeconfirmelibx264avecyuv420p, et un flux audio AAC stéréo (silencieux ignoré). - Sous-titres WebVTT – L'en-tête
WEBVTTest présent ; les timbres des temps sont strictement croissants ; les timestamps-->ne dépassent pas lemin_visual_scorrespondant +lead_in_s + inter_beat_gap_s. - Contenu HTML5 hors ligne – Aucune source CDN (
http://,https://) ; aucun émoji ou caractère hors plan Unicode (\u200B..\uFFFF) en dehors du code ; laContent-Security-Policyen ligne interditconnect-srcsaufblob:etdata:.