Que fait B narrée ?
Déclencheurs supplémentaires
présentation vidéo de la PR, faire un enregistrement de démo, démo avec voix off, enregistrer les deux piles, vidéo explicative, filtre drawtext manquant, Playwright recordVideo, shell headless manquant
Un MP4 par jeu d'affirmations : carte titre, clip A, clip B, carte evidence, carte verdict, chaque segment est voice.
Conçu pour macOS sur Apple Silicon (say, le cache Playwright dans ~/Library/Caches/ms-playwright, chrome-mac-arm64). Ailleurs, définissez CHROMIUM_EXE et utilisez --eleven pour la voix. Nécessite Bun, ffmpeg et Python 3.
Procédure
S=${SKILL_DIR:-~/.claude/skills/narrated-ab-video}
$S/preflight.sh /tmp/vid # chromium, playwright ffmpeg, ffmpeg, say voice
bun $S/record.ts a.json raw/a-run # un scénario par côté (copier examples/scenario.json)
bun $S/record.ts b.json raw/b-run
bun $S/cards.ts cards.json cards # PNGs titre/evidence/verdict + bannière-A/B.png
python3 $S/build.py plan.json # [--voice Samantha] [--rate 182]
open -a "QuickTime Player" demo.mp4 # ouvre en pause ; ne le lisez jamais vous-même
Exemples dans examples/: scenario.json (étapes : wait, select, click, clickIfPresent, type, send, waitStable, expand, fetchText, waitText, probeEnabled, reload ; voir l'en-tête de record.ts), cards.json, plan.json. Les chemins du plan sont relatifs au fichier plan. Validé de bout en bout sur une construction de 4 segments, 40s à partir de deux vrais clips.
Pourquoi c'est construit ainsi (frictions identifiées)
- Le shell headless épinglé de Playwright n'est souvent pas téléchargé.
record.tsetcards.tslancent la version complètechromium-*la plus récente dans le cache (ou$CHROMIUM_EXE) au lieu d'échouer. - recordVideo nécessite sa propre version de ffmpeg. Son absence signifie pas de webm. preflight lit la révision depuis
browsers.jsonet exécutebunx playwright install ffmpeg. - ffmpeg de Homebrew peut manquer
drawtext. Les bannières sont des PNG transparents générés parcards.ts, superposés dans build.py. Pas de recherche de polices ou de filtres. - Les réponses du modèle prennent 10 à 60 secondes de silence.
sendmarque "envoyé" etwaitStablemarque "répondu". build.py joue cet intervalle àspeed(défaut 10x) et le reste à 1x. - La narration est généralement plus longue que le clip. build.py gèle la dernière image (
tpad stop_mode=clone) jusqu'à la fin de la voix, de sorte qu'une réponse reste à l'écran pendant qu'elle est expliquée. - Entrée dynamique (codes OTP, IDs d'un tour précédent) : mettez
{{fn:NAME}}dans une étapetypeet exportezNAME(page, state)depuishooks.tsà côté du scénario.state.lastJsoncontient le dernier JSON POST correspondant àcaptureJsonFrom. - Sélecteurs : les boutons sont appariés par rôle et nom regex (
"Outils utilisés","^Envoyer$"). Le CSS est uniquement pour les inputs et les selects. Trouvez les noms avec une capture d'écran rapide avant de scripter. - La concaténation nécessite des encodages identiques. Chaque segment est ré-encodé avec le même codec, débit et configuration audio, puis assemblé avec
-c copy. - Meilleure voix :
ELEVENLABS_API_KEY=... build.py plan.json --eleven <voice_id>(modèle eleven_multilingual_v2 ; les clips sont mis en cache par hash voix+texte, donc les réexécutions ne consomment pas de caractères). Gardez la clé dans un fichier 0600, jamais dans le plan, et supprimez-la après. - Démo simple face (sans A/B) : enregistrez un scénario, donnez une seule bannière à
cards.json, et découpez le même clip en plusieurs segments du plan, une affirmation chacun, avec desspansaccélérant les attentes (ex. 5x). Même construction, pas de côté A. - L'utilisateur peut coller juste
<voice_id> <key>. Cela signifie reconstruire avec--eleven <voice_id>. Une voix plus lente étire la vidéo (72s avecsaydevient 92s), car build.py gèle la dernière image jusqu'à la fin de la voix. - Re-mettre en place une démo démontée avant le tournage : l'authentification à courte durée de vie (JWT locaux, URL de tunnel, ressources cloud temporaires) est généralement morte le lendemain. Re-vérifiez avec un appel API avant d'enregistrer ; un "401 JWT token expiré" aurait filmé un tour dont les résultats n'arrivent jamais.
- Clip brut perdu : découpez les images de ce côté depuis un MP4 de sortie précédent (trouvez son span en échantillonnant la couleur du coin de la bannière), sauvegardez-le dans
<dir>/a.webmavec-f mp4et un marks.json vide, et utilisez un span explicite. Supprimez labannercar elle est intégrée. - Audio :
say -oécrit uniquement des fichiers. Ne lisez jamais la vidéo ou l'audio sans l'accord explicite de l'utilisateur. Ouvrez-la en pause et remettez-la. - Prouvez avant de filmer. Exécutez l'A/B et vérifiez le résultat (API ou log) d'abord. La vidéo montre un résultat prouvé ; ce n'est pas le test.
- Les cartes doivent correspondre à la taille du clip. build.py ne redimensionne pas. Un enregistrement avec
"viewport": [1440, 900]nécessite"size": [1440, 900]dans cards.json, sinon la concaténation aura des tailles d'image mélangées. - Les UIs asynchrones ont besoin de marques, pas d'attentes fixes.
waitTextmarque quand une regex apparaît (outimeout:<mark>),probeEnabledavecwaitMsinterroge un bouton et enregistreprobes[]dans marks.json,tryMssur type/send/click marqueskipped:<step>au lieu d'annuler, de sorte qu'un côté "reste désactivé" termine quand même le clip. - Sondez le même élément que celui sur lequel vous cliquez. Une sonde CSS (
button[aria-label=...]) a touché un bouton correspondant différent de celui du rôle-nomsendet a lu 'désactivé' alors que l'envoi passait ; utilisez"role"dansprobeEnabled. - Les superpositions en mode développement (query devtools, toasts d'erreur) apparaissent dans le clip. Ajoutez
{"clickIfPresent": "^Fermer .* devtools$"}après la première attente. - Attendre une réponse qui ne se "stabilise" jamais : un badge de temps écoulé en direct modifie le texte du corps toutes les secondes. Vérifiez
marks.jsonpour une marquerepliedavant de construire. - Vérifiez que la preuve est dans un texte visible. Les cartes d'outils réduites peuvent ne pas se développer (
expandclique silencieusement sur rien). Lisezmarks.jsonbodypour la chaîne de l'affirmation. Si elle manque, changez le prompt ou prouvez-la sur une carte à la place. - Prouvez la parité avant de filmer une affirmation "la base échoue". Une fois, une base locale a échoué pour une raison (un flag activé localement, désactivé à distance) que l'environnement réel ne rencontre jamais, et la narration l'a affirmé. Faites correspondre d'abord les flags distants, puis filmez.
- Carte titre : commits testés et flags de chaque côté. Une affirmation par segment de clip. Terminez par une carte verdict.
Liens connexes
narrated-screenshot: un seul visuel annoté au lieu d'une vidéo.pr-ab-evidence: la méthode rouge/vert A/B dont cette vidéo est le résultat filmé.