Creare un video con voce narrante
Una guida end-to-end che scrive un copione con un LLM, genera le clip, sintetizza la voce fuori campo e le combina — usando una sola chiave API.
Questo tutorial concatena tre tipi di modello in un unico script: un modello linguistico scrive la narrazione, un modello video genera le riprese e un modello vocale legge il copione ad alta voce. È un esempio realistico del perché un'unica API e un unico saldo condiviso tra i tipi di modello siano utili.
Cosa ti serve: Python 3.9+, una chiave API e ffmpeg se alla fine vuoi unire audio e video.
Questo esegue attività di generazione reali e consuma crediti reali. Il video è la parte costosa — parti da una durata breve mentre stai sperimentando e usa la stima dei prezzi se prima vuoi una cifra esatta.
Preparazione
pip install requests
export ATLASCLOUD_API_KEY="your-api-key"Passo 1 — Funzioni di supporto condivise
Ogni attività sui media segue la stessa struttura invia-e-poi-fai-polling, quindi definiscila una volta sola.
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:
"""Invia un'attività. I parametri sono piatti — non racchiuderli in un oggetto `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:
"""Fa polling fino allo stato terminale, applicando un backoff progressivo."""
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"Job failed: {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 pathPasso 2 — Scrivere il copione con un LLM
I modelli linguistici usano un URL di base diverso e sono sincroni, quindi non passano da 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)Passo 3 — Generare le riprese
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 generazione video richiede minuti, non secondi. In produzione, registra un webhook e lascia che sia l'attività a richiamarti, invece di tenere aperto un ciclo di polling.
Passo 4 — Sintetizzare la voce fuori campo
La narrazione del passo 2 diventa qui l'input. Voce, musica e trascrizione condividono lo stesso endpoint — è il modello a decidere quale delle tre ottieni.
audio_id = submit(
"generateAudio",
"bytedance/seed-audio-1.0",
text=narration,
format="mp3",
sample_rate=44100,
speech_rate=-5, # Un ritmo leggermente più lento funziona meglio per la narrazione
)
audio = wait(audio_id)
download(audio["outputs"][0], "voiceover.mp3")Vedi Modelli audio per l'insieme completo dei parametri, comprese le voci di riferimento.
Passo 5 — Combinare
ffmpeg -i footage.mp4 -i voiceover.mp3 \
-c:v copy -c:a aac -shortest narrated.mp4Facoltativo — Generare i sottotitoli
Passa di nuovo la voce fuori campo attraverso un modello di trascrizione per ottenere i tempi parola per parola da usare nei sottotitoli.
stt_id = submit(
"generateAudio",
"bytedance/seed-asr-2.0",
audio_url=audio["outputs"][0], # Attenzione al nome del campo: audio_url
enable_punc=True,
show_utterances=True,
)
stt = wait(stt_id)
# Per i modelli di trascrizione, outputs[0] è il testo stesso, non l'URL di un file
print(stt["outputs"][0])
for word in stt.get("stt_result", {}).get("words", [])[:10]:
print(word["start"], word["end"], word["text"])I modelli di trascrizione accettano audio_url, ma altri modelli di riconoscimento vocale accettano invece audio. Controlla il riferimento API del modello — passare il nome del campo sbagliato fa fallire la validazione.
Dove andare adesso
- Sostituisci il modello video con uno che accetta un'immagine di riferimento e guida l'aspetto a partire da un fermo immagine che fornisci tu
- Passa dal polling ai webhook, così le attività lunghe non bloccano il tuo processo
- Esegui più clip in parallelo e concatenale in una sequenza più lunga
- Aggiungi la gestione dei retry descritta in Errori e limiti di frequenza
Argomenti correlati
Last updated on