@optima-chat/gen-cli 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +1 -2
  2. package/.claude/skills/digital-human/SKILL.md +0 -309
  3. package/.claude/skills/digital-human/references/avatar-catalog.md +0 -47
  4. package/.claude/skills/digital-human/references/edit.md +0 -219
  5. package/.claude/skills/digital-human/references/generate.md +0 -378
  6. package/.claude/skills/digital-human/references/manage.md +0 -137
  7. package/.claude/skills/digital-human/references/train.md +0 -276
  8. package/.claude/skills/gen/SKILL.md +0 -366
  9. package/.claude/skills/motion-control/SKILL.md +0 -68
  10. package/.claude/skills/multigrid-poster/SKILL.md +0 -194
  11. package/.claude/skills/multigrid-poster/layouts/2x2.json +0 -34
  12. package/.claude/skills/multigrid-poster/layouts/3x3.json +0 -43
  13. package/.claude/skills/multigrid-poster/scripts/compose.py +0 -116
  14. package/.claude/skills/multigrid-poster/scripts/placeholder.png +0 -0
  15. package/.claude/skills/multigrid-poster/shared/fonts/MaShanZheng-Regular.ttf +0 -0
  16. package/.claude/skills/video-compose/SKILL.md +0 -144
  17. package/.claude/skills/video-compose/scripts/video_compose.py +0 -290
  18. package/.claude/skills/video-edit/SKILL.md +0 -332
  19. package/.claude/skills/video-gen/SKILL.md +0 -642
  20. package/.claude/skills/video-gen/references/cinematic-language.md +0 -158
  21. package/.claude/skills/video-gen/references/confirm-card.md +0 -49
  22. package/.claude/skills/video-gen/references/prompt-craft.md +0 -73
  23. package/.claude/skills/video-gen/templates/INDEX.md +0 -78
  24. package/.claude/skills/video-gen/templates/before-after-beauty.md +0 -183
  25. package/.claude/skills/video-gen/templates/drama-fmcg.md +0 -183
  26. package/.claude/skills/video-gen/templates/kol-reaction-food.md +0 -193
  27. package/.claude/skills/video-gen/templates/multi-point-apparel.md +0 -185
  28. package/.claude/skills/video-gen/templates/pain-solution-home.md +0 -184
  29. package/.claude/skills/video-gen/templates/pdp-360-showcase.md +0 -189
  30. package/.claude/skills/video-gen/templates/pdp-feature-highlight.md +0 -182
  31. package/.claude/skills/video-gen/templates/scene-digital.md +0 -183
  32. package/.claude/skills/video-translate/SKILL.md +0 -547
@@ -1,378 +0,0 @@
1
- # Generate Talking-Head Video(音频先行流程)
2
-
3
- 用已有分身(用户自训的 或 公共池的)合成口播视频。
4
-
5
- **关键设计(v2,音频先行)**:
6
- - **音频拆段、视频不拆**。脚本按句拆成 N 段,**逐段先出音频**给用户试听;用户确认音色 / 内容后,把各段音频拼成整条,**单条连续渲染**出成片。
7
- - **为什么**:实测把长视频拆成段再拼(逐段渲染 + ffmpeg/多场景拼接)会在段边界产生卡顿 / 冻帧;单条连续渲染最流畅。音频侧拆段则让用户能**只重生不满意的那一句**(音频试听免费 / 极廉),不用整条重来。
8
- - 每条视频独立 workspace 目录(`~/digital-human/videos/<video-id>/`),音频段 + 整条音频 + 成片都持久化。详见 [edit.md](edit.md)。
9
-
10
- > **关键不变量**:用户**逐段听过并确认**每段音频之前,**绝不进行任何视频渲染**(视频才烧额度)。音频段可反复单独重生(免费 / 极廉),视频只在最终确认后整条渲染一次——这是进入视频的**唯一闸门**。
11
-
12
- ## 0. 运行环境约束:bash 是**受限沙箱**(必读,否则会撞墙)
13
-
14
- agent-runtime 的 bash 有 baseline 安全策略(claude-code 移植,烤进镜像、部署关不掉)。下面这些**会被 soft-deny 拦**,本 SKILL 的所有命令都**不要用**:
15
-
16
- - ❌ `$( )` 命令替换(如 `VAR=$(cmd)`、`--text "$(cat f)"`)
17
- - ❌ `for ... do ... done` / `while` 循环,命令内换行,`;` / `&&` / `||` 串多条
18
- - ❌ `nohup ... &` 后台,`> file` / `>>` 重定向,`| jq` / `| sed` / `| wc` 管道
19
-
20
- **改用这些沙箱安全的等价做法:**
21
-
22
- | 想做的事 | ❌ 别写 | ✅ 改成 |
23
- |---|---|---|
24
- | 读文件内容喂给命令 | `--text "$(cat seg.txt)"` | 先用 **Read tool** 读出文本,再把内容**内联**进命令:`--text "大家好,我是张医生。"` |
25
- | 解析 CLI 的 JSON 输出 | `VAR=$(cmd \| jq -r .x)` | 直接跑 `cmd`(单条),**从它 stdout 的 JSON 里读**你要的字段,后续命令里内联用 |
26
- | 并发跑 N 段 | `for ...; nohup cmd & done` | **LLM 并行 tool_use**:一次性发 N 个独立 bash 调用,每个**只一条命令**,runtime 自动并发 |
27
- | 写文件 | `echo "x" > f` / `cat > f <<EOF` | 用 **Write tool** 写 |
28
-
29
- **核心心法**:**一条 bash 只跑一条简单命令**;循环 / 并发 / 取值 / 读写文件,统统交给 **LLM 自己的 tool_use 编排**(Read/Write/并行 bash),不要让 shell 做。下面各步的命令都按这个写;看到旧式 shell-magic 一律按上表翻译。
30
-
31
- ## 主流程: 用 X 做视频说 Y
32
-
33
- ### 1. 确定 X 是什么
34
-
35
- 按 [SKILL.md "LLM 如何区分用户输入是名字还是 external_id"](../SKILL.md) 表格判断:
36
- - 名字 → 在 `~/digital-human/avatars.md` "名字"列模糊匹配
37
- - 新格式 external_id (`dh-[a-z0-9]{8}`) → 直接走 §2
38
- - 存量 ASCII slug (例 `dr-wang`) → 走 [manage.md "存量分身"](manage.md) 流程
39
- - 歧义 → 反问消歧
40
-
41
- 匹配结果:
42
- - 1 个匹配 → 取该行 external_id,走 §2
43
- - 多个匹配 → 反问消歧("你说的是'张医生'还是'张主任'?")
44
- - 0 个匹配 → 反问"没找到'X',要训新的吗?"(走 [train.md](train.md))
45
-
46
- ### 2. 反查 voice_id + avatar_id
47
-
48
- 后端 CLI 要求传 `--avatar` + `--voice` 两个 UUID,不支持 `--external-id` 自动反查,所以**必须先查**:
49
-
50
- ```bash
51
- gen avatar profile <external_id>
52
- ```
53
-
54
- 跑这一条,从它 stdout 的 JSON 里读两个字段(**别用 `$()`/`jq` 截**,直接看输出):
55
- - `heygen_voice_id` → 后面音频段(`gen voice preview`)用,**内联**进 `--voice` 参数
56
- - `heygen_avatar_look_ids[0]` → 最终视频(audio-driven)用,**内联**进 `--avatar` 参数
57
-
58
- > 记住这两个值(在你后续命令里直接写出来),不要存 shell 变量——沙箱里 `VAR=$(...)` 会被拦。
59
-
60
- ### 3. 创建 video 工作目录
61
-
62
- VIDEO_ID = 纯时间戳 `YYYYMMDD-HHMM`(决策表 #11,无 slug v1)。**你(LLM)知道当前时间,直接写出这个 id**,不要用 `$(date)`。
63
-
64
- 先 `ls ~/digital-human/videos/` 看有没有同名目录;有的话给 id 追加 `-2`/`-3`。然后单条命令建目录:
65
-
66
- ```bash
67
- mkdir -p ~/digital-human/videos/20260531-1430/segments
68
- ```
69
-
70
- (后续命令里 `~/digital-human/videos/20260531-1430` 这个路径直接写全,不要存 `$VIDEO_DIR` 变量。)
71
-
72
- ### 4. 拆段(按句,用于逐段试听)
73
-
74
- **拆段目的变了**:不再是规避"长 prompt 让 trained avatar 挂死"(那是旧的逐段视频约束;音频驱动单条渲染已实测能吃长文本)。现在拆段**纯粹为了让用户逐段试听 + 只重生不满意的那一句**。所以按**自然句**拆,一句一段,粒度便于复听 / 重 roll。
75
-
76
- text 很短(一两句)→ 也走 segments 流程(单段也存),保持一致性。
77
-
78
- **拆段分隔符优先级**(从强到弱依次尝试):
79
-
80
- 1. **句末标点**:`。`(中文句号)/ `.`(英文句号)/ `?` / `?` / `!` / `!`
81
- 2. **句中标点**(若句子过长想再切):`,` / `,` / `;` / `;` / `:` / `:`
82
- 3. **空格**(英文 / 长拼接句)
83
- 4. 没有任何分隔符的超长句 → 整句作一段(音频无硬字数限制,单条渲染也能吃)
84
-
85
- > 旧版的"每段 ≤ 50 字"硬约束**已取消**——那是 text-driven 逐段视频的限制,音频先行下不适用。拆段只为试听粒度,不为绕字数。
86
-
87
- **写每段 text 文件**:用 **Write tool** 逐个写(2 位零填充,支持到 99 段),**别用 `echo > seg.txt`**(重定向沙箱拦,见 §0)。每段一个文件:
88
-
89
- - `~/digital-human/videos/<VIDEO_ID>/segments/seg-01.txt` ← 内容 `大家好,我是张医生。`
90
- - `~/digital-human/videos/<VIDEO_ID>/segments/seg-02.txt` ← 内容 `今天给大家推荐一款...`
91
- - ……(拆出几段写几个文件)
92
-
93
- > 这些 text 文件是 workspace 的真值,§6 出音频时文本你手头就有(直接内联进 `--text`),后续 §6.6 改词 / §6.8 续生才需要 Read 回来。
94
-
95
- ### 5. 写 meta.md(初版,state=audio generating)
96
-
97
- "当前版本"字段是 **state machine** 的 marker:`(audio generating)` → `(audio-review pending)` → `(rendering)` → `final-vN.mp4`。本步写第 1 阶段 placeholder `(audio generating)`,表示音频段还在生成。
98
-
99
- 跨 session 续接(§6.8)和 manage.md "列我的视频"都靠这字段路由。
100
-
101
- ```markdown
102
- # Video: <slug from text or topic>
103
-
104
- | 字段 | 值 |
105
- |---|---|
106
- | video-id | <VIDEO_ID> |
107
- | avatar | <display_name> (external_id: <external_id>) |
108
- | 创建时间 | <YYYY-MM-DD HH:MM> |
109
- | 当前版本 | (audio generating) |
110
-
111
- ## 总文本
112
-
113
- > <用户给的完整 text>
114
-
115
- ## Segments
116
-
117
- | seg | text | 音频时长 | 音频文件 | 最后更新 |
118
- |---|---|---|---|---|
119
- | 01 | "大家好,我是张医生。" | ~3s | segments/seg-01.wav | <时间> |
120
- | 02 | "今天给大家推荐..." | ~4s | segments/seg-02.wav | <时间> |
121
- | ... |
122
- ```
123
-
124
- > **不写 voice_id / avatar_id UUID** 到 meta.md —— 用户能看到这个文件,UUID 不冒泡;需要时 LLM 重新 `gen avatar profile` 反查。
125
-
126
- ### 6. 逐段生成音频(**用 LLM 并行 tool_use,不要 shell 循环**)
127
-
128
- 每段用 `gen voice preview` 出**音频**(纯音频试听,**不烧视频额度、不单独计费**)。`gen voice preview` 是**同步**的——一条命令跑完就拿到那段 .wav + 它的时长。所以:
129
-
130
- **做法:你(LLM)一次性发出 N 个并行 bash 调用,每个就一条命令**,形如:
131
-
132
- ```bash
133
- gen voice preview --voice <voice_id> --text "大家好,我是张医生。" --output ~/digital-human/videos/<VIDEO_ID>/segments/seg-01.wav
134
- ```
135
-
136
- - 每段文本从你 §4 拆好的内容**内联**进 `--text`(你手里就有这些句子,不用 `cat`/`$()` 去读)。
137
- - N 个调用**并行发出**(同一轮 tool_use 里多个 bash),runtime 自动并发——等价于以前的"并发派发",但**没有** `for` / `nohup &` / `$()` / `>` 重定向。
138
- - 幂等:已存在的 seg-NN.wav 不用重发。
139
- - 每条命令的输出里有该段的 `duration`、`audio_path` 等——**记下每段时长**(§6.5 要用,不必再 ffprobe)。
140
-
141
- > **严禁**:`for ... gen voice preview ... done` 循环、`nohup ... &`、`--text "$(cat ...)"`、`> seg.log`。这些在沙箱里全被拦(见 §0)。并发靠 LLM 并行发多条,不是 shell。
142
-
143
- 每段独立音频文件,文件名跟 text 零填充对齐(seg-01 ↔ seg-01.wav)。某段命令报错就单独重发那一段。
144
-
145
- ---
146
-
147
- ### 6.5 音频 review checkpoint(**核心 — 进入视频的唯一闸门**)
148
-
149
- > 实施 [SPEC 2026-05-30 digital-human-audio-first-flow](docs/2026-05-30-digital-human-audio-first-flow.md) §4.3。
150
- > **N==1 单段也走本 checkpoint** —— 音频先行下,单段也要让用户先听过音色 / 内容再出片(视频烧额度,听了再出最稳)。
151
-
152
- **Step 1: 更新 meta.md state**:`(audio generating)` → `(audio-review pending)`。
153
-
154
- **Step 2: 取每段时长**:用 §6 每条 `gen voice preview` 输出里已经返回的 `duration`(不用再 ffprobe;那是个 `for`+`$()` 循环,沙箱拦)。若确实漏了某段时长,单条 `ffprobe -v error -show_entries format=duration -of csv=p=0 ~/digital-human/videos/<VIDEO_ID>/segments/seg-NN.wav` 补一段即可(一次一条,别写循环)。
155
-
156
- **Step 3: 向用户逐段呈现音频供试听 + 列 segments 表**
157
-
158
- 把**每段文本 + 对应音频文件**都列出来,让用户逐条听:
159
-
160
- ```text
161
- ✅ N 段音频已生成,出视频前请逐段听一下(确认音色 + 内容):
162
-
163
- | seg | text | 音频时长 | 试听文件 |
164
- |---|---|---|---|
165
- | 01 | "大家好,我是张医生。" | 2.95s | segments/seg-01.wav |
166
- | 02 | "今天介绍一款..." | 4.12s | segments/seg-02.wav |
167
- | ... |
168
-
169
- 逐段听完怎么处理?
170
- - "都 OK,出视频吧" → 我把音频拼成整条,渲染最终视频
171
- - "seg-N 改成 X"(改词)/ "seg-N 重生一遍"(同词重 roll) → 只重生那一段音频(免费 / 极廉),改完再回来给你听
172
- - "都不要,重新搞" → 删了用同一段 text 重新生成
173
- ```
174
-
175
- **Note**:user-facing 文案**禁止泄露内部 §-编号**。用户看不懂章节号,用自然语言。
176
-
177
- **Step 4: LLM turn-end 等用户响应**
178
-
179
- CC chat 单 turn 内无法同步 block 等输入,必须 turn-end。state 由 meta.md `(audio-review pending)` 持久化,即使用户中途关 session,新 session 也能通过 §6.8 重展 checkpoint。
180
-
181
- > **再次强调闸门**:只有用户明确"都 OK 出视频"才进 §7。任意一段被指出要改 → 走 §6.6 只重生那段音频、回到本 checkpoint 重新听。**没听完确认前不渲染视频。**
182
-
183
- ### 6.6 Checkpoint 内改段(只重生音频,免费 / 极廉,不版本化)
184
-
185
- 跟 [edit.md](edit.md) 主流程区别:edit.md 改的是**已出片**的视频(要重渲染整条);本段是**出片前**改音频,只重生该段音频,**不触发任何视频渲染**。
186
-
187
- **完整 5 步**(state 不变,仍是 `(audio-review pending)`):
188
-
189
- 1. **编辑距离阈值检查**(沿用 [edit.md §4 决策 #14](edit.md)):
190
- - 计算 `levenshtein(旧 text, 新 text) / max(len(旧), len(新))`
191
- - `> 30%` → 反问"确认改吗?";`≤ 30%` → typo/标点微调,静默继续
192
- - (音频 preview 免费 / 极廉,这里阈值确认主要防误改内容,不是防烧钱)
193
- 2. **覆盖 `segments/seg-NN.txt`**(用 **Write tool** 写新文本,别用 `echo >`)+ **重生 `segments/seg-NN.wav`**(一条命令,新文本**内联**):
194
- - voice_id:若这是新 session、手头没有,先 Read `meta.md` 拿 external_id(形如 `dh-xxxxxxxx`)→ 跑 `gen avatar profile <external_id>` → 从输出读 `heygen_voice_id`(别 `$()`/`jq`)。
195
- ```bash
196
- gen voice preview --voice <voice_id> --text "<新 text>" --output ~/digital-human/videos/<VIDEO_ID>/segments/seg-02.wav
197
- ```
198
- (其他段音频不动;voice preview 无语速/语气调节,只能改词或同词重 roll)
199
- 3. **更新 meta.md** segments 表第 N 行"最后更新"列
200
- 4. **显式写 history.md** 一行:
201
- ```
202
- - **<当前时间>** [audio-review] 改了 seg-N 音频: '<旧>' → '<新>' (<编辑距离比例 X%>)
203
- ```
204
- `[audio-review]` 标记区分出片后的 `[v1]/[v2]`,跨 session 续接时能看到历史改动
205
- 5. **回 §6.5 checkpoint 重展**(防止改完 seg-2 又发现 seg-3 也要改的二次 edit)
206
-
207
- ### 6.7 用户说"都不要"——丢弃当前 video
208
-
209
- **二次确认**(rm -rf 不可恢复,跟 manage.md 删视频对齐):
210
-
211
- > "确认删除 `videos/<VIDEO_ID>/`?这条还没出片,音频段删了不可恢复。
212
- > 删完会用**同一段 text** 重新生成(每次音色 / 韵律会有细微差异,所以'重新搞'本身有意义)。"
213
-
214
- 用户确认 → 走(把 `<VIDEO_ID>` 这段路径**写全**,别用 `$VIDEO_ID` shell 变量——沙箱里它没被安全赋过值):
215
-
216
- ```bash
217
- rm -rf ~/digital-human/videos/20260531-1430
218
- ```
219
-
220
- 回到 §3 用新 VIDEO_ID + **原 text** 重建(不再问用户 text)。
221
-
222
- ### 6.8 跨 session 续接
223
-
224
- 用户开新 session 说"出视频 X" / "刚才那条出片" / "继续 X 视频":
225
-
226
- ```bash
227
- ls -t ~/digital-human/videos/ # 按 mtime 降序
228
- ```
229
-
230
- 逐目录 Read `meta.md` 取"当前版本",**按 state 路由**:
231
-
232
- | state | 行为 |
233
- |---|---|
234
- | `(audio generating)` | 音频段还没生完(可能 §6 跑一半中断)→ 走下面"续生音频流程" |
235
- | `(audio-review pending)` | 音频都生完,等用户确认 → **重展 §6.5 checkpoint** |
236
- | `(rendering)` | 已确认、视频在渲染(可能中断)→ 走 §7 重跑渲染(concat 已有则跳过,直接重渲) |
237
- | `(generating)` / `(review pending)`(旧 v1 字面量) | 旧流程(逐段视频)半成品,跟音频先行**不兼容**(段是 .mp4)→ 反问"这条是旧流程的半成品,续接方式已变,要用原文重新生成吗?" 同意 → §3 用原 text 全新生成,**不**走下面续生流程 |
238
- | `final-vN.mp4` | 已出片,跳过(不是未完成视频) |
239
-
240
- 匹配结果:1 个 → 按 state 决定;多个 → 反问"以下是未完成的:..."(列 state);0 个 → "没找到未完成的视频,要新建吗?"
241
-
242
- **`(audio generating)` 续生音频流程**(跟 §6 同款,沙箱安全):
243
-
244
- 1. `ls ~/digital-human/videos/<VIDEO_ID>/segments/` 看哪些 `seg-NN.txt` 还**缺**对应的 `seg-NN.wav`。
245
- 2. voice_id:Read `meta.md` 拿 external_id → `gen avatar profile <external_id>` → 从输出读 `heygen_voice_id`(别 `$()`/`jq`)。
246
- 3. 对**每个缺的段**,各发**一条** `gen voice preview`(LLM 并行多条,不写 `for`/`nohup`),文本从对应 `seg-NN.txt` 用 **Read tool** 读出来内联:
247
- ```bash
248
- gen voice preview --voice <voice_id> --text "<该段文本>" --output ~/digital-human/videos/<VIDEO_ID>/segments/seg-NN.wav
249
- ```
250
-
251
- **续生完成后**:全部 seg-NN.wav 存在 → 转 state 到 `(audio-review pending)` + 跳 §6.5 checkpoint 让用户逐段试听。**不**直接出片(用户没听过)。
252
-
253
- ---
254
-
255
- ### 7. 确认后:拼接音频 → 单条渲染 → final-v1.mp4
256
-
257
- 仅当 §6.5 用户明确"都 OK 出视频"才进本步。
258
-
259
- **Step 1: 更新 meta.md state**:`(audio-review pending)` → `(rendering)`。
260
-
261
- **Step 2: 把确认的各段音频拼成整条** —— **必须用 `gen voice concat`**(单条 CLI,内部用 concat filter 重编码、非 stream-copy,避免拼缝杂音;不会撞沙箱):
262
-
263
- ```bash
264
- gen voice concat --workspace-dir ~/digital-human/videos/<VIDEO_ID>/ --out ~/digital-human/videos/<VIDEO_ID>/full-audio.mp3
265
- ```
266
-
267
- > 自动扫 `segments/seg-*.{wav,mp3}` 按文件名自然排序拼接(Mode A)。从它 stdout 的 JSON 读 `duration_sec`(别 `$()`/`jq`)。
268
- >
269
- > 🔴 **严禁自己跑 `ffmpeg -f concat ...` 拼音频** —— 即使 §6 撞过 soft-deny,也**绝不**回退到手搓 ffmpeg。`gen voice concat` 就是为此封装的规范 wrapper;手搓 ffmpeg = 绕过抽象 + 易出拼缝问题。这是硬规矩(见 SKILL.md Global Rule 关于 anti-fabrication / 不绕 CLI 抽象)。
270
-
271
- **Step 3: 用整条音频单条渲染 avatar 视频**(audio-driven,无 scene 边界):
272
-
273
- ```bash
274
- gen avatar video --avatar <avatar_id> --audio ~/digital-human/videos/<VIDEO_ID>/full-audio.mp3 -o ~/digital-human/videos/<VIDEO_ID>/final-v1.mp4
275
- ```
276
-
277
- - 音频驱动:嘴型跟随 `full-audio.mp3`,成片声音 = 用户逐段确认过的那条,分毫不差。
278
- - **单条连续渲染**,不拆 scene → 无段边界卡顿。
279
-
280
- **渲染成功后,更新 meta.md "当前版本"**:`(rendering)` → `final-v1.mp4`。
281
-
282
- **失败处理**:
283
- - `gen avatar video --audio` 返回 `音频驱动 doctor-video 暂未开放` → 后端音频驱动未启用(feature flag off / 未上线)。**这是部署状态,不是用户错误**。告诉用户"音频驱动出片功能正在灰度,稍后可用",**不要**反复重试。
284
- - 其他渲染失败 → 看 `error_message`,必要时重跑本步(音频已拼好,直接重渲不用重做音频)。
285
-
286
- ### 8. 写 history.md
287
-
288
- 用 **Write/Edit tool** 写 `~/digital-human/videos/<VIDEO_ID>/history.md`(别用 `echo >>` / `cat <<EOF`,沙箱拦)。时间用你(LLM)知道的当前时间内联,别用 `$(date)`。
289
-
290
- - 若 history.md 已存在(§6.6 期间有过 `[audio-review]` 改动行)→ 用 Edit **追加**一行,保留旧记录:
291
- ```
292
- - **2026-06-01 14:30** [v1] 出片 (N 段音频拼接 + 单条渲染)
293
- ```
294
- - 否则用 Write 新建:
295
- ```
296
- # History
297
-
298
- - **2026-06-01 14:30** [v1] 出片 (N 段音频拼接 + 单条渲染)
299
- ```
300
- ```
301
-
302
- ### 9. 报告 final-v1.mp4 给用户
303
-
304
- > "✅ 视频生成完成: ~/digital-human/videos/<VIDEO_ID>/final-v1.mp4
305
- > 共 N 段音频拼成整条、单条连续渲染(无段间卡顿)。
306
- > 想改哪句,说一声——我重生那段音频给你听,确认后重渲一次。"
307
-
308
- ---
309
-
310
- ## 没指定分身但要"做个口播视频"(公共 avatar)
311
-
312
- 公共 avatar 不是用户自己的声音,**不走音频先行试听**(没有"确认音色"的意义)。但仍**单条渲染、不拆段**——这是另一半的卡顿修复(拆段拼接才卡)。
313
-
314
- 1. 展示 [avatar-catalog.md](avatar-catalog.md) 公共 avatar / voice 清单(用 preview 图或名称,不冒泡 UUID)
315
- 2. 让用户选一个 avatar + 一个 voice
316
- 3. 走 workspace 持久化(VIDEO_ID 同 §3),但**整段一次渲染**(text 不拆;单条渲染已实测能吃长文本)。**一条命令、文本内联**(别用 `\` 续行换行、别 `$(cat)`——沙箱拦,见 §0):
317
- ```bash
318
- gen video --provider heygen --avatar <公共 avatar id> --voice <公共 voice id> --text "大家好,今天给大家介绍……(整段文案内联在这里)" -o ~/digital-human/videos/<VIDEO_ID>/final-v1.mp4
319
- ```
320
- (文案是用户给的、你手头就有,直接写进 `--text`;太长也照样内联成一行,不要拆文件再 `cat`。)
321
- 4. meta.md state 直接 `(rendering)` → `final-v1.mp4`(无音频 review 阶段);§8 history + §9 报告同主流程
322
-
323
- 公共 avatar 视频的 `meta.md` "avatar" 字段写 `公共: Abigail (id 不冒泡)` 即可。
324
-
325
- > 公共池若想也走"逐段试听",需要先有该 voice 的 `gen voice preview` + 后端公共-avatar 音频驱动支持——**v2 暂不做**,公共路径保持单条 text 渲染。
326
-
327
- ---
328
-
329
- ## 可选参数
330
-
331
- **音频段(`gen voice preview`)**:`--text-type <text|ssml>`。**注:`voice preview` 不支持 `--voice-speed`/`--voice-emotion`**——音频先行下逐段音频暂无语速/语气调节(改词 / 重 roll 即可;真要语速/情绪是后续 CLI + 上游支持的 follow-up,别凭印象拼 flag,见 SKILL rule #4)。
332
-
333
- **公共 avatar 单段视频(`gen video --provider heygen`)**:
334
-
335
- | 参数 | 说明 | 默认值 |
336
- |---|---|---|
337
- | `--avatar-style <style>` | normal \| circle \| closeUp | normal |
338
- | `--voice-speed <n>` | 语速 0.5-1.5 | 1.0 |
339
- | `--voice-emotion <emotion>` | Excited \| Friendly \| Serious \| Soothing \| Broadcaster | — |
340
- | `--bg-color <hex>` | 背景色 | — |
341
- | `--caption` | 启用字幕 | — |
342
- | `--title <title>` | 视频标题 | — |
343
-
344
- `gen avatar video`(trained avatar)接受 `--avatar` / `--voice` / `--text`(文本驱动)**或** `--avatar` / `--audio`(音频驱动)/ `--title` / `-o`。
345
-
346
- ---
347
-
348
- ## 训完立即 demo(可选)
349
-
350
- 如果用户在原始训练请求里说了想"看效果 / 做一段试试 / 给我个 demo":训完直接走 §主流程,text 用用户给的或默认 `"大家好,我是 X"`(X = 名字)。短 demo 也走音频先行(让用户先听音色)。
351
-
352
- **降级**:如果 trained avatar 还在 still-processing(`gen avatar video` 返回错误)→ 音频段照常出(voice 已训好),但视频改用公共 avatar + 该音频单条渲染应急,告诉用户:
353
- > "分身的视觉部分还在最后定型(几十分钟内会好),先用通用形象演示。**这条 demo 仅证明 voice 训成,不展示真分身效果** — 几小时后用 'X 做个视频说 Y' 重试就能用真分身了。"
354
-
355
- > **注意区分**:这是 still-processing 期间的**临时降级**,不算 mix-and-match。
356
-
357
- ---
358
-
359
- ## Advanced: mix-and-match(参考实现,**不**进 SKILL.md 主流程)
360
-
361
- > [workspace SPEC §2](../../../../docs/superpowers/specs/2026-05-14-digital-human-workspace-naming.md) 已声明 mix-and-match 是非目标。用户主动要求时再让 advanced agent 临时执行。
362
-
363
- 例:"用张医生的声音 + 公共 Aditya 形象":逐段用张医生 voice 出音频 → 拼接 → `gen video --provider heygen --avatar Aditya_public_4 --audio full-audio.mp3`(若公共路径支持 --audio;否则用 `--voice <张医生.voice_id> --text` 单条文本渲染)。LLM 主动告诉用户:"这是参考实现,未来若呼声高再进主流程。"
364
-
365
- ---
366
-
367
- ## Common Mistakes
368
-
369
- | ❌ Mistake | ✅ 正确做法 | 为什么 |
370
- |---|---|---|
371
- | 没让用户听音频就出视频 | 必须先 §6.5 逐段试听 + 确认,才 §7 渲染 | 视频烧额度;音色 / 内容问题在音频阶段(免费)就该抓出来 |
372
- | 把视频拆段渲染再拼接 | 整条音频**单条渲染**(`gen avatar video --audio`) | 拆段拼接是卡顿根因;单条连续渲染最流畅 |
373
- | 用 `gen avatar video --text` 出 trained avatar 视频 | 走音频先行:`voice preview` 逐段 → `voice concat` → `avatar video --audio` | text 驱动跳过了试听闸门,且回到逐段问题 |
374
- | `gen voice concat` 用 mp3 `-c copy` 拼 | 用 `gen voice concat`(内部 concat filter 重编码) | stream-copy 拼 mp3 有 priming/gap 杂音 |
375
- | 把 voice_id / avatar_id UUID 报给用户 | 只回 mp4 路径 + "用 X 做了" | UUID 不冒泡是核心 UX 原则 |
376
- | 公共"做个口播"也拆段 | 公共走 `gen video --provider heygen` **单条** text 渲染 | 公共不是用户声音、不试听;但单条渲染仍要(防卡顿) |
377
- | segments 写 ephemeral 目录(`/tmp`) | 写到 `~/digital-human/videos/<id>/segments/` | 用户后续想改一句,workspace 是单一真值 |
378
- | `gen avatar video --audio` 报"暂未开放"当成 bug 反复重试 | 这是后端灰度状态,告知用户稍后可用,不重试 | 音频驱动有 feature flag,未上线时正常拒绝 |
@@ -1,137 +0,0 @@
1
- # Manage Avatars & Videos
2
-
3
- 管理 `~/digital-human/` 工作空间下的分身(`avatars.md`)+ 生成过的视频(`videos/<id>/`)+ 训练素材(`training/<id>/`)。
4
-
5
- ## "我的分身有哪些"
6
-
7
- 直接 Read `~/digital-human/avatars.md` 渲染给用户。
8
-
9
- 文件不存在 → "你还没训过分身,要训一个吗?"(走 [train.md](train.md))
10
-
11
- 文件存在但只有表头(无数据行)→ 同上。
12
-
13
- ## "我的视频有哪些" / "列我的视频" / "做过哪些视频"
14
-
15
- > ⚠️ **沙箱 bash 须知**(同 [generate.md §0](generate.md)):agent-runtime 的 bash 软拒绝 `for`/`while` 循环、`$( )`、`| grep`/`| sed`/`| awk`/`| wc` 管道、`>` 重定向。所以**别写 `for d in $(ls ...)` 循环**逐目录抓字段——靠 **LLM 自己迭代**:一条 `ls` 列目录,再对每个目录用 **Read tool** 读 meta.md,字段(分身 / 当前版本 / 总文本 / 段数)你读出来自己提取、自己映射 emoji,**不靠 shell**。
16
-
17
- **步骤**:
18
-
19
- ```bash
20
- ls -t ~/digital-human/videos/
21
- ```
22
-
23
- 对列出的每个 `<video-id>`,**用 Read tool** 读 `~/digital-human/videos/<video-id>/meta.md`,从中读出:`avatar`(分身)、`当前版本`、`总文本`前 30 字、segments 表行数(= 段数)。**按"当前版本"值映射 state emoji**:
24
-
25
- | "当前版本" 值 | 状态标注 |
26
- |---|---|
27
- | `(audio generating)` | 🚧 音频生成中 (中断,可续接) |
28
- | `(audio-review pending)` | ⏳ 待试听确认 (audio-review pending) |
29
- | `(rendering)` | 🎬 渲染中 |
30
- | `(generating)` / `(review pending)`(旧 v1 字面量) | 📼 旧流程视频 (v1,改动需整条重生) |
31
- | `final-vN.mp4` | ✅ final-vN.mp4 |
32
- | 其它 | ❓ 未知 state |
33
-
34
- 渲染给用户(markdown 表格更友好):
35
-
36
- | video-id | 分身 | 总文本(前 30 字) | 段数 | 状态 |
37
- |---|---|---|---|---|
38
- | 20260515-1430 | 张医生 | "大家好,我是张医生。今天介..." | 4 | ✅ final-v2.mp4 |
39
- | 20260515-1620 | 李老师 | "今天讲..." | 5 | ⏳ 待试听确认 (audio-review pending) |
40
- | 20260516-0915 | 王主任 | "..." | 3 | 🚧 音频生成中 (中断,可续接) |
41
- | 20260514-0945 | 张医生 | "..." | 6 | ✅ final-v1.mp4 |
42
-
43
- **用户暗示 + 操作引导**(给 LLM 的内部规则,**对用户**只输出自然语言话术,**不**报 §-编号):
44
- - 看到 ⏳ → user-facing 提示 "想继续把 X 的音频听一下、确认出片吗?"(LLM 内部跳 [generate.md §6.5 重展试听 checkpoint](generate.md))
45
- - 看到 🚧 → user-facing 提示 "想续生剩余音频段吗?"(LLM 内部跳 [generate.md §6.8 续生流程](generate.md))
46
- - 看到 🎬 → user-facing 提示 "X 还在渲染,稍等;若中断了我可以重渲"(LLM 内部跳 [generate.md §6.8 → (rendering) 重渲](generate.md))
47
- - 看到 📼 → 旧流程(v1 逐段视频)视频;用户说"改"时 [edit.md §1.5 Step 0](edit.md) 会拦下、引导整条重新生成(新旧不兼容)
48
- - 看到 ✅ → 视频已完成,用户说"改 seg-N"时由 [edit.md §1.5 state check](edit.md) 路由
49
-
50
- 文件夹不存在 / 空 → "你还没生成过视频,要做一条吗?"(走 [generate.md](generate.md))
51
-
52
- ## "把张医生改成张主任"
53
-
54
- 1. Read `~/digital-human/avatars.md`
55
- 2. 在"名字"列找 `张医生`
56
- 3. Edit 该行"名字"列的 cell:`张医生` → `张主任`
57
- 4. 反馈"已改名,external_id 不变,以后可以用'张主任'引用了"
58
-
59
- 如果用户给的旧名字找不到 → 反问"没找到叫'张医生'的分身,你现在的分身有:[列出]"
60
-
61
- ## "删了张医生"
62
-
63
- 1. Read `~/digital-human/avatars.md`
64
- 2. 找到"张医生"行,记下该行所有字段(名字 / external_id / 训练时间 / preview URL)
65
- 3. **二次确认 + 显式列出恢复锚点**:
66
- > "确认删除'张医生'? **注意**:这是对你视角的硬删除 —
67
- > - 你失去所有引用路径(external_id 从你视野消失 + 没有 list 端点)
68
- > - 后端 DB 行成为 orphan 数据,**找回需要联系运维直接查 DB**
69
- > - 运维查 DB 需要锚点,以下信息删除后你将看不到,**建议先记下来或截图**:
70
- > - 名字:`张医生`
71
- > - external_id:`dh-a7f2k9m1`
72
- > - 训练时间:`2026-05-14`
73
- > - preview:`https://...preview.jpg`
74
- > 真要删吗?"
75
- 4. 用户确认 → 删该行
76
- 5. 反馈"已从你的列表删除'张医生'。如需找回请向运维提供上述 4 个锚点(名字 / external_id / 训练时间 / preview URL)。"
77
-
78
- 如果用户给的名字找不到 → 同上反问。
79
-
80
- ## 存量分身(用户在前 SPEC 落地之前训过 `dr-wang` 等)
81
-
82
- 用户说"用 dr-wang 做视频"但 `~/digital-human/avatars.md` "名字"列没有 `dr-wang`:
83
-
84
- 1. LLM 判断 `dr-wang` 是纯 ASCII + 含 `-` + 不在名字列 → 怀疑是存量 slug
85
- 2. LLM 试着 `gen avatar profile dr-wang`(后端单查仍支持任意 external_id)
86
- 3. **找到**(返回非 null) → 询问:
87
- > "我发现你有个分身叫 'dr-wang',要起个友好名字加到你的列表吗?(以后可以用'张医生'这种说法引用,而不是记英文 slug)"
88
- - 用户给名字 → 追加一行到 `avatars.md`:`| <用户给的名字> | dr-wang | <profile.created_at 或当天> | <preview_image_url> |`
89
- - 用户说"不用" → 直接走生成流程,但下次还会反问
90
- 4. **找不到** → 反问"没训过 dr-wang,要训吗?"
91
-
92
- 这是**自动迁移路径**,不强制,用户继续用 dr-wang 也行。
93
-
94
- ## "删了视频 20260515-1430"
95
-
96
- 视频删除跟分身删除不同 —— 视频 workspace 是用户私有目录,直接删干净,**没有**后端 DB 行残留:
97
-
98
- 1. 确认 video-id 存在(`ls ~/digital-human/videos/<id>`)
99
- 2. 二次确认:
100
- > "确认删除视频 `<id>`(<段数> 段, 当前 final-vX.mp4)?
101
- > 这会删除整个目录(segments / final-v* / meta.md / history.md),**不可恢复**。"
102
- 3. 用户确认 → `rm -rf ~/digital-human/videos/<id>`
103
- 4. 反馈"已删除视频 `<id>`。"
104
-
105
- 跟分身删除的对比:分身有后端 DB 行,删 markdown 行 = orphan;视频纯客户端,删目录 = 真删干净。
106
-
107
- ## "改某段视频"
108
-
109
- 走 [edit.md](edit.md)。
110
-
111
- ## 磁盘占用查看
112
-
113
- 用户问"我用了多少磁盘"/"workspace 多大" → 让 ta 自己跑:
114
-
115
- ```bash
116
- du -sh ~/digital-human/
117
- du -sh ~/digital-human/training/*/
118
- du -sh ~/digital-human/videos/*/
119
- ```
120
-
121
- 每个 training/<id>/ ~ 50-200 MB(主要是 source-video);每个 videos/<id>/ ~ 8-20 MB。
122
-
123
- v1 不做自动清理。用户嫌占空间:
124
- - 删旧视频:`rm -rf ~/digital-human/videos/<old-id>`
125
- - 删某分身训练素材:`rm -rf ~/digital-human/training/<external_id>/`(但保留 `avatars.md` 那一行,以后还能用 ID 生成视频,只是不能重训了)
126
-
127
- ## "我有哪些分身做过 X"(进阶筛选)
128
-
129
- v1 不支持。用户问就告诉:"workspace 不索引'分身 → 视频'反向关系。但可以 `grep` workspace:
130
- ```bash
131
- grep -l 'external_id: dh-a7f2k9m1' ~/digital-human/videos/*/meta.md
132
- ```
133
- 找出该分身做过的所有视频。如需要常态化筛选请告诉我们。"
134
-
135
- ## LLM 读 avatars.md 时的健全性校验
136
-
137
- 每次 Read 后必须做一遍。**规则详见 [SKILL.md "工作空间" 段](../SKILL.md#工作空间)**,单源真值,本段不复述以免 drift。