Production de vidéo promotionnelle (chaîne d'outils locale)
Indépendant de l'agent : écrit pour tout agent IA codeur capable de lire des fichiers et d'exécuter des commandes shell — installé nativement pour Claude Code, GitHub Copilot CLI / Microsoft Scout et Google Antigravity via install.sh ; utilisable depuis Cursor, Codex, Windsurf et autres comme un runbook en markdown simple.
Chaîne d'outils démontrée pour la première fois par Idan Shimon (Microsoft, github.com/idanshimon) — son vidéo explicative de 58 secondes, produite par un agent IA utilisant cette pile localement à 0 $, est ce à partir de quoi cette compétence a été élaborée.
Ne pas supposer d'emplacement d'installation : définir PROMO_VIDEO_SKILL_DIR sur le répertoire contenant ce SKILL.md. Son répertoire scripts/ contient le script TTS, son aide à la prononciation des acronymes, et les dépendances TTS épinglées ; assets/ contient le squelette de composition, les modèles de composants spatiaux et le schéma du journal des décisions ; references/ contient l'amorçage de la chaîne d'outils (install.md) à effectuer une fois par machine.
Principe fondamental : le script est verrouillé en premier ; chaque nombre en aval est DÉRIVÉ de l'audio de narration mesuré, jamais deviné.
Promotion, pas explication : cette compétence vend le POURQUOI — public, problème, transformation, preuve, un seul appel à l'action. Le mécanisme ("comment ça marche") obtient au plus un temps, et seulement lorsque l'utilisateur le demande ; les demandes d'enseignement du fonctionnement appartiennent à la compétence complémentaire explainer-video.
Profils des outils
| Outil | Emplacement | Version éprouvée | Licence | Rôle | Invocation |
|---|---|---|---|---|---|
| OpenMontage | ~/OpenMontage | checkout juillet 2026 | AGPLv3 (outillage uniquement ; rien n'est livré dans la vidéo) | Gouvernance du pipeline : ordre des étapes, journal des décisions, portes, discipline d'auto-révision post-rendu | Lire son AGENT_GUIDE.md + pipeline_defs/animation.yaml ; vous (l'agent) êtes l'orchestrateur — aucun démon ne s'exécute |
| Kokoro | PyPI | Kokoro-82M (hexgrad), kokoro==0.9.4 | Code Apache-2.0 ; poids du modèle Apache-2.0 | TTS de narration, WAV mono 24 kHz par scène | Environnement virtuel Python 3.12 ; KPipeline(lang_code="a"), voix af_heart, paramètre speed |
| HyperFrames | ~/hyperframes | 0.7.57, compilé à partir des sources | Apache 2.0 | Composition HTML/CSS/GSAP → MP4 déterministe via Chrome headless | node ~/hyperframes/packages/cli/dist/cli.js <cmd> |
| FFmpeg | Sources ~/FFmpeg ; binaire dans un répertoire de build | git master (ère N-125608) | Build GPL (libx264 activé) | Synthèse musicale (aevalsrc), encodage, QA ffprobe, loudness, packaging plateforme | Mettre le répertoire de build sur PATH avant les commandes hyperframes |
| bun | npm-global | 1.3.14 | — | Compile le monorepo hyperframes | npm i -g bun (brew cask a échoué silencieusement une fois) |
| Node.js | nvm | 24.x (≥22 requis) | — | Exécute le CLI hyperframes | — |
| Python | géré par uv | 3.12 | — | Inférence Kokoro | Toujours un environnement virtuel (uv venv --python 3.12 .venv) ; propre black + pylint |
| espeak-ng | Homebrew ou apt | quelconque | GPL | Repli G2P Kokoro (requis) | macOS : brew install espeak-ng ; Debian/Ubuntu : sudo apt install espeak-ng |
| gsap | fichier local | 3.x gsap.min.js | Licence standard GSAP | Animation temporelle dans la composition | Copier dans video/assets/ — JAMAIS de <script src> CDN (déterminisme hors ligne) |
Vérification de l'environnement (une fois par machine)
export FFMPEG_BUILD_DIR="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}"
export HYPERFRAMES_DIR="${HYPERFRAMES_DIR:-$HOME/hyperframes}"
export PATH="$FFMPEG_BUILD_DIR:$PATH"
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" doctor
FFmpeg, FFprobe, Node et Chrome doivent tous réussir avant la production (les autres lignes de doctor sont des composants optionnels que cette compétence n'utilise pas). Si l'un des quatre échoue, suivre $PROMO_VIDEO_SKILL_DIR/references/install.md — l'amorçage complet une fois par machine : paquets système (macOS/Linux), clonage des outils source (y compris le checkout de référence OpenMontage), compilation de FFmpeg avec libx264 à partir des sources, construction du CLI HyperFrames, et conseils d'autorisation pour l'agent. Ne pas charger ce fichier lorsque doctor réussit déjà.
Chrome : le CLI utilise automatiquement un chrome-headless-shell mis en cache sous ~/.cache/puppeteer/ (téléchargement unique si absent). La première exécution TTS télécharge également les paquets Python et les poids Kokoro-82M. Après ces téléchargements uniques, la synthèse, la validation, le rendu et le packaging ont un chemin hors ligne.
Structure du projet (créer par vidéo, propre dépôt git, commits atomiques)
<projet>/
.venv/ # uv venv, python 3.12
.gitignore # .venv/ __pycache__/ video/snapshots/ .DS_Store
README.md # comment c'était fait + reproduction (écrire EN DERNIER)
production/
research/research-brief.md # faits avec sources + tableau honnête des concurrents
proposal.md # concept, liste des scènes, promesse de livraison, décision d'exécution
script/script.md # narration VERROUILLÉE
scene_plan/scene-plan.md # visuels par scène + notes de mouvement
checkpoints/decision-log.json # ajout seul ; la clé est (catégorie, sujet)
checkpoints/self-review.md # preuves post-rendu
assets/audio/ # WAV kokoro + durations.json
renders/ # master + dérivés plateforme
youtube/ # metadata.md + thumbnail.jpg
tools/tts_generate.py # copier depuis cette compétence, éditer SCENE_NARRATIONS
tools/tts_pronounce.py # depuis scripts/, prononciation des acronymes (ne pas éditer)
tools/pronunciation.local.json # lexique optionnel du projet
video/
index.html # LA composition
assets/gsap.min.js # fichier local
assets/audio/ # copies des WAV (chemins relatifs à la composition)
Étape 1 — Recherche (d'abord hors ligne)
Explorer le propre dépôt/README/doc du sujet — en recherchant les résultats et les preuves (chiffres, adoption, résultats avant/après), pas l'architecture. Produire research-brief.md avec :
- Bloc de positionnement (obligatoire — le script ne peut pas être verrouillé sans lui). Une phrase chacun : public (à qui cela s'adresse), problème (ce qu'ils ressentent aujourd'hui), transformation (ce qui change pour eux), différenciateur (la seule raison de choisir ceci). C'est le message ; chaque étape en aval le sert.
- Faits sur le produit, chacun vérifié par rapport à la source — conservés seulement s'ils soutiennent un avantage (le test "et alors ?" : qu'est-ce que le spectateur en retire ?).
- Un tableau des concurrents (faiblesse → comment le sujet y répond). Règle d'honnêteté : concéder de vrais points aux concurrents (un ✓ dans leur colonne achète de la crédibilité pour la vôtre).
- Une liste "revendications autorisées pour le script".
Étape 2 — Proposition + décisions
Écrire proposal.md : reformuler le bloc de positionnement, concept de 5 à 7 scènes, cible ≈60s, promesse de livraison ("animé, pas un diaporama"), tableau de la chaîne d'outils. Enregistrer dans decision-log.json — commencer à partir du schéma vide à $PROMO_VIDEO_SKILL_DIR/assets/decision-log.json
(champs : category, subject, selected, options_considered,
rejected_because, confidence) : pipeline_selection,
render_runtime_selection (enregistrer Remotion comme alternative envisagée — règle dure OpenMontage), composition_mode (atelier pour le travail héroïque),
voice_selection, music_selection, output_profile, approval_gates, et
cta_selection — EXACTEMENT un appel à l'action (mettre une étoile au dépôt / installer / regarder la démo / visiter le site) ; les vidéos promotionnelles meurent en terminant sur une liste de fonctionnalités au lieu d'une demande. Si le
brief de l'utilisateur a fixé un choix, marquer approved_by: "user brief". Changé une décision plus tard ? AJOUTER une nouvelle entrée avec la même (catégorie, sujet) — ne jamais modifier l'ancienne.
Étape 3 — Script (le point de verrouillage)
~150-155 mots ≈ 60s à kokoro af_heart vitesse 1.1. Arc promotionnel (vendre le pourquoi) : accroche/enjeux/problème → révélation (nom + la transformation promise) → avantages dans le monde du spectateur → preuve (comparaison honnête, résultats réels) → aspiration → l'unique CTA du journal des décisions. Les fonctionnalités apparaissent uniquement comme preuve d'un avantage : chaque phrase doit passer le test "et alors ?" (qu'est-ce que le spectateur en retire ?) ou être réécrite. Au plus UN temps expliquant le fonctionnement, et seulement lorsque l'utilisateur le demande — enseigner le mécanisme est le travail de la compétence explainer-video. Voix : deuxième personne, présent, phrases courtes et percutantes ("vous livrez plus vite", pas "cela permet une livraison plus rapide"). Écrire les acronymes naturellement ("OID", "MCP", "IA") à la fois dans le script et le texte à l'écran — au moment de la synthèse, tools/tts_pronounce.py dérive la forme parlée (mot vs lettre par lettre) à partir de son lexique, et les acronymes inconnus sont épelés lettre par lettre et signalés. Classifier les termes du domaine dans un tools/pronunciation.local.json optionnel ({"NASA": "word", "ECG": "letters", "NGINX": "engine X"}). Éviter l'emphase en MAJUSCULES : un mot en majuscules est traité comme un acronyme. Budget de mots par scène ≈ sa part de 60s × 2,55 mots/s.
Étape 4 — Narration + dérivation du timing (l'astuce qui porte le système)
Créer l'environnement Python 3.12 du projet à partir des dépendances directes épinglées fournies, copier le modèle générique, et éditer SCENE_NARRATIONS uniquement :
uv venv --clear --python 3.12 .venv
uv pip install --python .venv/bin/python \
-r "$PROMO_VIDEO_SKILL_DIR/scripts/tts-requirements.txt"
mkdir -p tools
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_generate.py" tools/tts_generate.py
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_pronounce.py" tools/tts_pronounce.py
PYTORCH_ENABLE_MPS_FALLBACK=1 .venv/bin/python tools/tts_generate.py
mkdir -p video/assets
curl -fL https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js \
-o video/assets/gsap.min.js
La première synthèse télécharge les poids Kokoro-82M (~330 Mo) ; les exécutions ultérieures utilisent le cache et peuvent être hors ligne. Elle écrit les WAV par scène + durations.json. Si l'exécution imprime un AVERTISSEMENT d'acronyme inconnu, classifier ces jetons dans tools/pronunciation.local.json et réexécuter avant de dériver le timing.
Considérer 150-155 mots à la vitesse 1.1 comme une estimation de départ, pas un calibrage. Total >62s ? Augmenter la vitesse ou couper des mots, puis mesurer à nouveau.
Dériver la timeline (intervalle ≈0,3-0,5s entre les scènes) :
n_start[0] = 0.5 # intro
scene_start[i] = n_start[i] - 0.15..0.4 # les visuels atterrissent juste avant la VO
n_start[i+1] = n_start[i] + dur[i] + 0.3..0.35
TOTAL = dernière n_end + 0.6..0.9 fin (fondu au noir)
Ces nombres vont à TROIS endroits qui doivent concorder : les data-start/data-duration des scènes, les valeurs <audio data-start>, et les constantes de timeline const S1..Sn en JS. Copier les WAV dans video/assets/audio/.
Étape 5 — Fond musical (aucun téléchargement)
Générer un pad synthétique déterministe avec le FFmpeg compilé à partir des sources. Définir TOTAL_SECONDS sur le total dérivé de la composition ; aucun échantillon externe ou référence privée n'est nécessaire :
TOTAL_SECONDS=60.5
FADE_SECONDS=3.5
FFMPEG_BIN="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}/ffmpeg"
FADE_OUT_START="$(awk -v total="$TOTAL_SECONDS" -v fade="$FADE_SECONDS" \
'BEGIN { printf "%.3f", total - fade }')"
mkdir -p production/assets/audio
"$FFMPEG_BIN" -hide_banner -y \
-f lavfi \
-i "aevalsrc=0.07*(sin(2*PI*110*t)+0.65*sin(2*PI*110.7*t)+0.45*sin(2*PI*164.81*t)+0.30*sin(2*PI*220.9*t)):s=48000:d=${TOTAL_SECONDS}" \
-af "highpass=f=55,lowpass=f=1200,afade=t=in:st=0:d=2,afade=t=out:st=${FADE_OUT_START}:d=${FADE_SECONDS},pan=stereo|c0=c0|c1=c0" \
-c:a pcm_s16le production/assets/audio/music-bed.wav
Le garder clairsemé ; mixé à data-volume="0.12..0.16" il se lit comme une atmosphère — pour l'énergie promotionnelle, se situer vers le haut de cette plage, jamais au-dessus de la narration. Copier le WAV dans video/assets/audio/ et utiliser une piste audio distincte.
Étape 6 — Création de la composition (contrat HyperFrames)
Commencer par $PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html (il incorpore chaque règle ci-dessous) ; avant d'écrire du HTML, parcourir $HYPERFRAMES_DIR/skills/hyperframes-core/SKILL.md.
Structure : index.html autonome, racine <div data-composition-id="X" data-start="0" data-duration="TOTAL" data-width="1920" data-height="1080">, scènes en tant que <section class="clip" data-start data-duration data-track-index="1">, UNE gsap.timeline({paused:true}) enregistrée de manière synchrone à window.__timelines["X"], positions des interpolations = S<i> + décalage_local.
Règles qui coûtent du temps de débogage réel — les respecter d'emblée :
- Chaque
<video>/<audio>a besoin d'unidexplicite et non vide. Dans HyperFrames, l'audio sans peut jouer dans un aperçu du navigateur mais rend le SILENCE (les erreurs de lint le signalent ; ne pas ignorer). - Ne jamais mettre un
transform:initial dans le CSS sur un élément interpolé par GSAP — utilisertl.fromTo(el, {y:24, opacity:0}, {y:0, opacity:1, ...}); GSAP possède la transformée complète. (opacity: 0seul dans le CSS est acceptable pour les révélations uniquement.to().) - Le remplissage d'arrière-plan va sur un ENFANT plein écran (
position:absolute; inset:0), jamais sur la racine (l'arrière-plan racine peut rendre en noir). - Les éléments média sont des enfants directs de la racine,
data-track-indexsur des pistes audio distinctes (par ex. 7=VO, 8=musique). - Pas de répétitions infinies (
repeat: -1interdit — utiliser fini, par ex. clignotement de curseurrepeat: 9, yoyo: true), pas deDate.now/Math.random, pas de récupérations réseau, pas de scripts CDN. idunique partout ; animer uniquement les transformées/opacités (+strokeDashoffsetpour les dessins de lignes SVG).- Éléments apparaissant au milieu d'une scène : état initial caché + interpolation à
S<i>+décalage.fromToaimmediateRenderdonc les recherches avant l'interpolation montrent l'état from — c'est ce qui le rend sûr pour la recherche. - Jetons de conception dans les variables CSS
:root= le thème ; échanger ground/ink/accent + la pile de polices pour changer l'apparence. Utiliser une police que le moteur de rendu fournit (JetBrains Mono) — les polices système sont de toute façon substituées (les infos de lint indiquent le mappage). - Garder le texte secondaire ≥ WCAG AA sur la couleur de fond —
checkÉCHOUE les builds sur le contraste et imprime la couleur conforme à utiliser. - Chevauchements/défilements intentionnels : marquer avec
data-layout-allow-overflow/-overlap/-occlusionau lieu de laisser les résultats inexpliqués.
Conseil de motif de scène : montrer l'ÉTAT DE RÉSULTAT — le monde du spectateur amélioré
(contraste avant/après, le problème disparu, le résultat à l'écran). Tirer
les aperçus de soutien de l'interface utilisateur/domaine réel du sujet (fausse palette de recherche,
terminal, arbre, table) comme preuve d'un avantage, pas d'une visite guidée du mécanisme. Les
templates d'arc narratif ; les motifs ne doivent pas l'être.
$PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html a des annotations de motif structurées.