Tutoriel
Utiliser Veo 3.1 Lite image-vers-vidéo sur HiAPI : tutoriel curl et Python
Ce que fait veo-3.1-lite/image-to-video
veo-3.1-lite/image-to-video est un modèle de conversion d'une première image en vidéo. Vous lui donnez une image statique et il anime cette image pour produire une courte vidéo. L'image que vous fournissez n'est ni une référence de style ni une source d'inspiration floue : c'est littéralement la première image de la vidéo de sortie. La composition, la position du sujet et le cadrage se prolongent donc directement dans les premières secondes du mouvement.
Ce choix de conception détermine la façon dont vous devez considérer l'entrée. Une image bien éclairée, nette et correctement cadrée produira une vidéo qui démarre là où vous l'attendez. Une image floue ou mal cadrée produira une vidéo dont le début sera flou et mal cadré, et qui dérivera davantage à mesure que le modèle génère le mouvement. L'essentiel du travail pour bien utiliser ce modèle se fait avant d'écrire la moindre ligne de code.
Côté sortie, le modèle renvoie une vidéo dotée d'un audio natif. C'est précisément ce qui le distingue des anciennes chaînes image-vers-vidéo, où l'on générait d'abord des images muettes, puis ajoutait une piste audio séparée. Ici, l'audio est généré dans le cadre de la même tâche : les sons d'ambiance restent cohérents avec la scène visuelle — aucun mixage manuel à effectuer, et aucun décalage temporel entre les effets sonores et le mouvement auquel ils correspondent.
La génération est asynchrone. Vous soumettez une tâche, recevez un identifiant, puis interrogez l'état jusqu'à ce que la tâche soit terminée. C'est un choix d'architecture délibéré : la génération vidéo prend bien plus de temps que le délai d'expiration d'une requête HTTP classique, l'API dissocie donc la soumission de l'achèvement. Chaque exemple de ce tutoriel suit ce schéma « soumettre d'abord, interroger ensuite ».
En bref : une image en entrée, une vidéo avec audio en sortie, livrée via une tâche asynchrone. Gardez ce modèle mental en tête, et le reste de l'API devient très direct.
Avant d'envoyer la première requête
Trois éléments sont nécessaires pour que le premier appel réussisse.
Une clé API
Chaque requête vers HiAPI.ai est authentifiée par une clé API. Créez-en une dans le tableau de bord de votre compte, puis chargez-la depuis une variable d'environnement plutôt que de la coller directement dans votre fichier source :
export HIAPI_API_KEY="your_api_key_here"
Si une clé a déjà été commitée dans un dépôt, faites-la tourner immédiatement. Remplacer une clé coûte peu, une fuite coûte cher.
Une image de première frame accessible
Le modèle doit récupérer l'image lui-même ; elle doit donc être accessible via une URL — un lien de stockage objet public, un chemin CDN, ou tout point de terminaison HTTP qui ne nécessite pas d'authentification interactive. Une URL signée à longue durée de validité convient ; une URL exigeant une session authentifiée ne convient pas. Utilisez un format courant comme PNG ou JPEG, avec une résolution raisonnable : une image nette et de taille modérée fournit au modèle des détails propres sans gaspiller de bande passante.
Si votre image source se trouve derrière un service interne, téléversez-la d'abord vers un stockage objet, puis passez l'URL générée. C'est de loin la cause la plus fréquente d'échec lors d'une première tentative.
Le chemin du point de terminaison
Le modèle est identifié par veo-3.1-lite/image-to-video. Traitez cette chaîne comme l'identifiant de modèle envoyé avec la tâche, et ne la codez pas en dur à une douzaine d'endroits — placez-la dans une constante, car vous aurez probablement besoin de basculer entre différentes variantes de modèles par la suite.
Une fois la clé exportée, l'URL de l'image récupérable et l'identifiant du modèle en main, vous êtes prêt à soumettre.
Une requête curl fonctionnelle
L'appel de soumission crée une tâche de génération. Voici un exemple complet et exécutable :
curl -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-lite/image-to-video",
"image": "https://your-bucket.example.com/first-frame.jpg",
"prompt": "晨光在场景中缓缓移动,镜头缓慢推进",
"duration": 5
}'
Reprenons point par point :
- L'URL est le point de terminaison de génération sur HiAPI. Tout le trafic des modèles passe par cette interface unique ; changer de modèle ne demande donc ensuite que de modifier le champ
model, et non le point de terminaison. - L'en-tête
Authorizationporte votre jeton Bearer. La notation$HIAPI_API_KEYlit la valeur depuis une variable d'environnement, la clé n'apparaît donc jamais dans l'historique du shell. modelidentifieveo-3.1-lite/image-to-video.imageest l'URL de la première frame. C'est ce champ qui fait de cet appel une conversion image-vers-vidéo plutôt qu'texte-vers-vidéo.promptest facultatif, mais fortement recommandé. Il décrit le mouvement souhaité — mouvement de caméra, évolution de l'éclairage, actions du sujet — et non une description de l'image statique, puisque le modèle voit déjà cette image.durationest la durée demandée de la vidéo (en secondes), limitée par la plage prise en charge par le modèle.
La réponse est un objet tâche contenant un id. Conservez-le :
{
"id": "job_abc123",
"status": "queued",
"model": "veo-3.1-lite/image-to-video"
}
Un statut queued ou processing est le résultat attendu — vous n'avez pas encore la vidéo, seulement une promesse. La section suivante explique comment transformer cette promesse en fichier.
Le même appel en Python
Cet appel curl se traduit directement en Python avec requests. La version ci-dessous encapsule la soumission dans une petite fonction, facile à réutiliser dans une chaîne de traitement :
import os
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def submit_video_job(image_url: str, prompt: str, duration: int = 5) -> str:
payload = {
"model": "veo-3.1-lite/image-to-video",
"image": image_url,
"prompt": prompt,
"duration": duration,
}
response = requests.post(
f"{BASE_URL}/video/generations",
headers=HEADERS,
json=payload,
timeout=30,
)
response.raise_for_status()
data = response.json()
print(f"Submitted job {data['id']} ({data['status']})")
return data["id"]
if __name__ == "__main__":
job_id = submit_video_job(
image_url="https://your-bucket.example.com/first-frame.jpg",
prompt="晨光在场景中缓缓移动,镜头缓慢推进",
)
Quelques détails méritent d'être conservés :
raise_for_status()transforme les réponses non 2xx en exceptions dès que possible. Sans cela, une erreur d'authentification ou de validation ressemble à un appel réussi qui renvoie un corps de réponse inattendu.- Le délai d'expiration de 30 secondes ne concerne que la soumission. La soumission doit revenir rapidement ; le travail long se fait côté serveur. Si la soumission elle-même expire, le problème vient de la connectivité ou de la taille de la charge utile, pas de la vitesse de génération.
- Renvoyer l'id de la tâche plutôt que la réponse entière garde la fonction composable — c'est à l'appelant de décider quoi faire de cette tâche.
Vous pouvez désormais placer cette fonction sans modification dans un worker de file d'attente, un point de terminaison Flask ou une cellule de notebook.
Interroger une tâche asynchrone
La génération vidéo prend des minutes, pas des millisecondes. Interrogez le point de terminaison d'état de la tâche à un intervalle proche du rythme humain, et arrêtez-vous dès que la tâche atteint un état terminal :
import time
import requests
def wait_for_video(job_id: str, timeout: int = 600, interval: int = 10) -> dict:
deadline = time.time() + timeout
url = f"{BASE_URL}/video/generations/{job_id}"
while time.time() < deadline:
response = requests.get(url, headers=HEADERS, timeout=30)
response.raise_for_status()
data = response.json()
status = data["status"]
print(f"Job {job_id}: {status}")
if status == "completed":
return data
if status == "failed":
raise RuntimeError(f"Generation failed: {data}")
time.sleep(interval)
raise TimeoutError(f"Job {job_id} did not finish within {timeout}s")
Trois pratiques rendent ce code fiable en production :
- Un backoff exponentiel. Commencez par un intervalle court et augmentez progressivement, avec un plafond raisonnable. La première interrogation renvoie généralement encore
queued, marteler le point de terminaison dans les premières secondes n'apporte donc rien. - Un délai d'expiration strict. Fixez toujours une limite au temps d'attente total. Une tâche bloquée en
processingdoit se manifester comme une erreur déclenchant une alerte, et non comme une boucle infinie. - La persistance de l'id de tâche. Écrivez-le en base avant de commencer à interroger. Si le processus plante en cours d'attente, le redémarrage peut reprendre l'interrogation, sans avoir à soumettre à nouveau ni payer deux fois le même travail.
Ne considérez comme états terminaux que completed et failed. Tout le reste — queued, processing — signifie qu'il faut continuer d'attendre.
Une dernière mise en garde : si vous exécutez ce code sur un serveur web, n'interrogez pas l'état à l'intérieur du gestionnaire de requête. Soumettez, renvoyez l'id de tâche au client, et laissez un worker en arrière-plan faire l'attente. Sinon, une génération lente occupera une connexion pendant des minutes.
Télécharger la vidéo de sortie
Lorsque la tâche est terminée, la réponse d'état contient l'URL de la vidéo finale. Téléchargez-la en données binaires et écrivez-la sur disque avec une extension appropriée :
def download_video(job: dict, path: str = "output.mp4") -> str:
video_url = job["output"]["url"]
response = requests.get(video_url, headers=HEADERS, timeout=120, stream=True)
response.raise_for_status()
with open(path, "wb") as f:
for chunk in response.iter_content(chunk_size=1 << 20):
f.write(chunk)
print(f"Saved {path}")
return path
Quelques points à soigner :
- Écrire en mode binaire. Ouvrir avec
"w"au lieu de"wb"corrompt silencieusement le fichier ; le téléchargement semble réussir, mais la vidéo ne se lit pas. - Traiter les gros fichiers en flux. L'option
stream=Trueci-dessus, combinée à la copie par blocs, maintient une utilisation mémoire stable quelle que soit la taille de la vidéo. - Calculez une somme de contrôle si vous archivez les sorties à grande échelle. Cela ne coûte qu'une ligne de code, mais détecte les téléchargements tronqués avant qu'ils n'atteignent vos utilisateurs.
À propos de l'audio : la vidéo renvoyée contient déjà sa piste audio native. Aucun fichier audio séparé à récupérer, aucune étape de mixage. Si vous remuxez le fichier en aval — par exemple pour changer de format de conteneur —, conservez la piste audio. Une erreur courante consiste à lancer une conversion qui supprime l'audio par défaut, puis à se demander où il est passé.
Enfin, l'URL de sortie est généralement limitée dans le temps. Téléchargez et enregistrez la vidéo dans votre propre bucket dès que possible, plutôt que de conserver l'URL et de la récupérer quelques jours plus tard, quand le lien aura probablement expiré.
Points d'échec fréquents
Voici les erreurs qui font perdre le plus de temps lors du diagnostic d'appels image-vers-vidéo :
URL d'image inaccessible
C'est de loin la cause d'échec la plus fréquente. Le modèle récupère votre image côté serveur ; une URL qui fonctionne dans votre navigateur authentifié ne prouve donc rien. Avant de soumettre la tâche, testez-la avec une requête simple et sans authentification. Une URL signée doit avoir une durée de validité nettement supérieure au temps de génération.
Des erreurs d'authentification qui ressemblent à des erreurs de modèle
Un en-tête Authorization absent ou mal formé renvoie généralement un 401 explicite, mais il arrive qu'un développeur encapsule le code d'initialisation dans une logique qui avale les codes de statut, puis signale une « panne du modèle ». Avant de déboguer la charge utile de la requête, vérifiez le code de statut brut.
Interrogation trop fréquente ou totalement absente
Interroger toutes les 100 millisecondes gaspille du quota et peut déclencher une limitation de débit. À l'inverse, interroger une seule fois, voir processing et abandonner signifie qu'une tâche réussit sans que personne ne le sache. Suivez le modèle backoff plus délai d'expiration présenté ci-dessus.
Confondre soumission et achèvement
Une soumission réussie renvoie une tâche, pas une vidéo. Le code qui lit le champ url dans la réponse de soumission ne trouvera rien. Seule la réponse d'état de fin porte la sortie.
Un prompt qui décrit l'image
Un prompt comme « un chat assis sur le rebord de la fenêtre » ne fait que répéter ce que le modèle voit déjà. Un prompt qui décrit le mouvement — « la caméra recule lentement tandis que la pluie commence à tomber » — produit de bien meilleurs résultats. Décrivez le changement, pas le contenu.
Perte de l'id de tâche en cas de plantage
Si le processus meurt entre la soumission et l'achèvement, un id de tâche non persisté signifie que le travail est irrécupérable. Persistez-le et interrogez l'état de façon asynchrone depuis un worker. Ce petit choix de conception évite les séances de débogage les plus interminables.
Articles similaires
Voir tous les articles →-
Tutoriel
Comment appeler hailuo-2.3-fast/image-to-video via l'API HiAPI : curl et Python en pratique
La première requête exploitable du palier image-vers-vidéo rapide et économique — de l'ID du modèle au téléchargement du MP4
-
Tutoriel
Comment appeler hailuo-2.3/text-to-video via l'API HiAPI : curl, Python et exemples de requêtes exécutables
-
Tutoriel
Utiliser Kandinsky 6.0 Pro texte-vers-vidéo sur HiAPI : tutoriel curl et Python
Démonstration pratique axée sur la requête : construire l'appel du prompt au clip vidéo, soumettre la requête, interroger l'état d'achèvement et télécharger le résultat