Skill: jancong/jancong-seedance-video-gen (version 1.0.0)

--- name: seedance-video-gen description: 使用火山引擎 Seedance 2.0 系列模型（doubao-seedance-2-0 / doubao-seedance-2-0-fast）通过方舟平台 API 生成高质量 AI 视频。支持文生视频、图生视频、视频参考、音频参考等多模态内容生成，适用于用户要求生成/制作/创建视频、文生视频、图生视频、AI 生视频以及泛化表述如"帮我做一个视频""用 AI 生成一段视频"等场景。 --- # Seedance 视频生成 Skill 使用火山引擎方舟平台的 **Seedance 2.0** 系列 AI 模型生成高质量视频。 ## 支持的模型 | 模型 ID | 特点 | 适用场景 | |---------|------|---------| | `doubao-seedance-2-0-260128` | 标准版，质量更高 | 高质量商业视频、精细画面 | | `doubao-seedance-2-0-fast-260128` | 快速版，速度更快 | 快速预览、批量测试 | ## 工作流程 ### Step 1: 准备请求参数 根据用户需求构建 API 请求体： **必需参数：** - `model`: 模型名称（从上表选择） - `content`: 多模态内容数组，至少包含一个 text 类型元素 **可选参数：** - `generate_audio`: 是否同时生成音频/语音 (true/false) - `ratio`: 视频比例 `"16:9"` | `"9:16"` | `"1:1"`（默认 16:9） - `duration`: 视频时长秒数，范围 5-11（默认 10） - `watermark`: 是否添加水印 (true/false) ### Step 2: 构建 content 数组 `content` 数组支持以下类型的多模态输入： ```jsonc { "content": [ // 1. 文本提示词（必需） { "type": "text", "text": "详细的视频描述提示词..." }, // 2. 参考图片（可选，可多个） { "type": "image_url", "image_url": { "url": "https://example.com/image.jpg" }, "role": "reference_image" }, // 3. 参考视频（可选，用于参考运镜/构图风格） { "type": "video_url", "video_url": { "url": "https://example.com/video.mp4" }, "role": "reference_video" }, // 4. 参考音频/背景音乐（可选） { "type": "audio_url", "audio_url": { "url": "https://example.com/audio.mp3" }, "role": "reference_audio" } ] } ``` > **注意**: 图片、视频、音频 URL 必须是公网可访问的地址。如果用户提供本地文件，需要先上传到 OSS 或使用 Base64 编码。 ### Step 3: 提交生成任务 调用 `scripts/generate_video.py` 脚本提交异步任务： ```bash python3 ~/.workbuddy/skills/seedance-video-gen/scripts/generate_video.py \ --model doubao-seedance-2-0-fast-260128 \ --prompt "你的视频描述" \ --ratio 16:9 \ --duration 10 \ [--image-url "https://..."] \ [--video-url "https://..."] \ [--audio-url "https://..."] \ [--generate-audio] \ [--no-watermark] ``` 或者直接使用 curl 调用 API（见 references/api_reference.md）。 API 返回任务 ID： ```json { "id": "cgt-20260410162021-xxxxx" } ``` ### Step 4: 轮询任务状态 提交后任务为异步处理，需轮询查询状态： ```bash python3 ~/.workbuddy/skills/seedance-video-gen/scripts/query_task.py <task_id> ``` 或直接 curl： ```bash curl -X GET "{ARK_API_URL}/api/v3/contents/generations/tasks/{task_id}" \ -H "Authorization: Bearer {API_KEY}" ``` **状态值：** | 状态 | 含义 | 操作 | |------|------|------| | `pending` | 排队中 | 继续等待 | | `running` | 生成中 | 继续等待 | | `completed` | 完成 | 获取结果 | | `failed` | 失败 | 查看错误信息 | **完成时的响应包含：** - `video_result.video_url`: 生成的视频地址 - `video_result.cover_image_url`: 封面图地址 ### Step 5: 下载并展示结果 任务完成后： 1. 下载视频文件到工作目录 2. 使用 preview_url 或 open_result_view 展示给用户 3. 告知用户视频路径和基本信息 ## 提示词撰写建议 参考 `references/prompt_guide.md` 获取完整的提示词撰写指南。核心原则： 1. **明确主体和动作**: 描述清楚谁在做什么 2. **指定镜头语言**: 运镜方式（推拉摇移）、视角（第一人称/俯拍等） 3. **时间轴描述**: 用时间戳分段描述不同时段的画面变化，如 `0-2 秒：xxx；3-5 秒：xxx` 4. **风格关键词**: 明确视觉风格（写实/动漫/电影感等） 5. **音效描述**: 如果 generate_audio=true，可在提示词中描述音效和配音需求 6. **参考素材说明**: 当有图片/视频参考时，在提示词中指明如何使用这些素材 ## 环境配置 脚本从环境变量读取以下配置： | 环境变量 | 说明 | 默认值 | |---------|------|--------| | `ARK_API_KEY` | 方舟平台 API Key | （必填） | | `ARK_API_URL` | 方舟 API 地址 | `https://ark.cn-beijing.volces.com` | 确保在使用前已设置 `ARK_API_KEY` 环境变量。

path: references/api_reference.md
content: # Seedance 2.0 API 参考文档

## 接口地址

- **提交任务**: `POST {ARK_API_URL}/api/v3/contents/generations/tasks`
- **查询任务**: `GET {ARK_API_URL}/api/v3/contents/generations/tasks/{task_id}`

## 认证方式

```
Authorization: Bearer {API_KEY}
Content-Type: application/json
```

## 提交生成请求 (POST)

### Request Body

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `model` | string | 是 | 模型名称，如 `doubao-seedance-2-0-260128` 或 `doubao-seedance-2-0-fast-260128` |
| `content` | array | 是 | 多模态内容数组，见下方详细说明 |
| `generate_audio` | boolean | 否 | 是否同步生成音频，默认 false |
| `ratio` | string | 否 | 视频比例，支持 `"16:9"` / `"9:16"` / `"1:1"`，默认 `"16:9"` |
| `duration` | integer | 否 | 视频时长（秒），范围 5-11，默认 10 |
| `watermark` | boolean | 否 | 是否添加水印，默认 false |

### content 数组元素类型

#### type: "text" (文本提示词)
```json
{ "type": "text", "text": "视频描述内容" }
```

#### type: "image_url" (参考图片)
```json
{
  "type": "image_url",
  "image_url": { "url": "https://example.com/image.jpg" },
  "role": "reference_image"
}
```

#### type: "video_url" (参考视频)
```json
{
  "type": "video_url",
  "video_url": { "url": "https://example.com/video.mp4" },
  "role": "reference_video"
}
```

#### type: "audio_url" (参考音频)
```json
{
  "type": "audio_url",
  "audio_url": { "url": "https://example.com/audio.mp3" },
  "role": "reference_audio"
}
```

### Response (成功)

```json
{
  "id": "cgt-20260410162021-xxxxx"
}
```

返回任务 ID，用于后续状态查询。

## 查询任务状态 (GET)

### Path Parameters

| 参数 | 类型 | 说明 |
|------|------|------|
| `task_id` | string | 任务 ID |

### Response - 处理中

```json
{
  "id": "cgt-20260410162021-xxxxx",
  "model": "doubao-seedance-2-0-fast-260128",
  "status": "running",       // pending / running
  "created_at": 1775809241,
  "updated_at": 1775809241,
  "service_tier": "default",
  "execution_expires_after": 172800,
  "generate_audio": true,
  "draft": false
}
```

### Response - 完成

```json
{
  "id": "cgt-20260410162021-xxxxx",
  "status": "completed",
  "video_result": {
    "video_url": "https://xxx/output.mp4",
    "cover_image_url": "https://xxx/cover.jpg"
  }
}
```

### Response - 失败

```json
{
  "id": "cgt-20260410162021-xxxxx",
  "status": "failed",
  "error": {
    "code": "error_code",
    "message": "错误描述"
  }
}
```

## 错误码说明

常见错误：
- 认证失败：检查 API Key 是否正确
- 参数校验失败：检查 content 数组格式、duration/ratio 范围
- 资源不可用：检查参考图片/视频/音频 URL 是否公网可访问
- 额度不足：检查账户余额或调用配额


path: references/prompt_guide.md
content: # Seedance 2.0 提示词撰写指南

## 核心原则

好的提示词是高质量视频生成的基础。Seedance 2.0 对自然语言理解能力很强，以下指南帮助写出更有效的提示词。

## 提示词结构建议

### 基础公式
```
[画面风格] + [主体描述] + [动作/运动] + [镜头语言] + [环境/背景] + [音效/配音]
```

### 时间轴分段描述（推荐）

对于有多个场景变化的视频，使用时间戳分段：
```
[整体风格和设定]
0-3 秒：第一个场景的详细描述；
4-6 秒：第二个场景的详细描述，注意转场方式；
7-10 秒：最终场景和结尾。
```

## 各维度详细说明

### 1. 画面风格关键词

| 风格类型 | 关键词 |
|---------|--------|
| 写实风格 | 写实、真实感、电影级画质、4K、超高清 |
| 动漫风格 | 二次元、日系动漫、赛璐珞风格、新海诚风格 |
| 电影感 | 电影质感、胶片感、宽银幕、电影调色 |
| 商业广告 | 广告质感、商业摄影、产品特写、高级感 |
| 艺术风格 | 油画风格、水彩、赛博朋克、复古怀旧 |

### 2. 镜头语言

**运镜方式：**
- 推镜头 / Dolly in：缓慢靠近主体
- 拉镜头 / Dolly out：逐渐远离
- 摇镜头 / Pan：水平移动视角
- 移镜头 / Track：跟随主体移动
- 环绕 / Orbit：围绕主体旋转
- 俯拍 / Top-down：从上往下拍摄
- 仰拍 / Low angle：从下往上拍
- 第一人称 POV：第一人称主观视角

**景别：**
- 特写 / Close-up：聚焦细节
- 中景 / Medium shot：半身或膝盖以上
- 全景 / Wide shot：全身+环境
- 远景 / Long shot：广阔场景

### 3. 运动与动态

- 明确描述物体的运动方向和速度
- 使用动词增强动感："飞舞"、"流淌"、"旋转"、"飘落"
- 描述相机与主体的相对运动
- 注意物理合理性：重力、惯性、碰撞反应

### 4. 光影与色彩

- 光源方向：侧光、逆光、顶光、自然光、柔光
- 时间氛围：黄金时刻、蓝色时刻、夜晚霓虹
- 色调：暖色调、冷色调、高对比度、低饱和度
- 特殊效果：镜头光晕、焦外光斑、体积光

### 5. 音效描述 (generate_audio=true 时)

在提示词中可以描述：
- 背景音乐风格：轻快鼓点、悠扬钢琴、电子节拍
- 环境音效：风声、雨声、脚步声、碰撞声
- 语音/旁白内容：直接用引号标注，如 `背景音：「鲜切现摇」`
- 音色要求：如"女生音色"、"低沉男声"

## 参考素材的使用说明

当提供参考图片/视频/音频时，应在提示词中明确说明：

- **参考图片**: "首帧为图片1"、"尾帧定格为图片2"
- **参考视频**: "全程使用视频1的第一视角构图"、"模仿视频1的运镜方式"
- **参考音频**: "全程使用音频1作为背景音乐"

## 最佳实践

1. **具体优于抽象**: "一只橘猫在阳光下伸懒腰" 比 "可爱的动物" 好 100 倍
2. **长度适中**: 100-500 字通常效果最佳，过短信息不足，过长可能互相矛盾
3. **避免负面描述**: 不要说"不要xxx"，而是正面描述想要什么
4. **物理一致性**: 确保描述的动作符合物理规律
5. **时间合理**: 在 duration 限制内合理安排场景数量（5秒建议1-2个场景，10秒建议2-4个场景）
6. **测试迭代**: 先用 fast 版本快速验证提示词效果，满意后再用标准版生成高质量版本

## 示例提示词

### 果茶宣传广告示例
```
全程使用视频1的第一视角构图，全程使用音频1作为背景音乐。第一人称视角果茶宣传广告，「苹苹安安」苹果果茶限定款；首帧为图片1，你的手摘下一颗带晨露的阿克苏红苹果，轻脆的苹果碰撞声；2-4 秒：快速切镜，你的手将苹果块投入雪克杯，加入冰块与茶底，用力摇晃，冰块碰撞声与摇晃声卡点轻快鼓点，背景音：「鲜切现摇」；4-6 秒：第一人称成品特写，分层果茶倒入透明杯，你的手轻挤奶盖在顶部铺展，镜头拉近看奶盖与果茶的分层纹理；6-8 秒：第一人称手持举杯，将果茶举到镜头前（模拟递到观众面前的视角），背景音「来一口鲜爽」，尾帧定格为图片2。背景声音统一为女生音色。
```

### 风景航拍示例
```
超高清航拍风光短片。清晨的喀纳斯湖面，晨雾缭绕在湖面上方，阳光从云层缝隙中洒下形成耶稣光。0-3 秒：无人机高空俯瞰，湖面如镜倒映着周围雪山，缓缓向前推进；3-6 秒：镜头下降掠过湖面，可以看到清澈见底的湖水和游弋的鱼群；6-9 秒：环绕拍摄岸边的白桦林，金黄的树叶随风飘落；9-10 秒：拉远至全景，展现整个山谷的壮丽景色。自然环境音效，宁静悠远。
```


path: scripts/download_video.py
content: #!/usr/bin/env python3
"""
Seedance 2.0 视频生成 - 下载结果脚本
用法:
  python download_video.py <task_id> [--output-dir ./output]
"""

import argparse
import json
import os
import sys
import urllib.request


def get_config():
    """获取 API 配置"""
    [REDACTED_SECRET]"ARK_API_KEY")
    if not [REDACTED_SECRET]"错误: 请设置环境变量 ARK_API_KEY", file=sys.stderr)
        sys.exit(1)

    base_url = os.environ.get("ARK_API_URL", "https://ark.cn-beijing.volces.com")
    return api_key, base_url


def query_task(api_key, base_url, task_id):
    """查询任务状态和结果"""
    url = f"{base_url}/api/v3/contents/generations/tasks/{task_id}"

    req = urllib.request.Request(
        url,
        headers={"Authorization": f"Bearer {api_key}"},
        method="GET",
    )

    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read().decode("utf-8"))


def download_file(url, output_path):
    """下载文件"""
    print(f"⬇️  下载中: {url}")
    print(f"   保存至: {output_path}")

    urllib.request.urlretrieve(url, output_path)

    size_mb = os.path.getsize(output_path) / (1024 * 1024)
    print(f"✅ 下载完成! 文件大小: {size_mb:.2f} MB")
    return output_path


def main():
    parser = argparse.ArgumentParser(description="下载 Seedance 生成的视频")
    parser.add_argument("task_id", help="任务 ID")
    parser.add_argument("--output-dir", default="./output", help="输出目录 (默认: ./output)")

    args = parser.parse_args()

    api_key, base_url = get_config()

    # 查询任务状态
    print(f"🔍 查询任务: {args.task_id}")
    result = query_task(api_key, base_url, args.task_id)

    status = result.get("status")
    if status != "completed":
        print(f"❌ 任务尚未完成，当前状态: {status}", file=sys.stderr)
        print("   请稍后重试或使用 query_task.py --poll 等待完成")
        sys.exit(1)

    # 创建输出目录
    os.makedirs(args.output_dir, exist_ok=True)

    video_result = result.get("video_result", {})
    downloaded_files = []

    # 下载视频
    video_url = video_result.get("video_url")
    if video_url:
        video_path = os.path.join(args.output_dir, f"{args.task_id}.mp4")
        download_file(video_url, video_path)
        downloaded_files.append(video_path)

    # 下载封面图
    cover_url = video_result.get("cover_image_url")
    if cover_url:
        cover_path = os.path.join(args.output_dir, f"{args.task_id}_cover.jpg")
        download_file(cover_url, cover_path)
        downloaded_files.append(cover_path)

    print()
    print(f"📁 所有文件已保存到: {os.path.abspath(args.output_dir)}")
    for f in downloaded_files:
        print(f"   📄 {f}")


if __name__ == "__main__":
    main()


path: scripts/generate_video.py
content: #!/usr/bin/env python3
"""
Seedance 2.0 视频生成 - 提交任务脚本
用法:
  python generate_video.py --prompt "视频描述" [选项]
"""

import argparse
import json
import os
import sys
import urllib.request


def get_config():
    """获取 API 配置"""
    [REDACTED_SECRET]"ARK_API_KEY")
    if not [REDACTED_SECRET]"错误: 请设置环境变量 ARK_API_KEY", file=sys.stderr)
        sys.exit(1)

    base_url = os.environ.get("ARK_API_URL", "https://ark.cn-beijing.volces.com")
    return api_key, base_url


def build_content(prompt, image_urls, video_urls, audio_urls):
    """构建 content 多模态数组"""
    content = [{"type": "text", "text": prompt}]

    for url in (image_urls or []):
        content.append({
            "type": "image_url",
            "image_url": {"url": url},
            "role": "reference_image"
        })

    for url in (video_urls or []):
        content.append({
            "type": "video_url",
            "video_url": {"url": url},
            "role": "reference_video"
        })

    for url in (audio_urls or []):
        content.append({
            "type": "audio_url",
            "audio_url": {"url": url},
            "role": "reference_audio"
        })

    return content


def submit_task(api_key, base_url, payload):
    """提交视频生成任务"""
    url = f"{base_url}/api/v3/contents/generations/tasks"

    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}",
        },
        method="POST",
    )

    with urllib.request.urlopen(req) as resp:
        result = json.loads(resp.read().decode("utf-8"))
    return result


def main():
    parser = argparse.ArgumentParser(description="Seedance 2.0 视频生成任务提交")
    parser.add_argument("--model", required=True, help="模型名称，如 doubao-seedance-2-0-fast-260128")
    parser.add_argument("--prompt", required=True, help="视频描述提示词")
    parser.add_argument("--ratio", default="16:9", choices=["16:9", "9:16", "1:1"], help="视频比例 (默认: 16:9)")
    parser.add_argument("--duration", type=int, default=10, help="视频时长秒数 5-11 (默认: 10)")
    parser.add_argument("--image-url", action="append", default=[], dest="image_urls", help="参考图片 URL（可多次指定）")
    parser.add_argument("--video-url", action="append", default=[], dest="video_urls", help="参考视频 URL（可多次指定）")
    parser.add_argument("--audio-url", action="append", default=[], dest="audio_urls", help="参考音频 URL（可多次指定）")
    parser.add_argument("--generate-audio", action="store_true", help="同时生成音频")
    parser.add_argument("--watermark", action="store_true", default=True, help="添加水印（默认开启）")
    parser.add_argument("--no-watermark", action="store_false", dest="watermark", help="不添加水印")

    args = parser.parse_args()

    # 参数校验
    if not 5 <= args.duration <= 11:
        print(f"错误: duration 必须在 5-11 之间，当前值: {args.duration}", file=sys.stderr)
        sys.exit(1)

    api_key, base_url = get_config()

    content = build_content(args.prompt, args.image_urls, args.video_urls, args.audio_urls)

    payload = {
        "model": args.model,
        "content": content,
        "generate_audio": args.generate_audio,
        "ratio": args.ratio,
        "duration": args.duration,
        "watermark": args.watermark,
    }

    print(f"🎬 提交 Seedance 视频生成任务...")
    print(f"   模型: {args.model}")
    print(f"   时长: {args.duration}s | 比例: {args.ratio} | 音频: {args.generate_audio} | 水印: {args.watermark}")
    print(f"   参考图: {len(args.image_urls)} 张 | 参考视频: {len(args.video_urls)} 个 | 参考音频: {len(args.audio_urls)} 个")
    print()

    try:
        result = submit_task(api_key, base_url, payload)
        task_id = result.get("id")
        print(f"✅ 任务提交成功！")
        print(f"   Task ID: {task_id}")
        print()
        print(f"查询状态命令:")
        print(f'   python3 "{os.path.abspath(__file__).replace('generate_video.py', 'query_task.py')}" {task_id}')
        return task_id
    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
        print(f"❌ API 错误 ({e.code}): {error_body}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/query_task.py
content: #!/usr/bin/env python3
"""
Seedance 2.0 视频生成 - 任务状态查询脚本
用法:
  python query_task.py <task_id> [--poll] [--interval <秒>]
"""

import argparse
import json
import os
import sys
import time
import urllib.request


def get_config():
    """获取 API 配置"""
    [REDACTED_SECRET]"ARK_API_KEY")
    if not [REDACTED_SECRET]"错误: 请设置环境变量 ARK_API_KEY", file=sys.stderr)
        sys.exit(1)

    base_url = os.environ.get("ARK_API_URL", "https://ark.cn-beijing.volces.com")
    return api_key, base_url


def query_task(api_key, base_url, task_id):
    """查询任务状态"""
    url = f"{base_url}/api/v3/contents/generations/tasks/{task_id}"

    req = urllib.request.Request(
        url,
        headers={
            "Authorization": f"Bearer {api_key}",
        },
        method="GET",
    )

    with urllib.request.urlopen(req) as resp:
        result = json.loads(resp.read().decode("utf-8"))
    return result


def format_status(result):
    """格式化状态输出"""
    status = result.get("status", "unknown")
    task_id = result.get("id", "N/A")
    model = result.get("model", "N/A")

    status_icons = {
        "pending": "⏳",
        "running": "🔄",
        "completed": "✅",
        "failed": "❌",
    }
    icon = status_icons.get(status, "❓")

    output = [f"{icon} 状态: {status.upper()}"]
    output.append(f"   Task ID: {task_id}")
    output.append(f"   模型: {model}")

    if "created_at" in result:
        from datetime import datetime
        created = datetime.fromtimestamp(result["created_at"])
        output.append(f"   创建时间: {created.strftime('%Y-%m-%d %H:%M:%S')}")

    if status == "completed":
        video_result = result.get("video_result", {})
        output.append(f"   🎥 视频: {video_result.get('video_url', 'N/A')}")
        output.append(f"   🖼️  封面: {video_result.get('cover_image_url', 'N/A')}")
    elif status == "failed":
        error = result.get("error", {})
        output.append(f"   错误码: {error.get('code', 'N/A')}")
        output.append(f"   错误信息: {error.get('message', 'N/A')}")

    return "\n".join(output)


def main():
    parser = argparse.ArgumentParser(description="Seedance 2.0 视频任务状态查询")
    parser.add_argument("task_id", help="任务 ID (如 cgt-20260410162021-xxxxx)")
    parser.add_argument("--poll", action="store_true", help="轮询模式，等待直到完成或失败")
    parser.add_argument("--interval", type=int, default=15, help="轮询间隔秒数 (默认: 15)")

    args = parser.parse_args()

    api_key, base_url = get_config()

    try:
        while True:
            result = query_task(api_key, base_url, args.task_id)
            print(format_status(result))
            print()

            status = result.get("status")

            if status in ("completed", "failed"):
                break

            if not args.poll:
                print(f"💡 使用 --poll 参数可持续轮询直到完成")
                break

            print(f"⏱️  {args.interval} 秒后再次查询...")
            time.sleep(args.interval)

    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
        print(f"❌ API 错误 ({e.code}): {error_body}", file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print("\n⚠️ 查询已中断")


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)