Vidéo explicative procédurale
Environnement
- Installez Manim :
uv tool install manim. Si pycairo ne trouve pas pkg-config, exécutez d'abordbrew install pkgconf. - La narration et la transcription utilisent l'outil en ligne de commande MiniMax
mmx. Installation :npm install -g mmx-cli, puis connectez-vous avecmmx auth login --api-key <votre clé API MiniMax>. Simmxsignale qu'il n'est pas connecté ou que le solde est insuffisant, arrêtez-vous et informez l'utilisateur. contact_sheet.pyutilise Pillow du Python système, ainsi queffmpegetffprobe(brew install ffmpeg).- macOS utilise PingFang SC par défaut. Sur Linux, installez d'abord une police chinoise, par exemple
fonts-noto-cjksous Debian/Ubuntu. La classe de base vérifie que la police existe et s'arrête si elle est absente. Pour des polices personnalisées, utilisezEXPLAINER_CN_FONTetEXPLAINER_EN_FONT; la police doit prendre en charge les caractères du texte. Sur les autres systèmes, installez Manim, Pillow et FFmpeg avec le gestionnaire de paquets correspondant. - La plupart des exemples de 3Blue1Brown en ligne sont écrits avec
manimlib; leur API diffère de Manim Community, ne les copiez pas directement. - Les
scripts/de ce skill :gen_audio.py: génère la narration et les horodatages mot par mot.explainer_base.py: classe de base des scènes, découpage des sous-titres, chronométrage, mise en page et nettoyage.contact_sheet.py: capture d'écran par nœud d'animation et segment de sous-titre le plus long, en vérifiant d'abord la disposition des sous-titres sur deux lignes.verify_video.py: vérifie la police des sous-titres, les horaires de narration et le décodage de la vidéo finale.test_subtitles.py: utilise le rendu Manim réel pour reproduire d'anciens bugs et valider les régressions de sous-titres.
Processus
Placez le répertoire du projet dans ~/video-studio/explainers/<sujet>/.
- Écrivez le plan. Suivez l'ordre « posez une question → exemple concret → comment l'exemple évolue → règle générale → testez avec un nouvel exemple → répondez à la question initiale », découpé en 5 à 10 segments, chacun ne traitant qu'un seul point. Si la durée totale dépasse 3 minutes, envoyez d'abord le plan à l'utilisateur pour confirmation.
- Écrivez
narration.json. Les clés sont les noms des segments (s1,s2, …) et les valeurs sont les narrations. Rédigez les narrations dans un style oral, avec des phrases courtes, chaque phrase ne traitant qu'un sujet. Si le skillchinese-writing-styleest installé, suivez ses consignes. Chaque segment fait 3 à 6 phrases. Si le TTS risque de mal prononcer un mot ou un acronyme anglais, écrivez la valeur sous la forme{"text": "texte affiché à l'écran et aligné", "tts": "prononciation donnée au TTS"}. - Exécutez
python3 <skill>/scripts/gen_audio.py <répertoire du projet>. Le timbre par défaut de la narration estChinese (Mandarin)_Warm_Girl. Pour les années, dates et autres nombres, écrivez la prononciation chinoise danstts, tout en conservant les chiffres arabes à l'écran danstext. Pour ne refaire que certains segments, ajoutez--only s3,s5. Si le message « la transcription et la narration ne correspondent pas » apparaît, déterminez si le TTS a mal prononcé un mot-clé. Si c'est le cas, modifiez lettsde ce segment et refaites avec--only. Si les horodatages mot par mot manquent, complétez d'abord la transcription ; les sous-titres ne peuvent pas être livrés avec une estimation temporelle basée sur les proportions de la longueur du segment. - Introduisez le script partagé
scripts/explainer_base.pyvia un lien symbolique, puis écrivezscene.py. Pour les projets existants, supprimez d'abord l'implémentation locale des sous-titres et la redéfinition destart/finish, conservez la logique visuelle, sauvegardez l'ancienne classe de base et remplacez-la par un lien. Le projet ne doit contenir que les éléments visuels de l'explication ; les sous-titres sont générés uniformément par la classe de base. - Dans le répertoire du projet, exécutez
manim -ql --disable_caching scene.py <nom de la classe>. Si l'erreur « l'animation ne suit pas la narration » apparaît, réduisez lerun_timede l'animation concernée ou supprimez l'animation, puis re-rendez. - Exécutez
python3 <skill>/scripts/contact_sheet.py <vidéo de prévisualisation> <répertoire du projet>et examinez chaque collage. Vérifiez la disposition des sous-titres, les chevauchements d'éléments et la direction des flèches. Les expressions animées dont la durée est estimée doivent être confirmées avec les horodatages des mots avant et après dansaudio/<nom du segment>.words.json. - Exécutez
manim --resolution 1920,1080 --frame_rate 30 --disable_caching scene.py <nom de la classe>. N'utilisez pas-qh(60 fps). Exécutez<skill>/scripts/verify_video.py <vidéo finale> <répertoire du projet>avec le Python utilisé pour installer Manim. L'interpréteur correspondant àuv tool install manimest$(uv tool dir)/manim/bin/python. - Les commandes de vérification doivent se terminer avec un code de sortie 0 et le champ
passeddeqa/video-check.jsondoit êtretrue. En cas d'échec de la police, du timing audio ou du positionnement des sous-titres, consultez le rapport, les captures d'écran et le fichierwords.jsoncorrespondant, corrigez, puis re-rendez et vérifiez. Les sous-titres estimés ne peuvent pas être approuvés sur la seule base d'une capture d'écran. - Une fois la vérification réussie, copiez le fichier sous
<sujet>-explication.mp4, informez l'utilisateur du chemin de la vidéo et, si l'environnement le permet, envoyez-la directement à l'utilisateur. Supprimez les prévisualisations basse résolution sousmedia/.
Code de la scène
from explainer_base import *
class Topic(ExplainerScene):
def s1(self):
self.start("s1") # Joue la narration et affiche automatiquement les sous-titres synchronisés
title = cn("Taux d'apprentissage", 60, HI)
self.play(Write(title))
self.until("Taux d'apprentissage trop petit") # Attend que la narration atteigne cette phrase
self.play(title.animate.to_edge(UP))
self.finish() # Attend la fin de la narration, nettoie les sous-titres et fond l'image
- Écrivez une méthode portant le même nom que le segment pour chaque segment.
- Ne redéfinissez pas
start,finishou la mise à jour des sous-titres dans le projet. Si l'utilisateur ne veut explicitement pas de sous-titres, définissez l'attribut de classeSUBTITLES = False. - Pour changer d'image dans un segment, fondez ou retirez uniquement les objets que vous avez créés, en conservant les sous-titres de la classe de base. N'effacez pas tous les
self.mobjectsni n'appelezself.clear(). - Les phrases dans
untildoivent apparaître textuellement dans letextde la narration de ce segment, et n'apparaître qu'une seule fois. - Utilisez
cn()pour le chinois,en()pour l'anglais. La taille de police ne doit pas être inférieure à 22. - Utilisez uniquement les 4 couleurs de la classe de base :
MAINpour le corps,HIpour l'accent,OKpour le correct,BADpour l'erreur. Une couleur doit avoir la même signification dans toute la vidéo. - Maximum 3 groupes d'éléments à l'écran. Avant d'introduire un nouveau contenu, fondez d'abord ce dont il n'est plus parlé.
- Réservez les 20 % inférieurs de l'écran aux sous-titres ; placez les graphiques et les textes explicatifs au-dessus de cette zone.
- Lorsque la narration mentionne un élément, cet élément doit apparaître, changer de couleur ou se déplacer.
Pièges courants
- Après avoir modifié le code partagé des sous-titres, exécutez
test_subtitles.pyavec le Python ayant installé Manim. Il doit réussir les vérifications d'alignement pour l'attente réelle, la transition entre segments, les sous-titres sur deux lignes, les phrases courtes répétées, la pagination des longues phrases et les différentes prononciations. Modifier manuellementrenderer.timeet appeler l'updater ne permet pas de valider le comportement d'attente statique de Manim. Textne passe pas automatiquement à la ligne ; utilisez\npour couper les longues phrases.- Pour le texte placé dans un cadre, limitez la largeur avec
fit(texte, largeur_du_cadre * 0.8). - Lorsque vous barrez une option avec
Cross, barrez uniquement le texte, pas les flèches ; changez les flèches en couleurBAD. - Pour colorer des mots dans une phrase, utilisez
Text(..., t2c={"mot": couleur}).