Membuat Video Bernarasi

Panduan menyeluruh yang menulis naskah dengan LLM, membuat klip video, menyintesis sulih suara, lalu menggabungkan semuanya — hanya dengan satu API key.

Tutorial ini merangkai tiga jenis model dalam satu skrip: model bahasa menulis narasinya, model video membuat rekamannya, dan model suara membacakan naskah tersebut. Ini contoh nyata mengapa satu API dan satu saldo yang berlaku lintas jenis model itu bermanfaat.

Yang Anda butuhkan: Python 3.9+, sebuah API key, dan ffmpeg jika di akhir Anda ingin menggabungkan audio dengan videonya.

Langkah-langkah ini menjalankan tugas pembuatan yang sesungguhnya dan menghabiskan kredit sungguhan. Video adalah bagian yang mahal — mulailah dengan durasi pendek selama Anda masih bereksperimen, dan gunakan estimasi harga jika Anda ingin mengetahui angka pastinya lebih dulu.

Persiapan

pip install requests
export ATLASCLOUD_API_KEY="your-api-key"

Langkah 1 — Fungsi bantu bersama

Setiap tugas media mengikuti pola kirim-lalu-polling yang sama, jadi definisikan sekali saja.

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:
    """Kirim sebuah tugas. Parameternya rata — jangan membungkusnya dalam objek `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:
    """Lakukan polling sampai status terminal, dengan backoff yang makin panjang."""
    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 path

Langkah 2 — Menulis naskah dengan LLM

Model bahasa memakai base URL yang berbeda dan bersifat sinkron, sehingga tidak melewati 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)

Langkah 3 — Membuat rekamannya

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")

Pembuatan video memakan waktu beberapa menit, bukan beberapa detik. Di produksi, daftarkan sebuah webhook dan biarkan tugas tersebut menghubungi Anda kembali, alih-alih membiarkan loop polling terus terbuka.

Langkah 4 — Menyintesis sulih suara

Narasi dari langkah 2 menjadi masukan di sini. Suara, musik, dan transkripsi memakai endpoint yang sama — modelnyalah yang menentukan hasil mana yang Anda dapatkan.

audio_id = submit(
    "generateAudio",
    "bytedance/seed-audio-1.0",
    text=narration,
    format="mp3",
    sample_rate=44100,
    speech_rate=-5,   # Tempo yang sedikit lebih lambat terdengar lebih pas sebagai narasi
)
audio = wait(audio_id)
download(audio["outputs"][0], "voiceover.mp3")

Lihat Model Audio untuk kumpulan parameter lengkapnya, termasuk rujukan suara.

Langkah 5 — Menggabungkan

ffmpeg -i footage.mp4 -i voiceover.mp3 \
  -c:v copy -c:a aac -shortest narrated.mp4

Opsional — Membuat subtitel

Umpankan kembali sulih suaranya ke model transkripsi untuk memperoleh waktu per kata bagi subtitel.

stt_id = submit(
    "generateAudio",
    "bytedance/seed-asr-2.0",
    audio_url=audio["outputs"][0],   # Perhatikan nama bidangnya: audio_url
    enable_punc=True,
    show_utterances=True,
)
stt = wait(stt_id)

# Pada model transkripsi, outputs[0] adalah teksnya sendiri, bukan URL berkas
print(stt["outputs"][0])
for word in stt.get("stt_result", {}).get("words", [])[:10]:
    print(word["start"], word["end"], word["text"])

Model transkripsi menerima audio_url, tetapi sebagian model speech-to-text lain justru menerima audio. Periksa referensi API model tersebut — mengirimkan nama bidang yang keliru akan gagal validasi.

Langkah berikutnya

  • Ganti model videonya dengan model yang menerima gambar rujukan, lalu tentukan tampilannya dari gambar diam yang Anda sediakan
  • Pindahkan polling ke webhook agar tugas yang lama tidak memblokir proses Anda
  • Jalankan beberapa klip secara paralel lalu gabungkan menjadi rangkaian yang lebih panjang
  • Tambahkan penanganan percobaan ulang dari Error & Batas Laju

Terkait

Last updated on

On this page