Seedance 2.0 API 教程:用 Python 从零生成你的第一个 AI 视频
手把手教你用 Python 调用 Seedance 2.0 API 生成 AI 视频。涵盖文生视频、图生视频、异步轮询、Webhook 回调和错误处理的完整教程。

Seedance 2.0 是字节跳动最强的 AI 视频生成模型——支持多模态引用、原生音频生成、电影级镜头控制,可生成 4–15 秒、最高 1080p 的视频。本教程将带你用 Python 走完整个 API 调用流程:从获取 API Key 到下载你生成的第一个视频。
读完这篇教程,你将拥有文生视频、图生视频、异步轮询、Webhook 回调和错误重试的完整可运行代码。所有代码示例都经过真实 API 测试验证。
关于 Seedance 2.0 与 1.5 的说明: Seedance 2.0 正在逐步开放中。你现在就可以用
seedance-1.5-pro测试完整流程——等 2.0 全面上线后,只需改一下模型名称即可。所有接口、参数和返回格式完全一致。2.0 的主要升级:多模态引用(图片、视频、音频混合输入)、原生音频生成、更好的物理模拟和视频编辑能力。本教程的所有内容对两个版本都适用。
免费获取 API Key,跟着教程一起动手。
你将构建什么(以及你需要什么)
先看看 Seedance 生成的视频效果——只用了一次 API 调用:
在本教程中,你将编写 Python 代码实现以下功能:
- 发送文本提示词 → 获得生成的视频
- 发送图片 → 将静态图片转为动态视频
- 异步轮询获取结果
- 像生产环境一样处理错误和重试
- 通过 Webhook 接收结果(无需轮询)
- 取消进行中的任务
前置条件
- Python 3.8+(用
python3 --version检查) - requests 库(
pip install requests) - 一个 EvoLink API Key(免费注册——下一节会讲怎么获取)
不需要 GPU,不需要 Docker,不需要复杂配置。只要 Python 和一个 API Key。
小贴士: 如果你在开发正式项目,建议用虚拟环境隔离依赖:
python3 -m venv seedance-env source seedance-env/bin/activate # macOS/Linux seedance-env\Scripts\activate # Windows pip install requests flask
获取 API Key
Seedance 2.0 通过 EvoLink 提供接入。EvoLink 是一个 API 网关,通过一个 API Key 统一接入多个 AI 视频模型——包括 Seedance 2.0、Kling 等。
获取步骤:
- 访问 evolink.ai/early-access 注册账号
- 进入 Dashboard → API Keys
- 点击 Create New Key
- 复制你的 Key——以
sk-开头
妥善保管你的 Key,不要提交到版本控制。我们用环境变量来存储:
export EVOLINK_API_KEY="sk-your-api-key-here"
这行命令在当前终端会话中设置 EVOLINK_API_KEY 环境变量。macOS/Linux 用户可以把它加到 ~/.bashrc 或 ~/.zshrc 中实现持久化。Windows 用户在命令提示符中用 set EVOLINK_API_KEY=sk-your-api-key-here,或者在系统属性 → 环境变量中设置。
注册后账户会包含初始额度供你体验。具体定价详情请查看快速入门文档。
常见错误: 不要把 API Key 硬编码在源文件里。如果你推送到 GitHub,自动化爬虫几分钟内就会发现它。务必使用环境变量或密钥管理服务(如 AWS Secrets Manager 或 HashiCorp Vault)。
搭建 Python 环境
安装唯一需要的依赖:
pip install requests
创建一个名为 seedance_tutorial.py 的文件,添加以下基础代码。本教程的所有示例都基于这个基础:
import requests
import time
import os
import json
# ── 配置 ─────────────────────────────────────────────────────
API_KEY = os.getenv("EVOLINK_API_KEY", "sk-your-api-key-here")
BASE_URL = "https://api.evolink.ai/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
逐行解释:
os.getenv("EVOLINK_API_KEY", "sk-your-api-key-here")— 从环境变量读取 API Key。第二个参数是默认值(仅在本地测试时替换为你的真实 Key)。BASE_URL— 所有 EvoLink API 端点的根 URL。所有请求都发送到https://api.evolink.ai/v1/...。HEADERS— 每个请求都携带的两个 Header:Authorization使用 Bearer Token 方式携带 API Key,Content-Type告诉服务器我们发送的是 JSON。
接下来添加可复用的辅助函数:
# ── 可复用的轮询辅助函数 ─────────────────────────────────────
def wait_for_video(task_id, poll_interval=10, timeout=600):
"""
轮询视频生成任务,直到完成或失败。
Args:
task_id: 生成接口返回的任务 ID。
poll_interval: 轮询间隔秒数(默认 10)。
timeout: 最大等待时间秒数(默认 600)。
Returns:
dict: 包含视频 URL 的已完成任务响应。
Raises:
TimeoutError: 任务在超时时间内未完成。
RuntimeError: 任务失败。
"""
elapsed = 0
while elapsed < timeout:
# 发送 GET 请求检查任务当前状态
response = requests.get(
f"{BASE_URL}/tasks/{task_id}",
headers=HEADERS
)
# 如果 HTTP 状态码表示错误,抛出异常
response.raise_for_status()
task = response.json()
# 从响应中提取状态和进度
status = task["status"]
progress = task.get("progress", 0)
print(f" [{elapsed}s] Status: {status} | Progress: {progress}%")
# 检查终态
if status == "completed":
return task
elif status == "failed":
error_info = task.get("error", {})
raise RuntimeError(
f"Task {task_id} failed: {error_info.get('message', 'Unknown error')}"
)
# 等待后再次轮询
time.sleep(poll_interval)
elapsed += poll_interval
raise TimeoutError(f"Task {task_id} timed out after {timeout}s")
这个函数的几个关键设计决策:
poll_interval=10— 10 秒是最佳间隔。太快浪费 API 配额,太慢又影响效率。timeout=600— 10 分钟足够宽裕。大多数视频在 30–120 秒内完成,但这能覆盖队列拥堵等极端情况。response.raise_for_status()— 将 HTTP 错误(4xx/5xx)转换为 Python 异常,避免静默失败。- 进度打印 —
[elapsed]s前缀帮你关联时间。调试慢生成时很有用。
# ── 辅助函数:下载视频 ───────────────────────────────────────
def download_video(url, filename="output.mp4"):
"""从 URL 下载视频文件。"""
print(f"Downloading video to {filename}...")
resp = requests.get(url, stream=True)
resp.raise_for_status()
with open(filename, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Saved: {filename} ({os.path.getsize(filename) / 1024:.0f} KB)")
这个函数以 8 KB 为单位流式下载,而不是把整个视频加载到内存。这很重要——生成的视频可能有 10–50 MB。stream=True 参数告诉 requests 增量下载。
以上三部分——配置、轮询、下载——就是基础框架。后面的每个代码示例都会用到它们,不再重复,只展示新的 payload。
完整 API 参考请查看视频生成文档。
生成你的第一个视频(文生视频)
开始生成视频吧。把以下代码添加到你的脚本中:
# ── 文生视频 ─────────────────────────────────────────────────
def text_to_video():
payload = {
"model": "seedance-2.0", # 使用的 AI 模型
"prompt": (
"A golden retriever puppy chases a butterfly through "
"a sunlit meadow. The camera follows the puppy with a "
"smooth tracking shot as wildflowers sway in the breeze."
),
"duration": 5, # 视频时长:4-15 秒
"quality": "720p", # 分辨率:480p、720p、1080p
"aspect_ratio": "16:9", # 标准宽屏
"generate_audio": True # AI 生成匹配的音频
}
print("Submitting text-to-video request...")
response = requests.post(
f"{BASE_URL}/videos/generations", # 视频生成接口
headers=HEADERS, # 认证 + content-type 头
json=payload # 自动序列化为 JSON
)
response.raise_for_status() # 非 200 则抛异常
task = response.json() # 解析 JSON 响应
# 打印响应中的关键信息
print(f"Task created: {task['id']}")
print(f"Estimated time: {task['task_info']['estimated_time']}s")
print(f"Credits reserved: {task['usage']['credits_reserved']}")
# 轮询直到视频就绪
result = wait_for_video(task["id"])
# results 数组包含一个或多个视频 URL
video_url = result["results"][0]
print(f"\nVideo URL: {video_url}")
download_video(video_url, "my_first_video.mp4")
return result
if __name__ == "__main__":
text_to_video()
逐一解释 payload 中的参数:
model— 使用哪个 Seedance 模型。设为seedance-2.0使用最新版;如果 2.0 在你所在地区还不可用,先用seedance-1.5-pro。prompt— 视频描述。尽量具体地描述主体、动作、镜头运动和氛围。上面的提示词用了三段式结构:主体("golden retriever puppy")、动作("chases a butterfly")、镜头("smooth tracking shot")。更多提示词技巧请看提示词工程指南。duration— 视频时长,单位秒(4–15)。短视频生成更快、消耗更少额度。测试时建议用 5 秒。quality— 分辨率档位。720p是开发阶段质量和速度的最佳平衡。480p适合快速迭代,1080p用于最终渲染。aspect_ratio— 输出比例。16:9适合 YouTube/横屏,9:16适合抖音/Reels/Shorts,1:1适合 Instagram 信息流。generate_audio— 设为true时,Seedance 会生成与视觉内容匹配的环境音和音乐,大约增加 ~2 秒生成时间。
运行:
python seedance_tutorial.py
API 返回内容
提交生成请求后,你会立即收到一个 task 对象——视频还没准备好。以下是实际的响应:
{
"created": 1772203771,
"id": "task-unified-1772203771-yf1dxogh",
"model": "seedance-2.0",
"object": "video.generation.task",
"progress": 0,
"status": "pending",
"task_info": {
"can_cancel": true,
"estimated_time": 132
},
"type": "video",
"usage": {
"billing_rule": "per_second",
"credits_reserved": 17.784,
"user_group": "default"
}
}
关键字段说明:
| 字段 | 含义 |
|---|---|
id | 任务 ID——用它来查询状态和获取结果 |
status | 初始为 pending,然后变为 processing,最终变为 completed 或 failed |
progress | 0–100 的百分比。在 processing 阶段实时更新 |
estimated_time | 预计完成时间(秒),服务端估算 |
credits_reserved | 本次任务预扣的额度。任务失败时自动退还 |
task_info.can_cancel | 是否可以取消该任务(完成前始终为 true) |
created | 任务提交时间的 Unix 时间戳 |
usage.billing_rule | 计费方式——per_second 表示按时长计费 |
小贴士: 提交后立即把
id保存到文件或数据库。如果你的脚本在轮询过程中崩溃了,可以用保存的任务 ID 调用wait_for_video()恢复。任务在服务器上保留 24 小时。
轮询过程
wait_for_video() 函数每 10 秒轮询一次。以下是真实的输出:
Submitting text-to-video request...
Task created: task-unified-1772203771-yf1dxogh
Estimated time: 132s
Credits reserved: 17.784
[0s] Status: pending | Progress: 0%
[10s] Status: processing | Progress: 7%
[20s] Status: processing | Progress: 13%
[30s] Status: processing | Progress: 20%
[40s] Status: processing | Progress: 27%
[50s] Status: completed | Progress: 100%
Video URL: https://files.evolink.ai/.../cgt-20260227224931-8vl7s.mp4
Downloading video to my_first_video.mp4...
Saved: my_first_video.mp4 (2847 KB)
就这么简单——从发起 API 调用到视频文件保存到本地,大约 50 秒。
重要: 视频 URL 在 24 小时后过期。务必及时下载文件或存储到你自己的对象存储(S3、GCS、Cloudflare R2 等)。
常见错误: 不要依赖视频 URL 做长期存储。你的流水线应该在生成完成后立即下载。如果你在异步处理视频,用 Webhook(下面会讲)在生成完成的那一刻触发下载。
更多提示词编写技巧请看 Seedance 2.0 提示词指南——涵盖分镜脚本格式、风格关键词和时间轴语法。
轮询结果:理解异步工作流
视频生成需要 30–120+ 秒,取决于时长和质量设置。API 使用异步任务模式——和 OpenAI、Stability AI 以及大多数生成式 AI API 用的是同一套模式:
- 提交 → POST 到
/v1/videos/generations→ 立即获得任务 ID - 轮询 → GET
/v1/tasks/{task_id}→ 定期检查状态 - 获取 → 当
status: "completed"时,results数组中包含视频 URL
这种模式存在的原因是视频生成计算量巨大。如果用同步 HTTP 请求,视频还没生成完就会超时。
任务状态生命周期
pending → processing → completed
↘ failed
| 状态 | 发生了什么 | 典型耗时 |
|---|---|---|
pending | 任务排队中,等待 GPU 资源 | 0–30 秒 |
processing | 视频正在生成——progress 实时更新 | 30–120 秒 |
completed | 完成!results 数组中有视频 URL | 终态 |
failed | 出错了——查看错误详情 | 终态 |
轮询最佳实践
轮询间隔: 10 秒是合理的默认值。太快浪费请求次数,还可能触发限流;太慢则拖延你的流水线。对于时间敏感的应用,可以 5 秒一次,但没必要更快了。
超时时间: 根据你的参数设置合理的上限:
| 配置 | 预期耗时 | 建议超时 |
|---|---|---|
| 4s, 480p | 20–40 秒 | 120 秒 |
| 5s, 720p | 30–60 秒 | 180 秒 |
| 10s, 720p | 60–90 秒 | 300 秒 |
| 15s, 1080p | 90–180 秒 | 600 秒 |
进度跟踪: progress 字段(0–100)提供细粒度反馈——适合在 UI 中构建进度条。在 processing 阶段大约每 5–7 秒更新一次。
取消任务
如果你需要停止正在进行的生成(提示词写错了、改主意了),可以取消它:
def cancel_task(task_id):
"""取消 pending 或 processing 状态的任务。额度会退还。"""
response = requests.post(
f"{BASE_URL}/tasks/{task_id}/cancel",
headers=HEADERS
)
if response.status_code == 200:
print(f"Task {task_id} cancelled. Credits refunded.")
else:
print(f"Cancel failed: {response.json()}")
取消在 task_info.can_cancel 为 true 时有效。任务达到 completed 或 failed 后无法取消。取消时预扣的额度会自动退还。
小贴士: 尽早在你的 UI 中加入取消功能。用户难免会提交错误的提示词,等 2 分钟生成一个不想要的视频既浪费时间又浪费额度。
前面搭建的 wait_for_video() 函数已经处理了标准轮询流程。如果你想完全跳过轮询,请直接跳到下面的 Webhook 章节。
让图片动起来(图生视频)
有产品图、角色插画或风景照想让它动起来?传入 image_url,Seedance 就会把它动画化。这是电商产品视频最强大的功能之一——把一张静态产品图变成吸引眼球的视频广告。
使用前面第一个示例中相同的配置和轮询函数。
# ── 图生视频 ─────────────────────────────────────────────────
def image_to_video():
payload = {
"model": "seedance-2.0",
"prompt": (
"@Image1 as the first frame. The scene slowly comes "
"to life — leaves rustle gently, soft light shifts "
"across the frame, and the subject blinks naturally."
),
"image_urls": [
"https://example.com/your-image.jpg"
],
"duration": 5,
"quality": "720p",
"aspect_ratio": "16:9"
}
print("Submitting image-to-video request...")
response = requests.post(
f"{BASE_URL}/videos/generations",
headers=HEADERS,
json=payload
)
response.raise_for_status()
task = response.json()
print(f"Task created: {task['id']}")
result = wait_for_video(task["id"])
video_url = result["results"][0]
download_video(video_url, "animated_image.mp4")
return result
与文生视频相比,这里有几个不同点:
image_urls— 一个公开可访问的图片 URL 数组。API 会直接拉取这些图片,所以它们必须能从公网访问(不能是localhost或内网地址)。@Image1在提示词中 — 这个标签告诉 Seedance 引用哪张图片以及如何使用它。它对应image_urls中的第一个 URL。如果你传了三张图片,就用@Image1、@Image2、@Image3。- 没有
generate_audio— 这里省略了,默认值是true。如果要生成无声动画,设为false。
@Image 标签的工作原理
提示词中的 @Image1 标签告诉 Seedance 如何使用图片,它引用的是 image_urls 数组中的第一个 URL。最多可以传 9 张图片(@Image1 到 @Image9)。关于多模态标签(包括 @Video 和 @Audio)的完整指南,请看 多模态 @Tags 指南。
常用模式:
| 提示词模式 | 效果 | 适用场景 |
|---|---|---|
@Image1 as first frame | 把图片用作开头帧 | 产品展示、场景设定 |
@Image1 as last frame | 把图片用作结尾帧 | Logo 揭示、转场 |
@Image1 as character reference | 保持角色外观一致 | 跨片段的角色一致性 |
@Image1 as style reference | 应用图片的视觉风格 | 品牌一致性、美术指导 |
@Image1 as first frame, @Image2 as last frame | 在两张图片之间创建过渡 | 前后对比、变形 |
我们测试得到的实际响应:
{
"created": 1772204037,
"id": "task-unified-1772204036-lify8u5p",
"model": "seedance-2.0",
"object": "video.generation.task",
"progress": 0,
"status": "pending",
"task_info": {
"can_cancel": true,
"estimated_time": 145
},
"type": "video",
"usage": {
"billing_rule": "per_second",
"credits_reserved": 17.784,
"user_group": "default"
}
}
图生视频和文生视频遵循完全相同的异步模式——提交、轮询、下载。estimated_time 会稍长一些,因为模型需要分析输入图片。
图片要求
| 约束 | 值 |
|---|---|
| 最大图片数 | 每次请求 9 张 |
| 最大文件大小 | 每张 30 MB |
| 支持格式 | JPEG、PNG、WebP、BMP、TIFF、GIF |
| URL 要求 | 必须公开可访问 |
| 推荐分辨率 | 短边至少 720px |
常见错误: 传本地文件路径而不是 URL。
image_urls字段需要公开可访问的 HTTP/HTTPS URL。如果你的图片在本地,先上传到 S3、Cloudflare R2 或临时文件托管服务。
限制: Seedance 不支持上传写实人脸图片。系统会自动拒绝。请使用插画或风格化角色。
为 API 托管图片
如果你没有 CDN,以下是快速获取公开 URL 的方法:
# 方式一:上传到 S3(如果你有 AWS)
import boto3
s3 = boto3.client('s3')
s3.upload_file('local_image.jpg', 'my-bucket', 'seedance/input.jpg')
image_url = f"https://my-bucket.s3.amazonaws.com/seedance/input.jpg"
# 方式二:使用临时文件托管 API
# 很多服务提供免费的临时托管用于测试
更多图生视频高级技巧——首尾帧控制、多图合成、电商产品动画——请看图生视频详解。
自定义你的视频
生成请求中可调整的所有参数:
| 参数 | 类型 | 默认值 | 选项 | 说明 |
|---|---|---|---|---|
model | string | — | seedance-2.0 | 必填。使用的模型。 |
prompt | string | — | ≤2000 tokens | 必填。视频描述,可使用 @tags。 |
duration | integer | 5 | 4–15 | 视频时长(秒)。 |
quality | string | 720p | 480p、720p、1080p | 分辨率档位。越高消耗越多额度。 |
aspect_ratio | string | 16:9 | 16:9、9:16、1:1、4:3、3:4、21:9 | 输出宽高比。 |
generate_audio | boolean | true | true、false | 启用 AI 生成音频/音乐。 |
image_urls | array | — | ≤9 张图片 | 参考图片。在提示词中用 @Image1、@Image2... 引用。 |
video_urls | array | — | ≤3 个视频 | 参考视频。在提示词中用 @Video1、@Video2... 引用。 |
audio_urls | array | — | ≤3 个音频 | 参考音频。在提示词中用 @Audio1、@Audio2... 引用。 |
callback_url | string | — | HTTPS URL | 完成时的 Webhook 回调地址。 |
Seedance 2.0 与 1.5 的区别: 以上所有参数对
seedance-2.0和seedance-1.5-pro都适用。主要区别在于:video_urls、audio_urls和多图引用(@Image2到@Image9)是 2.0 独有功能。在 1.5 上使用它们,API 会返回400错误,并明确提示该功能不支持。
快速示例
竖版社交媒体视频(抖音/Reels):
使用前面第一个示例中相同的配置和轮询函数。
payload = {
"model": "seedance-2.0",
"prompt": "A barista pours latte art in slow motion. Close-up overhead shot.",
"duration": 8,
"quality": "1080p",
"aspect_ratio": "9:16", # 竖版,适合手机
"generate_audio": True
}
9:16 比例生成 1080×1920 的视频——抖音、Instagram Reels 和 YouTube Shorts 的原生分辨率。1080p 质量档保证手机屏幕上的清晰度。
电影级宽屏 + 镜头运动:
payload = {
"model": "seedance-2.0",
"prompt": (
"Aerial drone shot over a misty mountain range at sunrise. "
"Camera slowly pushes forward, revealing a hidden valley. "
"Cinematic color grading, volumetric lighting."
),
"duration": 10,
"quality": "1080p",
"aspect_ratio": "21:9", # 超宽屏电影比例
"generate_audio": True
}
关于编程控制镜头——推拉变焦、环绕镜头、希区柯克式运镜——请看镜头运动 API 指南。
无声网页背景视频:
payload = {
"model": "seedance-2.0",
"prompt": "Abstract flowing particles in deep blue and gold. Slow, meditative movement.",
"duration": 15, # 最长时长,适合无缝循环
"quality": "720p",
"aspect_ratio": "21:9", # 宽屏背景
"generate_audio": False # 自动播放背景不需要音频
}
低成本草稿(快速迭代):
payload = {
"model": "seedance-2.0",
"prompt": "A cat wearing sunglasses sits at a DJ booth. Neon club lighting.",
"duration": 4, # 最短时长 = 最快生成
"quality": "480p", # 最低质量 = 最省额度
"aspect_ratio": "16:9"
}
小贴士: 开发阶段始终用
duration: 4和quality: "480p"。这是最便宜最快的组合——非常适合迭代提示词。满意后再用1080p和你需要的时长渲染最终版。
额度消耗估算
额度消耗随时长和质量递增。大致参考:
| 质量 | 4s | 5s | 10s | 15s |
|---|---|---|---|---|
| 480p | ~8 | ~10 | ~20 | ~30 |
| 720p | ~14 | ~18 | ~36 | ~53 |
| 1080p | ~22 | ~28 | ~55 | ~83 |
近似额度。实际费用以 credits_reserved 字段为准。查看 EvoLink 控制台了解当前费率。
多模态引用系统——@Image、@Video、@Audio 标签——是 Seedance 2.0 真正强大的地方。你可以从参考视频中复刻镜头运动,保持跨镜头的角色一致性,还能与音频节拍同步。完整指南请看多模态 @Tags 终极指南。
优雅地处理错误
API 调用会失败,网络会中断,限流会被触发。以下是如何构建健壮的代码来应对每种真实的错误场景。
常见错误响应
所有错误遵循相同的格式:
{
"error": {
"message": "description of what went wrong",
"type": "error_category",
"code": "specific_error_code"
}
}
error 对象始终包含 message 和 type。code 字段在大多数错误中存在但不是全部。优先检查 type,再看 code 获取具体信息。
以下是 API 返回的真实错误响应:
401 — API Key 无效:
{
"error": {
"message": "Invalid token (request id: 20260227225245660301729AApJNAhJ)",
"type": "evo_api_error"
}
}
说明你的 API Key 错误、过期或已被撤销。检查 EVOLINK_API_KEY 环境变量。常见原因:复制 Key 时带了尾部空格。
400 — 缺少必填字段:
{
"error": {
"code": "invalid_parameter",
"message": "prompt cannot be empty",
"type": "invalid_request_error"
}
}
prompt 字段在所有生成请求中都是必填的。传空字符串或纯空格也会触发这个错误。
400 — 参数值无效:
{
"error": {
"code": "invalid_parameter",
"message": "duration must be between 4 and 15",
"type": "invalid_request_error"
}
}
当你传 duration: 3 或 duration: 20 时会出现。有效范围是 4–15 秒(含)。
400 — 不支持的质量档位:
{
"error": {
"code": "invalid_parameter",
"message": "quality must be one of: 480p, 720p, 1080p",
"type": "invalid_request_error"
}
}
常见于传 "quality": "4k" 或 "quality": "hd" 时。必须使用精确的字符串:480p、720p 或 1080p。
402 — 额度不足:
{
"error": {
"message": "Insufficient credits. Required: 17.784, Available: 2.100",
"type": "insufficient_quota_error"
}
}
你的账户额度不够。错误信息会告诉你需要多少、还剩多少。去 EvoLink 控制台 充值。
404 — 任务未找到:
{
"error": {
"message": "Task not found",
"type": "invalid_request_error",
"code": "task_not_found"
}
}
通常是任务 ID 错误,或者任务创建超过 24 小时已过期。确认你用的是创建响应中的 id 字段,而不是其他字段。
413 — 图片太大:
{
"error": {
"message": "Image file size exceeds 30MB limit",
"type": "request_too_large_error"
}
}
上传前先压缩图片。对 API 来说,超过 2–3 MB 的图片对生成效果的提升微乎其微。
429 — 触发限流:
{
"error": {
"message": "Rate limit exceeded. Please retry after 60 seconds.",
"type": "rate_limit_error"
}
}
请求太频繁了。默认限制对开发来说很宽裕,但批量脚本可能会触发。实现指数退避(见下文)。
422 — 内容审核拒绝:
{
"error": {
"message": "Content rejected by safety filter",
"type": "content_policy_violation",
"code": "content_filtered"
}
}
你的提示词或输入图片触发了内容审核系统。修改提示词避免受限内容。image_urls 中的写实人脸图片会被自动拒绝。
错误速查表
| HTTP 状态码 | 类型 | 含义 | 可重试? | 处理方式 |
|---|---|---|---|---|
| 400 | invalid_request_error | 参数错误 | 否 | 修正 payload |
| 401 | authentication_error | API Key 无效 | 否 | 检查 Key |
| 402 | insufficient_quota_error | 额度不足 | 否 | 充值 |
| 404 | not_found_error | 任务或模型未找到 | 否 | 检查 task_id / 模型名 |
| 413 | request_too_large_error | 请求体太大 | 否 | 减小文件大小 |
| 422 | content_policy_violation | 内容被过滤 | 否 | 修改提示词 |
| 429 | rate_limit_error | 请求太频繁 | 是 | 等 60 秒后重试 |
| 500 | internal_server_error | 服务器问题 | 是 | 几秒后重试 |
| 502 | bad_gateway | 上游错误 | 是 | 5 秒后重试 |
| 503 | service_unavailable_error | 服务不可用 | 是 | 30 秒后重试 |
生产级错误处理
用重试逻辑包装你的 API 调用,处理瞬态错误:
使用前面第一个示例中相同的配置和轮询函数。
import random
def generate_video_with_retry(payload, max_retries=3):
"""
提交视频生成请求,对瞬态错误(429、500、502、503)自动重试。
使用指数退避 + 随机抖动避免惊群效应:
- 第 1 次:等待 ~1s
- 第 2 次:等待 ~2s
- 第 3 次:等待 ~4s
不可重试的错误(400、401、402、404、413、422)立即失败,
因为重试不会修复根本问题。
"""
for attempt in range(max_retries):
try:
response = requests.post(
f"{BASE_URL}/videos/generations",
headers=HEADERS,
json=payload,
timeout=30 # 30 秒连接超时
)
# 成功——返回 task 对象
if response.status_code == 200:
return response.json()
# 解析错误响应
error = response.json().get("error", {})
error_type = error.get("type", "")
error_msg = error.get("message", "Unknown error")
# 不可重试的错误——立即失败
if response.status_code in (400, 401, 402, 404, 413, 422):
raise ValueError(
f"API error {response.status_code}: {error_msg}"
)
# 可重试的错误——指数退避 + 随机抖动
if response.status_code in (429, 500, 502, 503):
wait = (2 ** attempt) + random.uniform(0, 1)
print(f" Retry {attempt + 1}/{max_retries} "
f"after {wait:.1f}s ({error_type}: {error_msg})")
time.sleep(wait)
continue
except requests.exceptions.Timeout:
# 服务器在 30 秒内没有响应
wait = (2 ** attempt) + random.uniform(0, 1)
print(f" Timeout. Retry {attempt + 1}/{max_retries} "
f"after {wait:.1f}s")
time.sleep(wait)
continue
except requests.exceptions.ConnectionError as e:
# DNS 解析失败、连接被拒等
wait = (2 ** attempt) + random.uniform(0, 1)
print(f" Connection error: {e}. Retry {attempt + 1}/{max_retries} "
f"after {wait:.1f}s")
time.sleep(wait)
continue
raise RuntimeError(f"Failed after {max_retries} retries")
这段代码处理了:
- 限流(429) — 指数退避 + 随机抖动避免多个客户端同步重试
- 服务器错误(500/502/503) — 自动重试,间隔递增
- 超时 — 30 秒超时防止在无响应的服务器上挂起
- 连接中断 — DNS 解析失败、连接被拒、网络波动
- 客户端错误(400/401/402/404/413/422) — 立即失败,因为重试不会修复错误的输入
小贴士: 对于生产系统,建议记录失败请求的完整 payload 和错误响应。凌晨 3 点出问题时,这会让调试轻松很多。
调用前校验输入
在发送 API 请求前先在本地捕获明显的错误,节省额度和时间:
def validate_payload(payload):
"""
在发送到 API 之前校验生成 payload。
捕获会导致 400 错误的常见错误。
"""
errors = []
# 必填字段
if not payload.get("model"):
errors.append("'model' is required")
if not payload.get("prompt") or not payload["prompt"].strip():
errors.append("'prompt' is required and cannot be empty")
# 时长范围
duration = payload.get("duration", 5)
if duration < 4 or duration > 15:
errors.append(f"'duration' must be 4-15, got {duration}")
# 质量值
valid_qualities = {"480p", "720p", "1080p"}
quality = payload.get("quality", "720p")
if quality not in valid_qualities:
errors.append(f"'quality' must be one of {valid_qualities}, got '{quality}'")
# 宽高比
valid_ratios = {"16:9", "9:16", "1:1", "4:3", "3:4", "21:9"}
ratio = payload.get("aspect_ratio", "16:9")
if ratio not in valid_ratios:
errors.append(f"'aspect_ratio' must be one of {valid_ratios}, got '{ratio}'")
# 图片 URL 校验
image_urls = payload.get("image_urls", [])
if len(image_urls) > 9:
errors.append(f"Maximum 9 images allowed, got {len(image_urls)}")
for i, url in enumerate(image_urls):
if not url.startswith(("http://", "https://")):
errors.append(f"image_urls[{i}] must be an HTTP(S) URL")
if errors:
raise ValueError(f"Payload validation failed:\n" + "\n".join(f" - {e}" for e in errors))
return True
常见错误: 忘记对图片 URL 中的特殊字符进行 URL 编码。如果你的图片路径包含空格或非 ASCII 字符,用
urllib.parse.quote()编码。
配置 Webhook(跳过轮询)
轮询对脚本和原型开发来说完全够用。对于生产系统,Webhook 更高效——API 在视频就绪时主动推送结果到你的服务器。不浪费请求,完成与通知之间零延迟。
工作原理
在生成请求中添加 callback_url:
使用前面第一个示例中相同的配置。
payload = {
"model": "seedance-2.0",
"prompt": "A spaceship launches from a desert landscape at sunset.",
"duration": 8,
"quality": "720p",
"callback_url": "https://your-server.com/api/webhook/seedance"
}
response = requests.post(
f"{BASE_URL}/videos/generations",
headers=HEADERS,
json=payload
)
task = response.json()
print(f"Task submitted: {task['id']}")
# 不需要轮询——你的 Webhook 会收到结果
视频就绪后,API 会向你的 callback_url 发送一个 POST 请求,携带完整的 task 对象——和你通过轮询拿到的内容完全一样。
Webhook 要求
| 要求 | 详情 |
|---|---|
| 协议 | 仅限 HTTPS(不支持 HTTP)——安全要求 |
| 响应 | 10 秒内返回 2xx |
| 重试 | 失败时重试 3 次(间隔 1s、2s、4s) |
| URL 长度 | ≤ 2048 字符 |
| 网络 | 不支持内网/私有 IP(localhost、10.x.x.x、192.168.x.x) |
| 请求体 | JSON POST,携带完整 task 对象 |
生产级 Flask Webhook 接收器
以下是一个完整的 Webhook 服务器,使用 Flask 实现,包含校验、错误处理和异步视频下载:
# webhook_server.py
"""
Seedance Webhook 接收器——处理视频生成完成的回调。
运行:pip install flask requests
python webhook_server.py
"""
from flask import Flask, request, jsonify
import json
import os
import threading
import requests as req # 重命名以避免和 flask.request 冲突
app = Flask(__name__)
# 保存已完成视频的目录
OUTPUT_DIR = os.getenv("VIDEO_OUTPUT_DIR", "./videos")
os.makedirs(OUTPUT_DIR, exist_ok=True)
def download_video_async(video_url, task_id):
"""在后台线程中下载视频,不阻塞 Webhook 响应。"""
try:
filename = os.path.join(OUTPUT_DIR, f"{task_id}.mp4")
print(f" Downloading {task_id} to {filename}...")
resp = req.get(video_url, stream=True, timeout=120)
resp.raise_for_status()
with open(filename, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
size_mb = os.path.getsize(filename) / (1024 * 1024)
print(f" Saved: {filename} ({size_mb:.1f} MB)")
except Exception as e:
print(f" Download failed for {task_id}: {e}")
@app.route("/api/webhook/seedance", methods=["POST"])
def handle_webhook():
"""
处理 Seedance 视频生成完成的 Webhook 回调。
视频生成完成(成功或失败)时,
API 会发送一个携带完整 task 对象的 POST 请求。
"""
# 解析传入的 task 对象
task = request.json
if not task:
return jsonify({"error": "Empty body"}), 400
task_id = task.get("id", "unknown")
status = task.get("status", "unknown")
model = task.get("model", "unknown")
print(f"\n{'='*50}")
print(f"Webhook received: task={task_id}")
print(f" Status: {status}")
print(f" Model: {model}")
if status == "completed":
# 从 results 中提取视频 URL
results = task.get("results", [])
if results:
video_url = results[0]
print(f" Video URL: {video_url}")
# 在后台线程中下载,确保快速响应
thread = threading.Thread(
target=download_video_async,
args=(video_url, task_id)
)
thread.start()
else:
print(f" WARNING: Completed but no results array!")
elif status == "failed":
error_info = task.get("error", {})
print(f" FAILED: {json.dumps(error_info, indent=2)}")
# TODO: 记录到你的错误追踪系统(Sentry 等)
# TODO: 可选择修改参数后重试生成
else:
print(f" Unexpected status: {status}")
print(f" Full payload: {json.dumps(task, indent=2)}")
# 始终快速返回 200——API 期望在 10 秒内收到响应
return jsonify({"received": True, "task_id": task_id}), 200
@app.route("/health", methods=["GET"])
def health_check():
"""负载均衡器的健康检查端点。"""
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
print(f"Starting webhook server...")
print(f"Videos will be saved to: {os.path.abspath(OUTPUT_DIR)}")
print(f"Webhook URL: http://localhost:5000/api/webhook/seedance")
app.run(host="0.0.0.0", port=5000, debug=True)
安装依赖并运行:
pip install flask requests
python webhook_server.py
这个服务器的几个关键设计决策:
- 后台下载 — 我们开一个线程下载视频,这样 Webhook 处理函数可以立即返回
200。API 期望 10 秒内收到响应,但视频下载可能需要更久。 - 健康检查端点 —
/health在部署到负载均衡器(ALB、nginx 等)后面时很有用。 - 错误日志 — 失败的任务会打印完整的错误 payload。生产环境中,把这些接入 Sentry、Datadog 或你的日志系统。
用 ngrok 暴露本地服务
本地开发时,用 ngrok 创建一个公开的 HTTPS URL,通过隧道转发到你的本地服务器:
# 安装 ngrok(macOS)
brew install ngrok
# 或者从 https://ngrok.com/download 下载
# 启动隧道
ngrok http 5000
ngrok 输出类似:
Forwarding https://a1b2c3d4.ngrok-free.app → http://localhost:5000
把这个 HTTPS URL 用作你的 callback_url:
payload = {
"model": "seedance-2.0",
"prompt": "Your prompt here",
"callback_url": "https://a1b2c3d4.ngrok-free.app/api/webhook/seedance"
}
常见错误: 使用 ngrok 的
http://URL 而不是https://。Seedance API 要求 Webhook 必须使用 HTTPS——使用纯 HTTP 的回调 URL 会返回 400 错误。
Webhook 安全
在生产环境中,验证 Webhook 请求确实来自 EvoLink API:
import hmac
import hashlib
def verify_webhook(request):
"""通过任务 ID 格式验证 Webhook 真实性。"""
task = request.json
task_id = task.get("id", "")
# EvoLink 的任务 ID 遵循特定格式
if not task_id.startswith("task-unified-"):
return False
# 额外验证:检查必填字段是否存在
required_fields = ["id", "status", "model", "created"]
if not all(field in task for field in required_fields):
return False
return True
Webhook vs 轮询:怎么选?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 快速原型 / 脚本 | 轮询 | 更简单,不需要服务器 |
| 生产级 Web 应用 | Webhook | 可扩展,不浪费请求 |
| 批量处理(100+ 视频) | Webhook + 消息队列 | 全部提交,按完成顺序处理 |
| CLI 工具 | 轮询 | 不需要服务器基础设施 |
| 移动应用后端 | Webhook | 完成后推送通知给用户 |
| Serverless(Lambda/Cloud Functions) | Webhook | 完美匹配——每次完成触发一个函数 |
小贴士: 对于批量处理,把 Webhook 和消息队列(Redis、RabbitMQ、SQS)结合使用。提交所有生成请求,然后按它们到达队列的顺序处理。这种方式将提交和处理解耦,并且优雅地处理重试。
批量处理:生成多个视频
实际场景中经常需要生成大量视频。以下是带限流控制的批量处理模式:
使用前面第一个示例中相同的配置和辅助函数。
import concurrent.futures
def batch_generate(prompts, max_concurrent=3):
"""
带并发控制的批量视频生成。
Args:
prompts: 提示词字符串列表。
max_concurrent: 最大同时生成数。
Returns:
(prompt, result_or_error) 元组列表。
"""
results = []
def generate_one(prompt, index):
"""生成单个视频并返回结果。"""
payload = {
"model": "seedance-2.0",
"prompt": prompt,
"duration": 5,
"quality": "720p"
}
try:
task = generate_video_with_retry(payload)
print(f"[{index}] Submitted: {task['id']}")
result = wait_for_video(task["id"])
video_url = result["results"][0]
download_video(video_url, f"batch_{index}.mp4")
return (prompt, result)
except Exception as e:
print(f"[{index}] Failed: {e}")
return (prompt, str(e))
# 分批处理以遵守限流规则
with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent) as executor:
futures = {
executor.submit(generate_one, prompt, i): i
for i, prompt in enumerate(prompts)
}
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
# 汇总
succeeded = sum(1 for _, r in results if isinstance(r, dict))
print(f"\nBatch complete: {succeeded}/{len(prompts)} succeeded")
return results
# 示例用法
prompts = [
"A hummingbird hovering near a red flower. Macro lens, shallow depth of field.",
"Ocean waves crashing on volcanic rocks at sunset. Slow motion.",
"A street musician playing violin in the rain. Cinematic lighting.",
]
batch_generate(prompts, max_concurrent=2)
批量处理的关键考虑:
max_concurrent=3— 不要同时提交太多请求。从 2–3 个并发开始,根据你的限流配额逐步增加。- ThreadPoolExecutor — 使用线程(而非进程),因为我们是 I/O 密集型(等待 API 响应),而不是 CPU 密集型。
- 错误隔离 — 每个视频生成都是独立的。一个失败不会中断整个批次。
下一步
你已经掌握了基础——文生视频、图生视频、异步轮询、Webhook、错误处理和批量处理。以下是深入学习的方向:
探索高级功能
- @Tags 多模态引用指南 — 掌握 @Image、@Video、@Audio 引用系统,实现多模态生成
- 镜头运动 API 指南 — 用代码复刻希区柯克变焦、一镜到底跟踪镜头和环绕镜头
- 图生视频详解 — 首尾帧控制、多图合成、电商产品视频
- 电商产品视频指南 — 批量将产品图转化为营销视频
- 提示词工程指南 — 分镜脚本格式、时间轴语法,以及我们 Demo 视频背后的提示词
参考文档
动手做点什么
把你学到的东西组合起来。以下是一些项目灵感:
- 自动化产品视频流水线 — 上传产品图,批量生成营销视频(参考电商视频指南)
- 社交媒体内容引擎 — 从文本简报生成竖版短视频,直接发布到抖音/Reels
- 分镜到视频工具 — 将连续图片转化为带镜头运动控制的动画场景
- AI 视频编辑流水线 — 利用 Seedance 2.0 的视频延展功能,从短片段创建更长的叙事
准备好了?免费获取 EvoLink API Key,今天就开始生成视频。
常见问题
Seedance 2.0 视频生成需要多长时间?
通常 30–120 秒,取决于时长和质量设置。5 秒 720p 的视频大约 50 秒完成。15 秒 1080p 的视频可能需要 2–3 分钟。API 在每个任务中返回 estimated_time 字段,方便你设置合理的超时时间。高峰时段,排队等待可能额外增加 10–30 秒。
Seedance 2.0 API 支持哪些图片格式?
JPEG、PNG、WebP、BMP、TIFF 和 GIF。每张图片不超过 30 MB。每次请求最多通过 image_urls 参数传 9 张图片。图片必须是公开可访问的 URL——API 会直接拉取。建议使用短边至少 720px 的图片。分辨率太低(低于 256px)可能导致动画模糊。
能生成超过 15 秒的视频吗?
单次生成最长 15 秒。要制作更长的内容,生成多个片段后用 FFmpeg 或任何视频编辑器拼接。Seedance 2.0 支持视频延展——你可以把生成视频的最后一帧作为下一次生成的首帧,实现无缝衔接。基本思路:生成片段 1,提取最后一帧,作为 @Image1 as first frame 传入片段 2。
通过 EvoLink 使用 Seedance 2.0 API 要多少钱?
定价基于视频时长和质量档位。5 秒 720p 的视频大约消耗 18 个额度。EvoLink 提供智能路由,相比直接调用 API 可以降低成本。具体的每秒费率请查看控制台。API 响应中的 credits_reserved 字段会在生成前显示精确费用——实际收费不会超过这个金额。
seedance-1.5-pro 和 seedance-2.0 有什么区别?
Seedance 2.0 新增了多模态引用(图片、视频、音频混合输入)、原生音频生成、更好的物理一致性和视频编辑能力。API 接口完全一致——相同的端点、相同的参数、相同的响应格式。你现在可以用 seedance-1.5-pro 测试,之后只需改模型名称切换到 seedance-2.0。1.5 的主要限制:仅支持单张图片输入(不支持 @Image2–9)、不支持视频/音频引用、不支持原生音频生成。详细对比请看 Seedance 2.0 vs Sora 2 评测。
如何处理"content rejected by safety filter"错误?
内容审核系统会拒绝涉及写实暴力、露骨内容和真实公众人物的提示词。通过 image_urls 上传的写实人脸图片也会被拒绝。要绕过人脸限制,使用插画、风格化或动漫风格的角色图片。对于提示词被拒的情况,修改措辞避免涉及受限主题。错误响应中包含 type: "content_policy_violation"——在你的错误处理代码中检查这个字段,给用户一个清晰的提示。
能在 Node.js / JavaScript 项目中使用 Seedance API 吗?
可以。REST API 与语言无关——任何 HTTP 客户端都能用。本教程中的概念(异步轮询、Webhook、错误处理)可以直接用 Node.js 的 fetch 或 axios 实现。EvoLink 还提供官方的 Node.js 和 Python SDK,帮你处理轮询和重试。
视频完成时我的 Webhook 服务器挂了怎么办?
API 会以递增间隔重试 3 次 Webhook 投递(1s、2s、4s)。如果 3 次都失败,Webhook 就放弃了——但视频仍然可用。你随时可以用 GET /v1/tasks/{task_id} 轮询获取结果作为兜底。因此,最佳实践是在提交时保存任务 ID,并设置一个后台任务定期检查是否有完成但未通过 Webhook 接收到的任务。
API 请求有限流吗?
有。默认限流对开发和中等规模的生产使用来说很宽裕。如果遇到 429 错误,按照错误处理章节中的方式实现指数退避。对于大规模使用场景(每天数千个视频),联系 EvoLink 支持 讨论自定义限流和专属容量。
Seedance 2.0 可以用于商业项目吗?
可以。通过 EvoLink API 生成的视频授权商业使用。你拥有输出内容的所有权,可以用于产品、营销材料、客户交付物和发布内容。详细的授权条款和商业使用最佳实践请看 Seedance 2.0 版权指南。
完整脚本
以下是本教程的完整代码,整合在一个文件中——复制、粘贴、填入你的 API Key,直接运行:
"""
Seedance 2.0 API 教程 — 完整脚本
文档:https://seedance2api.app/docs/video-generation
API Key:https://evolink.ai/early-access
"""
import requests
import time
import os
import json
import random
# ── 配置 ─────────────────────────────────────────────────────
API_KEY = os.getenv("EVOLINK_API_KEY", "sk-your-api-key-here")
BASE_URL = "https://api.evolink.ai/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# ── 可复用辅助函数 ───────────────────────────────────────────
def wait_for_video(task_id, poll_interval=10, timeout=600):
"""轮询视频生成任务直到完成。"""
elapsed = 0
while elapsed < timeout:
response = requests.get(
f"{BASE_URL}/tasks/{task_id}",
headers=HEADERS
)
response.raise_for_status()
task = response.json()
status = task["status"]
progress = task.get("progress", 0)
print(f" [{elapsed}s] Status: {status} | Progress: {progress}%")
if status == "completed":
return task
elif status == "failed":
raise RuntimeError(f"Task {task_id} failed: {task}")
time.sleep(poll_interval)
elapsed += poll_interval
raise TimeoutError(f"Task {task_id} timed out after {timeout}s")
def download_video(url, filename="output.mp4"):
"""从 URL 下载视频文件。"""
print(f"Downloading to {filename}...")
resp = requests.get(url, stream=True)
resp.raise_for_status()
with open(filename, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Saved: {filename} ({os.path.getsize(filename) / 1024:.0f} KB)")
def generate_video_with_retry(payload, max_retries=3):
"""提交生成请求,对瞬态错误自动重试。"""
for attempt in range(max_retries):
try:
response = requests.post(
f"{BASE_URL}/videos/generations",
headers=HEADERS,
json=payload,
timeout=30
)
if response.status_code == 200:
return response.json()
error = response.json().get("error", {})
if response.status_code in (400, 401, 402, 404, 413, 422):
raise ValueError(
f"API error {response.status_code}: "
f"{error.get('message', 'Unknown')}"
)
if response.status_code in (429, 500, 502, 503):
wait = (2 ** attempt) + random.uniform(0, 1)
print(f" Retry {attempt+1}/{max_retries} after {wait:.1f}s")
time.sleep(wait)
continue
except requests.exceptions.RequestException:
wait = (2 ** attempt) + random.uniform(0, 1)
print(f" Retry {attempt+1}/{max_retries} after {wait:.1f}s")
time.sleep(wait)
continue
raise RuntimeError(f"Failed after {max_retries} retries")
def validate_payload(payload):
"""在 API 调用前校验生成 payload。"""
errors = []
if not payload.get("model"):
errors.append("'model' is required")
if not payload.get("prompt") or not payload["prompt"].strip():
errors.append("'prompt' is required")
duration = payload.get("duration", 5)
if duration < 4 or duration > 15:
errors.append(f"'duration' must be 4-15, got {duration}")
quality = payload.get("quality", "720p")
if quality not in {"480p", "720p", "1080p"}:
errors.append(f"Invalid quality: {quality}")
if errors:
raise ValueError("Validation failed:\n" + "\n".join(f" - {e}" for e in errors))
def cancel_task(task_id):
"""取消 pending 或 processing 状态的任务。"""
response = requests.post(
f"{BASE_URL}/tasks/{task_id}/cancel",
headers=HEADERS
)
if response.status_code == 200:
print(f"Task {task_id} cancelled.")
else:
print(f"Cancel failed: {response.json()}")
# ── 示例 1:文生视频 ─────────────────────────────────────────
def text_to_video():
payload = {
"model": "seedance-2.0",
"prompt": (
"A golden retriever puppy chases a butterfly through "
"a sunlit meadow. The camera follows the puppy with a "
"smooth tracking shot as wildflowers sway in the breeze."
),
"duration": 5,
"quality": "720p",
"aspect_ratio": "16:9",
"generate_audio": True
}
validate_payload(payload)
task = generate_video_with_retry(payload)
print(f"Task: {task['id']} (ETA: {task['task_info']['estimated_time']}s)")
result = wait_for_video(task["id"])
download_video(result["results"][0], "text_to_video.mp4")
# ── 示例 2:图生视频 ─────────────────────────────────────────
def image_to_video():
payload = {
"model": "seedance-2.0",
"prompt": (
"@Image1 as the first frame. The scene slowly comes "
"to life — leaves rustle gently, soft light shifts "
"across the frame."
),
"image_urls": ["https://example.com/your-image.jpg"],
"duration": 5,
"quality": "720p"
}
validate_payload(payload)
task = generate_video_with_retry(payload)
print(f"Task: {task['id']}")
result = wait_for_video(task["id"])
download_video(result["results"][0], "image_to_video.mp4")
# ── 示例 3:竖版社交媒体视频 ─────────────────────────────────
def social_media_video():
payload = {
"model": "seedance-2.0",
"prompt": (
"A barista pours latte art in slow motion. "
"Close-up overhead shot, warm cafe lighting."
),
"duration": 8,
"quality": "1080p",
"aspect_ratio": "9:16",
"generate_audio": True
}
validate_payload(payload)
task = generate_video_with_retry(payload)
print(f"Task: {task['id']}")
result = wait_for_video(task["id"])
download_video(result["results"][0], "social_video.mp4")
if __name__ == "__main__":
print("=== 文生视频 ===")
text_to_video()
# print("\n=== 图生视频 ===")
# image_to_video() # 取消注释并设置你的图片 URL
# print("\n=== 社交媒体视频 ===")
# social_media_video()
提示: 要用当前可用的模型测试,把
"seedance-2.0"改成"seedance-1.5-pro"。API 接口完全一致——相同的端点、相同的参数、相同的响应格式。等 Seedance 2.0 全面上线后,再把模型名改回来就行。