ModelSell 文档
视频系列Vidu 视频

Vidu Python 轮询与下载

创建 Vidu Q3 任务,保存公共 ID,使用有界轮询处理终态,并下载生成视频;支持原生和统一格式。

下面的完整示例通过原生接口创建一条 Q3 Turbo 文生视频任务。每次创建都会产生独立的计费任务;程序不会自动重试创建。先安装依赖并设置 ModelSell 凭据:

pip install requests
export MODELSELL_BASE_URL="https://api.modelsell.com"
export MODELSELL_API_KEY="<your-modelsell-api-key>"
import os
import time
from pathlib import Path
from urllib.parse import quote, urlparse

import requests

BASE = os.environ.get("MODELSELL_BASE_URL", "https://api.modelsell.com").rstrip("/")
KEY = os.environ["MODELSELL_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
PROMPT = "A red ceramic teapot on a wooden table in warm morning light."


def create_task(path, body, family="native"):
    response = requests.post(BASE + path, headers=HEADERS, json=body, timeout=300)
    response.raise_for_status()
    payload = response.json()
    task_id = payload.get("task_id") if family == "native" else payload.get("id")
    if not isinstance(task_id, str) or not task_id.startswith("task_"):
        raise RuntimeError("Creation returned no ModelSell public task ID")
    print(f"Save this task ID: {task_id}", flush=True)
    return task_id


def wait_for_task(task_id, family="native", max_wait=1800, interval=12):
    escaped_id = quote(task_id, safe="")
    path = (f"/ent/v2/tasks/{escaped_id}/creations"
            if family == "native" else f"/v1/videos/{escaped_id}")
    waiting = ({"created", "queueing", "processing"} if family == "native"
               else {"queued", "in_progress"})
    success = "success" if family == "native" else "completed"
    deadline = time.monotonic() + max_wait
    while time.monotonic() < deadline:
        remaining = deadline - time.monotonic()
        response = requests.get(
            BASE + path, headers=HEADERS,
            timeout=min(120, max(0.1, remaining)),
        )
        response.raise_for_status()
        task = response.json()
        state = task.get("state" if family == "native" else "status")
        if state == success:
            return task
        if state == "failed":
            detail = task.get("err_code") if family == "native" else task.get("error")
            raise RuntimeError(f"Task {task_id} failed: {detail}")
        if state not in waiting:
            raise RuntimeError(f"Unexpected state {state!r}; retain ID {task_id}")
        print(f"{task_id}: {state}", flush=True)
        time.sleep(min(interval, max(0, deadline - time.monotonic())))
    raise TimeoutError(f"Local wait expired; query this task later: {task_id}")


def result_url(task, family="native"):
    if family == "native":
        creations = task.get("creations") or []
        url = creations[0].get("url") if creations else None
    else:
        url = task.get("video_url") or (task.get("metadata") or {}).get("url")
    if not isinstance(url, str) or urlparse(url).scheme not in {"http", "https"}:
        raise RuntimeError("Successful task has no downloadable video URL")
    return url


def download_video(url, destination):
    target = Path(destination)
    temporary = target.with_suffix(target.suffix + ".part")
    try:
        # Result URLs may use a CDN: do not send the ModelSell API Key.
        with requests.get(url, stream=True, timeout=300) as response:
            response.raise_for_status()
            with temporary.open("wb") as output:
                for chunk in response.iter_content(65536):
                    if chunk:
                        output.write(chunk)
        temporary.replace(target)
    except Exception:
        temporary.unlink(missing_ok=True)
        raise


if __name__ == "__main__":
    # Set MODELSELL_TASK_ID to resume polling without creating another task.
    task_id = os.environ.get("MODELSELL_TASK_ID")
    if not task_id:
        task_id = create_task("/ent/v2/text2video", {
            "model": "viduq3-turbo", "prompt": PROMPT,
            "duration": 5, "resolution": "720p", "aspect_ratio": "16:9",
            "audio": False, "is_rec": False, "watermark": False,
        })
    task = wait_for_task(task_id)
    download_video(result_url(task), "vidu-output.mp4")

轮询每 12 秒执行一次,最多等待 30 分钟。网络错误、HTTP 错误、失败终态或未知状态都会结束示例。保存程序打印的 task_… 公共 ID;超时不代表取消或退款,稍后可用同一 API Key 继续查询。

要继续查询已有任务而不创建新任务:

export MODELSELL_TASK_ID="<previously-returned-task-id>"
python vidu_example.py

将完整示例保存为 vidu_example.py 后运行。确认需要创建新任务时再取消该环境变量。原生查询的响应 ID 字段为 id,创建响应则为 task_id,不要混淆。

改用统一视频格式

复用上述函数,将主程序的创建、查询和下载部分替换为以下代码。统一格式的三个步骤均使用 family="generic":

task_id = create_task("/v1/videos", {
    "model": "viduq3-turbo", "prompt": PROMPT, "seconds": "5", "size": "720p",
    "metadata": {
        "vidu_operation": "text2video", "aspect_ratio": "16:9",
        "audio": False, "is_rec": False, "watermark": False,
    },
}, "generic")
task = wait_for_task(task_id, "generic")
download_video(result_url(task, "generic"), "vidu-generic-output.mp4")

该片段会创建新任务;恢复已有统一任务时只设置真实 task_id,直接调用 wait_for_task(task_id, "generic"),不要再次执行 create_task。使用统一视频格式中的 metadata.vidu_operation 指定图生、首尾帧或参考生操作。

视频 URL 可能属于独立 CDN,下载时不要附带 ModelSell API Key。官方结果地址有效期为 24 小时;成功后应及时保存,失败时保留任务 ID 和错误信息。

On this page