Claude Opus 5 is now live on CometAPI →

Cara Menambah Penjanaan Video AI ke Aplikasi SaaS

CometAPI
AnnaJun 5, 2026
Cara Menambah Penjanaan Video AI ke Aplikasi SaaS

Menambah penjanaan video pada aplikasi anda tidak sama seperti menambah penjanaan imej. Panggilan API memulangkan respons serta-merta — tetapi videonya belum siap. Anda akan menerima ID tugas, dan anda perlu terus bertanya "sudah siap?" sehingga siap.

Kebanyakan pembangun mengalami perkara ini pada kali pertama mereka memanggil API video, menunggu badan respons dengan URL video, tetapi menerima ID tugas sebaliknya. Panduan ini menerangkan keseluruhan aliran: menghantar tugas, polling untuk keputusan, mengendalikan kegagalan, dan menyimpan output sebelum URL tamat tempoh.

Apa yang akan anda bina

Satu perkhidmatan backend yang menerima prompt teks atau imej, menghantar tugas penjanaan video, memantau sehingga selesai, dan memulangkan URL video akhir. Anda akan menggunakan empat model — Veo 3 Fast, Sora 2, Kling Video, dan Runway — semuanya menggunakan satu kunci API.

Prasyarat:

  • Python 3.8+ atau Node.js 18+
  • Kunci CometAPI
  • Kefahaman asas tentang REST API

Fahami mengapa penjanaan video berbeza

Dengan penjanaan imej, anda menghantar permintaan dan menerima imej dalam respons yang sama. Penjanaan video menggunakan barisan tugas asinkron:

  1. Hantar permintaan penjanaan → menerima task_id
  2. Poll endpoint status setiap beberapa saat
  3. Apabila status mencapai keadaan terminal, anda akan menerima URL video
  4. Muat turun dan simpan video — URL adalah sementara

Jika anda memperlakukan penjanaan video seperti penjanaan imej dan menunggu respons pertama mengandungi video, permintaan anda akan tamat masa setiap kali.

Dalam perkhidmatan web produksi, gelung polling ini patut berjalan dalam pekerja latar (Celery, Bull, atau seumpamanya), bukan dalam pengendali permintaan anda. Contoh di bawah menggunakan polling segerak — sesuai untuk skrip dan prototaip, tetapi bukan untuk menangani pengguna serentak.

Pilih model

ModelPenyediaTempoh maksimumHarga (melalui CometAPI)Terbaik untuk
Veo 3 FastGoogle8 saat$0.05/saatPembuatan prototaip pantas, klip sosial
Sora 2OpenAI (melalui ID model CometAPI)~10 saat$0.08/saatVideo pendek kreatif berkualiti tinggi
Kling VideoKuaishou10 saat$0.13–$2.64/tugasKandungan pemasaran, kawalan terperinci
Runway Gen-3A TurboRunway5 atau 10 saat$0.32/tugasImej-ke-video, kandungan komersial

Source**: Halaman model CometAPI, Mei 2026. Nota: "Sora 2" ialah pengenal model CometAPI — rujuk halaman model mereka untuk butiran model asas.

  • Veo 3 Fast menyokong teks-ke-video dan imej-ke-video. Paling murah per saat, titik mula yang baik.
  • Sora 2 menjana audio secara natif bersama video — dialog, bunyi ambien, dan kesan tanpa langkah TTS berasingan.
  • Kling Video menyediakan negative_prompt, cfg_scale, tetapan pergerakan kamera, dan mod pro. Paling banyak kawalan antara empat ini.
  • Runway melalui CometAPI adalah imej-ke-video sahaja. Beri imej statik dan penerangan pergerakan, dan ia akan menganimaskannya.

Hantar tugas Veo

Veo menggunakan multipart/form-data. Gunakan files= dalam Python requests untuk menghantarnya dengan betul — data=dict akan menghantar application/x-www-form-urlencoded yang tidak sama:

import requestsimport osfrom dotenv import load_dotenv​load_dotenv()​def submit_veo_task(prompt: str, size: str = "16x9") -> str:    """Submit a Veo 3 Fast text-to-video task. Returns task_id."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    response = requests.post(        "https://api.cometapi.com/v1/videos",        headers={"Authorization": f"Bearer {api_key}"},        files={            "prompt": (None, prompt),            "model": (None, "veo3-fast"),            "size": (None, size)        },        timeout=30    )    response.raise_for_status()    return response.json()["id"]​​task_id = submit_veo_task("A paper kite drifting above a wheat field on a windy afternoon")print(f"Task submitted: {task_id}")

Polling untuk keputusan

import time​def poll_veo_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str:    """Poll until Veo task completes. Returns video URL."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    headers = {"Authorization": f"Bearer {api_key}"}    url = f"https://api.cometapi.com/v1/videos/{task_id}"    elapsed = 0​    while elapsed < max_wait:        response = requests.get(url, headers=headers, timeout=30)        response.raise_for_status()        result = response.json()        status = result.get("status")​        if status == "succeeded":            return result["output"][0]        elif status in ("failed", "cancelled"):            raise RuntimeError(                f"Task {task_id} failed with status '{status}': "                f"{result.get('error', 'no error detail returned')}"            )​        time.sleep(interval)        elapsed += interval​    raise TimeoutError(f"Task {task_id} did not complete within {max_wait} seconds")​​video_url = poll_veo_task(task_id)print(f"Video ready: {video_url}")

Guna Kling Video untuk kawalan lebih terperinci

Kling mempunyai struktur endpoint yang berbeza dan menggunakan JSON. Perhatikan bahawa rentetan status terminal Kling ialah "succeed" (bukan "succeeded") — ini sepadan dengan format respons API sebenar:

def submit_kling_task(prompt: str, duration: str = "5", mode: str = "std") -> str:    """Submit a Kling text-to-video task. Returns task_id."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    response = requests.post(        "https://api.cometapi.com/kling/v1/videos/text2video",        headers={            "Authorization": f"Bearer {api_key}",            "Content-Type": "application/json"        },        json={            "model_name": "kling-v1-6",            "prompt": prompt,            "negative_prompt": "blurry, low quality, watermark",            "cfg_scale": 0.5,            "mode": mode,         # "std" or "pro"            "aspect_ratio": "16:9",            "duration": duration  # "5" or "10"        },        timeout=30    )    response.raise_for_status()    return response.json()["data"]["task_id"]​​def poll_kling_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str:    """Poll Kling task until complete. Returns video URL."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    headers = {"Authorization": f"Bearer {api_key}"}    url = f"https://api.cometapi.com/kling/v1/videos/text2video/{task_id}"    elapsed = 0​    while elapsed < max_wait:        response = requests.get(url, headers=headers, timeout=30)        response.raise_for_status()        result = response.json()        status = result["data"]["task_status"]​        if status == "succeed":  # Kling uses "succeed", not "succeeded"            return result["data"]["task_result"]["videos"][0]["url"]        elif status == "failed":            error_detail = result.get("data", {}).get("task_result", "no detail")            raise RuntimeError(                f"Kling task {task_id} failed: {error_detail}"            )​        time.sleep(interval)        elapsed += interval​    raise TimeoutError(f"Kling task {task_id} timed out after {max_wait}s")

Source**: Dokumen Kling Video CometAPI

Animasi imej statik dengan Runway

Runway adalah imej-ke-video sahaja. Ia juga memerlukan header tambahan (X-Runway-Version):

def submit_runway_task(image_url: str, motion_prompt: str, duration: int = 5) -> str:    """Submit a Runway image-to-video task. Returns task_id."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    response = requests.post(        "https://api.cometapi.com/runwayml/v1/image_to_video",        headers={            "Authorization": f"Bearer {api_key}",            "X-Runway-Version": "2024-11-06",            "Content-Type": "application/json"        },        json={            "model": "gen3a_turbo",            "promptImage": image_url,  # must be a stable HTTPS URL            "promptText": motion_prompt,            "duration": duration,            "ratio": "1280:720",            "watermark": False        },        timeout=30    )    response.raise_for_status()    return response.json()["id"]​​def poll_runway_task(task_id: str, interval: int = 5, max_wait: int = 600) -> str:    """Poll Runway task. Returns video URL when done."""    api_key = os.getenv("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY environment variable is not set")​    headers = {        "Authorization": f"Bearer {api_key}",        "X-Runway-Version": "2024-11-06"    }    url = f"https://api.cometapi.com/runwayml/v1/tasks/{task_id}"    elapsed = 0​    while elapsed < max_wait:        response = requests.get(url, headers=headers, timeout=30)        response.raise_for_status()        result = response.json()        status = result.get("status")​        if status == "task_not_exist":            # CometAPI-specific: task is still initializing, retry after a few seconds            time.sleep(interval)            elapsed += interval            continue        elif status == "succeeded":            return result["output"][0]        elif status in ("failed", "cancelled"):            raise RuntimeError(f"Runway task {task_id} failed: {result.get('error', 'no detail')}")​        time.sleep(interval)        elapsed += interval​    raise TimeoutError(f"Runway task {task_id} timed out after {max_wait}s")

Source**: Dokumen Runway CometAPI

Simpan video sebelum URL tamat tempoh

URL video daripada API penjanaan adalah sementara. Muat turun fail serta-merta dan simpan di tempat yang anda kawal:

import requestsimport pathlib​def download_video(url: str, output_path: str) -> None:    """Download video from URL to local file using streaming."""    out = pathlib.Path(output_path)    if out.parent != pathlib.Path("."):        out.parent.mkdir(parents=True, exist_ok=True)​    with requests.get(url, stream=True, timeout=60) as r:        r.raise_for_status()        with open(out, "wb") as f:            for chunk in r.iter_content(chunk_size=8192):                f.write(chunk)    print(f"Saved to {output_path}")​​# Full flowtask_id = submit_veo_task("A timelapse of clouds moving over a city skyline")video_url = poll_veo_task(task_id)download_video(video_url, "output/city_timelapse.mp4")

Dalam produksi, gantikan penulisan fail setempat dengan muat naik ke S3, Cloudflare R2, atau storan pilihan anda. Corak penstriman kekal sama — salurkan bait secara terus dan bukannya memuatkan keseluruhan video ke dalam memori.

Tangani kegagalan

SimptomPunca berkemungkinanPenyelesaian
Tugas tersekat dalam queued selama 10+ minBeban pelayan atau model tidak tersediaCuba semula dengan model berbeza
task_not_exist pada polling Runway pertamaTugas masih dalam proses permulaanTunggu 5 saat dan cuba semula — tingkah laku CometAPI yang didokumenkan
gagal tanpa mesej ralatPrompt mencetuskan penapis kandunganUbah cara penulisan prompt
URL video memulangkan 403URL tamat tempoh sebelum muat turunMuat turun serta-merta selepas memperoleh URL
Tamat masa selepas 10 minPenjanaan mengambil masa terlalu lamaTingkatkan max_wait atau beralih ke Veo 3 Fast
Kling mengembalikan "succeed" bukan "succeeded"API Kling menggunakan rentetan status tidak piawaiIni betul — lihat kod polling Kling di atas

Sumber: Dokumen penjanaan video CometAPI

Versi Node.js

Node.js 18+ menyertakan fetch dan FormData secara natif. Contoh ini merangkumi keempat-empat model:

// Node.js 18+ — no extra packages needed​const API_KEY = process.env.COMETAPI_KEY;if (!API_KEY) throw new Error('COMETAPI_KEY is not set');​// --- Veo 3 Fast ---async function submitVeoTask(prompt, size = '16x9') {  const form = new FormData();  form.append('prompt', prompt);  form.append('model', 'veo3-fast');  form.append('size', size);​  const res = await fetch('https://api.cometapi.com/v1/videos', {    method: 'POST',    headers: { 'Authorization': `Bearer ${API_KEY}` },    body: form  });  if (!res.ok) throw new Error(`Veo submit failed: ${res.status}`);  return (await res.json()).id;}​async function pollVeoTask(taskId, intervalMs = 10000, maxWaitMs = 600000) {  let elapsed = 0;  while (elapsed < maxWaitMs) {    const res = await fetch(`https://api.cometapi.com/v1/videos/${taskId}`, {      headers: { 'Authorization': `Bearer ${API_KEY}` }    });    if (!res.ok) throw new Error(`Poll failed: ${res.status}`);    const result = await res.json();​    if (result.status === 'succeeded') return result.output[0];    if (['failed', 'cancelled'].includes(result.status)) {      throw new Error(`Task ${taskId} failed: ${result.error ?? 'no detail'}`);    }    await new Promise(r => setTimeout(r, intervalMs));    elapsed += intervalMs;  }  throw new Error(`Task ${taskId} timed out`);}​// --- Kling Video ---async function submitKlingTask(prompt, duration = '5', mode = 'std') {  const res = await fetch('https://api.cometapi.com/kling/v1/videos/text2video', {    method: 'POST',    headers: {      'Authorization': `Bearer ${API_KEY}`,      'Content-Type': 'application/json'    },    body: JSON.stringify({      model_name: 'kling-v1-6',      prompt,      negative_prompt: 'blurry, low quality, watermark',      cfg_scale: 0.5,      mode,      aspect_ratio: '16:9',      duration    })  });  if (!res.ok) throw new Error(`Kling submit failed: ${res.status}`);  return (await res.json()).data.task_id;}​async function pollKlingTask(taskId, intervalMs = 10000, maxWaitMs = 600000) {  let elapsed = 0;  while (elapsed < maxWaitMs) {    const res = await fetch(      `https://api.cometapi.com/kling/v1/videos/text2video/${taskId}`,      { headers: { 'Authorization': `Bearer ${API_KEY}` } }    );    if (!res.ok) throw new Error(`Kling poll failed: ${res.status}`);    const result = await res.json();    const status = result.data.task_status;​    if (status === 'succeed') return result.data.task_result.videos[0].url;    if (status === 'failed') {      throw new Error(`Kling task ${taskId} failed: ${JSON.stringify(result.data.task_result ?? 'no detail')}`);    }    await new Promise(r => setTimeout(r, intervalMs));    elapsed += intervalMs;  }  throw new Error(`Kling task ${taskId} timed out`);}​// --- Runway (image-to-video) ---async function submitRunwayTask(imageUrl, motionPrompt, duration = 5) {  const res = await fetch('https://api.cometapi.com/runwayml/v1/image_to_video', {    method: 'POST',    headers: {      'Authorization': `Bearer ${API_KEY}`,      'X-Runway-Version': '2024-11-06',      'Content-Type': 'application/json'    },    body: JSON.stringify({      model: 'gen3a_turbo',      promptImage: imageUrl,      promptText: motionPrompt,      duration,      ratio: '1280:720',      watermark: false    })  });  if (!res.ok) throw new Error(`Runway submit failed: ${res.status}`);  return (await res.json()).id;}​async function pollRunwayTask(taskId, intervalMs = 5000, maxWaitMs = 600000) {  let elapsed = 0;  while (elapsed < maxWaitMs) {    const res = await fetch(      `https://api.cometapi.com/runwayml/v1/tasks/${taskId}`,      { headers: { 'Authorization': `Bearer ${API_KEY}`, 'X-Runway-Version': '2024-11-06' } }    );    if (!res.ok) throw new Error(`Runway poll failed: ${res.status}`);    const result = await res.json();    const status = result.status;​    if (status === 'task_not_exist') {      // CometAPI-specific: task still initializing      await new Promise(r => setTimeout(r, intervalMs));      elapsed += intervalMs;      continue;    }    if (status === 'succeeded') return result.output[0];    if (['failed', 'cancelled'].includes(status)) {      throw new Error(`Runway task ${taskId} failed: ${result.error ?? 'no detail'}`);    }    await new Promise(r => setTimeout(r, intervalMs));    elapsed += intervalMs;  }  throw new Error(`Runway task ${taskId} timed out`);}​// Usage exampleconst taskId = await submitVeoTask('A paper kite drifting above a wheat field');const videoUrl = await pollVeoTask(taskId);console.log('Video ready:', videoUrl);

Langkah seterusnya

Anda kini mempunyai kod berfungsi untuk empat model video, gelung polling yang mengendalikan kegagalan, dan langkah muat turun yang mengelakkan anda kehilangan kandungan yang dijana.

Masalah seterusnya yang sering dihadapi pembangun: mereka mengeraskan satu model, dan menukar kepada pilihan yang lebih murah atau pantas memerlukan pengubahsuaian berbilang fail. Artikel seterusnya membincangkan cara menghala permintaan merentasi model tanpa menulis semula kod anda.

Seterusnya: Cara Bertukar Antara Model AI Tanpa Menulis Semula Kod Anda

Soalan Lazim

S: Mengapa saya menerima ID tugas dan bukannya video dalam respons API?

J: Penjanaan video adalah asinkron — model seperti Veo, Sora, Kling, dan Runway mengambil masa 2–5 minit untuk render. API memulangkan ID tugas serta-merta supaya permintaan anda tidak tamat masa. Anda akan melakukan polling pada endpoint status berasingan sehingga tugas mencapai keadaan terminal (succeeded, succeed, failed).

S: Berapa lama URL video yang dijana kekal sah?

J: URL video daripada API penjanaan adalah sementara. Muat turun fail serta-merta selepas memperoleh URL dan simpan dalam storan anda sendiri (S3, Cloudflare R2, dsb.). Jangan simpan URL dan mengharapkan ia berfungsi beberapa jam kemudian.

S: Apakah perbezaan antara Veo 3 Fast dan Kling Video?

J: Veo 3 Fast lebih murah ($0.05/saat), lebih pantas, dan lebih ringkas untuk dipanggil. Kling Video memberikan lebih banyak kawalan: negative_prompt, cfg_scale, tetapan pergerakan kamera, dan mod kualiti pro. Jika anda perlu menala hasil dengan lebih halus, gunakan Kling. Jika anda perlukan kelajuan dan kos rendah, gunakan Veo 3 Fast.

S: Bolehkah saya menjana video daripada imej dan bukannya prompt teks?

J: Ya. Veo menyokong imej-ke-video dengan menghantar fail input_reference. Kling menyokongnya melalui endpoint /kling/v1/videos/image2video dengan parameter image (URL atau base64). Runway adalah imej-ke-video sahaja — ia tidak menerima prompt teks sahaja melalui CometAPI.

S: Mengapa Runway memulangkan task_not_exist pada polling pertama?

J: Ini adalah tingkah laku CometAPI yang didokumenkan — tugas masih dalam proses permulaan di backend. Tunggu beberapa saat dan cuba semula. Ini bukan ralat. Kod polling di atas mengendalikannya secara automatik.

S: Mengapa Kling menggunakan "succeed" dan bukan "succeeded"?

J: Itulah format respons API Kling sebenar. Ia bukan kesilapan taip. Veo dan Runway menggunakan "succeeded" — Kling menggunakan "succeed". Jika anda membina pembungkus polling bersatu, anda perlu mengendalikan kedua-dua rentetan.

S: Adakah gelung polling segerak selamat digunakan dalam pelayan web?

J: Tidak. Gelung polling dalam panduan ini menyekat thread selama beberapa minit. Dalam perkhidmatan web sebenar dengan pengguna serentak, jalankan polling dalam pekerja latar (Celery untuk Python, Bull untuk Node.js). Hantar tugas dalam pengendali permintaan, pulangkan ID tugas kepada klien, dan biarkan pekerja memaklumkan klien apabila video sedia.

Bersedia untuk mengurangkan kos pembangunan AI sebanyak 20%?

Mulakan secara percuma dalam beberapa minit. Kredit percubaan percuma disertakan. Tiada kad kredit diperlukan.

Baca Lagi