模型定價企業
500+ AI 模型 API,全部整合在一個 API 中。就在 CometAPI
模型 API
開發者
快速入門說明文件API 儀表板
公司
關於我們企業
資源
AI模型部落格更新日誌支援
服務條款隱私政策
© 2026 CometAPI · All rights reserved
Home/Models/Doubao/Doubao-Seedance-2-0
D

Doubao-Seedance-2-0

每秒:$0.063
Seedance 2.0 是 ByteDance 的新一代多模態影片基礎模型,聚焦於電影級、多鏡頭敘事的影片生成。有別於單鏡頭的文本轉影片示範,Seedance 2.0 著重於基於參考的控制(圖像、短片、音訊)、跨鏡頭的角色與風格一致性,以及原生的聲畫同步——旨在使 AI 影片可用於專業創作與預視化工作流程。
新
商業用途
Playground
概覽
功能
定價
API
版本

Doubao Seedance 2.0 的技術規格

項目詳細資訊
產品系列ByteDance Seedance 影片生成系列
CometAPI slugdoubao-seedance-2-0
提供方ByteDance / BytePlus ModelArk
模型類型影片生成模型
輸入模態文字、圖片
輸出類型MP4 影片
輸出解析度480p, 720p, 1080p
長寬比21:9, 16:9, 4:3, 1:1, 3:4, 9:16
輸出時長4–15 秒
API 風格非同步、任務式影片生成

什麼是 Doubao Seedance 2.0?

Doubao Seedance 2.0 是 ByteDance 的旗艦級多模態影片生成模型(於 2026 年 2 月發佈)。它採用統一的音訊與影像聯合生成架構,可同時參考多張圖片、影片片段與音訊檔,實現精準的導演級控制。它擅長生成具有電影感、運動穩定且原生音訊同步的影片——非常適合需要真實物理效果、角色一致性與複雜場景構成的專業創作者。

CometAPI 上的 Doubao Seedance 2.0 主要功能

  • 統一的多模態生成: 在單一流程中同時接收文字、圖片、音訊與影片輸入,讓使用者能以比純文字生成器更豐富的參考素材來指導場景。
  • 導演式控制: 模型可依照提示控制表演、燈光、陰影、鏡頭運動、動作節奏與聲音特徵。
  • 影片編輯與延展: 不僅限於首輪生成;支援針對特定片段、角色、動作與故事線的編輯,並可向前/向後延展。
  • 強大的聲畫同步: ByteDance 強調雙聲道立體聲輸出,背景音樂、環境音與旁白行為同步,帶來更沉浸的效果。
  • 複雜場景的高可控性: 相較於 Seedance 1.5,在複雜互動、運動場景與物理合理性方面的處理有所提升。
  • 面向製作流程的輸出: API 支援可設定的比例、時長與浮水印,使模型適合於可重複的內容工作流。

基準效能

ByteDance 表示 Seedance 2.0 採用內部的 SeedVideoBench-2.0 評測,並在文生影片、圖生影片與多模態任務等維度名列前茅。相較於 Seedance 1.5,該模型在生成品質、物理準確性、真實感與可控性方面有所提升,特別是在複雜互動與運動場景中表現更佳。

CometAPI 上的 Seedance 2.0 API 與 Seedance 2.0 Fast API 模式

模型CometAPI 中的模型名稱最佳適用場景主要差異
Seedance 2.0doubao-seedance-2-0最高品質的多模態影片創作在 Seedance 2.0 系列中擁有最廣的參考素材堆疊與最強可控性。
Seedance 2.0 fastdoubao-seedance-2-0-fast當可在品質上做出少許取捨時的更快速生產ByteDance 表示其保有與 Seedance 2.0 相同的模型能力,但生成速度更快。
Seedance 1.5 Prodoubao-seedance-1-5-pro較早一代的聲畫聯合生成Seedance 1.5 Pro 定位為原生聲畫聯合模型,而 Seedance 2.0 擴展了多模態參考與編輯能力堆疊。

對比競品的優勢:

  • vs Kling 3.0:更佳的多模態參考控制與原生音訊。
  • vs Sora 2:更優的參考精度與多鏡頭能力;最長時長略低。
  • vs Veo 3.1:在角色一致性與重參考工作流的提示遵從方面更強;Veo 在原生電影級調色與更長片段方面領先。

在人像為中心、以表演驅動的影片中表現突出。

在 CometAPI 上試用 Seedance 2.0 AI 影片生成器

步驟 1:步驟 1:註冊取得 API 金鑰

直接從 CometAPI Playground 開始,無需任何設定或寫程式即可體驗 Doubao Seedance 2.0。登入你的 CometAPI 帳戶,前往 doubao-seedance-2-0 的模型頁面,上傳參考圖片、短影片片段或音訊檔,加入描述性提示詞,即可即時生成預覽影片。這是最快了解 Seedance 2.0 如何處理運動一致性、角色外觀、鏡頭運動與原生音訊同步的方式。

步驟 2:在 CometAPI 上取得 Seedance 2.0 的 API 存取

建立或使用你現有的 CometAPI 金鑰以啟用對 Doubao Seedance 2.0 的完整存取權。登入後,前往控制台的 API Token 區產生新金鑰並複製。接著造訪 Seedance 2.0 模型詳情頁與 CometAPI 的 API 文件,以檢視整合到你專案時所支援的參數。

步驟 3:以 Seedance 2.0 API 發送你的第一個請求

使用 CometAPI 的端點提交你的第一個影片生成請求,包含清晰的文字提示以及可選的參考檔(圖片、影片或音訊)。系統將以非同步方式處理任務、回傳任務 ID,並在完成後提供可下載的 MP4 影片。之後你可以微調提示詞、調整設定,並擴大用於行銷影片、社群內容、自動化影片管線或以 Seedance 2.0 驅動的創意應用。

此流程讓你能在 Playground 快速試驗,並順暢過渡到透過 CometAPI 進入生產使用。

選擇 CometAPI 上的 Seedance 2.0 的主要原因

為何在 CometAPI 上使用 Seedance 2.0

  • 透過 API 或 Playground 直接存取
  • 參數控制簡單(時長、解析度、格式)
  • 同時支援 text-to-video 與 image-to-video 工作流
  • 內建非同步影片生成的作業處理

統一且對開發者友善的 API

CometAPI 提供乾淨、標準化的端點,能無縫配合熟悉的 OpenAI 風格的格式或專用的影片任務端點。你可獲得簡潔的任務建立、輪詢與 MP4 下載流程,無需面對 Volcengine 複雜的驗證或區域限制。

具成本效益的定價

相較於直接供應商,CometAPI 通常提供更具競爭力的每秒計價,讓你能在行銷、社群或自動化管線中大量生成高品質影片,而不致於預算爆表。

以 Playground 快速測試

立即在 CometAPI Playground 試驗。上傳參考圖片、影片與音訊檔,調整提示詞,幾分鐘內預覽結果——非常適合在進入生產前迭代風格、動作與音訊同步。

總之,如果你想要 Seedance 2.0 的創作威力——一流的參考控制、自然動態與原生音訊——又不想承擔直接對接 ByteDance 的麻煩,CometAPI 目前是使用它的最佳平台之一。

常見問題

What kinds of inputs does Seedance 2.0 support for video generation?

Seedance 2.0 支援多模態輸入,包括文字提示、最多 9 張圖片、最多 3 段短影片,以及最多 3 個音訊檔,可自由組合以進行更豐富且可控的生成。

Can Seedance 2.0 maintain character and style consistency across multiple video shots?

是的——Seedance 2.0 專為連貫的多鏡次敘事而設計,能在多個場景間維持角色、視覺風格與氛圍的一致性,從而減少常見的 AI 影片漂移問題。

What outputs and quality levels can I expect from Seedance 2.0 videos?

Seedance 2.0 可生成電影級影片(最高至 2K 解析度),具備原生音訊、同步對白與自然的動作合成,通常為 5–60 秒的片段。

How does Seedance 2.0 handle audio and lip synchronization?

模型會聯合生成音訊與影像,提供原生的聲畫同步,並在 8 種以上語言中實現音素級口型同步,帶來自然的語音與音效。

Is Seedance 2.0 suitable for professional creative projects like marketing or narrative shorts?

是的——Seedance 2.0 的多模態控制、多鏡次連貫性與高保真輸出,使其適用於行銷影片、敘事短片、廣告等專業應用。

How do referencing assets (images, video clips) work in Seedance 2.0 prompts?

使用者可上傳參考素材,並以自然語言描述各素材應如何影響動作、鏡頭運動或風格元素,從而對生成內容進行精細控制。

Does Seedance 2.0 allow editing and extension of existing videos?

是的——該模型支援影片延伸與針對性編輯,例如新增場景、更換角色或更改特定片段,同時保留未編輯的部分。

What are known limitations or typical generation lengths with Seedance 2.0?

典型輸出長度每支影片約為 ~5 至 ~60 秒,且合併多個素材或使用高解析度設定可能會增加生成時間。

Doubao-Seedance-2-0 的定價

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

doubao-seedance Video Generation Pricing

Parameters

ParameterDescription
Duration (seconds)4–15 seconds, default 5 seconds
sizeRefer to documentation

*1080p is available for all models except doubao-seedance-2-0-fast

Pricing (Per Second)

Model480p720p1080p
doubao-seedance-2-0$0.063$0.1368$0.3366
doubao-seedance-2-0-fast$0.0504$0.108—
doubao-seedance-1-5-pro$0.024$0.052$0.118
doubao-seedance-1-0-pro$0.032$0.084$0.1992

💡 Billed per second. Total cost = price per second × video duration (seconds). Duration range: 4–15 seconds.

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

存取完整的範例程式碼和 API 資源,以簡化您的 Doubao-Seedance-2-0 整合流程。我們詳盡的文件提供逐步指引,協助您在專案中充分發揮 Doubao-Seedance-2-0 的潛力。
POST
/v1/videos
Python
JavaScript
Curl
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)

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));
}

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

Doubao-Seedance-2-0的版本

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