---
name: video-gen
description: "帮商家用 AI 生成产品视频（社媒投放 / PDP / 复刻爆款）。触发场景：用户说\"给这个产品做条 TikTok 视频/做条小红书种草视频/复刻这条爆款/做条 PDP 展示视频/这产品配一段视频\"——通常附产品图或参考视频。范围：所有\"画面以产品为主体\"的视频。不含\"画面里有真人讲话\"的场景（例：\"用张医生介绍产品\"是数字人意图，不在范围）。"
version: 1.1.0
owner_repo: Optima-Chat/optima-gen
---

# Video Generation Skill

帮电商商家用 AI 视频生成替代传统实拍。核心价值：**帮用户形成一份满意的 Storyboard，然后按 Storyboard 执行生成。**

## Global Rules

优先级高于任何 pipeline 步骤。违反会导致泄漏、超支或数据丢失。

1. **User-facing 不出现模型名或服务名**
   status / 成本 / 进度 / 错误 / 总结 统一用"视频生成中"。模型名仅用于 skill 内部路由，**代码注释里的也不能抄给用户**。

2. **花钱前必过确认卡**
   任何 `gen video` / `gen image` 调用之前必过一次确认卡（见 `references/confirm-card.md`）。**Fast-path、迭代、重试均无例外**（重新 gate 边界见 §执行生成）。**成本由后端 billing 按实际时长扣费——skill 不自行估价、不编单价**；确认卡只列**计费口径**（段数 / 总秒数 / 画质），并标明"实际扣费以 billing 为准、生成后告知"。〔正解：待 billing 报价接口落地后，确认卡调它显示生成前真价；在此之前不显示任何 skill 自算的金额。〕

3. **任何 pipeline 第一步必须 Per-video init**
   分析 / Storyboard / Plan / 生成 / Review 执行前先跑 §Per-video init（建目录 + cd）。否则后续 write 写错位置。迭代场景用 `{旧id}-vN`。

4. **Anti-fabrication**
   未在本 skill 显式列出的命令 / flag / 参数，**不允许凭印象拼**。要么走 §相关工具 里的精确命令，要么 ask user。ffmpeg / gen / scout 子命令同样适用。

5. **git 只前进，不 reset**
   每步完成立刻 commit。迭代用 `-vN` 目录不覆盖。**禁止** `git reset --hard` / `git checkout .` / `git clean -f`。

## 工作目录

所有状态都在 `~/video-gen/`，整个目录是一个 git repo。

```
~/video-gen/
├── preferences.md
├── history.md
└── videos/{video-id}/
    ├── analysis.md
    ├── storyboard.md
    ├── production-plan.md
    ├── quality-report.md
    ├── assets/
    │   ├── product.jpg
    │   ├── reference.mp4
    │   └── keyframes/
    ├── prompts/
    │   └── seg-N.txt
    ├── clips/
    │   ├── seg-N.pending.json
    │   ├── seg-N.meta.json
    │   └── seg-N.mp4
    └── final.mp4
```

## 每次 session 的启动流程

按顺序执行：

1. **检查工作目录**：`ls ~/video-gen/preferences.md`
   - 不存在 → 走「First-time init」
   - 存在 → 继续步骤 2
2. **扫描未完成任务**：找 `~/video-gen/videos/` 下无 `quality-report.md` 且创建 < 7 天的目录
   - 有 → 在回复用户前主动提一次："你有 N 个视频没完成..."
   - 无 → 继续步骤 3
3. **读 preferences.md** 和 **history.md**
4. **按用户意图分流**（**任何路径的第一步都是 Per-video init，见下文专段**）：
   - 有视频 + 有产品图 → Fast-path：直接走「分析 → Storyboard → Production Plan → 生成」全管道
   - 有视频，无产品图 → 问"换成哪个产品？"
   - **只有产品图（社媒目标）** → **爆款驱动默认流程**：
     1. agent 从产品图 + preferences 推断**品类 + 平台 + 关键词**
     2. 走「参考发现」（见下段）：自动跑 scout 取 Top 3 爆款
     3. 展示候选 → 用户选一条 → 进「分析源视频」→ 复刻 storyboard
     4. 用户说**"跳过"/"不要参考"** → 从模板构建 storyboard（opt-out 必须显式）
   - **只有产品图（PDP 目标）** → 从 PDP 模板构建 Storyboard（PDP 场景**不走爆款**，因为 PDP 视频不是社交叙事）
   - **用户意图模糊**（没说社媒 vs PDP）→ 问**一个**关键问题确认 → 再分流
   - "再试 / 更活泼 / 改 X" → 走「迭代」

**为什么社媒默认走爆款驱动**：单靠模板 + 产品图生成的视频是"平均水平"——爆款的节奏 / Hook / 卡点来自具体成功案例，不来自抽象模板。agent 主动拉爆款 → 分析 → 复刻，是把 video-gen 从"出得来"升级到"出得好"。用户想要纯模板产出 → 一句"跳过"即可，opt-out 成本为 0。

**Fast-path 触发条件**（必须全部满足才走 fast-path，否则先问）：

| 场景 | 必须有 | 可从 preferences 兜底 |
|---|---|---|
| **复刻（用户自带参考）** | 视频文件/链接 + 产品图 | 调性、平台 |
| **生成（社媒）** | 产品图 + 明确意图动词 + 平台 | 调性；**品类关键词从产品图 + preferences 推**，不够时追问 1 次 |
| **生成（PDP）** | 产品图 + 明确说"详情页"/"PDP" | 调性 |
| **迭代** | 明确的 delta（"更活泼"/"暖一点"）+ history 无歧义匹配 | — |

**社媒 Fast-path 的"爆款发现"不算"问用户"**——agent 自动拉 Top 3 + 展示让用户选 1 条是**必要数据输入**（和"选模板"同级），不是"审批"。用户选条或"跳过"都保持在 Fast-path 内。

**Fast-path 行为**：原创 Storyboard 展示后告知而非审批；**复刻例外——必过确认卡**（`references/confirm-card.md`）。两种情况**生成前都走确认卡**，确认卡是唯一付费确认点（实扣以 billing 为准）。用户可随时喊停。

## First-time init

首次使用时执行：

```bash
mkdir -p ~/video-gen/videos
cd ~/video-gen
git init -b main
```

创建 `~/video-gen/preferences.md`：

```markdown
# Preferences

## Brand
name:
palette:
tone:
forbidden:

## Defaults
platform:
aspect_ratio:
duration_seconds:

## Learned
```

创建 `~/video-gen/history.md`：

```markdown
# History

| date | video-id | product | purpose | template | storyboard-summary | parent | status | liked |
|---|---|---|---|---|---|---|---|---|
```

创建 `.gitignore`：

```
videos/**/assets/reference.mp4
videos/**/assets/*.mov
videos/**/clips/*.mp4
videos/**/clips/*.mov
videos/**/final.mp4
videos/**/final.mov
```

```bash
git add -A && git commit -m "init: video-gen workspace"
```

**Minimal init**：不要在首次使用时做问卷。只建骨架文件，preferences 留空。

如果用户的首条消息已经包含了足够的信息（产品图 + 意图），**直接进管道**，在过程中自然补充 preferences：
- 用户说了"TikTok" → 默默填 Defaults.platform = TikTok
- 用户说了"warm 风格" → 默默填 Brand.tone = warm
- 都没说 → 从模板默认值兜底，不打断流程去问

只有当管道完全无法推进时（例如连产品图都没有），才问**一个**关键问题。

每次有新信息写入 preferences → `git commit -m "prefs: update <field>"`

## 参考发现（社媒默认触发 · 用户可跳过）

**触发**：按上方「分流」的第 3 条（社媒目标 · 无用户自带参考视频）自动进入。

**不触发**：用户自带参考视频（走"分析源视频"）/ PDP 目标 / 用户明确说"跳过"。

### 步骤

1. **推断品类 + 关键词**（不打断用户）
   - 从产品图（文件名、用户文字、先前 preferences）推断，例："懒人拖把" → 品类=家居清洁 / 关键词="拖把"
   - preferences 有 `default_platform` 用 preferences；无则按"TikTok（海外）/ 抖音（国内）"默认，等用户纠正
   - 关键词低置信（产品图模糊或全新品类）→ 追问 1 次 `"产品属于哪个品类？（如美妆/家居/数码）"`，否则不打断
2. **调 scout**
   - 平台=TikTok → `scout tiktok search-videos "<关键词>" --region <US|...>`
   - 平台=抖音 → `scout douyin search-videos "<关键词>"` 或 `scout douyin hot-search`（按品类筛）
   - 其他平台：按 `scout --help` 列出的可用命令对应
3. **过滤 + 排序**：品牌契合（不违反 preferences.forbidden）+ 产品形态匹配 + 按 likes 降序取 Top 3
4. **展示 3 个候选**：平台 / 时间 / 指标（点赞 · 分享 · 评论）/ 风格摘要（hook 类型 · 时长 · BGM 类型）
5. **用户选一条** → 写入 `assets/reference.mp4` 或保留 URL → 继续「分析源视频」
6. **用户说"跳过"/"都不要"/"不要参考"** → 记录到 preferences.Learned（"此类任务偏好无参考"）→ 从模板构建 Storyboard

### 硬规则

- **绝不自动选参考**——Top 3 必须让用户选 1 条（或明说跳过）
- **"跳过"必须是一键**，不问理由（用户说过就记 preferences，未来同类不再默认拉爆款）
- **scout 调用成本算进 COST-GATE**——参考发现本身消耗 Optima credits（scout API 不免费）

## Per-video init

**触发条件**：走任何管道（分析 / Storyboard / Production Plan / 生成）前**必须**先执行此段。否则后续 write 操作会失败或写到错位置。

**每次新 video session 第一步**：

```bash
# <slug> = 由 agent 从产品名 / 用户意图提取的 kebab-case 短语（前 20 字符内）
# 例："懒人拖把" → "lazy-mop"；"免洗洗手液" → "no-rinse-sanitizer"
VIDEO_ID="$(date +%Y%m%d-%H%M)-<slug>"
mkdir -p ~/video-gen/videos/$VIDEO_ID/{assets/keyframes,prompts,clips}
cd ~/video-gen/videos/$VIDEO_ID
echo "Using VIDEO_ID=$VIDEO_ID"
```

**迭代场景**（用户说"再试" / "改 X"）：保留旧版本、写入新目录。用自动递增版本号防覆盖：

```bash
BASE_ID="<旧 video-id>"   # 例如 20260422-1430-product-demo
n=2
while [ -d ~/video-gen/videos/${BASE_ID}-v${n} ]; do n=$((n+1)); done
VIDEO_ID="${BASE_ID}-v${n}"
mkdir -p ~/video-gen/videos/$VIDEO_ID/{assets/keyframes,prompts,clips}
cd ~/video-gen/videos/$VIDEO_ID
echo "Using VIDEO_ID=$VIDEO_ID"
```

**场景路由**：

| 用户输入 | 触发 Per-video init 的路径 |
|---|---|
| 产品图 + 意图（无参考视频） | 进 Storyboard 前先跑 Per-video init |
| 产品图 + 参考视频 | 进「分析源视频」前先跑 Per-video init |
| 迭代（沿用旧 video） | 建 `{旧id}-vN` 目录（N 自动递增）+ cd |

## 分析源视频

**前置**：已完成 Per-video init。

有源视频时执行。产出 `videos/{id}/analysis.md`。

1. 保存素材到 `assets/`

2. ffmpeg 场景检测：

```bash
ffprobe -v quiet -print_format json -show_format -show_streams assets/reference.mp4
ffmpeg -i assets/reference.mp4 -vf "select='gt(scene,0.3)',showinfo" -vsync 0 -f null - 2>&1 | grep showinfo
```

3. 每段抽 3 帧（首/中/尾），再**拼成一张 contact sheet**（一次看全片，别逐帧）：

```bash
# 每段抽 3 帧
ffmpeg -i assets/reference.mp4 -vf "select='eq(n\,<FRAME_NUM>)'" -vsync 0 assets/keyframes/seg-<N>-<pos>.jpg
# 拼成一张网格图（按文件名排序，每行≈一段的首/中/尾）；<R>=段数
ffmpeg -y -pattern_type glob -i 'assets/keyframes/seg-*.jpg' -filter_complex "scale=360:-1,tile=3x<R>" -frames:v 1 assets/contact-sheet.jpg
```

4. **读这一张 `contact-sheet.jpg`——1 次 vision 调用看全片**，**按 `$CLAUDE_SKILL_DIR/references/cinematic-language.md` §9 导演拆镜 Checklist 逐镜拆**：景别 → 机位 → 运镜（起幅/落幅）→ 焦段/景深 → 光线（光向/光质/色温）→ 构图 → 动作弧 → beat → 与下一镜怎么接。**别再每帧单独调 vision——逐帧调十几次是分析慢的主因，一张 contact sheet 一次看完。** 用电影语言拆（「85mm 长焦浅景深、缓慢 dolly in、柔和侧逆光」不是「画面里有个杯子」）——**参考片好看的原因就藏在这些参数里，读不出来就复刻不出来**。**动作弧**（从头到尾发生了什么、主体怎么从 A 变到 B、镜头怎么运动）是写 prompt 第 ② 段的直接来源，漏了复刻必走样（见 `references/prompt-craft.md`）。

5. 验证 ffmpeg 切段合理性（修正软切/漏切）

6. 以表格写入 `analysis.md`：

```markdown
# Analysis

## 元数据
| 项目 | 值 |
|---|---|
| 总时长 | 7.0s |
| 比例 | 9:16 |
| FPS | 30 |
| 片段数 | 4 |

## 镜头分析

| 段 | 时长 | 主体 | 景别 | 机位 | 运镜 | 镜头/景深 | 光线 | 构图 | 动作弧 | beat |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0-1.5s | 产品+手 | ECU 大特写 | 平视 | 缓慢 dolly in | 85mm 浅景深 | 暖白柔和侧逆光 | 产品居右三分 | 手伸入握起产品→翻转露 logo | hook |
| ... |

## 整体风格
| 项目 | 值 |
|---|---|
| Pacing | slow |
| Mood | aspirational, minimalist |
| Hook | subtle visual intrigue |
| CTA | logo reveal |
```

8. `git commit -m "video: analyze $VIDEO_ID — N segments, Xs"`

## 形成 Storyboard

**前置**：已完成 Per-video init。

核心产出。**用户语言写，用户能读能改。**

1. 读输入：preferences + analysis（如有）+ history 匹配 liked 记录 + 匹配模板（**先读 `$CLAUDE_SKILL_DIR/templates/INDEX.md` 路由决定选哪个，再读选中的那一份**，不要把所有模板都读一遍）

2. 有模板则以模板为骨架填入产品/品牌信息；否则从零构建

3. **复刻铁律（默认且唯一的复刻行为，违反就是没复刻）**：**严格按源视频 1:1 复现——镜头数、每镜时长、景别 / 机位 / 运镜、切点、剪辑节奏、整体调性、场景全部照搬源视频，唯一变量是把产品换成用户给定的。** ❌ 禁止压缩 / 合并段数（技术合并见下方区分）、❌ 禁止改场景 / 调性、❌ 禁止自定节奏快慢、❌ 禁止把源片的动态镜拍成静态摆拍。爆款值得复刻，就因为它的分镜 + 节奏已被市场验证能火——动了它就等于扔掉复刻的全部意义（客户实测：agent 擅自把 5 段压成 4 段、把"治愈系"改成"学院风"、自定 4×4.5s 慢静态镜 → 又慢又走样，正是反面教材）。**节奏不归你定，照搬源片；源片快你就快，源片慢你就慢。**
   - **物理合理性预检**：只有当用户产品**物理上做不出**源片某镜的动作时（如源片捏软杯、用户是硬陶瓷杯），才对**那一镜**做**最小替换**（换成该产品能做的最接近动作）+ **明确告诉用户这一镜改了什么、为什么**——不是默默重设计，更不是借机重排全片。其余镜头一律照搬。

4. 写 `videos/{id}/storyboard.md`：

```markdown
# Storyboard

## 基本信息
| 项目 | 值 |
|---|---|
| 产品 | <名称> |
| 平台 | <TikTok / IG / PDP> |
| 时长目标 | <Ns> |
| 调性 | <调性> |
| 参考来源 | <来源或"从模板构建"> |

## 镜头表（导演级分镜——别写成"画面 + 文案"的种草脚本）
| 段 | 时长 | 景别 | 机位 | 运镜 | 镜头/景深 | 主体动作弧/调度 | 光线/影调 | 切点→下一镜 | beat/卡点 | 替换说明 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0-1.5s | 大特写 ECU | 平视 | 手持微晃 | 85mm 长焦浅景深、产品在焦背景虚化 | 手从画面下方入框握起产品、抬至胸前缓慢翻转 90° 露正面 logo | 晨光柔和侧逆、暖白、中高光比 | 翻转到位的动作落点硬切（cut on action） | hook：第 1 拍就给产品高光 | 产品换成 <X> |
| ... |

> **写法要求（这一步决定成片"像不像导演拍的"）——术语和写法全部查 `$CLAUDE_SKILL_DIR/references/cinematic-language.md`**：
> - **景别**用电影语言（ECU 大特写 / CU 特写 / MCU 中近 / MS 中 / MLS 中全 / WS 全 / EWS 远），不是"画面里有个杯子"。
> - **机位**写明（平视 / 俯 / 仰 / 顶视 / 斜角 / 过肩 / 主观 POV）——角度即叙事：产品仰拍=高级、顶视=平铺展示。
> - **运镜**具体到一种（固定 / 推 dolly in / 拉 / 摇 / 横移 / 跟 / 手持 / 环绕 arc），别空着也别堆叠；**分清 dolly（机身移、透视变）和 zoom（变焦、透视不变）**。
> - **镜头/景深**写焦段 + 景深（产品特写＝长焦浅景深隔离主体）——这才是「干净画面」的专业说法。
> - **主体动作弧/调度**＝这一拍主体怎么从 A 动到 B、进出画、与产品怎么互动（导演分镜的核心，不是静态画面描述）。
> - **光线/影调**写光向 + 光质 + 色温 + 光比（"窗户来的柔和侧逆光、暖白、半脸入暗"），不是只写"暖光"。
> - **切点→下一镜**写清在哪个动作点切、怎么接（动作剪辑 cut on action / 匹配剪辑 / 叠化），并守 continuity（视线匹配、不越轴、运动方向一致，见 cinematic-language §7）。
> - **beat/卡点**＝这一拍的叙事作用（hook / 痛点 / 卖点 / 转折 / CTA），对齐 BGM 卡点。
> - **复刻时按 cinematic-language §9 的 9 项把参考片每镜拆出来照搬**——它的景别 / 机位 / 运镜 / 光线 / 卡点节奏就是它好看的原因。

> **段时长约束**：**复刻时——分段 = 源视频的分段，逐镜照搬段数和每镜时长，不增、不减、不压缩。** 只有原创（无参考）时才按叙事节奏自由分段（推荐 2-15s）。
> **⚠️ 技术合并 ≠ 创意压缩（最容易搞混，上次就栽这）**：Seedance 单段最短 4s，所以源片里 <4s 的短镜要「生成一段 4-15s 的长片 → 用 ffmpeg trim 切回源片那一镜的精确时长」。这**纯粹是为凑模型最短时长的技术手段，trim 后成片的镜头数 / 每镜时长 / 切点节奏必须和源片逐帧一致**。**绝不允许把「为生成而合并」当成「把 N 段叙事压成更少段」的创意重设计**——前者改不了成片节奏，后者直接毁掉复刻。单段 >15s 必须拆，**拆出的连续片段必须链式衔接**（后段首帧 = 前段末帧，见 Production Plan「依赖列」），否则不连续。段时长精度 0.5s。

## Hook
<第 1 秒如何抓注意力>

## CTA
<末尾留什么>

## 物理合理性预检
<复刻时：替换是否合理。无参考时写 N/A>
```

5. 给用户**预览 storyboard**（逐镜脚本 + 编辑后首帧），先改大方向（段落 / 产品替换 / 节奏）。
   - ⚠️ **这只是预览，不是「确认卡」**。真正的确认卡在下一步：等 Production Plan 把**真正喂模型的 `call-N.txt` prompt** 写出来后，让用户确认 / 改**那份 prompt**（见「产出 Production Plan」§3）。因为 storyboard → prompt 这步会丢内容（客户 P1「分析对了、提示词只剩一只手」），所以**必须让用户确认最终 prompt，而不是大纲**。
   - 原创 Fast-path：可跳过本预览，直接进 plan + prompt + 确认卡。

6. 预览 / 调整后 `git commit -m "video: storyboard $VIDEO_ID — <summary>"`（硬确认在 §3 prompt 那步）

## 产出 Production Plan

**前置**：已完成 Per-video init + Storyboard。

据 storyboard 推导 plan + 写出每个 Call 的真实 prompt，**然后过确认卡（§3）让用户确认 / 改这份 prompt**——确认的、改的，就是真正喂模型的那份。

### 0. 选策略

**默认视频生成路径**支持文生 / 图生视频。**默认成片自带模型生成的音轨**（audio 默认开）；若要叠 BGM / 旁白，先用 ffmpeg 去掉原音轨再叠，避免两条音叠加。**不要手动指定 `--provider`**，让后端自动路由到当前最优 provider。

| 特征 | 策略 |
|---|---|
| 1 镜头，无参考 | single-shot |
| 多镜头，无参考 | multi-shot-concat |
| **有参考（复刻爆款）** | **clone-faithful —— 1:1 照搬源视频、只换产品（见「形成 Storyboard」§3 复刻铁律）。没有"大致复刻 / 高度还原"之分：复刻就是忠实复现，不存在"松"的档** |
| PDP 多卖点 | pdp-feature-concat |

> **回退选项**（仅在默认路径不可用或有特殊需求时显式指定）：
> - `--provider dashscope` — 需要自动配音 / 负向提示词，仅支持图生视频，时长 5\|10s
>
> 其他情况一律不传 `--provider`，由后端选默认值。

### 1. 写 `videos/{id}/production-plan.md`

默认 video 路径支持 **4-15 秒**整段生成。Storyboard 的细粒度段在这里**合并为 gen video 调用单元——纯技术操作，目的只是凑够模型 4s 最短时长，不改变成片的分镜结构**：

- 相邻短段（同一连续镜头 / 场景）合并成一个 4-15s 的 gen video 调用去生成
- 该调用的 prompt 覆盖所有子段的内容（含每个子段各自的动作弧）
- **生成后用 ffmpeg trim 把每一镜切回它在源片里的精确时长** —— **复刻时 trim 后的成片镜头数 / 每镜时长 / 切点必须和源片逐帧一致**
- 单独一个 ≥4s 的 storyboard 段直接映射为一个 gen video 调用
- **⚠️ 合并只为生成、trim 后必须还原源片节奏；绝不允许借合并把源片的镜头数压少（那是创意重设计，违反复刻铁律）**
- **⚠️ 同一个连续镜头 >15s（Seedance 单次上限）必须拆成多个 Call 时：拆出的相邻 Call **必须链式衔接**——后一个 Call 依赖列填「前 Call 末帧」，首帧 = 前 Call 末帧（`ffmpeg -sseof -0.1` 抽）。否则一段连续动作被切成独立并行生成 → 画面不连续（客户实测「25s 拆 15+10 看起来不是连续的」就是漏了这步）。判定：「按长度拆的同一镜头」必链；「本就不同的镜头/场景」才可并行。**

```markdown
# Production Plan

## 策略
<策略名>
原因: <选择理由>

## 生成调用
| Call | 合并段 | 依赖 | gen_duration | seed | aspect_ratio | Prompt 摘要 |
|---|---|---|---|---|---|---|
| 1 | seg 1-3 (共 8s) | - | 8 | <seed 或 `-`> | <16:9 \| 9:16 \| 1:1> | <合并 prompt> |
| 2 | seg 4 (12s) | - | 12 | $PRODUCT_SEED | <同上> | <prompt> |
| 3 | seg 5 (10s) | Call 2 末帧 | 10 | $PRODUCT_SEED | <同上> | <承接 Call 2 末帧> |
| ... |

**依赖列怎么填**：
- `-`：跟其它 Call 无依赖，可并行提交。**仅用于本就独立的镜头/场景。**
- `Call N`：本 Call 的首帧来自 Call N 的末帧（链式衔接），或输入图来自前面某个 `gen image` 产出，必须等前置完成。
- **判定规则**：**同一个连续镜头被「按长度」拆开的相邻片段 → 必须 `Call N` 链式（保连续）**；不同镜头/场景之间 → 用 `-` 并行（省时间）。**别为了并行把一段连续动作拆成无依赖——那正是长视频不连贯的根因（客户 P2）。**

## Trim 映射
| seg | 来源 Call | trim 区间 | 输出 |
|---|---|---|---|
| 1 | Call 1 | 0:00-2.5s | clips/seg-1.mp4 |
| 2 | Call 1 | 2.5-5.5s | clips/seg-2.mp4 |
| 3 | Call 1 | 5.5-8.0s | clips/seg-3.mp4 |
| 4 | Call 2 | 0:00-12.0s | clips/seg-4.mp4 |

**seed 规则**：默认路径（Seedance）**不透传 seed**，产品一致性靠「同一张产品首帧图 + 末帧链式承接」，不靠 seed。seed 列一律填 `-`（除非用户显式切到支持 seed 的 provider）。

## 验收标准
- 比例: <从 Storyboard>
- 时长: <从 Storyboard ±20%>
- Hook: <从 Storyboard>
- CTA: <从 Storyboard>
```

### 2. 为每个需 prompt 的步骤生成 `prompts/call-N.txt`

**照 `$CLAUDE_SKILL_DIR/references/prompt-craft.md` 的五段式结构写**（开场构图 → 动作弧 → 运镜 → 光线 → 结束状态；80–220 词；不写时长 / 比例 / 声音）。这是复刻成败的命门——默认路径把这段文本**原样**喂给模型，中间没有任何改写兜底，prompt 糙 = 成片糙。复刻镜头**忠于参考的动作与运镜**，不自补创意。

### 3. 确认卡 ——「确认的、改的，就是真正喂模型的那份」（见 `references/confirm-card.md`）

确认卡**逐 Call 把每个 `call-N.txt` 的全文逐字贴进对话（每段一个代码块）+ 编辑后首帧 + 计费口径**（不是 storyboard 大纲、不是「镜头 / 内容」摘要表）。

> ❌ **禁止用「摘要表 + 📄 call-N.txt 文件链接」代替贴全文。** 用户不会去开文件——看不到真实 prompt 就等于没确认（客户实测：agent 只摆了一张摘要表 + 几个文件链接就「确认通过」开跑，用户根本没看到、没确认到真正喂模型的 prompt）。**必须把全文贴出来、明确停下等用户改 / 确认，确认前绝不调 `gen video`。**

用户可逐条改：

- 用户说「第 N 段改成 X / 这镜运镜不对 / 加个特写」→ **直接覆写对应的 `prompts/call-N.txt`**（改的就是喂模型的），改完把更新后的全文再贴回卡上。
- 确认通过 → 写 `.confirmed`，并**把当前 prompt 的校验和绑进去**（保证生成用的就是用户确认的这版，没被偷偷改写）：

```bash
sha256sum prompts/call-*.txt > .confirmed        # 绑定：确认的 = 这份 prompt
echo "user said: <把用户确认的原话填进来>" >> .confirmed
```

- 用户「太贵 / 换个便宜的」→ 回 §0 换策略，重写 prompt 再出卡。**确认前绝不调 `gen video`。**

### 4. `git commit -m "video: plan $VIDEO_ID — <strategy>"`

## 执行生成

**前置**：已完成 Per-video init + Storyboard + Production Plan。

<COST-GATE>
**生成前必须已过确认卡**（在 Production Plan §3 完成：用户确认了**真实 `call-N.txt` prompt** + 计费口径，`.confirmed` 已写并绑定 prompt 校验和）。**成本由后端 billing 实扣，确认卡不自算金额**——只列计费口径（段数 / 总秒数 / 画质）+ 脚注「重生/改图另计费、实扣以 billing 为准」。

**确认后禁止再改写 / 重推 prompt**——生成只读用户确认的那份 `call-N.txt` 原文。要改就先 `rm .confirmed` 回 §3 重新确认。

**重新 gate 边界（D-E）**：已确认集内替换/重生 → 不重出卡（**有意放松**：集内重生独立计费、会产生额外扣费，实扣以 billing 为准）；方案改/净新增/画质升级/新一轮 N → 先 `rm .confirmed` 再重出卡。
</COST-GATE>

按 production-plan.md 步骤表执行，**默认并行提交，不要写成顺序循环**。

### 并行执行规则

`gen video` 在 bash 层是阻塞调用（内部 submitAndPoll），单条命令耗时主要花在远端排队 + 渲染上。所以：

- **同一波内（依赖列都是 `-` 的 Call）必须用后台 `&` 并行提交，最后 `wait`** — wall time 从 N×T 降到 ~T
- **有依赖的 Call** 放下一波，等前置 `wait` 完再启动
- **每个 Call 的 pending.json 写入 + commit 仍在该 Call 自己的 subshell 里做**，不要等全部完成才一次性写

典型 3 Call 全独立的写法：

```bash
# 机械 guard：未过确认卡 / prompt 跟确认的不是同一版 → 拒绝付费生成（见 references/confirm-card.md）
[ -f .confirmed ] || { echo "ERR: 未过确认卡，拒绝付费生成"; exit 1; }
grep '\.txt$' .confirmed | sha256sum -c --status || { echo "ERR: prompt 跟用户确认的不是同一版（被改写过）→ 回 §3 重新确认"; exit 1; }
# 一波并发：3 个独立 gen video 同时提交
(
  echo '{"task_id":"","submitted_at":"'$(date -Iseconds)'","step":1,"tool":"gen video"}' > clips/seg-1.pending.json
  gen video assets/keyframes/seg-1.jpg --prompt "$(cat prompts/call-1.txt)" --duration 8 --aspect-ratio 9:16 --resolution 720p --seed $PRODUCT_SEED -o clips/call-1.mp4 \
    && rm clips/seg-1.pending.json
) &
(
  echo '{"task_id":"","submitted_at":"'$(date -Iseconds)'","step":2,"tool":"gen video"}' > clips/seg-2.pending.json
  gen video assets/keyframes/seg-2.jpg --prompt "$(cat prompts/call-2.txt)" --duration 12 --aspect-ratio 9:16 --resolution 720p --seed $PRODUCT_SEED -o clips/call-2.mp4 \
    && rm clips/seg-2.pending.json
) &
(
  echo '{"task_id":"","submitted_at":"'$(date -Iseconds)'","step":3,"tool":"gen video"}' > clips/seg-3.pending.json
  gen video --prompt "$(cat prompts/call-3.txt)" --duration 10 --aspect-ratio 9:16 --resolution 720p -o clips/call-3.mp4 \
    && rm clips/seg-3.pending.json
) &
wait

# 波次失败传播 — 没这段 wait 永远返回 0，LLM 会把"并行 + 静默失败"传到 Trim
# 每个 Call 成功才 rm 自己的 pending.json；任何残留 = 该 Call 失败
pending=$(ls clips/seg-*.pending.json 2>/dev/null)
[ -z "$pending" ] || { echo "ERR: 波次有失败: $pending"; exit 1; }
```

依赖关系的写法（Call 3 依赖 Call 2 的末帧）：

```bash
# 机械 guard：未过确认卡 / prompt 跟确认的不是同一版 → 拒绝付费生成（见 references/confirm-card.md）
[ -f .confirmed ] || { echo "ERR: 未过确认卡，拒绝付费生成"; exit 1; }
grep '\.txt$' .confirmed | sha256sum -c --status || { echo "ERR: prompt 跟用户确认的不是同一版（被改写过）→ 回 §3 重新确认"; exit 1; }
# Wave 1: Call 1 / 2 独立并行（pending.json 写入 + && rm 模板见上面 3-Call 例子）
( ... gen video ... -o clips/call-1.mp4 && rm clips/seg-1.pending.json ) &
( ... gen video ... -o clips/call-2.mp4 && rm clips/seg-2.pending.json ) &
wait

# Wave 1 失败传播（同上）
pending=$(ls clips/seg-*.pending.json 2>/dev/null)
[ -z "$pending" ] || { echo "ERR: Wave 1 有失败: $pending"; exit 1; }

# Wave 1 完了再抽 Call 2 末帧 → Wave 2
# 即使 wait 上方扫过 pending.json，这里仍要 `[ -f ]` 守一道：依赖 Call 输出文件存在性
[ -f clips/call-2.mp4 ] || { echo "ERR: clips/call-2.mp4 不存在，Wave 2 中止"; exit 1; }
ffmpeg -sseof -0.1 -i clips/call-2.mp4 -frames:v 1 assets/keyframes/seg-3.jpg
gen video assets/keyframes/seg-3.jpg --prompt "$(cat prompts/call-3.txt)" -o clips/call-3.mp4
```

**不要把多个 `gen video` 写成 sequential 的 for 循环 / 多条独立命令**——LLM 默认顺序写 bash，要明确按上面的 `( … ) &` + `wait` 结构组装。

**每个 `wait` 之后必须扫 `clips/seg-*.pending.json` 残留**——光 `wait` 不暴露子 shell 失败（无参数 wait 返回 0 或最后 reap 的状态），跟 skill 后面"失败：自动重试 1 次 → 仍失败标记 needs_user_review"机制对齐。残留 = 该 Call 未走完 `&& rm`，必须停下来处理。

**每步执行前**写 `clips/seg-N.pending.json`：

```json
{
  "task_id": "",
  "submitted_at": "<ISO>",
  "step": 1,
  "tool": "<gen video | gen image | ffmpeg>"
}
```

**工具调用**：

```bash
# 图生视频（默认路径，--duration 支持 4-15 秒）
gen video <input_image> \
  --prompt "$(cat prompts/call-N.txt)" \
  --duration <4-15> \
  --aspect-ratio <16:9|9:16|1:1> \
  --resolution 720p \
  --seed $PRODUCT_SEED \
  -o clips/call-<N>.mp4

# 纯文生视频（无输入图像）
gen video \
  --prompt "$(cat prompts/call-N.txt)" \
  --duration <4-15> \
  --aspect-ratio <16:9|9:16|1:1> \
  --resolution 720p \
  -o clips/call-<N>.mp4

# 注意：不要传 --provider，让后端选默认路径（kie / Seedance 2 Fast）。需要 dashscope / heygen 特殊功能时再显式指定。
# ⚠️ 默认 kie 路径**必须显式传 `--resolution`（全小写），社媒默认 `720p`**：CLI 默认是 1080P，而 kie 只收 480p/720p →
#    不传会被后端拒、gen video 秒退失败（这正是 stage 复刻卡住的原因之一）。
#    **画质默认 720p**（496×864 那种 480p 出片太糊，社媒不能用）；用户明确要省成本才降 480p。

# ffmpeg trim — 按 Trim 映射表切回 storyboard 各段
ffmpeg -i clips/call-1.mp4 -ss 0 -t 2.5 -c copy clips/seg-1.mp4
ffmpeg -i clips/call-1.mp4 -ss 2.5 -t 3.0 -c copy clips/seg-2.mp4

# 变量约定（production-plan.md 每个 call 提供）：
#   $PRODUCT_SEED  = seed 值（仅对显式指定、支持 seed 的 provider 生效；默认 Seedance 不透传）

# 注意：默认路径（Seedance）当前不透传 seed，--seed 等于空操作。
# 产品一致性不能靠 seed，要靠：同一张产品首帧图 + 末帧链式承接（后一镜用前一镜末帧）。

# 首帧编辑（产品替换）— 双图模式
# ⚠️ "<编辑指令>" 是【紧凑的图像编辑指令】（1-2 句：把 X 换成产品 Y，保持构图 / 光线 / 角度），
#    **绝不要套 prompt-craft 的视频五段式**（那是给 gen video 的）。长的多段结构化 prompt 会让 gpt-image-2 报
#    UPSTREAM_UNKNOWN 失败（stage 复刻首帧全挂就是这原因）。
# ⚠️ **物理合理（复刻铁律的一环）**：新产品必须占据源产品在画面里的**同一位置 / 同一尺寸 / 同样接触关系**
#    （在盒里就在盒里、被手握住就被握住），**不能悬空、不能比容器还大、不能尺寸失真**
#    （客户实测：拆箱镜 HKUST 杯飘在礼盒上方、比盒子还大 → 一眼假）。编辑指令里写明「放在原产品所在位置、同样大小、自然接触」。
# 例：gen image "把礼盒里的杯子换成这张产品图里的 HKUST 马克杯，放在盒内原位、同样大小、与盒子自然接触，保持原构图、暖色侧光、俯拍角度" -i seg-1.jpg -i product.jpg -o assets/keyframes/seg-1-edited.jpg
gen image "<紧凑图像编辑指令>" -i <原始首帧> -i <产品图> -o assets/keyframes/seg-<N>-edited.jpg

# 拼接所有 seg 为最终视频
printf "file '%s'\n" clips/seg-*.mp4 > concat_list.txt
ffmpeg -f concat -safe 0 -i concat_list.txt -c copy final.mp4

# 任务查询 / 取消 / 重试
gen task get <task_id>
gen task cancel <task_id>
gen task retry <task_id>
```

**Duration**：`--duration` 接受 **4–15 秒**（默认路径）。Production Plan 的 Trim 映射表负责将生成的完整视频切回 Storyboard 各段时长。

**每步完成后**：
- 删 `seg-N.pending.json`
- 写 `seg-N.meta.json`（duration_s / aspect_ratio / status）
- `git commit -m "video: receive $VIDEO_ID seg-N"`

**全部完成**：`git commit -m "video: done $VIDEO_ID"`

**失败**：自动重试 1 次 → 仍失败标记 `needs_user_review`

**生成过程中不问用户。** 并行规则见上方"生成"小节。

## Review

生成完成后对照 storyboard.md 检查：

1. **硬约束**（不过 → 告知用户）：
   - 比例匹配
   - 时长 ±20%
   - 文件可播放（`ffprobe final.mp4`）

2. **软推荐**（不过 → 写入迭代建议）：
   - 产品可见性（抽 5 帧）
   - 主体一致性（5 帧比对）
   - 首帧质量

3. 写 `quality-report.md` → `git commit -m "video: review $VIDEO_ID"`

4. 展示结果 + 质量摘要 + **2-3 个迭代建议**

5. 更新 `~/video-gen/history.md` 追加一行

6. 用户说"好/完美" → 在 preferences.md Learned 追加一条 → `git commit -m "prefs: learn from $VIDEO_ID — <pattern>"`

## 迭代

用户说"再试 / 更 X / 改 Y"：

1. history.md 找匹配项
2. 读该 video 的 storyboard.md
3. 判断：
   - "故事不对" → 改 storyboard → production-plan 重推 → 重新生成
   - "效果不对" → storyboard 不动 → 改 production-plan → 重新生成
4. 新 video-id = `{旧id}-v2`，history 的 parent 指向旧 id

## 核心规则（流程偏好）

> 硬规则见 §Global Rules。以下为流程偏好。

- **信息够就直接做（Fast-path）**——最重要的一条
- **Storyboard 是核心产物**，所有路径汇聚于它
- **Production Plan 由 skill 自动推导**
- **每步完成立刻 git commit**
- **生成过程零打扰**
- **参考视频永远让用户选**
- **生成完给迭代建议**
- **新会话有未完成任务先告知一次**

## 错误处理

| 故障 | 处理 |
|---|---|
| gen 返回 failed | 重试 1 次 → 仍失败告知用户 |
| gen video 返回 "duration out of range" | **不**自动重试/裁剪，告知用户并建议调整该段 storyboard 时长（默认路径支持 4-15 秒；DashScope 5\|10 秒）后重新 Production Plan |
| ffmpeg 失败 | 检查输入完整性，告知用户 |
| 源视频无法下载 | 建议手动下载后重试 |
| scout 无结果 | 告知，转模板构建 |
| 产品物理上做不出某镜动作 | 只对那一镜做最小替换 + 告诉用户改了啥（不重设计、不重排全片，见复刻铁律）；整体仍 1:1 照搬源片 |
| 会话关闭 | 状态在文件系统 + git，下次接续 |
| gen task 超时 | 按 pending.json 重提 |
| git commit 失败 | 说明原因，不 reset |

## 相关工具

- `gen video` — 图/文 → 视频
- `gen image` — 文 → 图
- `gen image` — 文生图，也是图像编辑（`-i` 传入输入图，可多次使用最多 8 张；产品替换首帧用 `-i 源帧 -i 产品图`）
- `gen task get/cancel/retry` — 异步任务控制
- `gen tts` — 文本 → 语音
- `ffmpeg` / `ffprobe` — 视频分析、抽帧、拼接
- `scout` — 爆款视频发现
