jn61vifdgvx0jciktb2ydcf60fknerv1.agent-cname.modelsell.cc Docs
VideosVidu Video

Vidu Python Polling and Download

Create a Vidu Q3 task, retain the public ID, handle terminal states with bounded polling, and download results in native or generic format.

This complete example creates one native Q3 Turbo text-to-video task. Every creation is a separate billable task; creation is never automatically retried. Install the dependency and set your jn61vifdgvx0jciktb2ydcf60fknerv1.agent-cname.modelsell.cc credentials:

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

Polling runs every 12 seconds, with a 30-minute local deadline. Network errors, HTTP errors, failure states, and unknown states stop the example. Save the printed task_… public ID. A timeout does not cancel a task or establish a refund; query it later with the same API Key.

To resume an existing task without creating another:

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

Save the complete example as vidu_example.py before running it. Unset the variable only when you intend to create a new task. Native query responses use id; native creation responses use task_id.

Switch to generic video

Reuse the functions above and replace the main creation, query, and download block with this code. All three generic-format steps use 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")

This snippet creates a new task. To resume an existing generic task, assign its actual task_id and call wait_for_task(task_id, "generic") without calling create_task again. See Generic Video Format to select image, first/last-frame, or reference operations with metadata.vidu_operation.

Result URLs may belong to a separate CDN; never attach your jn61vifdgvx0jciktb2ydcf60fknerv1.agent-cname.modelsell.cc API Key to downloads. Official result URLs expire after 24 hours. Save successful videos promptly and retain task IDs and error information when generation fails.

On this page