Tài liệu

Tài liệu

Tạo video

Tạo video từ văn bản và từ hình ảnh với dòng mô hình Veo 3.1 của Google. API có hai dạng: endpoint đồng bộ một lần, chờ cho đến khi video sẵn sàng, và một cặp endpoint gửi tác vụ rồi thăm dò bất đồng bộ, dành cho khối lượng công việc môi trường sản xuất.

Endpoint: POST /v1/video/generations (đồng bộ, trình bày trước), và POST /v1/videos + GET /v1/videos/{id} (bất đồng bộ, được khuyến nghị cho môi trường sản xuất). Thời gian tạo dao động từ 30 giây đến vài phút tùy mô hình và thời lượng — với endpoint đồng bộ, hãy đặt thời gian chờ phía client đủ dài (600s là an toàn).

Hàm fetch tích hợp sẵn của Node ngừng chờ header phản hồi sau 300 giây, bất kể bạn đặt thời gian chờ là bao nhiêu: khi gọi từ Node, hãy dùng API bất đồng bộ, hoặc gọi hàm fetch từ gói undici với tùy chọn dispatcher: new Agent({ headersTimeout: 600_000 }).

Tạo video từ văn bản

Truyền prompt và chọn một mô hình. aspect_ratio là tùy chọn.

import os

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane unfolding, soft light",
        "aspect_ratio": "16:9",
    },
    timeout=600,  # video generation can take minutes
)
print(resp.json()["data"][0]["url"])

duration (giây) đặt độ dài clip trên các mô hình tính phí theo giây: các mô hình Seedance và mô hình Wan 2.7. Các mô hình Veo bỏ qua tham số này; mỗi clip Veo dài 8 giây và được tính phí theo video.

# The models billed per second take a clip length: here 5 seconds of
# Seedance 2. Leave "duration" out and these models make a 5-second clip.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "seedance-2",
        "prompt": "a paper crane unfolds into a flock of birds over a city at dawn",
        "duration": 5,
        "aspect_ratio": "16:9",
    },
    timeout=600,
)
print(resp.json()["data"][0]["url"])

Chuyển hình ảnh thành video

Thêm image_url và Veo sẽ tạo chuyển động từ hình ảnh đó — mặc định, ảnh tĩnh trở thành khung hình đầu tiên của video.

# Image-to-video: animate a still image. The source image can be an
# https URL, or inline base64 — Kunavo hosts it for the model automatically.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "the crane gently lifts off the desk",
        "image_url": "https://your-cdn.com/source.png",
    },
    timeout=600,
)
print(resp.json()["data"][0]["url"])
image_url chấp nhận URL https:// hoặc URI base64 nội tuyến data:. base64 sẽ được giải mã và tự động lưu trữ trên CDN của Kunavo trước khi mô hình chạy.

Khung hình đầu, khung hình cuối và ảnh tham chiếu

image_mode điều khiển cách Veo sử dụng (các) hình ảnh. Truyền image_urls (một mảng) để gửi nhiều hơn một ảnh.

  • frame (mặc định) — một ảnh được dùng làm khung hình đầu; hai ảnh lần lượt làm khung hình đầu và cuối, Veo tạo phần chuyển tiếp giữa chúng.
  • reference — tối đa ba ảnh được dùng làm ảnh tham chiếu phong cách và chủ thể. Chúng định hướng diện mạo nhưng không bao giờ xuất hiện thành khung hình.
# First + last frame: pass two images and Veo fills in the motion.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a smooth transition between the two poses",
        "image_urls": [
            "https://your-cdn.com/first.png",
            "https://your-cdn.com/last.png",
        ],
        "image_mode": "frame",
    },
    timeout=600,
)

Tải khung hình nguồn lên

Khi chỉ có tệp cục bộ, hãy tải tệp lên một lần qua POST /v1/files rồi dùng lại URL vĩnh viễn được trả về.

# No public URL? Upload the still first, then animate it.
with open("frame.png", "rb") as f:
    up = requests.post(
        "https://api.kunavo.com/v1/files",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": f},
    ).json()

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "slow push-in, volumetric light",
        "image_url": up["url"],
    },
    timeout=600,
)

Tham số

Tham sốLoạiGhi chú
modelstring (bắt buộc)Slug mô hình video từ /v1/models.
promptstring (bắt buộc)Mô tả bằng văn bản về cảnh quay.
image_urlstringMột hình ảnh nguồn cho tác vụ tạo video từ hình ảnh. URL hoặc URI data:.
image_urlsstring[]Nhiều hình ảnh nguồn: hai khung hình hoặc ảnh tham chiếu. Được ưu tiên hơn image_url.
image_modestring"frame" (mặc định — khung hình đầu/cuối) hoặc "reference" (ảnh tham chiếu phong cách).
durationintĐộ dài clip tính bằng giây trên các mô hình tính phí theo giây, là 5 khi không truyền: Seedance 2, Seedance 2 Fast và Seedance 2.0 Mini nhận 4–15, Seedance 2.5 nhận 4–30, Wan 2.7 nhận 2–15. Độ dài nằm ngoài phạm vi của một mô hình sẽ bị từ chối với lỗi 400. Veo bỏ qua tham số này: mỗi clip Veo dài 8 giây.
aspect_ratiostringví dụ: "16:9", "9:16", "1:1".
resolutionstringKhi mô hình cung cấp các mức — ví dụ: "720p", "1080p".
inputobjectTham số mở rộng để bỏ qua giới hạn — xem Raw input bên dưới.

GET /v1/models liệt kê giá trị của hai trong số các tham số ở trên mà từng mô hình chấp nhận. Trường kunavo.tiers cung cấp mọi cấp độ resolution cùng giá niêm yết của từng cấp độ, và cấp độ được áp dụng cho yêu cầu nếu yêu cầu không chỉ định cấp độ. Trường kunavo.duration_seconds cung cấp phạm vi duration của một mô hình được tính phí theo giây.

Một hình ảnh mà mô hình không nhận, hoặc một giá trị resolution không thuộc các cấp độ của mô hình, sẽ bị từ chối với lỗi 400 trước khi bất kỳ khoản phí nào được tính, và error.param của lỗi đó nêu tên trường tương ứng.

capabilities của một mô hình trong GET /v1/models liệt kê những hình ảnh mà mô hình đó nhận:

  • image-to-video — một ảnh, dùng làm khung hình đầu.
  • first-last-frame — hai ảnh, dùng làm khung hình đầu và cuối.
  • reference-image — ảnh tham chiếu, được gửi với image_mode đặt thành reference. max_reference_images là số ảnh tham chiếu tối đa mà một yêu cầu có thể gửi.

Tham số mở rộng để truyền dữ liệu thô

Trên các mô hình tính phí theo giây, input được gửi nguyên văn lên thượng nguồn, thay cho phần đầu vào mà bộ chuyển đổi của chúng tôi sẽ tạo. Các mô hình Veo bỏ qua tham số này.

Các trường quyết định giá là ngoại lệ: resolution luôn được gửi dưới dạng một trong các cấp độ của mô hình (cấp độ mặc định nếu trường này không được cung cấp hoặc giá trị không phải một trong các cấp độ đó), còn duration phải là số giây nguyên nằm trong phạm vi của mô hình (mọi giá trị khác đều bị từ chối với lỗi 400; 5 được gửi nếu trường này không được cung cấp). Khi input được thiết lập, resolution và duration của chính yêu cầu sẽ không được đọc.

Các mô hình video

SlugNhà cung cấpGhi chú
veo-3GoogleVeo 3.1 Fast — nhanh hơn và rẻ hơn Quality, âm thanh tích hợp sẵn.
veo-3-qualityGoogleVeo 3.1 Quality — độ trung thực cao nhất.
veo-3-liteGoogleVeo 3.1 Lite — mức tiết kiệm cho bản xem trước.
seedance-2-5ByteDanceSeedance 2.5 — video tối đa 30s, tham chiếu đa phương thức, tính phí theo giây.
seedance-2ByteDanceSeedance 2 — tính nhất quán giữa nhiều cảnh quay, độ phân giải tối đa 1080p, tính phí theo giây.
seedance-2-fastByteDanceSeedance 2 Fast — mức tiết kiệm, độ phân giải tối đa 720p, tính phí theo giây.
seedance-2-miniByteDanceSeedance 2.0 Mini — mức giá thấp nhất, chỉ 480p/720p, dành cho bản nháp và khối lượng lớn.
wan-2-7AlibabaWan 2.7 — tạo video điện ảnh từ văn bản, tính phí theo giây.

Luôn kiểm tra /models để xem danh sách hiện tại và giá của từng mô hình.

Độ trễ & thời gian chờ

  • Kunavo thăm dò tác vụ ở nhà cung cấp thượng nguồn tối đa 540 giây trước khi trả về lỗi 504.
  • Các mức Fast (Veo 3.1 Fast / Lite): thường mất 30–90s.
  • Các mức Quality và thời lượng dài hơn: 2–5 phút.
  • Đặt thời gian chờ của client HTTP ít nhất là 600s.
  • Hàm fetch tích hợp sẵn của Node ngừng chờ header phản hồi sau 300 giây, bất kể bạn đặt thời gian chờ là bao nhiêu: khi gọi từ Node, hãy dùng API bất đồng bộ, hoặc gọi hàm fetch từ gói undici với tùy chọn dispatcher: new Agent({ headersTimeout: 600_000 }).
  • Với mạng không ổn định hoặc client di động, hãy ưu tiên API bất đồng bộ — yêu cầu gửi tác vụ trả về trong vài giây, sau đó bạn thăm dò để lấy kết quả.

API bất đồng bộ (gửi yêu cầu + thăm dò)

Endpoint: POST /v1/videos + GET /v1/videos/{id}. Đối tượng tác vụ mà chúng trả về sử dụng tên trường và giá trị trạng thái của đối tượng video của OpenAI, đồng thời bổ sung output chứa URL của đoạn clip. Yêu cầu là một phần thân JSON với các tham số ở trên.

Endpoint đồng bộ ở trên giữ kết nối HTTP mở tối đa chín phút — tiện cho các tập lệnh chạy một lần nhưng kém tin cậy trên thiết bị di động và kém hiệu quả trên máy chủ. Cặp endpoint bất đồng bộ trả về ngay một mã vid_*; bạn thăm dò GET /v1/videos/{id} cho đến khi status là completed hoặc failed. Cùng mô hình, cùng giá, cùng URL lưu trữ.

import os
import time

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]

# 1. Submit — returns immediately with a task id. No long-lived connection.
submit = requests.post(
    "https://api.kunavo.com/v1/videos",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        # Optional: a repeat under the same key returns the original task
        # rather than submitting again. A new task needs a new key.
        "Idempotency-Key": "my-task-uuid",
    },
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane",
        "aspect_ratio": "16:9",
    },
    timeout=60,
).json()

task_id = submit["id"]              # "vid_abc..."
status  = submit["status"]          # "queued"

# 2. Poll until terminal. Recommended cadence: 5s, backing off to 30s.
while status not in ("completed", "failed"):
    time.sleep(5)
    r = requests.get(
        f"https://api.kunavo.com/v1/videos/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()
    status = r["status"]

if status == "failed":
    raise RuntimeError(r["error"]["message"])
print(r["output"]["url"])

Với OpenAI SDK, hãy gửi phần thân đó bằng client.post và thăm dò trạng thái bằng client.get, như bên dưới. client.videos.create riêng của SDK không hoạt động ở đây: phương thức này chỉ nhận các tham số của Sora và gửi chúng dưới dạng biểu mẫu multipart (trong Python SDK kể từ phiên bản 2.27.0); endpoint này từ chối yêu cầu multipart đó với mã 400.

import os
import time

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["KUNAVO_API_KEY"],
    base_url="https://api.kunavo.com/v1",
)

# The same JSON body, sent with the client's own post().
task = client.post(
    "/videos",
    body={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane",
        "aspect_ratio": "16:9",
    },
    # object hands back the JSON as parsed. Passing dict instead raises
    # after the task has been created.
    cast_to=object,
    options={"headers": {"Idempotency-Key": "my-task-uuid"}},
)

while task["status"] not in ("completed", "failed"):
    time.sleep(5)
    task = client.get(f"/videos/{task['id']}", cast_to=object)

if task["status"] == "failed":
    raise RuntimeError(task["error"]["message"])
print(task["output"]["url"])

Cấu trúc phản hồi

TrườngLoạiGhi chú
idstringID tác vụ có tiền tố vid_. Ổn định qua các thay đổi ở nhà cung cấp thượng nguồn.
objectstring"video".
statusstringđang chờ | đang xử lý | đã hoàn tất | thất bại.
modelstringSlug model bạn đã gửi.
created_atintThời điểm Unix tính bằng giây khi chúng tôi chấp nhận yêu cầu.
completed_atint | nullThời điểm Unix tính bằng giây khi tác vụ kết thúc — null khi đang chờ.
expires_atintGiây Unix, 30 ngày sau khi gửi: có thể truy vấn tác vụ ít nhất cho đến lúc đó. URL kết quả của tác vụ không hết hạn.
progressint0–100. Hiện chúng tôi báo cáo 0 cho đến khi tác vụ kết thúc, sau đó là 100.
outputobject | null{url, urls[], duration_seconds, resolution} khi hoàn tất. duration_seconds là độ dài clip đã yêu cầu mô hình tạo và là null với Veo; resolution là cấp độ phân giải của clip, hoặc null nếu không có cấp độ nào được ghi lại.
errorobject | null{code, message} khi thất bại.

Tiêu đề và quy ước

  • Idempotency-Key (tùy chọn, tối đa 128 ký tự) — gửi hai lần cùng một khóa trên cùng tài khoản sẽ trả về tác vụ ban đầu thay vì tạo tác vụ trùng lặp. Hữu ích cho các vòng lặp thử lại.
  • Một khóa idempotency ứng với một yêu cầu: khóa đã được dùng để bắt đầu một tác vụ trên endpoint khác, cho model khác, hoặc cho một yêu cầu có prompt khác hay tham số khác sẽ bị từ chối với lỗi 400. Khóa dài hơn 128 ký tự và khóa được gửi kèm một yêu cầu đồng bộ cũng vậy. URL hình ảnh không được so sánh: yêu cầu dùng URL được ký lại vẫn là cùng một yêu cầu.
  • POST /v1/videos trả về 202 Accepted khi gửi mới và 200 OK khi khóa idempotency khớp và phát lại kết quả trước đó.
  • GET /v1/videos/{id} được giới hạn theo chủ sở hữu: truy vấn tác vụ thuộc tài khoản khác sẽ trả về 404 — chúng tôi không bao giờ xác nhận rằng một ID tồn tại với người khác.
  • Nhịp thăm dò được khuyến nghị: 5 s, với thời gian chờ lũy tiến tối đa 30 s. Tác vụ thường hoàn tất trong 30 s – 5 min tùy mô hình và độ phân giải.

Webhook

Gửi webhook_url (https) khi tạo tác vụ; ngay khi tác vụ kết thúc, Kunavo sẽ POST sự kiện video.completed / video.failed có chữ ký đến đó — gửi kèm cơ chế thử lại với thời gian chờ tăng dần. Trường data của sự kiện chính xác là nội dung phản hồi GET ở trên.

  • X-Kunavo-Webhook-Id — mã sự kiện ổn định, giống hệt nhau qua các lần thử lại (dùng để khử trùng lặp).
  • X-Kunavo-Webhook-Timestamp — thời điểm tính chữ ký, tính bằng giây Unix.
  • X-Kunavo-Webhook-Signature — HMAC-SHA256 của {timestamp}.{rawBody}, được tính bằng sha256= và khóa bí mật webhook của bạn.

Nếu webhook_url không phải là URL https, hoặc có chứa tên người dùng hay mật khẩu, yêu cầu sẽ bị từ chối với lỗi 400, và không có gì được tạo ra hay bị tính phí.

Khóa bí mật webhook của bạn nằm trong bảng điều khiển, tại Cài đặt → Webhooks. Khóa này bắt đầu bằng kwh_, và cùng một khóa bí mật được dùng để ký mọi sự kiện hình ảnh, video và nhạc của tài khoản bạn. Bạn có thể thay khóa tại đó; mọi sự kiện được gửi sau khi thay khóa, kể cả các lần thử lại, đều được ký bằng khóa bí mật mới.

Thử gửi lại với thời gian chờ tăng dần trong khoảng 9 giờ. Toàn bộ thời gian này, bản ghi bất đồng bộ vẫn có thể được thăm dò, nên nếu bỏ lỡ webhook, bạn luôn có thể khôi phục bằng GET.

Khi nào nên dùng model nào

Đồng bộ /v1/video/generationsBất đồng bộ /v1/videos
Phù hợp nhất choTập lệnh dùng một lần, notebookMôi trường sản xuất, thiết bị di động, tác vụ chạy lâu
MạngGiữ kết nối tối đa 9 minGửi trong vài giây; thăm dò theo lịch của bạn
Khôi phục sau sự cốMất phản hồi = mất tác vụTruy vấn lại ID bất kỳ lúc nào trước khi expires_at hết hạn
Tính đồng thờiMột worker máy chủ cho mỗi lệnh gọiGửi tác vụ và trả kết nối ngay lập tức
Cấu trúc phản hồiCấu trúc /images/generations của OpenAICấu trúc /videos của OpenAI

Phản hồi và lưu trữ

Phản hồi có cấu trúc tiêu chuẩn của OpenAI — data[].url. URL là liên kết vĩnh viễn files.kunavo.com: Kunavo lưu trữ mọi kết quả trên CDN riêng, vì vậy URL không bao giờ hết hạn như URL thô của nhà cung cấp thượng nguồn. Mọi video cũng xuất hiện trong /app/assets.

Tiếp theo nên xem gì