Tutoriale
Come chiamare hailuo-2.3-fast/image-to-video tramite l'API HiAPI: pratica con curl e Python
La prima richiesta funzionante per il livello image-to-video veloce ed economico — dall'ID del modello al download di un MP4
Cosa può fare per te hailuo-2.3-fast/image-to-video
hailuo-2.3-fast/image-to-video è il livello ottimizzato per la velocità nella linea di prodotti image-to-video di HiAPI. Gli fornisci un'immagine statica e un prompt testuale che descrive il movimento desiderato, e ti restituisce una breve clip video che si anima a partire da quell'immagine. Il "fast" nell'ID del modello è il punto chiave: questo livello scambia parte della fedeltà dei dettagli dei modelli video più pesanti con una latenza inferiore e un costo per render più basso, il che lo rende ideale per l'iterazione e la produzione in batch.
Scegli questo modello quando ti serve una prima versione funzionante di una clip accettabile, non un'immagine "hero": anteprime di storyboard, video loop pensati per i social, materiale di sfondo in movimento, o la fase di bozza in una pipeline in cui il modello pesante si occupa del render finale. Se il tuo output richiede inquadrature lunghe, una regia complessa o una rigorosa coerenza dell'identità tra molti frame, affida questi compiti ai livelli più pesanti — mantenendo la stessa struttura di richiesta, così il passaggio è solo una modifica di configurazione.
Tutto ciò che segue ruota attorno a una richiesta che puoi davvero eseguire. L'obiettivo è far arrivare un MP4 su disco, non uno screenshot di una risposta riuscita.
Panoramica della richiesta
Un render video richiede da pochi secondi a qualche minuto, quindi l'API è asincrona: invii un'attività, ricevi un identificatore dell'attività, poi interroghi l'attività finché non segnala il completamento e ti consegna l'output. Questo significa che ogni integrazione ha due fasi — invio e polling — e solo la fase di invio varia in base al modello.
La richiesta di invio contiene quattro elementi sotto il tuo controllo:
- Endpoint e metodo — una richiesta
POSTall'endpoint di generazione video di HiAPI. - Header di autenticazione — la tua chiave, nella forma
Authorization: Bearer $HIAPI_API_KEY. Metti la chiave in una variabile d'ambiente, mai inline nel codice inviato. - ID del modello — la stringa letterale
hailuo-2.3-fast/image-to-video. È ciò che seleziona il livello. - Input — un
promptche descrive il movimento e un'immagine del primo frame. L'immagine è fornita come URL accessibile pubblicamente o come data URI, a seconda di ciò che il tuo account accetta su quell'endpoint; iniziare da un URL è la cosa più semplice. Il corpo JSON viene inviato conContent-Type: application/json.
Metti questi quattro campi in un unico posto nella tua codebase — un dizionario, un file di configurazione, un builder guidato da variabili d'ambiente — così passare in futuro a un altro modello video HiAPI richiederà una sola riga, non una riscrittura. La risposta è una piccola busta JSON in cui l'identificatore dell'attività è l'unico campo che devi persistere; il resto sono metadati di stato.
La richiesta curl funzionante
Questo è il passo di invio completo. Sostituisci l'URL dell'immagine con un indirizzo accessibile al tuo account ed esporta prima la tua chiave.
export HIAPI_API_KEY="la-tua-chiave-qui"
curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hailuo-2.3-fast/image-to-video",
"prompt": "slow push in, gentle breeze moving the fabric, film grain",
"image": "https://example.com/first-frame.jpg"
}'
Una risposta riuscita è un JSON contenente l'identificatore dell'attività, solitamente nella forma {"id": "task_...", "status": "queued"}. La forma esatta dei campi della busta può cambiare, quindi analizza in modo difensivo: leggi l'ID dell'attività per chiave e non per posizione, e tratta i campi sconosciuti come informativi.
Se stai debuggando, aggiungi -i per vedere la riga di stato HTTP. 401 significa quasi sempre che l'header di autenticazione manca o che la variabile si è espansa in una stringa vuota — esegui echo $HIAPI_API_KEY prima di dare la colpa al corpo della richiesta. 400 di solito indica il corpo della richiesta: URL dell'immagine non raggiungibile, model mancante o nome del campo non corrispondente all'endpoint.
Copia l'ID dell'attività restituito in una variabile della shell, così i passaggi successivi non richiedono di reinserirlo:
TASK_ID=$(curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"hailuo-2.3-fast/image-to-video","prompt":"slow push in","image":"https://example.com/first-frame.jpg"}' \
| python -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "$TASK_ID"
La stessa richiesta, scritta in Python
La versione Python mantiene il corpo della richiesta byte per byte equivalente a quello di curl, così quando indaghi su un bug puoi confrontare direttamente il risultato di curl e quello di Python.
import os
import requests
API_BASE = "https://api.hiapi.ai/v1"
MODEL = "hailuo-2.3-fast/image-to-video"
def submit_video(prompt: str, image_url: str) -> str:
resp = requests.post(
f"{API_BASE}/video/generations",
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
json={
"model": MODEL,
"prompt": prompt,
"image": image_url,
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["id"]
Ci sono tre abitudini importanti qui. os.environ[...] (non .get) fallisce in modo esplicito quando la variabile manca, ed è proprio così che vuoi che fallisca. Un timeout esplicito impedisce a un socket bloccato di occupare indefinitamente un worker. E raise_for_status() trasforma un corpo di risposta 4xx in un'eccezione registrabile, invece di far sì che la riga successiva generi silenziosamente un KeyError per l'ID mancante.
Questa funzione restituisce deliberatamente solo l'ID dell'attività. Invio e attesa sono preoccupazioni separate; mescolarle rende il ciclo di polling difficile da testare. Nota che l'ID del modello appare una sola volta, a livello di modulo — è l'unico punto da modificare quando passi a un altro modello video HiAPI.
Eseguire il polling dell'attività finché il video è pronto
Il controllo dello stato è una richiesta GET alla risorsa dell'attività. Un'attività in coda o in esecuzione restituisce uno stato non terminale; un'attività completata restituisce uno stato terminale con l'output allegato.
import time
def wait_for_video(task_id: str, timeout: float = 600.0) -> dict:
deadline = time.monotonic() + timeout
delay = 2.0
url = f"{API_BASE}/video/generations/{task_id}"
while True:
r = requests.get(
url,
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
timeout=30,
)
r.raise_for_status()
task = r.json()
if task.get("status") == "succeeded":
return task
if task.get("status") in {"failed", "cancelled"}:
raise RuntimeError(f"task {task_id} ended: {task}")
if time.monotonic() >= deadline:
raise TimeoutError(f"task {task_id} still {task.get('status')} after {timeout}s")
time.sleep(delay)
delay = min(delay * 1.5, 15.0)
Questo è un ciclo di retry, non un'attesa fissa. Il backoff parte da due secondi e cresce fino a un tetto di quindici secondi, così i render brevi si completano rapidamente e quelli lunghi non martellano l'endpoint. La scadenza viene controllata prima del sonno, quindi il tempo totale effettivo rimane entro il limite invece di superarlo di un intervallo. Usa time.monotonic() invece di time.time(), così le regolazioni dell'orologio non prolungano il ciclo.
Tratta failed e cancelled in modo diverso da "continua ad aspettare": sono terminali ma non riusciti. Quando accade, registra l'intero oggetto attività — è l'unica diagnostica che ottieni, e riprodurre un fallimento a partire da un solo ID è molto doloroso. Tratta le stringhe di stato sconosciute come non terminali e continua il polling, così i nuovi stati intermedi non mandano in crash il tuo worker.
Scaricare ed elaborare l'output
Un'attività completata contiene uno o più URL di output. Recupera i byte e scrivili dove hai il controllo.
def download_video(task: dict, dest: str) -> str:
url = task["output"][0]["url"] if isinstance(task.get("output"), list) else task["output"]["url"]
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
return dest
Scarica in streaming, invece di chiamare .content; un video può essere abbastanza grande che bufferizzarlo interamente in memoria è uno spreco per un worker che gestisce più attività. La forma esatta dell'URL di output può variare leggermente a seconda del livello del modello, quindi verifica se è una lista prima di indicizzarla — il ramo sopra copre entrambi i casi.
Regola importante: tratta l'URL restituito come un handle a breve scadenza, non come archiviazione. Appena un'attività riesce, copia il file nel tuo bucket o nel tuo percorso locale e servilo da lì. Persistere l'URL nel database e recuperarlo giorni dopo è il motivo per cui ottieni 403 e una dashboard vuota. Per l'object storage, lo stesso ciclo di byte in streaming può alimentare direttamente il metodo di upload del tuo client — se il tuo SDK accetta file-like object, non serve un file temporaneo intermedio.
Consigli pratici e trappole comuni
- Usa l'header di autenticazione, non i parametri di query. La chiave va in
Authorization: Bearer .... Una chiave incollata nell'URL è una chiave nei log di accesso. - La raggiungibilità dell'immagine conta più della correttezza dell'immagine. Un
400all'invio è molto più spesso un URL che l'API non riesce a recuperare — localhost, bucket privati, host con hotlink protection, link che scadono — piuttosto che un problema del prompt. Prima di debuggare qualsiasi altra cosa, testa quell'URL con una sessione browser nuova. - Content type del corpo della richiesta. Invia JSON con
Content-Type: application/json. Se dimentichi-Hquando invii con curl, il server potrebbe non analizzare affatto il corpo della richiesta. - Polling troppo aggressivo. Un
while Truesenza sleep ti fa finire in rate limit e non rende il render più veloce. Backoff più scadenza sono l'intero trucco. - Timeout assenti ovunque. Imposta timeout sulle chiamate di invio, polling e download. Un timeout mancante trasforma un provider lento in una coda bloccata nel tuo stesso sistema.
- Il prompt descrive il movimento, non l'immagine. Descrivi ciò che cambia tra un frame e l'altro — movimento di camera, azioni del soggetto, elementi ambientali — e mantienilo breve. Ripetere l'immagine statica non aggiunge nulla.
- Una configurazione, più modelli. Metti ID del modello, prompt e input immagine nello stesso builder del payload. Passare da questo livello veloce a uno più pesante richiede solo la modifica di una stringa.
Il modo più rapido per verificare la configurazione end-to-end è: esegui l'invio con curl, incolla l'ID dell'attività nel ciclo di polling e conferma che un MP4 arrivi su disco — una volta che questo funziona, il resto è solo impacchettamento.
Articoli correlati
Vedi tutti gli articoli →-
Tutoriale
Come chiamare hailuo-2.3/text-to-video tramite l'API HiAPI: curl, Python ed esempi di richiesta eseguibili
-
Tutoriale
Utilizzare Kandinsky 6.0 Pro su HiAPI per la generazione di video da testo: tutorial con curl e Python
Dimostrazione pratica guidata dalla richiesta: costruire la chiamata da prompt a clip video, inviare la richiesta, monitorare lo stato di completamento e scaricare il risultato
-
Tutoriale
Come usare kling-4.0/text-to-video tramite l'API HiAPI: curl, Python e un esempio di richiesta funzionante
Guida pratica per sviluppatori: prompt cinematografici, audio sincronizzato, polling asincrono e download dell'output