# 生成视频 文生视频、首帧、首尾帧、参考图和参考音视频的请求写法。 所有场景都调用同一个接口 `POST /v1/video/generations`,区别只在 `content` 里放什么。任务、费用预留和幂等记录一起被接受,接口返回 `202` 和任务 ID,生成在后台进行。完整参数见「[创建视频任务](./api-docs-zh-api-video-tasks-create.md)」。 ## 文生视频 `content` 里只放一项 `text`,写清画面、动作和镜头。 ```bash IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)} printf '%s\n' "保存 IDEMPOTENCY_KEY,重试时原样复用:${IDEMPOTENCY_KEY}" >&2 curl --request POST "${CHENVOYA_BASE_URL}/v1/video/generations" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" \ --header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \ --header 'Content-Type: application/json' \ --data '{ "model": "dreamina-seedance-2-0-260128", "content": [ { "type": "text", "text": "A paper boat crossing a moonlit lake." } ] }' ``` ```python import urllib.request import urllib.parse import json import os import sys import re import uuid key = os.environ["CHENVOYA_API_KEY"].strip() if not key or re.search(r"[\x00-\x1f\x7f-\x9f]", key): sys.exit("Set CHENVOYA_API_KEY to a non-empty value without control characters") base = os.environ["CHENVOYA_BASE_URL"].rstrip("/") headers = {"Authorization": "Bearer " + key} idempotency = os.environ.get("IDEMPOTENCY_KEY") or str(uuid.uuid4()) print("保存 IDEMPOTENCY_KEY,重试时原样复用:" + idempotency, file=sys.stderr) headers["Idempotency-Key"] = idempotency path = "/v1/video/generations" body_text = "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}" headers["Content-Type"] = "application/json" body = json.dumps(json.loads(body_text)).encode("utf-8") request = urllib.request.Request(base + path, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(request) as response: print(response.read().decode("utf-8")) except urllib.request.HTTPError as error: print(error.read().decode("utf-8")) sys.exit(1) ``` ```javascript // 需要 Node.js 20 及以上版本,保存为 .mjs 文件后运行。 const key = process.env.CHENVOYA_API_KEY?.trim(); if (!key || /[\x00-\x1f\x7f-\x9f]/.test(key)) { console.error("Set CHENVOYA_API_KEY to a non-empty value without control characters"); process.exit(1); } const base = process.env.CHENVOYA_BASE_URL; if (!base) throw new Error("Set CHENVOYA_BASE_URL"); const headers = {Authorization: `Bearer ${key}`}; const idempotency = process.env.IDEMPOTENCY_KEY || crypto.randomUUID(); console.error("保存 IDEMPOTENCY_KEY,重试时原样复用:" + idempotency); headers["Idempotency-Key"] = idempotency; const path = "/v1/video/generations"; let bodyText = "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}"; headers["Content-Type"] = "application/json"; const body = JSON.stringify(JSON.parse(bodyText)); const response = await fetch(base.replace(/\/$/, "") + path, {method: "POST", headers, body: body}); console.log(await response.text()); if (!response.ok) process.exitCode = 1; ``` ```go package main import ( "crypto/rand" "fmt" "io" "net/http" "os" "regexp" "strings" ) func main() { key := strings.TrimSpace(os.Getenv("CHENVOYA_API_KEY")) if key == "" || regexp.MustCompile(`[\x00-\x1f\x7f-\x9f]`).MatchString(key) { fail("Set CHENVOYA_API_KEY to a non-empty value without control characters") } base := strings.TrimRight(os.Getenv("CHENVOYA_BASE_URL"), "/") if base == "" { fail("Set CHENVOYA_BASE_URL") } path := "/v1/video/generations" bodyText := "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}" request, err := http.NewRequest("POST", base+path, strings.NewReader(bodyText)) if err != nil { fail(err.Error()) } request.Header.Set("Authorization", "Bearer "+key) request.Header.Set("Content-Type", "application/json") idempotency := os.Getenv("IDEMPOTENCY_KEY") if idempotency == "" { idempotency = newOperationKey() } fmt.Fprintln(os.Stderr, "保存 IDEMPOTENCY_KEY,重试时原样复用:"+idempotency) request.Header.Set("Idempotency-Key", idempotency) response, err := http.DefaultClient.Do(request) if err != nil { fail(err.Error()) } text, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { fail(err.Error()) } fmt.Println(string(text)) if response.StatusCode < 200 || response.StatusCode > 299 { os.Exit(1) } } func fail(message string) { fmt.Fprintln(os.Stderr, message) os.Exit(1) } // newOperationKey returns a random UUID; save it to retry with the same value. func newOperationKey() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { fail(err.Error()) } b[6] = b[6]&0x0f | 0x40 b[8] = b[8]&0x3f | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } ``` ## 首帧和首尾帧 用 `image_url` 项放图片,`role` 写 `first_frame` 或 `last_frame`。只有一张图片时可以省略 `role`,按首帧处理;有多张图片时每张都要写 `role`。 ```json "content": [ {"type": "text", "text": "A paper boat crossing a moonlit lake."}, {"type": "image_url", "role": "first_frame", "image_url": {"url": "https://example.com/first.png"}}, {"type": "image_url", "role": "last_frame", "image_url": {"url": "https://example.com/last.png"}} ] ``` ## 参考图 `role` 写 `reference_image`,最多 9 张。下面的例子引用一个已登记的素材: ```bash IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)} printf '%s\n' "保存 IDEMPOTENCY_KEY,重试时原样复用:${IDEMPOTENCY_KEY}" >&2 ASSET_ID_PATTERN='^ast-[0-9a-f]{32}$' if [[ ! ${ASSET_ID} =~ ${ASSET_ID_PATTERN} || ${ASSET_ID} == *[!A-Za-z0-9._-]* ]]; then printf '%s\n' '请先设置创建响应中的 ASSET_ID。' >&2 exit 1 fi curl --request POST "${CHENVOYA_BASE_URL}/v1/video/generations" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" \ --header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \ --header 'Content-Type: application/json' \ --data '{ "model": "dreamina-seedance-2-0-260128", "duration": 5, "ratio": "16:9", "resolution": "720p", "content": [ { "type": "text", "text": "Animate the reviewed reference image." }, { "type": "image_url", "role": "reference_image", "image_url": { "url": "asset://'"${ASSET_ID}"'" } } ] }' ``` ```python import urllib.request import urllib.parse import json import os import sys import re import uuid key = os.environ["CHENVOYA_API_KEY"].strip() if not key or re.search(r"[\x00-\x1f\x7f-\x9f]", key): sys.exit("Set CHENVOYA_API_KEY to a non-empty value without control characters") base = os.environ["CHENVOYA_BASE_URL"].rstrip("/") headers = {"Authorization": "Bearer " + key} idempotency = os.environ.get("IDEMPOTENCY_KEY") or str(uuid.uuid4()) print("保存 IDEMPOTENCY_KEY,重试时原样复用:" + idempotency, file=sys.stderr) headers["Idempotency-Key"] = idempotency path = "/v1/video/generations" body_text = "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}" ASSET_ID = os.environ["ASSET_ID"] if not re.fullmatch("^ast-[0-9a-f]{32}$", ASSET_ID): sys.exit("Set ASSET_ID to the ID from the creation response") body_text = body_text.replace("\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", json.dumps("asset://" + ASSET_ID)) headers["Content-Type"] = "application/json" body = json.dumps(json.loads(body_text)).encode("utf-8") request = urllib.request.Request(base + path, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(request) as response: print(response.read().decode("utf-8")) except urllib.request.HTTPError as error: print(error.read().decode("utf-8")) sys.exit(1) ``` ```javascript // 需要 Node.js 20 及以上版本,保存为 .mjs 文件后运行。 const key = process.env.CHENVOYA_API_KEY?.trim(); if (!key || /[\x00-\x1f\x7f-\x9f]/.test(key)) { console.error("Set CHENVOYA_API_KEY to a non-empty value without control characters"); process.exit(1); } const base = process.env.CHENVOYA_BASE_URL; if (!base) throw new Error("Set CHENVOYA_BASE_URL"); const headers = {Authorization: `Bearer ${key}`}; const idempotency = process.env.IDEMPOTENCY_KEY || crypto.randomUUID(); console.error("保存 IDEMPOTENCY_KEY,重试时原样复用:" + idempotency); headers["Idempotency-Key"] = idempotency; const path = "/v1/video/generations"; let bodyText = "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}"; const ASSET_ID = process.env.ASSET_ID; if (typeof ASSET_ID !== "string" || (new RegExp("^ast-[0-9a-f]{32}$")).exec(ASSET_ID)?.[0] !== ASSET_ID) throw new Error("Set ASSET_ID to the ID from the creation response"); bodyText = bodyText.replaceAll("\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", JSON.stringify("asset://" + ASSET_ID)); headers["Content-Type"] = "application/json"; const body = JSON.stringify(JSON.parse(bodyText)); const response = await fetch(base.replace(/\/$/, "") + path, {method: "POST", headers, body: body}); console.log(await response.text()); if (!response.ok) process.exitCode = 1; ``` ```go package main import ( "crypto/rand" "fmt" "io" "net/http" "os" "regexp" "strconv" "strings" ) func main() { key := strings.TrimSpace(os.Getenv("CHENVOYA_API_KEY")) if key == "" || regexp.MustCompile(`[\x00-\x1f\x7f-\x9f]`).MatchString(key) { fail("Set CHENVOYA_API_KEY to a non-empty value without control characters") } base := strings.TrimRight(os.Getenv("CHENVOYA_BASE_URL"), "/") if base == "" { fail("Set CHENVOYA_BASE_URL") } path := "/v1/video/generations" bodyText := "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}" assetID := os.Getenv("ASSET_ID") if !regexp.MustCompile(`\A(?:ast-[0-9a-f]{32})\z`).MatchString(assetID) { fail("Set ASSET_ID to the ID from the creation response") } bodyText = strings.ReplaceAll(bodyText, "\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", strconv.Quote("asset://"+assetID)) request, err := http.NewRequest("POST", base+path, strings.NewReader(bodyText)) if err != nil { fail(err.Error()) } request.Header.Set("Authorization", "Bearer "+key) request.Header.Set("Content-Type", "application/json") idempotency := os.Getenv("IDEMPOTENCY_KEY") if idempotency == "" { idempotency = newOperationKey() } fmt.Fprintln(os.Stderr, "保存 IDEMPOTENCY_KEY,重试时原样复用:"+idempotency) request.Header.Set("Idempotency-Key", idempotency) response, err := http.DefaultClient.Do(request) if err != nil { fail(err.Error()) } text, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { fail(err.Error()) } fmt.Println(string(text)) if response.StatusCode < 200 || response.StatusCode > 299 { os.Exit(1) } } func fail(message string) { fmt.Fprintln(os.Stderr, message) os.Exit(1) } // newOperationKey returns a random UUID; save it to retry with the same value. func newOperationKey() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { fail(err.Error()) } b[6] = b[6]&0x0f | 0x40 b[8] = b[8]&0x3f | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } ``` ## 参考视频和音频 - 参考视频用 `video_url` 项,`role` 写 `reference_video`;参考音频用 `audio_url` 项,`role` 写 `reference_audio`。 - 单段 2–15 秒,视频合计和音频合计都不超过 15 秒,最多 3 段视频和 3 段音频。 - 参考音频必须和参考图片或参考视频一起使用。 - 用 HTTPS 链接的参考视频按 15 秒输入预留费用;准备时按实际时长检查,总时长超限的任务以 `input_duration_exceeded` 失败,全额释放预留,不收费。 ## 素材来源 每个图片、视频或音频项可以用三种来源之一: | 来源 | 写法 | 说明 | | --- | --- | --- | | HTTPS 链接 | `https://…` | 平台先下载、检查并保存,再用于生成;这期间任务处于 `preparing_assets`。准备失败时全额释放预留,不收费。 | | 已登记的素材 | `asset://` 加素材 ID | 先[登记素材](./api-docs-zh-assets.md),等它变为 `active` 再引用。 | | base64 data URI | `data:image/png;base64,…` | 图片支持 JPEG、PNG、WebP,解码后小于 30 MB;音频支持 WAV、MP3,最大 15 MB、时长 2–15 秒;视频不支持。 | HTTPS 链接要用 443 端口,不能带用户信息、IP 地址或本地主机名,最长 2048 个字节。同一个请求里,HTTPS 链接和 base64 可以一起用,但都不能和 `asset://` 引用混用。 ## 声音、画幅与时长 - `generate_audio`:是否同时生成声音,默认 `false`。 - `ratio`:画幅,默认 `adaptive`,由模型按输入选择。 - `duration`:4–15 秒,默认 5 秒。 - `resolution`:默认 `720p`;Seedance 2.0 Fast 和 Mini 最高 `720p`。 - `watermark`、`seed`、`return_last_frame`:见「[视频模型](./api-docs-zh-video-models.md#outputs)」。 ## 大请求 请求体最大 64 MiB。大于 2 MiB 的请求体要在 120 秒内传完;每个团队同时处理的大请求有上限,满额时返回 `429 rate_limited`,稍后用同一个 `Idempotency-Key` 重试。 ## 下一步 - [任务状态与结果](./api-docs-zh-video-tasks.md):任务状态的含义、轮询和回调两种取结果方式、下载结果和取消任务。 - [上传素材](./api-docs-zh-assets.md):先登记素材,等它可用后在生成请求里用 asset:// 引用。 - [创建视频任务](./api-docs-zh-api-video-tasks-create.md):提交提示词和可选的图片、视频或音频,创建一个异步生成任务,立即返回任务 ID 和预留金额。