Annonce animée sur narration
Le principe
Ce qu’on entend = ce qu’on voit. La narration dicte les timings. On enregistre d’abord, Whisper donne le temps de chaque mot, puis tout texte, scène, notification et effet se cale sur un mot. Dans le code, pas de numéros de frames : on écrit at: 'clients' et le temps est extrait de public/words.json. Nouvel enregistrement = npm run vo, et la vidéo se synchronise toute seule.
Aucune seconde à l’écran ne reste statique. Une seconde sans mouvement = le spectateur défile.
Règles d’or
- Tu n’entends pas. Le propriétaire est la seule oreille. Toute vérification audio de ta part est numérique (
silencedetect,ffprobe, retranscription). Il est interdit d’écrire « l’audio est correct » sans citer la sortie. - D’abord nettoyer les silences, ensuite retranscrire. Whisper se trompe sur les timings près des silences.
prep-vo.shfait les deux dans le bon ordre. - Après un changement audio, relancer
npm run vo. Sans ça, toute la synchronisation se décale. - La vidéo se termine quand la narration se termine + 8 frames (
DURATIONdanstime.ts).render-all.shimprime la durée de chaque fichier par rapport à la narration. S’ils diffèrent, quelque chose a été coupé. - Texte uniquement entre y=260 et y=1570. Au-dessus et en dessous, Instagram couvre.
validate()stoppe le rendu avec une explication si un texte sort de cette zone ou dépasse 1000px de large. - Les effets sont en dessous de la voix. Fixés dans
Ad.tsx: narration 1.15, musique 0.22, chaque effet multiplié parSFX = 0.38. « Les effets sont trop forts » = changer un seul nombre. - stills avant mp4.
npm run checkrend 10 frames dansout/stills. Les examiner toutes (lire les PNG) avant le rendu complet. - Contenu authentique uniquement. Chiffres, paiements et messages de clients : ce qui s’est vraiment passé. On peut masquer les noms, mais il est interdit d’inventer des résultats. À la fois à cause de la politique Meta et parce que les gens sentent le faux.
- Hébreu : Les polices (Rubik, Heebo, variables, OFL) sont déjà dans le template et chargées avec
delayRender. La racine est endirection: rtl. Les chiffres dans une étiquette reçoiventdirection: ltr.
Première installation du projet
cp -R ~/.claude/skills/animated-voiceover-ad/template ~/ads/<nom-de-lannonce>
cd ~/ads/<nom-de-lannonce>
npm install
npm run setup # Effets et musique depuis Mixkit
Il faut : Node 18+, ffmpeg, whisper-cli (brew install ffmpeg whisper-cpp) et le modèle whisper dans ~/.whisper/models/ggml-large-v3-turbo.bin (s’il manque, words.mjs affiche une commande de téléchargement).
Le pipeline
| Étape | Ce qu’on fait |
|---|---|
| 1. Script | Selon references/script-formula.md. Chaque phrase est pré-mappée à une scène. On montre au propriétaire avant qu’il enregistre. |
| 2. Enregistrement | Sur téléphone ou QuickTime, un seul fichier. Erreur ? On s’arrête une seconde et on recommence la phrase. Ensuite on supprime la mauvaise prise (voir references/audio.md). |
| 3. Nettoyage et transcription | npm run vo -- recording.m4a. Affiche tous les mots avec leurs timings. On montre la transcription au propriétaire et on corrige les mots mal retranscrits par Whisper directement dans public/words.json (uniquement le champ w). |
| 4. Mise en scène | On modifie uniquement src/Ad.tsx : KTS (texte cinétique), notifications, SCENES. Chaque temps = un mot, ['encore', 2] pour la deuxième occurrence. Détail des composants dans references/scenes.md. |
| 5. Vérification | npm run check (tsc + stills). On examine chaque still. |
| 6. Rendu | npm run render. Chaque musique dans MUSICS (Root.tsx) = fichier séparé dans out/, pour test A/B. |
| 7. Corrections | Chaque remarque du propriétaire = une modification unique : un mot différent dans at, volume, until. On ne réécrit pas une scène entièrement. |
Versions pour la pub
- Musique : Déjà intégrée, 3 compositions.
- Accroches différentes : Les 3 premières secondes sont cruciales. On enregistre 2-3 accroches différentes en fichiers séparés, on concatène chacune avec le corps via
ffmpeg concat, on lancenpm run vosur chaque concaténation et on rend dans un dossier séparé. ToutAd.tsxreste identique tant que les mots du corps sont les mêmes. - Sous-titres complets :
CAPTIONS = truedansAd.tsx. Une seule ligne, coupée selon la ponctuation. Utile car la plupart des vues sur pub se font sans son.
Problèmes connus
| Problème | Solution |
|---|---|
Le mot "X" n’a pas été trouvé dans la narration | Le message suggère des mots proches. En hébreu, Whisper colle ou sépare les préfixes (ו/ה/ל/ב). On utilise le mot tel qu’il apparaît dans words.json. |
| Le rendu montre du code ancien | Ancien bundle dans $TMPDIR/remotion-webpack-bundle-*. render-all.sh le supprime. |
| TypeScript 7 installé | Remotion nécessite [email protected]. |
delayRender se bloque | --timeout=180000 (déjà dans render-all). |
| Le tampon rétrécit avant d’atterrir | overshootClamping: true sur son spring (déjà présent). |
| Mots collés après nettoyage des silences | Augmenter stop_duration dans prep-vo.sh à 0.35. |
| Seconde « ennuyeuse » | Ajouter une ligne de KT, une notification ou un effet sur ce mot. Aucune seconde sans événement. |
La démo dans le template
Le template est livré avec une narration de démonstration (voix TTS, public/vo.wav + public/words.json) et un Ad.tsx synchronisé, pour voir tout fonctionner avant d’enregistrer : npm install && npm run setup && npm run check. Pour une vraie annonce : npm run vo -- recording.m4a écrase les deux, puis on réécrit Ad.tsx selon le script.