feat(video): 切换到火山引擎 Seedance 视频生成 API

This commit is contained in:
cc 2026-03-21 16:16:50 +08:00
parent 5ef9a1d4cd
commit 326931826d
6 changed files with 4310 additions and 590 deletions

View File

@ -0,0 +1,102 @@
---
name: replace-video-api-to-seedance
overview: 将 VideoExplanation.vue 中视频生成部分从阿里云万相(wan2.2-kf2v-flash)切换到火山引擎 Seedance 1.5 Pro (doubao-seedance-1-5-pro-251215),修改 API 调用、请求体结构和轮询逻辑。
todos:
- id: replace-video-api-config
content: 修改 VideoExplanation.vue 的 API 配置常量:删除 VIDEO_API_KEY/VIDEO_API_URL/VIDEO_TASK_URL/VIDEO_MODEL替换为 Seedance 的 VIDEO_API_URL 和 VIDEO_TASK_URL
status: completed
- id: rewrite-generate-single-clip
content: 重写 generateSingleClip 函数,适配 Seedance 的 content 数组请求格式和响应字段结构
status: completed
dependencies:
- replace-video-api-config
---
## Product Overview
将 VideoExplanation.vue 中视频生成部分从阿里云万相模型wan2.2-kf2v-flash切换为豆包 Seedance 1.5 Pro 模型doubao-seedance-1-5-pro-251215通过 Ark 平台 API 实现首尾帧图片生视频功能。
## Core Features
- 替换视频生成 API 端点、请求结构和鉴权方式(从 DashScope 切换到 Ark
- 适配 Seedance 的 content 数组格式text + first_frame image_url + last_frame image_url
- 适配 Seedance 的异步任务查询接口和响应字段id/status/content.video_url
- 复用已有的 /ark-api 代理和 DOUBAO_KEY 密钥,无需新增代理或配置
## Tech Stack
- 前端框架: Vue 3 (Composition API)
- HTTP 请求: axios已在项目中使用
- API 代理: Vite proxy/ark-api 已配置指向 ark.cn-beijing.volces.com
## Implementation Approach
将视频生成 API 从 DashScope 万相模型切换到 Seedance 1.5 Pro核心变化如下
### API 对比
| 维度 | 万相 (当前) | Seedance 1.5 Pro (目标) |
| --- | --- | --- |
| 创建任务 | POST `/dashscope-api/api/v1/services/aigc/image2video/video-synthesis` | POST `/ark-api/api/v3/contents/generations/tasks` |
| 请求体 | `{ model, input: { first_frame_url, last_frame_url, prompt }, parameters: { resolution, prompt_extend, watermark } }` | `{ model, content: [{ type:"text", text }, { type:"image_url", image_url:{url}, role:"first_frame" }, { type:"image_url", image_url:{url}, role:"last_frame" }], ratio, duration, watermark }` |
| 异步标识 | Header: `X-DashScope-Async: enable` | 无需异步头,创建即返回任务 ID |
| 查询任务 | GET `/dashscope-api/api/v1/tasks/{taskId}` | GET `/ark-api/api/v3/contents/generations/tasks/{id}` |
| 响应字段 | `output.task_id` / `output.task_status` / `output.video_url` | `id` / `status` / `content.video_url` |
| 状态值 | `SUCCEEDED` / `FAILED` | `succeeded` / `failed` |
| 鉴权 | 独立 VIDEO_API_KEY | DOUBAO_KEYArk 平台统一鉴权) |
| 时长范围 | 无限制 | 4~12 秒(当前 CLIP_DURATION=5 符合) |
### 关键技术决策
1. **复用 DOUBAO_KEY**: Seedance 同属 Ark 平台,直接复用 config/index.js 中已有的 DOUBAO_KEY无需新增配置
2. **复用 /ark-api 代理**: vite.config.js 已有 `/ark-api` 代理指向 `ark.cn-beijing.volces.com`,无需修改代理配置
3. **保留首尾帧逻辑**: 当前分镜图片生成流程(首帧+尾帧)与 Seedance 的 first_frame/last_frame 能力完全匹配,图片生成部分不需改动
## Implementation Notes
- 仅修改 VideoExplanation.vue 中的视频生成相关代码,不改动脚本生成和图片生成逻辑
- 修改范围集中在两个区域API 配置常量第42-50行和 generateSingleClip 函数第251-319行
- Seedance 任务轮询间隔保持 15 秒不变,最大轮询次数 24 次足够(最长等待 6 分钟)
- 删除 VIDEO_API_KEY 常量,统一使用 DOUBAO_KEY
## Directory Structure
```
src/views/VideoExplanation.vue # [MODIFY] 替换视频生成 API修改 API 配置常量、重写 generateSingleClip 函数
```
## Key Code Structures
### generateSingleClip 函数签名(不变)
```typescript
const generateSingleClip = async (shot, firstFrameUrl, lastFrameUrl): Promise<string | null>
```
### Seedance 创建任务请求体结构
```typescript
{
model: "doubao-seedance-1-5-pro-251215",
content: [
{ type: "text", text: string },
{ type: "image_url", image_url: { url: string }, role: "first_frame" },
{ type: "image_url", image_url: { url: string }, role: "last_frame" }
],
ratio: "16:9",
duration: 5,
watermark: false
}
```
### Seedance 查询响应结构
```typescript
{
id: string, // 任务 ID
status: "succeeded" | "failed" | "queued" | "running",
content: { video_url: string },
error: { code: string, message: string }
}
```

32
package-lock.json generated
View File

@ -8,6 +8,8 @@
"name": "ai-demo", "name": "ai-demo",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"axios": "^1.13.6", "axios": "^1.13.6",
"vue": "^3.5.30", "vue": "^3.5.30",
"vue-router": "^5.0.3" "vue-router": "^5.0.3"
@ -113,6 +115,36 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@ffmpeg/ffmpeg": {
"version": "0.12.15",
"resolved": "https://registry.npmmirror.com/@ffmpeg/ffmpeg/-/ffmpeg-0.12.15.tgz",
"integrity": "sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw==",
"license": "MIT",
"dependencies": {
"@ffmpeg/types": "^0.12.4"
},
"engines": {
"node": ">=18.x"
}
},
"node_modules/@ffmpeg/types": {
"version": "0.12.4",
"resolved": "https://registry.npmmirror.com/@ffmpeg/types/-/types-0.12.4.tgz",
"integrity": "sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A==",
"license": "MIT",
"engines": {
"node": ">=16.x"
}
},
"node_modules/@ffmpeg/util": {
"version": "0.12.2",
"resolved": "https://registry.npmmirror.com/@ffmpeg/util/-/util-0.12.2.tgz",
"integrity": "sha512-ouyoW+4JB7WxjeZ2y6KpRvB+dLp7Cp4ro8z0HIVpZVCM7AwFlHa0c4R8Y/a4M3wMqATpYKhC7lSFHQ0T11MEDw==",
"license": "MIT",
"engines": {
"node": ">=18.x"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",

View File

@ -9,6 +9,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"axios": "^1.13.6", "axios": "^1.13.6",
"vue": "^3.5.30", "vue": "^3.5.30",
"vue-router": "^5.0.3" "vue-router": "^5.0.3"

3044
src/md/doubao_video.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,10 @@ export default defineConfig({
}, },
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
proxy: { proxy: {
'/tts-api': { '/tts-api': {
target: 'https://openspeech.bytedance.com', target: 'https://openspeech.bytedance.com',