Tutorial
Cómo usar omnihuman-1.5 a través de la API de HiAPI: tutorial con curl y Python
Recorrido completo de la solicitud para la generación de videos de avatares digitales a partir de audio, sondeo asíncrono y descarga de resultados
Qué hace la API de omnihuman-1.5 y para qué sirve
omnihuman-1.5 convierte un fragmento de audio más una imagen de retrato en un video de un avatar digital hablando: el rostro de la imagen se anima para que los movimientos de boca y cabeza sigan la voz proporcionada. En HiAPI, este modelo se ofrece como endpoint de audio a video: envías el audio, una imagen de referencia y unos pocos parámetros de generación, y la API devuelve una tarea que luego consultas repetidamente para obtener el video final.
En qué supera la API a las herramientas manuales
Las herramientas de navegador van bien para ediciones puntuales. Cuando la generación de video es una pieza dentro de un sistema mayor, la API es la opción correcta:
- Pipelines por lotes: generar grandes cantidades de clips de avatares digitales a partir de colas de scripts y pistas de audio, sin intervención manual.
- Funcionalidades de producto: narraciones dentro de una app, videos de onboarding localizados o mensajes personalizados, usando cada renderizado un audio distinto y un retrato distinto.
- Automatización de backend: los flujos de trabajo de contenido generan video y este se entrega a un CMS, a un bucket de almacenamiento o a un servicio de distribución.
Forma de la estructura de la solicitud
Cada llamada a omnihuman-1.5 en HiAPI sigue la misma forma: un POST envía la tarea y las solicitudes posteriores comprueban su estado. Como el renderizado no es instantáneo, el patrón de integración es enviar → sondear → descargar, y no enviar → recibir el video en la misma respuesta. El resto de este tutorial construye ese flujo primero con curl y luego lo implementa en Python.
Requisitos previos y tus credenciales de HiAPI
Antes de la primera llamada, confirma tres cosas.
1. Cuenta y clave de API
Necesitas una cuenta de HiAPI y una clave de API creada en la consola. Trata la clave como un secreto: no la pongas en el control de versiones, en el código del cliente ni en los logs. En desarrollo, cárgala desde una variable de entorno; en producción, desde el gestor de secretos de tu plataforma.
export HIAPI_API_KEY="your-api-key-here"
2. Acceso a la red y herramientas de solicitud
Con curl basta cualquier shell moderno. Para la versión en Python solo necesitas la librería requests: no hace falta un SDK específico de un proveedor, porque la API es HTTPS normal con cuerpo JSON.
pip install requests
3. Entradas que requiere el modelo
Prepara lo siguiente antes de escribir la solicitud:
- Audio: la pista de voz que impulsa la animación de la boca, proporcionada como URL que la API pueda leer o como referencia a un archivo ya subido, según el formato de entrada que acepte el endpoint.
- Imagen de retrato: el rostro que se va a animar. Una foto nítida, frontal y con la boca visible produce los resultados más predecibles.
- Parámetros de generación: cualquier opción de resolución, duración o calidad que exponga el modelo. Envía solo los campos que realmente necesites; en las primeras pruebas los valores por defecto suelen ser razonables.
Cada solicitud pasa la clave en la cabecera Authorization con la forma Bearer <your-key>. Si la primera llamada devuelve 401, revisa primero esta cabecera.
Hacer la solicitud con curl
La llamada de envío es un POST con cuerpo JSON. La ruta exacta y los nombres de campo de omnihuman-1.5 figuran en la página de referencia de la API de ese modelo; la estructura siguiente muestra la forma de la llamada y los campos que vas a rellenar.
curl -X POST "https://api.hiapi.ai/v1/omnihuman-1.5/generate" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://your-bucket.example.com/speech.mp3",
"image_url": "https://your-bucket.example.com/portrait.jpg"
}'
Explicación campo por campo
Authorization: lleva la clave de API. Sin ella, la solicitud se rechaza antes de empezar cualquier trabajo.Content-Type: application/json: el cuerpo es JSON; omitir esta cabecera es una causa habitual de errores de validación confusos.audio_url: la pista de voz. Usa una URL de lectura pública o accesible de otro modo por la API, y prioriza ubicaciones estables frente a enlaces firmados que caducan a mitad del renderizado.image_url: el retrato que se va a animar. Se aplican las mismas reglas de accesibilidad.
Qué se devuelve
Un envío correcto no devuelve el video, sino un objeto de tarea que contiene un identificador (normalmente el campo id) y un estado como queued o processing. Guarda ese identificador; es el asa que usarás en cada comprobación de estado posterior.
{
"id": "job_abc123",
"status": "queued"
}
Puedes confirmar que el envío se realizó comprobando el código de estado HTTP y la presencia del identificador de tarea. Si la respuesta contiene mensajes de validación, apuntarán a los campos erróneos, normalmente URLs ausentes o mal formadas.
Implementar la misma solicitud en Python
La llamada curl se traduce directamente a Python. La versión siguiente está pensada para colocarla tal cual en un servicio: la clave viene de una variable de entorno, el payload es un diccionario que se construye dinámicamente y los errores se lanzan en lugar de silenciarse.
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_omnihuman_job(audio_url: str, image_url: str, **options) -> str:
payload = {
"audio_url": audio_url,
"image_url": image_url,
}
payload.update(options)
response = requests.post(
f"{BASE_URL}/omnihuman-1.5/generate",
headers=HEADERS,
json=payload,
timeout=60,
)
response.raise_for_status()
data = response.json()
return data["id"]
Por qué está escrita así
json=payloadhace querequestsserialice el cuerpo y establezca el tipo de contenido, así no olvidas la cabecera JSON.**optionsmantiene la función flexible: los parámetros de generación se pasan como argumentos con nombre sin tener que modificar el literal del payload.timeout=60evita que una conexión colgada bloquee indefinidamente el proceso de trabajo. El envío es una llamada rápida; las esperas largas corresponden al paso de sondeo, no aquí.raise_for_status()convierte las respuestas no 2xx en excepciones que puedes capturar y registrar, y permite obtener el cuerpo de la respuesta para el diagnóstico.
Conserva el identificador de tarea devuelto y pásalo a la función auxiliar de sondeo; esa función se construye en la sección siguiente.
Gestionar el ciclo de vida asíncrono: sondear hasta completar
La generación de video lleva tiempo, así que la API es asíncrona por diseño. Tras el envío, consultas repetidamente el estado de la tarea hasta que alcanza un estado terminal.
Bucle de sondeo
import time
def wait_for_job(job_id: str, interval: int = 5, max_wait: int = 600) -> dict:
waited = 0
while waited < max_wait:
response = requests.get(
f"{BASE_URL}/omnihuman-1.5/jobs/{job_id}",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
job = response.json()
status = job["status"]
if status == "succeeded" or status == "completed":
return job
if status == "failed" or status == "error":
raise RuntimeError(f"Job {job_id} failed: {job}")
time.sleep(interval)
waited += interval
raise TimeoutError(f"Job {job_id} did not finish within {max_wait}s")
Decidir cuándo detenerse
Tres resultados terminan el bucle y tu código debe manejar cada uno:
- Éxito: la tarea reporta un estado de finalización e incluye una referencia al video de salida. Sal del bucle y pasa a la descarga.
- Fallo: la tarea reporta un estado de error. No reintentes a ciegas; revisa primero el mensaje de error, porque los fallos suelen deberse a URLs de entrada ilegibles o audio no admitido.
- Tiempo de espera agotado: se alcanza tu propio límite de tiempo real. Trátalo como algo distinto de un fallo: la tarea podría completarse aún en el servidor, así que decide de forma deliberada si seguir sondeando, ampliar la ventana de tiempo o abandonar.
Etiqueta del sondeo
Usa un intervalo moderado en lugar de un bucle apretado. Para el renderizado de video, unos segundos entre comprobaciones es suficiente y mantiene el volumen de solicitudes predecible. Si prevés colas largas, aplica un retroceso progresivo y establece siempre un límite para que un proceso de trabajo atascado no sondee para siempre. En producción, prefiere procesos de trabajo en segundo plano o colas de tareas a sondear dentro del manejador de una solicitud web.
Descargar y almacenar el video de salida
Cuando el sondeo devuelve una tarea completada, la respuesta incluye una referencia al video, normalmente un campo de URL dentro del objeto de resultado. Recupéralo y guárdalo en tu propio almacenamiento, en lugar de depender de ese enlace a largo plazo.
def download_video(job: dict, destination: str) -> None:
video_url = job.get("output_url") or job.get("result", {}).get("video_url")
if not video_url:
raise ValueError(f"No video reference in job payload: {job}")
with requests.get(video_url, stream=True, timeout=120) as response:
response.raise_for_status()
with open(destination, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
Precauciones que ahorran tiempo de depuración
- Descarga en streaming. Los videos son grandes; escribir por bloques evita cargar el archivo entero en memoria, algo importante en procesos de trabajo con memoria limitada.
- Usa nombres de archivo deterministas. Derívalos del identificador de tarea más una marca de tiempo, para que las ejecuciones sean rastreables y las repeticiones no colisionen.
- Descarga primero a un archivo temporal y luego muévelo. Escribe en una ruta temporal y renombra al terminar, para que una descarga parcial nunca se confunda con un archivo completo.
- Trata la URL devuelta como temporal. Copia los bytes en tu propio bucket o sistema de archivos y registra el identificador de tarea junto al objeto almacenado para localizarlo después.
- Verifica antes de declarar el éxito. Antes de marcar la tarea como completada en la base de datos, comprueba que el archivo descargado no está vacío y tiene un tamaño razonable.
Solucionar fallos habituales de solicitud y sondeo
La mayoría de los problemas de integración caen en unas pocas categorías. Revísalas en orden.
Errores de autenticación y solicitud
401 Unauthorized: la clave falta, está mal formada o no se envía como tokenBearer. Revisa con cuidado la cabecera, incluido el espacio después deBearer.403: la clave es válida pero no tiene permiso para este endpoint; confirma que la clave pertenece al proyecto que esperas.- Errores de validación
422/400: lee el mensaje. Las causas habituales incluyen falta deaudio_url, falta deimage_url, URL ilegible o formato de audio no admitido. - HTML inesperado en la respuesta: has accedido a un host o ruta incorrectos. Verifica la URL base en la referencia de la API en lugar de reconstruirla de memoria.
La tarea falla o nunca termina
- La API no puede acceder a la URL de entrada: un enlace que funciona en el navegador puede estar bloqueado por una red privada, una firma caducada o protección contra enlaces desde otros dominios. Prueba primero con una URL pública estable.
- El audio y la imagen no encajan: pistas silenciosas, extremadamente largas o en formatos no admitidos pueden estancar o hacer fallar el renderizado. Valida el pipeline primero con un fragmento corto en formato estándar.
- Sondeo demasiado frecuente: un bucle apretado puede activar límites de tasa y producir errores que parecen fallos de la tarea. Baja el ritmo y añade retroceso.
- Tiempo de sondeo demasiado corto: abandonar unos segundos después con una tarea en cola produce tiempos de espera agotados falsos. Fija un tiempo máximo de espera realista y registra el estado visto antes de detenerte.
- Estados terminales no gestionados: si el bucle solo comprueba el éxito, los fallos se quedarán girando hasta el tiempo de espera. Ramifica explícitamente para los estados de fallo.
Hábito de validación mínima
Cuando algo falla, reduce a la prueba mínima: un fragmento de audio corto, un retrato pequeño, un envío y una comprobación de estado unos segundos después. Si eso funciona, el problema está en tu payload, en la lógica de sondeo o en el alojamiento de las entradas, no en la llamada a la API en sí.
Artículos relacionados
Ver todos los artículos →-
Tutorial
Cómo llamar a hailuo-2.3-fast/image-to-video a través de la API de HiAPI: práctica con curl y Python
La primera solicitud ejecutable del nivel rápido y de bajo coste de imagen a vídeo: del ID del modelo a la descarga del MP4
-
Tutorial
Cómo llamar a hailuo-2.3/text-to-video mediante la API de HiAPI: curl, Python y ejemplos de solicitud ejecutables
-
Tutorial
Uso de Kandinsky 6.0 Pro texto a video en HiAPI: tutorial con curl y Python
Demostración práctica centrada en la solicitud: construir la llamada de prompt a clip de video, enviar la solicitud, sondear el estado de finalización y descargar el resultado