Crear un video narrado
Un recorrido de principio a fin que escribe un guion con un LLM, genera clips, sintetiza una voz en off y lo combina todo — con una sola clave de API.
Este tutorial encadena tres tipos de modelo en un mismo script: un modelo de lenguaje escribe la narración, un modelo de video genera las imágenes y un modelo de voz lee el guion en voz alta. Es un ejemplo realista de por qué resulta útil tener una sola API y un saldo común para todos los tipos de modelo.
Lo que necesitas: Python 3.9+, una clave de API y ffmpeg si quieres mezclar el audio y el video al final.
Esto lanza tareas de generación reales y consume créditos reales. El video es la parte cara — empieza con una duración corta mientras iteras y usa la estimación de precios si quieres una cifra exacta de antemano.
Preparación
pip install requests
export ATLASCLOUD_API_KEY="your-api-key"Paso 1 — Utilidades compartidas
Toda tarea de medios sigue la misma forma de enviar y consultar, así que la definimos una sola vez.
import os, time, requests
API_KEY = os.environ["ATLASCLOUD_API_KEY"]
BASE = "https://api.atlascloud.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
TERMINAL = {"completed", "succeeded", "failed", "timeout"}
def submit(endpoint: str, model: str, **params) -> str:
"""Envía una tarea. Ojo: los parámetros van planos, no dentro de input."""
r = requests.post(f"{BASE}/model/{endpoint}",
headers=HEADERS, json={"model": model, **params}, timeout=60)
r.raise_for_status()
return r.json()["data"]["id"]
def wait(prediction_id: str, timeout: int = 900) -> dict:
"""Consulta hasta llegar a un estado final, con el intervalo creciendo poco a poco."""
deadline, delay = time.time() + timeout, 2.0
while time.time() < deadline:
r = requests.get(f"{BASE}/model/prediction/{prediction_id}",
headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()["data"]
if data.get("status") in TERMINAL:
if data["status"] in ("failed", "timeout"):
raise RuntimeError(f"La tarea falló: {data.get('error') or data['status']}")
return data
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
raise TimeoutError(prediction_id)
def download(url: str, path: str) -> str:
r = requests.get(url, timeout=300)
r.raise_for_status()
with open(path, "wb") as f:
f.write(r.content)
return pathPaso 2 — Escribir el guion con un LLM
Los modelos de lenguaje usan una base URL distinta y son síncronos, así que no pasan por submit/wait.
def write_narration(topic: str) -> str:
r = requests.post(
"https://api.atlascloud.ai/v1/chat/completions",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"model": "deepseek-ai/deepseek-v3.2",
"messages": [
{"role": "system",
"content": "You write narration for short videos. "
"Reply with two sentences of spoken narration and nothing else."},
{"role": "user", "content": f"Topic: {topic}"},
],
"max_tokens": 200,
},
timeout=120,
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"].strip()
narration = write_narration("how ocean waves shape a coastline")
print(narration)Paso 3 — Generar las imágenes
video_id = submit(
"generateVideo",
"alibaba/wan-2.5/text-to-video",
prompt="slow aerial shot of waves breaking against a rocky coastline at golden hour",
duration=5,
)
video = wait(video_id)
download(video["outputs"][0], "footage.mp4")La generación de video tarda minutos, no segundos. En producción, registra un webhook y deja que la tarea te avise en lugar de mantener abierto un bucle de polling.
Paso 4 — Sintetizar la voz en off
La narración del paso 2 se convierte aquí en la entrada. Voz, música y transcripción comparten el mismo endpoint — es el modelo el que decide qué obtienes.
audio_id = submit(
"generateAudio",
"bytedance/seed-audio-1.0",
text=narration,
format="mp3",
sample_rate=44100,
speech_rate=-5, # Un poco más lento, así la narración se entiende mejor
)
audio = wait(audio_id)
download(audio["outputs"][0], "voiceover.mp3")Consulta Modelos de audio para el conjunto completo de parámetros, incluidas las voces de referencia.
Paso 5 — Combinar
ffmpeg -i footage.mp4 -i voiceover.mp3 \
-c:v copy -c:a aac -shortest narrated.mp4Opcional — Generar subtítulos
Pasa la voz en off por un modelo de transcripción para obtener marcas de tiempo por palabra y crear subtítulos.
stt_id = submit(
"generateAudio",
"bytedance/seed-asr-2.0",
audio_url=audio["outputs"][0], # Ojo: el campo se llama audio_url
enable_punc=True,
show_utterances=True,
)
stt = wait(stt_id)
# En los modelos de transcripción, outputs[0] es el texto en sí, no una URL de archivo
print(stt["outputs"][0])
for word in stt.get("stt_result", {}).get("words", [])[:10]:
print(word["start"], word["end"], word["text"])Los modelos de transcripción reciben audio_url, pero algunos otros modelos de voz a texto reciben audio. Consulta la referencia de API del modelo — pasar el nombre de campo equivocado falla en la validación.
Por dónde seguir
- Cambia el modelo de video por uno que acepte una imagen de referencia y dirige el estilo con una imagen fija que tú proporciones
- Pasa del polling a los webhooks para que las tareas largas no bloqueen tu proceso
- Lanza varios clips en paralelo y concaténalos en una secuencia más larga
- Añade la gestión de reintentos de Errores y límites de tasa
Relacionado
Last updated on