Pipeline utilisé pour fabriquer une « scène » animée en trois étapes indépendantes :
- Image de base (le décor) — FLUX.1 [dev] via fal.ai
- Vidéos du personnage (boucles) — MiniMax H3 (image → vidéo)
- Voix du personnage — TTS Qwen3 (vLLM-Omni)
Chaque étape se pilote en quelques appels HTTP (curl). Aucun outil annexe requis.
Le décor est généré en text-to-image avec FLUX.1 [dev] hébergé sur fal.ai.
Endpoint : POST https://fal.run/fal-ai/flux/dev
Authentification : en-tête Authorization: Key $FAL_KEY (clé créée sur fal.ai/login).
Corps de requête minimal :
{
"prompt": "Ton prompt détaillé ici…",
"image_size": "landscape_16_9",
"num_images": 1,
"seed": 1995609076,
"enable_safety_checker": false
}Paramètres utiles :
| Paramètre | Rôle |
|---|---|
prompt |
La description complète de l'image. Obligatoire. |
image_size |
Format : énuméré (square_hd, landscape_16_9, portrait_4_3, …) ou objet {"width": …, "height": …} (arrondi au format supporté le plus proche). |
num_images |
Nombre d'images (1 à 4). |
seed |
Graine aléatoire. Réutiliser la même graine + prompt = image identique (précieux pour itérer sur un décor sans tout changer). |
enable_safety_checker |
Passez-le à false pour les scènes stylisées sans risque. |
curl --request POST \
--url https://fal.run/fal-ai/flux/dev \
--header "Authorization: Key $FAL_KEY" \
--header "Content-Type: application/json" \
--data '{
"prompt": "Background art from a modern 2D animated TV cartoon, flat cel shading, clean bold outlines. A wide 16:9 establishing shot of a cozy late-night home office corner. A large desktop computer monitor at a three-quarter angle, screen dark and blank, an empty swivel chair behind the desk, retro keyboard, chipped mug, warm desk lamp, faint cool blue monitor glow, posters, shelves, potted plant, deep navy shadows, teal and amber accents. No people, no text, no logos.",
"image_size": { "width": 1360, "height": 760 },
"num_images": 1,
"enable_safety_checker": false
}'Réponse :
{
"images": [{ "url": "https://v3.fal.media/files/…/xxx.png", "width": 1024, "height": 768 }],
"seed": 1995609076,
"timings": { "inference": 3.02 }
}Téléchargez images[0].url. Conservez seed pour reproduire ou décliner ce décor.
Astuce scène cohérente : gardez le même vocabulaire visuel dans tous vos prompts (style, palette, éclairage) — ex. « flat cel shading, dark moody lighting, teal and amber accents ». La graine garantit la reproductibilité, le vocabulaire commun garantit la cohérence entre plans.
Les boucles du personnage sont générées en image-to-video : on donne l'image de base (ou un plan recadré) comme première image, et MiniMax H3 anime la scène.
L'API fonctionne en asynchrone : on soumet une tâche, on récupère un task_id, puis on interroge le statut jusqu'à succès (le résultat contient l'URL de la vidéo).
Endpoint création : POST https://api.minimax.io/v2/video_generation (version internationale ; variante Chine : api.minimax.chat)
Endpoint statut : GET https://api.minimax.io/v2/query/video_generation/{task_id}
Authentification : en-tête Authorization: Bearer $MINIMAX_API_KEY
Corps de requête (mode image → vidéo, avec première image role: first_frame) :
{
"model": "MiniMax-H3",
"content": [
{
"type": "text",
"text": "Fry from Futurama appears sitting in the office chair at the desk in front of the big computer monitor, viewed from behind. He leans slightly forward and types on the keyboard with both hands, fingers tapping keys, head subtly nodding, cool blue monitor glow flickering softly on him. Gentle motion, subtle 2D cartoon look, everything else in the room stays perfectly still."
},
{
"type": "image_url",
"image_url": { "url": "https://v3.fal.media/files/…/scene.png" },
"role": "first_frame"
}
],
"duration": 4,
"resolution": "768P",
"ratio": "adaptive"
}Points clés :
duration: 4 à 15 secondes, entier uniquement.resolution:768P(rapide/économique) ou2K.- Avec une première image,
ratioest forcé àadaptive(le format suit l'image fournie). Sans image, on préciseratio: "16:9"par exemple (mode texte → vidéo). - Contenu multimodal :
contentaccepte des élémentstext/image_url/video_url/audio_url.
# 1. Créer la tâche
TASK_ID=$(curl -s https://api.minimax.io/v2/video_generation \
-H "Authorization: Bearer $MINIMAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{…corps ci-dessus…}' | jq -r '.task_id')
# 2. Poller toutes les 10 s jusqu'au succès
while true; do
STATUS=$(curl -s "https://api.minimax.io/v2/query/video_generation/$TASK_ID" \
-H "Authorization: Bearer $MINIMAX_API_KEY")
STATE=$(echo "$STATUS" | jq -r '.task.status')
echo "statut: $STATE"
[ "$STATE" = "succeeded" ] && break
[ "$STATE" = "failed" ] && { echo "$STATUS" >&2; exit 1; }
sleep 10
done
# 3. Récupérer la vidéo
URL=$(echo "$STATUS" | jq -r '.task.content.url')
curl -s "$URL" -o clip.mp4Astuce boucle parfaite : pour des clips en boucle (« idle », « speaking », …), demandez dans le prompt une caméra fixe et une pose de fin identique à la pose de départ :
STATIC LOCKED-OFF CAMERA, no camera movement, no pan, no tilt, no zoom. … He ends in the exact same pose as the start.Le raccord boucle devient invisible.
Alternative : MiniMax H3 est aussi disponible sur fal.ai (endpoint
fal-ai/minimax-h3) avec la même logique de queue que FLUX si vous préférez un seul prestataire.
La voix est générée par un side-car TTS : le modèle Qwen3-TTS servi par vLLM-Omni, exposant une API compatible OpenAI (/v1/audio/speech) avec streaming audio réel (premier son ≈ 0,2 s).
| Élément | Valeur |
|---|---|
| Modèle | Qwen/Qwen3-TTS-12Hz-0.6B-Base (variante 1.7B possible) |
| Voix | Clone zero-shot de Fry (VF) : ref_audio + ref_text envoyés à chaque requête |
| Moteur | vLLM-Omni 0.26.0, service systemd qwen3-tts-omni |
| Nœud | Machine dédiée (Linux + GPU, réseau local) |
| Port | 8092 (port client, réseau local) |
| API | POST /v1/audio/speech, GET /v1/audio/voices, GET /health, GET /v1/models |
| Sortie | WAV ou PCM brut S16LE 24 kHz mono |
Pile mise en place sur la machine TTS :
- venv dédié
.vllm/+ clonevllm-omni/(tag v0.26.0) ; - unité systemd
qwen3-tts-omni.service(démarrage auto,Restart=on-failure) ; - 3 variables d'environnement obligatoires dans l'unité :
VLLM_CACHE_ROOT,TRITON_CACHE_DIR,VLLM_USE_FLASHINFER_SAMPLER=0; fry_ref.wav(16,8 s, transcript verrouillé) pour le clone vocal ;- la référence Fry est injectée automatiquement côté serveur : on n'a pas à la fournir à chaque appel.
Gestion du service :
sudo systemctl status qwen3-tts-omni # état du moteur
sudo systemctl restart qwen3-tts-omni
sudo journalctl -u qwen3-tts-omni -f # logsAPI « friendly » (recommandée) — la référence Fry est injectée automatiquement. Langue = code court (fr, en, de, it, es, pt, ja, ko, ru, zh, auto) ou nom complet (French). Adresse : localhost si la machine TTS est la machine courante, sinon remplacez-la par l'adresse IP de votre machine TTS.
# Une réplique → fichier WAV
curl http://localhost:8092/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"text":"Bonne nouvelle, les gens !","language":"fr"}' \
-o out.wav
# Lecture directe dans les enceintes (PCM 24 kHz mono → pacat)
curl -sN http://localhost:8092/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"text":"Bonne nouvelle, les gens !","language":"fr","stream":true,"response_format":"pcm"}' \
| sed -n 's/^data: //p' | jq -r '.audio // empty' | base64 -d \
| pacat --format=s16le --rate=24000 --channels=1Payload complet (si l'on veut contrôler la voix soi-même) :
{
"model": "Qwen/Qwen3-TTS-12Hz-0.6B-Base",
"input": "Texte à prononcer",
"voice": "vivian",
"task_type": "Base",
"ref_audio": "data:audio/wav;base64,…",
"ref_text": "Transcription exacte de la référence",
"language": "French",
"stream": true,
"response_format": "pcm"
}Variantes : stream:false (ou omission) = réponse complète, pas de streaming ; response_format:"wav" = sortie WAV au lieu de PCM.
Format du streaming (SSE, pas des octets bruts) : "stream": true renvoie des Server-Sent Events — event: speech.audio.delta suivi de data: {"type":"speech.audio.delta","audio":"<PCM int16 24 kHz en base64>"}, terminés par event: speech.audio.done. C'est ce format qu'un client navigateur doit parser (SSE + base64) pour jouer le flux presque en temps réel.
Pièges connus :
- Utilisez bien le port :8092 (port client du service, réseau local uniquement).
- Ne pas repiper les octets bruts du flux directement dans un lecteur : il faut décoder le champ
audio(base64 → PCM) comme ci-dessus. pacatlit stdin (paspaplay, niaplaybrut sous PulseAudio).
| Usage | Appel |
|---|---|
| Image (FLUX) | POST https://fal.run/fal-ai/flux/dev — Authorization: Key $FAL_KEY |
| Vidéo (MiniMax H3) | POST https://api.minimax.io/v2/video_generation puis poll …/v2/query/video_generation/{id} — Authorization: Bearer $MINIMAX_API_KEY |
| Voix (TTS) | POST http://localhost:8092/v1/audio/speech — aucun secret, réseau local |