Unlock exclusive introductory pricing for the newly launched Gemini 3.5 Flash.
D

Doubao-Seedance-2-0

每秒:$0.056
發布時間:Apr 19, 2026

Seedance 2.0 是 ByteDance 面向電影級、多鏡頭敘事影片生成的新一代多模態影片基礎模型。不同於單鏡頭的文字轉影片示範,Seedance 2.0 強調基於參考的控制(圖像、短片段、音訊)、角色與風格在跨鏡頭間的連貫與一致性,以及原生的聲畫同步——旨在讓 AI 影片切實服務於專業創作與前期預視工作流程。

熱門
商業用途

Doubao-Seedance-2-0 的 Playground

探索 Doubao-Seedance-2-0 的 Playground — 一個互動式環境,可測試模型並即時執行查詢。嘗試提示、調整參數,並立即迭代以加速開發並驗證使用案例。

Technical specifications of Doubao Seedance 2.0

ItemDetails
Product familyByteDance Seedance video generation family
CometAPI slugdoubao-seedance-2-0
ProviderByteDance / BytePlus ModelArk
Model typeVideo generation model
Input modalitiesText, image
Output typeMP4 video
Output resolution480p, 720p, 1080p
Aspect ratios21:9, 16:9, 4:3, 1:1, 3:4, 9:16
Output duration4–15 seconds
API styleAsynchronous task-based video generation

What is Doubao Seedance 2.0?

Doubao Seedance 2.0 is ByteDance’s flagship multimodal video generation model (released February 2026). It uses a unified audio-video joint generation architecture that allows precise director-level control by referencing multiple images, video clips, and audio files simultaneously. It excels at producing cinematic, motion-stable videos with native audio synchronization — ideal for professional creators needing realistic physics, consistent characters, and complex scene composition.

Main features of Doubao Seedance 2.0 on CometAPI

  • Unified multimodal generation: It accepts text, image, audio, and video inputs in one workflow, so users can direct a scene with richer reference material than a text-only generator.
  • Director-style control: The model is built to follow prompts for performance, lighting, shadow, camera movement, motion rhythm, and sound characteristics.
  • Video editing and extension: Seedance 2.0 is not limited to first-pass generation; it supports editing specific clips, characters, actions, and storylines, plus forward/backward extension.
  • Strong audiovisual sync: ByteDance highlights dual-channel stereo output and synchronized background music, ambient sound, and voiceover behavior for more immersive results.
  • High controllability for complex scenes: It emphasize improved handling of complex interactions, motion scenes, and physical plausibility compared with Seedance 1.5.
  • Production-oriented output: API supports configurable ratio, duration, and watermark settings, which makes the model practical for repeatable content workflows.

Benchmark performance

ByteDance says Seedance 2.0 uses the internal SeedVideoBench-2.0 evaluation and places in the leading position across text-to-video, image-to-video, and multimodal task dimensions. The model improves generation quality, physical accuracy, realism, and controllability versus Seedance 1.5, especially in complex interaction and motion scenes.

Seedance 2.0 API and Seedance 2.0 Fast API Modes on CometAPI

ModelModel Name in CometAPIBest forMain difference
Seedance 2.0doubao-seedance-2-0Highest-quality multimodal video creationBroadest reference stack and strongest controllability in the Seedance 2.0 family.
Seedance 2.0 fastdoubao-seedance-2-0-fastFaster production when quality can be traded a bitByteDance says it keeps the same model capabilities as Seedance 2.0, but generates faster.
Seedance 1.5 Prodoubao-seedance-1-5-proEarlier-generation audio-video creationSeedance 1.5 Pro is positioned as a native joint audio-video model, while Seedance 2.0 expands the multimodal reference and editing stack.

Strengths vs Competitors:

  • vs Kling 3.0: Better multimodal reference control and native audio.
  • vs Sora 2: Superior reference precision and multi-shot capability; slightly lower max duration.
  • vs Veo 3.1: Stronger in character consistency and prompt adherence for reference-heavy workflows; Veo leads in raw cinematic grading and longer clips.

Excels in human-centric and performance-driven videos.

Try Seedance 2.0 AI Video Generator on CometAPI

Step 1: Step 1: Sign Up for API Key

Start directly in the CometAPI Playground to experience Doubao Seedance 2.0 without any setup or coding. Simply log into your CometAPI account, go to the model page for doubao-seedance-2-0, upload your reference images, short video clips, or audio files, add a descriptive prompt, and generate preview videos instantly. This is the fastest way to understand how Seedance 2.0 handles motion consistency, character appearance, camera movements, and native audio synchronization.

Step 2: Get API Access for Seedance 2.0 on CometAPI

Create or use your existing CometAPI key to enable full access to Doubao Seedance 2.0. After logging in, navigate to the API Token section in your console, generate a new key, and copy it. Then visit the Seedance 2.0 model detail page and API doc on CometAPI to review the supported parameters integrating into your projects.

Step 3: Send Your First Request with Seedance 2.0 API

Use the CometAPI endpoint to submit your first video generation request with a clear text prompt and optional reference files (images, video, or audio). The system will process the task asynchronously, return a task ID, and provide a downloadable MP4 video once completed. You can then refine your prompts, adjust settings, and scale up for marketing videos, social media content, automated video pipelines, or creative applications powered by Seedance 2.0.

This workflow gives you quick experimentation in the playground and smooth transition into production use via the CometAPI.

Top Reasons to Choose Seedance 2.0 on CometAPI

Why Use CometAPI for Seedance 2.0

  • Direct access via API or Playground
  • Easy parameter control (duration, resolution, format)
  • Supports both text-to-video and image-to-video workflows
  • Built-in job handling for asynchronous video generation

Unified & Developer-Friendly API

CometAPI offers a clean, standardized endpoint that works seamlessly with familiar OpenAI-style formats or dedicated video task endpoints. You get straightforward task creation, polling, and MP4 downloads without dealing with Volcengine’s complex authentication or regional limitations.

Cost-Effective Pricing

CometAPI typically delivers more competitive per-second rates compared to direct providers, allowing you to generate high-quality videos at scale for marketing, social media, or automated pipelines without breaking your budget.

Fast Testing with Playground

Experiment immediately in the CometAPI Playground. Upload reference images, videos, and audio files, tweak prompts, and preview results in minutes — perfect for iterating on style, motion, and audio sync before going into production.

In short, if you want the creative power of Seedance 2.0 — best-in-class reference control, natural motion, and native audio — without the hassle of direct ByteDance access, CometAPI is currently one of the best platforms to use it.

常見問題

Doubao-Seedance-2-0 的定價

探索 Doubao-Seedance-2-0 的競爭性定價,專為滿足各種預算和使用需求而設計。我們靈活的方案確保您只需為實際使用量付費,讓您能夠隨著需求增長輕鬆擴展。了解 Doubao-Seedance-2-0 如何在保持成本可控的同時提升您的專案效果。

Model480p720p1080p4k
doubao-seedance-2-0$0.056$0.1216$0.2992$0.624
doubao-seedance-2-0-fast$0.0448$0.096
doubao-seedance-2-0-mini$0.032$0.064
doubao-seedance-1-5-pro$0.0192$0.0416$0.0928
doubao-seedance-1-0-pro$0.0192$0.0416$0.0976
doubao-seedance-1-0-pro-fast$0.008$0.016$0.0384

Doubao-Seedance-2-0 的範例程式碼和 API

存取完整的範例程式碼和 API 資源,以簡化您的 Doubao-Seedance-2-0 整合流程。我們詳盡的文件提供逐步指引,協助您在專案中充分發揮 Doubao-Seedance-2-0 的潛力。

#!/bin/bash

set -euo pipefail

# Get your CometAPI key from https://www.cometapi.com/console/token
# Export it as: export COMETAPI_KEY="your-key-here"

if [[ -z "${COMETAPI_KEY:-}" ]]; then
  echo "Set COMETAPI_KEY before running this example."
  exit 0
fi

BASE_URL="https://api.cometapi.com"
OUTPUT_DIR="./output"
POLL_INTERVAL_SECONDS=10
RETRY_DELAY_SECONDS=5
MAX_CREATE_ATTEMPTS=5
MAX_QUERY_ATTEMPTS=3

is_progress_complete() {
  local progress="$1"
  local normalized="${progress%%%}"

  if [[ -z "$normalized" ]]; then
    return 1
  fi

  [[ "$normalized" =~ ^[0-9]+([.][0-9]+)?$ ]] || return 1
  awk -v value="$normalized" 'BEGIN { exit !(value >= 100) }'
}

create_task() {
  local attempt=1
  while (( attempt <= MAX_CREATE_ATTEMPTS )); do
    local response
    local status_code
    response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos" \
      -H "Authorization: Bearer $COMETAPI_KEY" \
      -F 'prompt="A slow cinematic camera push across a coastal landscape at sunrise."' \
      -F 'model="doubao-seedance-2-0"' \
      -F 'seconds="5"' \
      -F 'size="16:9"')

    status_code=$(echo "$response" | tail -n 1)
    CREATE_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if [[ "$status_code" == "429" || "$status_code" =~ ^5 ]] && (( attempt < MAX_CREATE_ATTEMPTS )); then
      echo "Create request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$CREATE_RESPONSE"
    return 1
  done
}

get_task() {
  local task_id="$1"
  local attempt=1
  while (( attempt <= MAX_QUERY_ATTEMPTS )); do
    local response
    local status_code
    response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos/${task_id}" \
      -H "Authorization: Bearer $COMETAPI_KEY")

    status_code=$(echo "$response" | tail -n 1)
    TASK_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if [[ "$status_code" == "429" || "$status_code" =~ ^5 ]] && (( attempt < MAX_QUERY_ATTEMPTS )); then
      echo "Status request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$TASK_RESPONSE"
    return 1
  done
}

create_task

TASK_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // .task_id // empty')

if [[ -z "$TASK_ID" ]]; then
  echo "$CREATE_RESPONSE" | jq .
  echo "No task id returned."
  exit 1
fi

echo "Task created: $TASK_ID"
echo "Initial status: $(echo "$CREATE_RESPONSE" | jq -r '.status // empty')"

while true; do
  get_task "$TASK_ID"

  STATUS=$(echo "$TASK_RESPONSE" | jq -r '.status // empty')
  NORMALIZED_STATUS=$(echo "$STATUS" | tr '[:upper:]' '[:lower:]')
  PROGRESS=$(echo "$TASK_RESPONSE" | jq -r '.progress // empty')
  SHOULD_TRY_DOWNLOAD=0

  if [[ "$NORMALIZED_STATUS" == "success" || "$NORMALIZED_STATUS" == "completed" ]]; then
    SHOULD_TRY_DOWNLOAD=1
  elif [[ "$NORMALIZED_STATUS" == "unknown" ]] && is_progress_complete "$PROGRESS"; then
    SHOULD_TRY_DOWNLOAD=1
  fi

  echo "Status: ${STATUS}, progress: ${PROGRESS}"

  if [[ "$SHOULD_TRY_DOWNLOAD" == "1" || "$NORMALIZED_STATUS" == "failed" || "$NORMALIZED_STATUS" == "error" ]]; then
    if [[ "$SHOULD_TRY_DOWNLOAD" == "1" ]]; then
      VIDEO_URL=$(echo "$TASK_RESPONSE" | jq -r '.video_url // empty')
      CONTENT_URL="${BASE_URL}/v1/videos/${TASK_ID}/content"
      OUTPUT_PATH="${OUTPUT_DIR}/${TASK_ID}.mp4"

      mkdir -p "$OUTPUT_DIR"
      curl -fsS "$CONTENT_URL" \
        -H "Authorization: Bearer $COMETAPI_KEY" \
        -o "$OUTPUT_PATH"

      if [[ ! -s "$OUTPUT_PATH" ]]; then
        echo "Failed to download video"
        exit 1
      fi

      echo "Video URL: ${VIDEO_URL}"
      echo "Content endpoint: ${CONTENT_URL}"
      echo "Saved to ${OUTPUT_PATH}"
      echo "File size: $(wc -c < "$OUTPUT_PATH" | tr -d ' ') bytes"
    else
      echo "$TASK_RESPONSE" | jq .
      exit 1
    fi
    break
  fi

  sleep "$POLL_INTERVAL_SECONDS"
done

cURL Code Example

#!/bin/bash

set -euo pipefail

# Get your CometAPI key from https://www.cometapi.com/console/token
# Export it as: export COMETAPI_KEY="your-key-here"

if [[ -z "${COMETAPI_KEY:-}" ]]; then
  echo "Set COMETAPI_KEY before running this example."
  exit 0
fi

BASE_URL="https://api.cometapi.com"
OUTPUT_DIR="./output"
POLL_INTERVAL_SECONDS=10
RETRY_DELAY_SECONDS=5
MAX_CREATE_ATTEMPTS=5
MAX_QUERY_ATTEMPTS=3

is_progress_complete() {
  local progress="$1"
  local normalized="${progress%%%}"

  if [[ -z "$normalized" ]]; then
    return 1
  fi

  [[ "$normalized" =~ ^[0-9]+([.][0-9]+)?$ ]] || return 1
  awk -v value="$normalized" 'BEGIN { exit !(value >= 100) }'
}

create_task() {
  local attempt=1
  while (( attempt <= MAX_CREATE_ATTEMPTS )); do
    local response
    local status_code
    response=$(curl -sS -w $'\n%{http_code}' "${BASE_URL}/v1/videos" \
      -H "Authorization: Bearer $COMETAPI_KEY" \
      -F 'prompt="A slow cinematic camera push across a coastal landscape at sunrise."' \
      -F 'model="doubao-seedance-2-0"' \
      -F 'seconds="5"' \
      -F 'size="16:9"')

    status_code=$(echo "$response" | tail -n 1)
    CREATE_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if [[ "$status_code" == "429" || "$status_code" =~ ^5 ]] && (( attempt < MAX_CREATE_ATTEMPTS )); then
      echo "Create request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$CREATE_RESPONSE"
    return 1
  done
}

get_task() {
  local task_id="$1"
  local attempt=1
  while (( attempt <= MAX_QUERY_ATTEMPTS )); do
    local response
    local status_code
    response=$(curl -sS -w $'\n%{http_code}' "${BASE_URL}/v1/videos/${task_id}" \
      -H "Authorization: Bearer $COMETAPI_KEY")

    status_code=$(echo "$response" | tail -n 1)
    TASK_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if [[ "$status_code" == "429" || "$status_code" =~ ^5 ]] && (( attempt < MAX_QUERY_ATTEMPTS )); then
      echo "Status request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$TASK_RESPONSE"
    return 1
  done
}

create_task

TASK_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // .task_id // empty')

if [[ -z "$TASK_ID" ]]; then
  echo "$CREATE_RESPONSE" | jq .
  echo "No task id returned."
  exit 1
fi

echo "Task created: $TASK_ID"
echo "Initial status: $(echo "$CREATE_RESPONSE" | jq -r '.status // empty')"

while true; do
  get_task "$TASK_ID"

  STATUS=$(echo "$TASK_RESPONSE" | jq -r '.status // empty')
  NORMALIZED_STATUS=$(echo "$STATUS" | tr '[:upper:]' '[:lower:]')
  PROGRESS=$(echo "$TASK_RESPONSE" | jq -r '.progress // empty')
  SHOULD_TRY_DOWNLOAD=0

  if [[ "$NORMALIZED_STATUS" == "success" || "$NORMALIZED_STATUS" == "completed" ]]; then
    SHOULD_TRY_DOWNLOAD=1
  elif [[ "$NORMALIZED_STATUS" == "unknown" ]] && is_progress_complete "$PROGRESS"; then
    SHOULD_TRY_DOWNLOAD=1
  fi

  echo "Status: ${STATUS}, progress: ${PROGRESS}"

  if [[ "$SHOULD_TRY_DOWNLOAD" == "1" || "$NORMALIZED_STATUS" == "failed" || "$NORMALIZED_STATUS" == "error" ]]; then
    if [[ "$SHOULD_TRY_DOWNLOAD" == "1" ]]; then
      VIDEO_URL=$(echo "$TASK_RESPONSE" | jq -r '.video_url // empty')
      CONTENT_URL="${BASE_URL}/v1/videos/${TASK_ID}/content"
      OUTPUT_PATH="${OUTPUT_DIR}/${TASK_ID}.mp4"

      mkdir -p "$OUTPUT_DIR"
      curl -fsS "$CONTENT_URL" \
        -H "Authorization: Bearer $COMETAPI_KEY" \
        -o "$OUTPUT_PATH"

      if [[ ! -s "$OUTPUT_PATH" ]]; then
        echo "Failed to download video"
        exit 1
      fi

      echo "Video URL: ${VIDEO_URL}"
      echo "Content endpoint: ${CONTENT_URL}"
      echo "Saved to ${OUTPUT_PATH}"
      echo "File size: $(wc -c < "$OUTPUT_PATH" | tr -d ' ') bytes"
    else
      echo "$TASK_RESPONSE" | jq .
      exit 1
    fi
    break
  fi

  sleep "$POLL_INTERVAL_SECONDS"
done

Python Code Example

import json
import os
import time

import requests

# Get your CometAPI key from https://www.cometapi.com/console/token, and paste it here
COMETAPI_KEY = os.environ.get("COMETAPI_KEY") or "<YOUR_COMETAPI_KEY>"
BASE_URL = "https://api.cometapi.com"
OUTPUT_DIR = "./output"
POLL_INTERVAL_SECONDS = 10
RETRY_DELAY_SECONDS = 5
MAX_CREATE_ATTEMPTS = 5
MAX_QUERY_ATTEMPTS = 3
TERMINAL_STATUSES = {"success", "completed", "failed", "error"}
SUCCESS_STATUSES = {"success", "completed"}


def is_progress_complete(progress):
    if isinstance(progress, int):
        return progress >= 100
    if isinstance(progress, float):
        return progress >= 100
    if isinstance(progress, str):
        try:
            return float(progress.rstrip("%")) >= 100
        except ValueError:
            return False
    return False


def is_transient_status(status_code):
    return status_code == 429 or 500 <= status_code < 600


def create_task(files):
    for attempt in range(1, MAX_CREATE_ATTEMPTS + 1):
        response = requests.post(
            f"{BASE_URL}/v1/videos",
            headers=headers,
            files=files,
            timeout=30,
        )
        if response.ok:
            return response
        if not is_transient_status(response.status_code) or attempt == MAX_CREATE_ATTEMPTS:
            response.raise_for_status()
        print(f"Create request returned {response.status_code}, retrying...")
        time.sleep(RETRY_DELAY_SECONDS)

    raise SystemExit("Failed to create task.")


def get_task(task_id):
    for attempt in range(1, MAX_QUERY_ATTEMPTS + 1):
        response = requests.get(
            f"{BASE_URL}/v1/videos/{task_id}",
            headers=headers,
            timeout=15,
        )
        if response.ok:
            return response
        if not is_transient_status(response.status_code) or attempt == MAX_QUERY_ATTEMPTS:
            response.raise_for_status()
        print(f"Status request returned {response.status_code}, retrying...")
        time.sleep(RETRY_DELAY_SECONDS)

    raise SystemExit("Failed to query task.")

if COMETAPI_KEY == "<YOUR_COMETAPI_KEY>":
    print("Set COMETAPI_KEY before running this example.")
    raise SystemExit(0)

headers = {"Authorization": f"Bearer {COMETAPI_KEY}"}

create_response = create_task(
    {
        "prompt": (None, "A slow cinematic camera push across a coastal landscape at sunrise."),
        "model": (None, "doubao-seedance-2-0"),
        "seconds": (None, "5"),
        "size": (None, "16:9"),
    }
)
create_response.raise_for_status()
create_result = create_response.json()

task_id = create_result.get("id") or create_result.get("task_id")
if not task_id:
    print(json.dumps(create_result, indent=2))
    raise SystemExit("No task id returned.")

print(f"Task created: {task_id}")
print(f"Initial status: {create_result.get('status')}")

while True:
    task_response = get_task(task_id)
    task_response.raise_for_status()
    task = task_response.json()
    status = str(task.get("status") or "unknown")
    normalized_status = status.lower()
    progress = task.get("progress")
    should_try_download = normalized_status in SUCCESS_STATUSES or (
        normalized_status == "unknown" and is_progress_complete(progress)
    )

    print(f"Status: {status}, progress: {progress}")

    if should_try_download or normalized_status in TERMINAL_STATUSES:
        if should_try_download:
            video_url = task.get("video_url") or ""
            content_url = f"{BASE_URL}/v1/videos/{task_id}/content"
            output_path = os.path.join(OUTPUT_DIR, f"{task_id}.mp4")

            os.makedirs(OUTPUT_DIR, exist_ok=True)
            with requests.get(
                content_url,
                headers=headers,
                timeout=120,
                stream=True,
            ) as video_response:
                video_response.raise_for_status()
                with open(output_path, "wb") as output_file:
                    for chunk in video_response.iter_content(chunk_size=8192):
                        if chunk:
                            output_file.write(chunk)

            print(f"Video URL: {video_url}")
            print(f"Content endpoint: {content_url}")
            print(f"Saved to {output_path}")
            print(f"File size: {os.path.getsize(output_path)} bytes")
        else:
            print(json.dumps(task, indent=2))
            raise SystemExit(1)
        break

    time.sleep(POLL_INTERVAL_SECONDS)

JavaScript Code Example

import fs from "fs";
import path from "path";

// Get your CometAPI key from https://www.cometapi.com/console/token, and paste it here
const api_key = process.env.COMETAPI_KEY || "<YOUR_COMETAPI_KEY>";
const base_url = "https://api.cometapi.com";
const output_dir = "./output";
const poll_interval_ms = 10_000;
const retry_delay_ms = 5_000;
const max_create_attempts = 5;
const max_query_attempts = 3;
const terminal_statuses = new Set(["success", "completed", "failed", "error"]);
const success_statuses = new Set(["success", "completed"]);

function is_progress_complete(progress) {
  if (typeof progress === "number") {
    return progress >= 100;
  }

  if (typeof progress === "string") {
    const numeric = Number(progress.replace(/%$/, ""));
    return Number.isFinite(numeric) && numeric >= 100;
  }

  return false;
}

function is_transient_status(status) {
  return status === 429 || status >= 500;
}

async function fetch_with_retry(url, options, attempts, label) {
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const response = await fetch(url, options);
    if (response.ok) {
      return response;
    }

    if (!is_transient_status(response.status) || attempt === attempts) {
      return response;
    }

    console.log(`${label} returned ${response.status}, retrying...`);
    await new Promise((resolve) => setTimeout(resolve, retry_delay_ms));
  }

  throw new Error(`${label} failed`);
}

if (api_key === "<YOUR_COMETAPI_KEY>") {
  console.log("Set COMETAPI_KEY before running this example.");
  process.exit(0);
}

const headers = {
  Authorization: `Bearer ${api_key}`,
};

const form = new FormData();
form.set("prompt", "A slow cinematic camera push across a coastal landscape at sunrise.");
form.set("model", "doubao-seedance-2-0");
form.set("seconds", "5");
form.set("size", "16:9");

const create_response = await fetch_with_retry(`${base_url}/v1/videos`, {
  method: "POST",
  headers,
  body: form,
}, max_create_attempts, "Create request");

if (!create_response.ok) {
  console.log(await create_response.text());
  process.exit(1);
}

const create_result = await create_response.json();
const task_id = create_result.id || create_result.task_id;

if (!task_id) {
  console.log(JSON.stringify(create_result, null, 2));
  process.exit(1);
}

console.log(`Task created: ${task_id}`);
console.log(`Initial status: ${create_result.status}`);

while (true) {
  const task_response = await fetch_with_retry(`${base_url}/v1/videos/${task_id}`, {
    headers,
  }, max_query_attempts, "Status request");

  if (!task_response.ok) {
    console.log(await task_response.text());
    process.exit(1);
  }

  const task = await task_response.json();
  const status = String(task.status || "unknown");
  const normalized_status = status.toLowerCase();
  const progress = task.progress;
  const should_try_download = success_statuses.has(normalized_status) || (
    normalized_status === "unknown" && is_progress_complete(progress)
  );

  console.log(`Status: ${status}, progress: ${progress}`);

  if (should_try_download || terminal_statuses.has(normalized_status)) {
    if (should_try_download) {
      const video_url = task.video_url || "";
      const content_url = `${base_url}/v1/videos/${task_id}/content`;
      const output_path = path.join(output_dir, `${task_id}.mp4`);

      if (!fs.existsSync(output_dir)) {
        fs.mkdirSync(output_dir, { recursive: true });
      }

      const video_response = await fetch(content_url, { headers });
      if (!video_response.ok) {
        console.log(await video_response.text());
        process.exit(1);
      }

      const video_buffer = Buffer.from(await video_response.arrayBuffer());
      fs.writeFileSync(output_path, video_buffer);

      console.log(`Video URL: ${video_url}`);
      console.log(`Content endpoint: ${content_url}`);
      console.log(`Saved to ${output_path}`);
      console.log(`File size: ${fs.statSync(output_path).size} bytes`);
    } else {
      console.log(JSON.stringify(task, null, 2));
      process.exit(1);
    }
    break;
  }

  await new Promise((resolve) => setTimeout(resolve, poll_interval_ms));
}

Doubao-Seedance-2-0的版本

Doubao-Seedance-2-0擁有多個快照的原因可能包括:更新後輸出結果存在差異需保留舊版快照以確保一致性、為開發者提供適應與遷移的過渡期,以及不同快照對應全球或區域端點以優化使用者體驗等潛在因素。各版本間的具體差異請參閱官方文件說明。

Version
doubao-seedance-2-0
doubao-seedance-2-0-fast