Que fait make-ai-short — courts métrages génératifs, de bout en bout ?
Les pixels proviennent d'un modèle vidéo ; Remotion ne fait que composer les clips + les sous-titres. Éprouvé sur
blue-man #1 « The Door ». Compagnon de /make-short (100 % TSX) — même discipline, plus trois
portes que TSX n'a jamais eues : coût annoncé avant génération, personnage verrouillé via
character.json, boucle fixée via conditionnement de l'image de fin.
Exécutez tout depuis la racine du dépôt. Nécessite FAL_KEY (clips + images) et ELEVENLABS_API_KEY
(voix) dans .env.
Les trois règles de fer
- NE JAMAIS générer un personnage à partir d'un texte deux fois.
ai-shorts/<char>/character.json+character.pngest la référence verrouillée. Toute image fixe qui nécessite le personnage estgen_image.py --ref character.png; chaque clip est une image-vers-vidéo à partir d'une image qui contient déjà le personnage. Si une fiche personnage n'existe pas encore, en créer UNE (et la soumettre à l'œil de l'utilisateur) est une étape à part avant toute autre chose. - ANNONCER LE COÛT AVANT DE LE DÉPENSER. Dérivé, non cité : Seedance 1080p audio désactivé =
h×w×24×dur/1024tokens × $1,2/1M ≈ 0,0583 $/s (API de tarification fal validée le 14/07/2026 — voir ai-shorts/IDEAS.md pour le tableau complet + les pièges Wan/Veo-Lite). Toujoursgenerate_audio:false— la voix vient d'ElevenLabs, le son vient de notre pipeline SFX. - Boucle par CONTRAINTE, pas par chance. La véritable image 0 du plan 1 (extraite du clip, pas de l'image
d'invite) est la cible de la boucle. Le plan final lui transmet
end_image_url(Seedance/Kling), et la composition se pose exactement sur cette image fixe pendant les ~10 dernières images (motifLoopSettledansremotion/src/shots/ai-1/Ai1Door.tsx).
Contrat des artefacts
ai-shorts/<name>/
script.md · beats.json — même contrat que /make-short (vo[] avec temps réels des mots)
character.json — LA RÉFÉRENCE VEROUILLÉE (+ bloc video_model = le modèle choisi)
shots/NN-slug.png — image de DÉPART par plan (gen_image.py --ref character.png [--ref scene])
shots/NN-slug.json — sidecar gen_clip (modèle, payload, id de requête)
shots/upload-urls.json — URLs de stockage fal (les uploads sont réutilisables entre les re-rolls)
voice/ · sfx-plan.json · output/ [voice/output gitignoré]
remotion/src/shots/ai-N/ — composition + vo.gen.ts
media/projects/<name>/ — les copies des clips pour staticFile() (commit — les pixels ne sont
pas reproductibles ; les copies de travail shots/*.mp4 sont gitignorées)
Ordre des opérations (voix avant pixels)
- Script — script.md + beats.json. Accroche composée à l'image 0 ; la thèse doit VOULOIR la boucle (blue-man #1 : « l'arrivée est un mythe » → fin = marcher vers la même porte). ~80-100 mots, 2,5-2,7 mots/s. Jamais de CTA de fin d'engagement — terminer sur la récompense, la boucle est la fin.
- Voix D'ABORD —
python tools/gen_voice.py --beats ... --emit-ts remotion/src/shots/ai-N/vo.gen.ts(voix et modèle depuis.env, ou un bloc"voice"dans beats.json quand une série veut un narrateur comme GeorgeJBFqnCBsd6RMkjVDRZzbavec les tags eleven_v3 ; agir sur les indicateurs LINE CHECK comme dans make-short Étape 4 avant toute génération de clip). Les temps réels des mots décident des fenêtres de plans ; Seedance accepte des durées entières 4-12 s, donc arrondir les durées de plans AU-DESSUS des fenêtres. Dialogue (deux personnages ou plus) : le champ"cast"de beats.json associe chaque locuteur à soncharacter.json(dont le bloc"voice"est utilisé), chaque ligne VO nomme son locuteur dans"who", et--pack 0.3dispose les lignes bout à bout à partir de leurs longueurs réelles au lieu de les compresser dans des fenêtres estimées. Une voix par personnage vit dans son character.json, nulle part ailleurs. Bouches en dialogue : le modèle vidéo ne sait pas qui parle ; il fait parler les auditeurs, fige les locuteurs et étire un rire sur la phrase suivante. Après les clips, exécuterpython tools/mouth_sync.py ai-shorts/<name>(le champ"shots"de beats.json donne à chaque clip son début global et sa durée ; le bloc"mouth"de chaque character.json donne côté, rayon, zone, style etkeep_above). Il efface les bouches du modèle et dessine la bouche de chaque personnage à partir du volume de la piste vocale. QA de la même manière : les deux visages toutes les demi-secondes par rapport à celui qui parle. Un changement d'action à l'intérieur d'une ligne (rire, puis parler) donne deux clips, le second commençant à partir de la première image du premier au point de coupure. Puis scanner chaque clip avec bouche image par image : un pic plus de quatre fois la médiane du clip est une coupure, un geste rapide ou un morphing de deux images avec des caractéristiques fantômes ; lister les images morph dans le champ"hold"de ce plan et relancer, pour que le saut se joue comme une coupure nette. - Images de départ — une par plan,
python tools/gen_image.py --model pro --aspect 9:16 --ref character.png(+ l'image fixe de la scène du plan 1 comme 2e référence quand un lieu/prop doit être cohérent entre les plans). QA de chaque PNG avant tout appel vidéo — c'est là que la dérive est peu coûteuse à corriger.- Surveiller les mains, les détails des vêtements, les proportions — les nommer explicitement dans l'invite lors des re-rolls.
- La géométrie doit être praticable (leçon du plan 6, coût 2 re-rolls) : avec une contrainte d'image de fin, l'image de départ doit permettre au mouvement d'atteindre l'état final en avançant — si l'image de fin du personnage est PLUS PROCHE de la caméra que l'image de départ, le modèle crée un clone à la place. Commencer par un personnage plus grand/plus proche que l'état final pour que le mouvement vers l'avant le rétrécisse dedans.
- Clips — annoncer le coût total dérivé, puis
python tools/gen_clip.pypar plan en parallèle (--set image_url=<url de stockage fal> --set 'duration="N"' --set resolution=1080p --set generate_audio=false, plan final--set end_image_url=<url de l'image 0>). Uploader les refs une fois viabakeoff_clip.upload_ref; conserver les URLs dans shots/upload-urls.json. Le formulaire fal ci-dessus est la valeur par défaut de ce checkout (CLIP_PROVIDER=fal,FAL_VIDEO_MODELdans .env). Sur kie.ai à la place (--provider kie) : les noms de champs suivent le modèle kie, pas fal. Pourbytedance/seedance-2:--aspect none --set first_frame_url=@ai-shorts/<name>/shots/NN-slug.png --set duration=N --set resolution=1080p --set generate_audio=false, plan final--set last_frame_url=@<frame0.png>.@pathupload le fichier local (kie le conserve 24 h, donc la réutilisation de upload-urls.json ne tient que dans une journée). Coût : kie.ai/pricing, pas le tableau fal ; l'exécution imprime les crédits utilisés.bakeoff_clip.pyreste uniquement fal. Inviter chaque clip avec : la phrase exacte du personnage + « EXACTEMENT UN personnage dans la scène pour tout le plan » (les clones sont l'échec n° 1) + mouvement de caméra + « pas de morphing » + ligne de style. - QA des clips, image par image — bande début/milieu/fin par clip + un côte à côte de la dernière image du clip final par rapport à la cible de la boucle. Re-roll des échecs (annoncer le coût du re-roll) ; un re-roll 5s 1080p coûte ~0,29 $ — bon marché par rapport à l'envoi d'un clone.
- Composition — Séquences de
OffthreadVideosur UNE seule timeline, fondus enchaînés de ~8 images (les clips sont ~1 s plus longs que leurs fenêtres — c'est la marge),Captions+ProgressBarà la racine, fonduLoopSettleà la fin. Copier les clips finaux dansmedia/projects/<name>/.npm run gen, frames.mjs aux limites + héros, LIRE chaque PNG, puis render-all --scale=1 et muxer voice.wav. - SFX — flux /suggest-sfx. Calibration Gappy-VO (blue-man #1) : les signaux dans les trous de VO ne reçoivent ni masquage ni duck — ils ont besoin d'environ 7-8 dB de MOINS que le tableau de narration dense (whooshes de trous -9..-13, pas -3..-5). Vérifier avec volumedetect RMS par rapport à une référence de parole moyenne ; la couche héros peut effleurer le niveau de la voix une fois. Le mix d'audition attend l'oreille de l'utilisateur (porte dure).
- Sauvegarder — cataloguer used_in, timings réels de beats.json (gen_voice les écrit), sidecars commit, registre des coûts dans le rapport final.
Fait =
personnage cohérent dans chaque image de chaque clip (QA image par image) · boucle vérifiée
(dernière image vs image 0 côte à côte) · sous-titres sur les temps réels des mots · coûts annoncés avant chaque
dépense et totalisés après · rendu d'audition SFX, en attente de l'oreille de l'utilisateur · pas de CTA de fin.
· tools/check_text.py propre sur le texte parlé et à l'écran · copie téléphone réalisée avec
tools/export_phone.py · une entrée datée dans LESSONS.md (et la compétence mise à jour, en termes généraux,
si la leçon change son fonctionnement).