Génération de clips vidéo (image fixe vers clip, puis synchronisation labiale)
Aperçu
Une image finie devient un clip de 4 à 9 s avec un modèle image-vers-vidéo. Ensuite, si un personnage parle à l'écran, un modèle de synchronisation labiale recalibre les mouvements de sa bouche sur votre propre dialogue. Vous ne pouvez pas regarder le résultat : jugez chaque clip à partir des images extraites (scripts/take_sheet.py et le skill viewing-videos), et laissez l'humain avoir le dernier mot sur le mouvement.
Prérequis : Python 3 (stdlib uniquement) et ffmpeg/ffprobe. Les clés vont dans les variables d'environnement : GEMINI_API_KEY (une clé Google AI Studio ; une clé limitée aux images fonctionne, et GEMINI_IMAGE_API_KEY est lue comme solution de repli) pour Omni, et FAL_KEY (la clé complète exactement comme fal la présente) pour la synchronisation labiale. Ne les imprimez jamais.
Choisir le modèle
| Modèle | Accès via | À utiliser pour | Notes |
|---|---|---|---|
Gemini Omni Flash 1.1 (gemini-omni-1.1-flash) | Google Interactions API, clé AI Studio | Par défaut pour les illustrations, les dessins animés et les cases ; a également fonctionné sur une image photoréaliste (un test) | Conserve le style artistique et les visages ; n'importe quel ratio d'image (une image 2:3 est revenue en 9:16 net, donc pas besoin de recadrer ou d'étendre au préalable) ; 720p 9:16 ou 16:9 ; synchrone, ~25-35 s par clip ; ajoute son propre son (à désactiver) |
| Veo 3.1 / Veo 3.1 Fast | API Gemini, image comme première image | Images photoréalistes | Non utilisé dans nos tests ; vérifiez d'abord la documentation actuelle |
| Seedance 2.5 | ElevenLabs Flows, ou directement ByteDance | Aspect alternatif | Nécessitait une approbation de compte lors de l'essai |
| Tout modèle dans ElevenLabs Flows | API ElevenLabs | Une seule clé pour tout | L'accès API nécessite le plan Pro (402 paid_plan_required) et l'autorisation image_video_generation de la clé (401 sans elle) ; Omni y est uniquement en mode application |
Pour la synchronisation labiale, Sync 3 sur fal (fal-ai/sync-lipsync/v3) est le modèle par défaut. Il fonctionne vidéo-vers-vidéo, donc il conserve le mouvement du clip et ne recalibre que la bouche. Comparaison et prix : references/providers.md.
Animer une image fixe
python3 <ce-dossier-skill>/scripts/omni_generate.py jobs.json clips/ [--takes 3] [--only S05]
{"base": "Anime cette image fixe telle quelle : même style artistique, palette, lumière et composition, et chaque personnage conserve le visage, les cheveux, la corpulence et les vêtements qu'il a sur l'image. Mouvement naturel et fluide. Rien d'écrit à l'écran, pas de bulles de dialogue, personne de nouveau, un plan continu. ",
"jobs": {"S05": {"image": "panels/p05.png",
"prompt": "[0-3s] La femme en veste rouge lève le téléphone et parle, excitée. [3-6s] L'ami en sweat à capuche vert se penche en arrière sur le canapé et rit. Léger zoom avant de la caméra."}}}
Comment rédiger le prompt (guide complet avec exemples dans references/gemini-omni.md) :
- Une chronologie par seconde, un mouvement par crochet. Le clip dure approximativement la durée de la chronologie.
- Désignez les personnes par ce qu'elles portent, jamais par leur nom : le modèle ne sait pas qui est « Dana ».
- Dites « parle » pour toute personne qui sera synchronisée labialement, afin que la bouche bouge déjà.
- Fixez ce qui ne doit pas changer : « la caméra reste immobile », « une seule fenêtre sur le mur gauche », « exactement UN personnage encapuchonné », « ses mains restent à plat sur la table ». Chaque indication dans
references/failure-modes.mdprovient d'un échec réel.
Examiner chaque prise avant de l'utiliser
python3 <ce-dossier-skill>/scripts/take_sheet.py sheet review/S09.jpg clips/S09_t1.mp4 clips/S09_t2.mp4 clips/S09_t3.mp4 --times 0.3,1,2,3,4,5,6
Lisez la feuille : les lignes sont les prises, les colonnes sont les temps. Recherchez les accessoires qui doublent, les portes ou les personnes qui apparaissent, les mains qui traversent les corps, les visages qui dérivent, et un geste effectué deux fois. Nommez la seconde exacte de chaque problème, car la correction dépend de son emplacement.
Un cut franc est facile à manquer entre les images échantillonnées. Listez-les directement ; aucune sortie signifie un plan continu :
ffmpeg -i clips/S09_t1.mp4 -vf "select='gt(scene,0.3)',showinfo" -an -f null - 2>&1 | grep pts_time
Ajuster un clip à son plan
Un montage a besoin d'un clip qui remplit exactement son plan, à la fréquence d'images du montage :
| Problème | Solution |
|---|---|
| Mauvais début (un geste avant sa réplique, un accessoire doublé dans la première seconde) | Commencer plus tard : --off 1.5. |
| Trop court après recadrage | Ralentir avec --speed 1.22 (jusqu'à ~1.25 reste correct) ; la dernière image est maintenue. |
| Mauvais milieu | Diviser le plan : jouer la première réplique sur un recadrage qui garde le problème hors du cadre, puis couper après. |
| Irréparable (une porte apparaît, une deuxième personne entre) | Régénérer avec un prompt verrouillé, 2-3 prises, et choisir sur une feuille. |
lipsync_parts.py normalize clip.mp4 shot.mp4 --frames N --off S --speed K effectue le décalage, la vitesse, les fps, l'échelle (couvrir et centrer-couper, jamais étirer) et le maintien de la dernière image en une seule étape, et renvoie toujours exactement N images.
Synchronisation labiale du dialogue
- Normalisez le clip au plan :
python3 <ce-dossier-skill>/scripts/lipsync_parts.py normalize clip.mp4 shot.mp4 --frames 132 --off 1.5 --speed 1.22. - Divisez aux limites des répliques de sorte que chaque partie ait un seul locuteur.
- Écrivez
shot.json: chaque réplique avec son début/fin dans le plan (en secondes) et son locuteur ;framesest égal au--framesque vous avez utilisé pour normaliser. Ajoutez"skip": truepour un locuteur vu de dos, hors écran, ou un cri de foule :{"id": "S05", "video": "shot.mp4", "fps": 24, "frames": 132, "lines": [{"id": "05-1", "audio": "lines/05-1.mp3", "start": 0.72, "end": 2.31, "speaker": "maya"}, {"id": "05-2", "audio": "lines/05-2.mp3", "start": 2.43, "end": 4.10, "speaker": "ido"}]} - Exécutez
lipsync_parts.py split shot.json parts/. En plus des parties, il écritparts/S05_lipsync_jobs.json: une entrée par partie non ignorée, avec les chemins absolus,outdéjà nommé comme l'attend join (synced/S05_<k>.mp4), etface: null.
- Écrivez
- Désignez le locuteur dans chaque partie, et écrivez-le dans ce fichier jobs sous la forme
"face": "x,y@<mid>"(midest dans chaque entrée).- Choisissez un pixel sur son visage sur l'image du milieu de la partie.
- Confirmez-le :
take_sheet.py point parts/S05_1.mp4 check.jpg --frame 30 --xy 410,560, puis lisez l'image. - Les détecteurs de visage ne sont pas fiables sur les visages dessinés, alors choisissez et confirmez à l'œil. Laissez
nulluniquement lorsqu'un seul visage est visible.
- Sync :
lipsync_fal.py --jobs parts/S05_lipsync_jobs.json. Attendez-vous à ~0,13 $ par seconde et ~70-150 s par partie ; 6 s'exécutent en parallèle. Les sorties existantes sont ignorées. Pour refaire une partie après avoir corrigé son visage, exécutez cette partie seule :lipsync_fal.py parts/S05_1.mp4 parts/S05_1.wav synced/S05_1.mp4 --face 410,560@30 --force(--forceavec--jobsreferait toutes les parties). - Assemblez :
lipsync_parts.py join parts/S05_plan.json synced/ final/S05.mp4. Chaque partie est forcée à son nombre exact d'images, les parties ignorées conservent leurs images d'origine, une partie sans fichier synchronisé est signalée (--stricten fait une erreur), et le nombre total d'images est vérifié.
Détails, choix du modèle et cas d'échec : references/lipsync.md.
Coûts et durées (mesurés en 2026-10)
| Étape | Quantité | Coût | Durée |
|---|---|---|---|
| Clip Omni | par seconde de vidéo de sortie (un clip de 6 s coûte ~0,60 $ ; prévoir ~1,5 appels par plan pour les reprises) | ~0,10 $ | ~30 s par appel, 4 en parallèle |
| Sync 3 synchronisation labiale | par seconde de sortie (une partie fait généralement 3-6 s) | ~0,13 $ | ~70-150 s par partie, 6 en parallèle |
Erreurs courantes
| Erreur | Résultat | Correction |
|---|---|---|
| Nommer les personnages dans le prompt vidéo | la mauvaise personne bouge | décrire par les vêtements |
| Garder l'audio d'Omni | voix et musique inventées sous votre dialogue | désactiver (-an) |
| Utiliser une prise sans feuille d'extraction | accessoires doublés et nouvelles portes livrées | extraire chaque prise |
| Relancer tout le clip pour une mauvaise première seconde | nouveaux problèmes ailleurs | décalage plus ralentissement |
| Synchroniser labialement tout un plan à deux locuteurs | une bouche bouge pour les deux répliques | diviser aux limites des répliques, un locuteur par partie |
| Laisser le modèle choisir le locuteur | le mauvais visage parle | --face X,Y@FRAME |
| Raccourcir la clé fal | 401 | passer la clé exactement comme fal la présente |
| Assembler les parties sans corriger les nombres d'images | le plan dérive et se désynchronise du montage | lipsync_parts.py join |
Skills associés
- image-generation : les images fixes (fiches personnages, cases).
- elevenlabs-tts : le dialogue sur lequel les bouches se synchronisent.
- viewing-videos : extraction d'images pour examiner les clips.
- producing-animated-episodes : le workflow complet d'épisode qui utilise ces clips.