Skip to content

Instantly share code, notes, and snippets.

@co-l
Created August 7, 2026 12:19
Show Gist options
  • Select an option

  • Save co-l/0d8c223565078d16357c37962b8251e1 to your computer and use it in GitHub Desktop.

Select an option

Save co-l/0d8c223565078d16357c37962b8251e1 to your computer and use it in GitHub Desktop.
Guide technique FR — génération de scène (FLUX sur fal.ai, MiniMax H3) + TTS Qwen3/vLLM-Omni

Guide technique — génération de scène + son

Pipeline utilisé pour fabriquer une « scène » animée en trois étapes indépendantes :

  1. Image de base (le décor) — FLUX.1 [dev] via fal.ai
  2. Vidéos du personnage (boucles) — MiniMax H3 (image → vidéo)
  3. Voix du personnage — TTS Qwen3 (vLLM-Omni)

Chaque étape se pilote en quelques appels HTTP (curl). Aucun outil annexe requis.


1. Scène — Image de base (FLUX sur fal.ai)

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.

Exemple curl

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.


2. Scène — Vidéos (MiniMax H3, image → vidéo)

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) ou 2K.
  • Avec une première image, ratio est forcé à adaptive (le format suit l'image fournie). Sans image, on précise ratio: "16:9" par exemple (mode texte → vidéo).
  • Contenu multimodal : content accepte des éléments text / image_url / video_url / audio_url.

Boucle complète (soumettre → attendre → télécharger)

# 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.mp4

Astuce 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.


3. Son — TTS (vLLM-Omni + Qwen3-TTS)

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).

3.1 Setup

É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/ + clone vllm-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        # logs

3.2 Utilisation

API « 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=1

Payload 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 Eventsevent: 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.
  • pacat lit stdin (pas paplay, ni aplay brut sous PulseAudio).

Résumé des clés

Usage Appel
Image (FLUX) POST https://fal.run/fal-ai/flux/devAuthorization: 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment