@optima-chat/gen-cli 2.2.0 → 2.4.0

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 (49) hide show
  1. package/.claude/skills/digital-human/SKILL.md +131 -17
  2. package/.claude/skills/digital-human/references/edit.md +121 -100
  3. package/.claude/skills/digital-human/references/generate.md +256 -124
  4. package/.claude/skills/digital-human/references/manage.md +28 -17
  5. package/.claude/skills/digital-human/references/train.md +47 -62
  6. package/.claude/skills/gen/SKILL.md +13 -0
  7. package/.claude/skills/motion-control/SKILL.md +68 -0
  8. package/.claude/skills/video-compose/SKILL.md +144 -0
  9. package/.claude/skills/video-compose/scripts/video_compose.py +272 -0
  10. package/.claude/skills/video-edit/SKILL.md +62 -1
  11. package/.claude/skills/video-gen/SKILL.md +72 -6
  12. package/.claude/skills/video-translate/SKILL.md +205 -69
  13. package/assets/video-compose/bgm-library/SOURCES.md +25 -0
  14. package/assets/video-compose/bgm-library/calm/calm-01.mp3 +0 -0
  15. package/assets/video-compose/bgm-library/calm/calm-02.mp3 +0 -0
  16. package/assets/video-compose/bgm-library/dramatic/dramatic-01.mp3 +0 -0
  17. package/assets/video-compose/bgm-library/dramatic/dramatic-02.mp3 +0 -0
  18. package/assets/video-compose/bgm-library/energetic/energetic-01.mp3 +0 -0
  19. package/assets/video-compose/bgm-library/energetic/energetic-02.mp3 +0 -0
  20. package/assets/video-compose/bgm-library/sad/sad-01.mp3 +0 -0
  21. package/assets/video-compose/bgm-library/sad/sad-02.mp3 +0 -0
  22. package/assets/video-compose/bgm-library/upbeat/upbeat-01.mp3 +0 -0
  23. package/assets/video-compose/bgm-library/upbeat/upbeat-02.mp3 +0 -0
  24. package/assets/video-compose/bgm-library/warm/warm-01.mp3 +0 -0
  25. package/assets/video-compose/bgm-library/warm/warm-02.mp3 +0 -0
  26. package/assets/video-compose/voice-samples/01-/346/270/251/346/232/226/345/260/221/345/245/263.mp3 +0 -0
  27. package/assets/video-compose/voice-samples/02-/347/224/234/347/276/216/345/245/263/345/243/260.mp3 +0 -0
  28. package/assets/video-compose/voice-samples/03-/347/224/234/347/276/216/345/205/203/346/260/224.mp3 +0 -0
  29. package/assets/video-compose/voice-samples/04-/346/270/205/347/224/234/345/260/221/345/245/263.mp3 +0 -0
  30. package/assets/video-compose/voice-samples/05-/345/276/241/345/247/220.mp3 +0 -0
  31. package/assets/video-compose/voice-samples/06-/346/210/220/347/206/237/347/237/245/346/200/247.mp3 +0 -0
  32. package/assets/video-compose/voice-samples/07-/345/245/263/344/270/273/346/222/255.mp3 +0 -0
  33. package/assets/video-compose/voice-samples/CATALOG.md +9 -0
  34. package/dist/commands/doctor.d.ts +15 -0
  35. package/dist/commands/doctor.d.ts.map +1 -1
  36. package/dist/commands/doctor.js +74 -11
  37. package/dist/commands/doctor.js.map +1 -1
  38. package/dist/commands/tts.d.ts.map +1 -1
  39. package/dist/commands/tts.js +19 -4
  40. package/dist/commands/tts.js.map +1 -1
  41. package/dist/commands/voice.d.ts +13 -0
  42. package/dist/commands/voice.d.ts.map +1 -1
  43. package/dist/commands/voice.js +280 -0
  44. package/dist/commands/voice.js.map +1 -1
  45. package/dist/services/generation-api.d.ts +16 -2
  46. package/dist/services/generation-api.d.ts.map +1 -1
  47. package/dist/services/generation-api.js +6 -0
  48. package/dist/services/generation-api.js.map +1 -1
  49. package/package.json +3 -2
@@ -7,6 +7,10 @@ owner_repo: Optima-Chat/optima-gen
7
7
 
8
8
  # Digital Human Skill
9
9
 
10
+ > **CLI 版本要求**:本 SKILL(v2 音频先行)依赖 `@optima-chat/gen-cli ≥ 2.3.0`(含 `gen voice preview` / `gen voice concat` / `gen avatar video --audio`)。LLM 首次调这些命令失败 `command not found` / `unknown command` / `unknown option` → 报错引导用户升级 `@optima-chat/gen-cli`,**不**回退到 inline ffmpeg / 旧的逐段视频拼接流程。
11
+ >
12
+ > **后端要求**:音频驱动出片(`gen avatar video --audio`)需后端启用 `DOCTOR_VIDEO_AUDIO_DRIVEN`。未启用时命令返回"音频驱动 doctor-video 暂未开放"——这是部署灰度状态,不是用户错误,告知用户稍后可用、不重试。
13
+
10
14
  数字分身一站式:训练用户上传的真人视频得到 voice + avatar,后续用名字引用做口播视频。
11
15
 
12
16
  > **用户向输出原则**:对话 / status / 报告里**不出现具体上游品牌 / 服务名**,统一用"训练服务" / "数字分身训练" / "上游" 等中性词。skill 内部 reasoning 引用(spec / probe / source code)可保留具体名称用于 LLM 思考。
@@ -18,7 +22,8 @@ owner_repo: Optima-Chat/optima-gen
18
22
  | 有视频 + 训练意图("训这个人"/"克隆"/"做 AI X") | [references/train.md](references/train.md) |
19
23
  | 已有分身 + 要做视频("用张医生说..."/"用我训的分身...") | [references/generate.md](references/generate.md) |
20
24
  | 没指定主体但要"数字人/口播/真人讲解" | [references/generate.md](references/generate.md) → "选公共 avatar" 分支 |
21
- | **要改已有视频的某段**("改第 N 段 / 重做第 2 段 / 刚才那条改一句话") | [references/edit.md](references/edit.md) |
25
+ | **要改已有视频的某段**("改第 N 段 / 重做第 2 段 / 刚才那条改一句话") | [references/edit.md](references/edit.md) **§1.5 state check** —— 实际走 generate.md §6.6 还是 edit.md §4 由 video 的 `meta.md` "当前版本" state 决定,**不靠用户措辞猜** |
26
+ | **出片确认** / **续接未完成视频**("都 OK 出视频 / 继续 X 视频 / 刚才那条出片") | [references/generate.md §6.5](references/generate.md)(重展音频试听 checkpoint)/ §6.8(跨 session 续接,自动识别 `(audio generating)` / `(audio-review pending)` / `(rendering)` 各 state) |
22
27
  | "我的分身有哪些"/"我的视频有哪些"/"X 训过吗"/"改名"/"删了 X" | [references/manage.md](references/manage.md) |
23
28
 
24
29
  公共 avatar / voice 清单见 [references/avatar-catalog.md](references/avatar-catalog.md)。
@@ -39,9 +44,10 @@ owner_repo: Optima-Chat/optima-gen
39
44
  ├── meta.md # 这条视频的 avatar / 总文本 / segments 表
40
45
  ├── segments/
41
46
  │ ├── seg-NN.txt # 这一段的文本
42
- │ └── seg-NN.mp4 # 这一段的成片
43
- ├── final-v1.mp4 # concat 出来的初版
44
- ├── final-vN.mp4 # 改段后重 concat 的新版(最多 10 个)
47
+ │ └── seg-NN.wav # 这一段的音频(voice preview 出,逐段试听用)
48
+ ├── full-audio.mp3 # 确认后拼接的整条音频(驱动渲染)
49
+ ├── final-v1.mp4 # 单条音频驱动渲染的初版
50
+ ├── final-vN.mp4 # 改段后重渲的新版(最多 10 个)
45
51
  └── history.md # 迭代记录
46
52
  ```
47
53
 
@@ -74,7 +80,7 @@ owner_repo: Optima-Chat/optima-gen
74
80
  | video-id | 20260515-1430 |
75
81
  | avatar | 张医生 (external_id: dh-a7f2k9m1) |
76
82
  | 创建时间 | 2026-05-15 14:30 |
77
- | 当前版本 | final-v1.mp4 |
83
+ | 当前版本 | final-v1.mp4 | ← 见下方"当前版本" 4 阶段 state machine
78
84
 
79
85
  ## 总文本
80
86
 
@@ -82,11 +88,22 @@ owner_repo: Optima-Chat/optima-gen
82
88
 
83
89
  ## Segments
84
90
 
85
- | seg | text | 时长 | 文件 | 最后更新 |
91
+ | seg | text | 音频时长 | 音频文件 | 最后更新 |
86
92
  |---|---|---|---|---|
87
- | 01 | "..." | ~Xs | segments/seg-01.mp4 | <时间> |
93
+ | 01 | "..." | ~Xs | segments/seg-01.wav | <时间> |
88
94
  ```
89
95
 
96
+ **"当前版本"字段 = 4 阶段 state machine**(音频先行,详见 [generate.md §5 / §6.5 / §7](references/generate.md)):
97
+
98
+ | state | 何时写入 | 含义 |
99
+ |---|---|---|
100
+ | `(audio generating)` | generate.md §5(初版 placeholder) | 音频段还在生成,跨 session 续接靠这个 marker |
101
+ | `(audio-review pending)` | generate.md §6.5(进音频试听 checkpoint) | 音频段都生成完,等用户逐段试听确认 |
102
+ | `(rendering)` | generate.md §7(确认后,拼音频 + 单条渲染中) | 已确认,视频渲染中 |
103
+ | `final-vN.mp4` | generate.md §7(渲染成功后) | 已出片,可正常 edit.md 主流程改段 |
104
+
105
+ `manage.md` 列视频时按 state 显示 ✅ / ⏳ / 🎬 / 🚧;`edit.md` §1.5 按 state 路由(state 即 routing source-of-truth)。
106
+
90
107
  **不写 UUID** —— meta.md 是用户视野;voice_id / avatar_id 需要时 LLM `gen avatar profile` 反查。
91
108
 
92
109
  ### `training/<external_id>/train-meta.md` 模板
@@ -141,46 +158,143 @@ owner_repo: Optima-Chat/optima-gen
141
158
  4. **Anti-fabrication**:命令 / flag / 端点必须用本 skill 列出的精确版本。CLI sub-command / API 端点不允许凭印象拼。**特别注意**:`gen avatar video` 要 `--avatar` + `--voice` 两个 UUID(不支持 `--external-id` 自动反查,必须先 `gen avatar profile` 拿 IDs)。
142
159
  5. **用户向不暴露上游品牌**:见上面"用户向输出原则"。
143
160
  6. **限制硬约束**(来自 spec §13):
144
- - 分身合成视频 text ≤ 50 字 / 输出 ≤ 10 秒(spec §13.3)
161
+ - ~~分身合成视频 text ≤ 50 字 / 输出 ≤ 10 秒~~ **已取消**:那是旧的逐段 text-driven 视频约束;音频先行下成片是**单条音频驱动渲染**,实测能吃长文本(69s+),无 50 字限制。拆段只为逐段试听粒度。
145
162
  - 视频 ≥ 30 秒(voice 训练用),建议 2-5 分钟(avatar 质量,spec §13.5)
146
163
  - 单声道音频质量好(噪声 / 多人混杂会 fail)
164
+ 7. **沙箱 bash 须知(issue #183)**:agent-runtime 的 bash 是安全沙箱,**软拒绝** `$( )` 命令替换、`for`/`while` 循环、命令内换行、`;`/`&&`/`||` 串多条、`> file`/`>>` 重定向、`| jq`/`| sed`/`| grep`/`| wc` 管道、`nohup &`。所以**取值 / 判断 / 写文件全走 LLM tool_use**:用 **Read tool** 读文件 / 把内容内联进命令,用 **Write/Edit tool** 写文件,从单条 CLI 的 stdout JSON 里**自己读字段**(不 `$()`/`jq` 截),并发靠**一轮发多条单命令**(不写 `for`/`nohup &`)。详见 [generate.md §0](references/generate.md) + [edit.md 顶部沙箱须知](references/edit.md)。**最严重后果**:撞 soft-deny 后回退去手搓 `ffmpeg -f concat` 拼音频 / 拼视频——**绝对禁止**,永远用 `gen voice concat`(这就是 #183 的根因)。
147
165
 
148
166
  ## 限制 + 失败模式(来自 spec §13)
149
167
 
150
168
  | 限制 | 说明 |
151
169
  |---|---|
152
- | text ≤ 50 / 输出 ≤ 10 秒 | prompt 在 trained avatar 上挂死,client 端拆段(spec §13.3 |
170
+ | ~~text ≤ 50 字~~ 已取消 | 音频先行单条渲染能吃长文本(旧约束是逐段 text-driven 才有;spec §13.3 对单条 audio-driven 不成立,已实证) |
153
171
  | 单视频 4 looks 只用第一个 | 上游服务未暴露 list-looks endpoint(spec §13.5,v1.1 issue #54) |
154
172
  | trained look "still processing" 短期 race | adapter 自动 retry,30min 失败降级公共 avatar(spec §13.2) |
155
173
  | 背景 baked | video gen `background` 参数无效,训练时必须干净背景(spec §13.4) |
156
174
  | consent | API 不强制,产品上自行做合规(医院授权书 / 法律协议)(spec §13.6) |
157
- | 跨设备同步 | workspace `~/digital-human/avatars.md` 本地,换设备看不到(已知妥协,spec [naming-ownership](https://github.com/Optima-Chat/optima-agent/blob/main/docs/superpowers/specs/2026-05-14-digital-human-naming-ownership.md) 是 must-have 时的恢复路径) |
175
+ | 跨设备同步 | workspace `~/digital-human/avatars.md` 本地,换设备看不到(已知妥协,spec [naming-ownership](../../../docs/superpowers/specs/2026-05-14-digital-human-naming-ownership.md) 是 must-have 时的恢复路径) |
158
176
 
159
177
  ## 不在范围
160
178
 
161
179
  - ❌ Multi-persona 一次训(一个 external_id 对应一次 onboard)
162
180
  - ❌ Background remove / matting(让用户重拍干净背景或自己 preprocess)
163
- - ❌ Long video gen(text > 50 字让用户拆段)
164
181
  - ❌ License / consent 流程(产品层做,不在 skill 内)
165
182
  - ❌ 跨设备 workspace 同步
166
183
  - ❌ 团队共享 / 跨用户分身
167
184
  - ❌ mix-and-match 主流程支持(见 [generate.md "advanced 参考实现"](references/generate.md))
168
185
 
186
+ ## Contract for business-layer wrappers
187
+
188
+ 业务层(例 sibada-doctor / 其他)可以基于 digital-human 包 wrapper SKILL。本节文档化**稳定契约**,wrapper 可以安全 depend on。
189
+
190
+ > **⚠️ v2 破坏性变更(skill 2.0.0 / gen-cli 2.3.0)**:音频先行流程把成片从"逐段 mp4 + `gen video stitch` 拼接"换成"逐段音频试听 + `gen voice concat` + `gen avatar video --audio` 单条渲染"。受影响的稳定面:`segments/seg-NN.mp4` → `seg-NN.wav` + `full-audio.mp3`;`gen video stitch`(仍在 CLI,但不再是 digital-human 出片路径);state machine 取值。Mode A wrapper(reference 本 skill reference 文档)自动获得新流程;Mode B wrapper(直接编排 CLI)需迁移到新命令。
191
+
192
+ ### 稳定 CLI surface(`@optima-chat/gen-cli ≥ 2.3.0`)
193
+
194
+ | 命令 | 用途 | 稳定性承诺 |
195
+ |---|---|---|
196
+ | `gen avatar train` | 训练 avatar look | flag 名 + 必填项稳定 |
197
+ | `gen avatar onboard` | 并发训 voice+avatar | flag 名 + 必填项稳定 |
198
+ | `gen avatar profile` | 查分身 profile | 返回 JSON 字段 `heygen_voice_id` / `heygen_avatar_look_ids` 稳定 |
199
+ | `gen voice preview` | 逐段出音频试听(纯音频,不烧视频额度) | `--voice / --text / --output` + 可选 `--text-type` 稳定(**无** `--voice-speed`/`--voice-emotion`) |
200
+ | `gen voice concat` | 多段音频拼成整条(concat filter 重编码) | `--workspace-dir / --inputs / --out` 稳定;返回 JSON `output_path / duration_sec / input_count` 稳定 |
201
+ | `gen avatar video` | 用训练分身合成 | `--avatar` + (音频驱动 `--audio` \| 文本驱动 `--voice / --text`) + `-o, --output / --title` 稳定 |
202
+ | `gen video --provider heygen` | 用任意 heygen avatar 合成(含公共 avatar) | `--avatar / --voice / --text / -o, --output` + 可选 `--voice-emotion / --bg-color / --caption / --title / --avatar-style / --voice-speed / --bg-image` 稳定 |
203
+ | `gen video stitch` | 多段 mp4 拼接(仍存在,但 digital-human v2 出片**不用**它) | `--workspace-dir / --inputs / --out / --codec` 稳定 |
204
+ | `gen voice clone` | 低层 voice 训练 | flag 稳定 |
205
+
206
+ 注:`gen doctor *` 是 `gen avatar *` 的兼容性别名,wrapper **应该用 `gen avatar`**,不用 doctor(虽然两者等价)。
207
+
208
+ ### 稳定 workspace 布局
209
+
210
+ ```
211
+ ~/digital-human/
212
+ ├── avatars.md # 用户分身索引
213
+ ├── training/<external_id>/ # 每分身训练素材
214
+ │ ├── source-video.mp4
215
+ │ ├── source-audio.wav
216
+ │ ├── train-meta.md
217
+ │ └── preview.jpg
218
+ └── videos/<YYYYMMDD-HHMM>/ # 每条视频 workspace
219
+ ├── meta.md
220
+ ├── segments/seg-NN.{txt,wav}
221
+ ├── full-audio.mp3
222
+ ├── final-vN.mp4
223
+ └── history.md
224
+ ```
225
+
226
+ wrapper 可以:
227
+ - 在 `videos/<id>/` 下加 wrapper 自己的文件(例 `sibada-meta.md` 存 hospital/department 字段),只要不重名 + 不动 digital-human 自己的文件
228
+ - 解析 `meta.md` 取分身、segments、当前版本(state machine);**不**写 meta.md 字段(LLM agent 才写)
229
+
230
+ ### 稳定 `meta.md` schema
231
+
232
+ "当前版本"字段 = 4 阶段 state machine(音频先行,详见 [audio-first-flow SPEC](docs/2026-05-30-digital-human-audio-first-flow.md)):
233
+
234
+ | state | 含义 |
235
+ |---|---|
236
+ | `(audio generating)` | 音频段还在生 |
237
+ | `(audio-review pending)` | 音频段都生完,等用户逐段试听确认 |
238
+ | `(rendering)` | 已确认,拼音频 + 单条渲染中 |
239
+ | `final-vN.mp4` | 已出片 |
240
+
241
+ wrapper depending on this schema:**字段名 / 表结构 / state machine 取值** 都稳定(v2 取值已换,见顶部破坏性变更说明);新增字段会向后兼容(只加不删)。
242
+
243
+ ### 两种集成模式
244
+
245
+ **Mode A: SKILL composition with pre/post hooks(推荐)**
246
+
247
+ wrapper SKILL 在自己 `references/*.md` 流程里 reference digital-human:
248
+ - (wrapper) 验证业务前置 → 走 [train.md](references/train.md) 训练 → (wrapper) post-hook 写业务 DB
249
+ - (wrapper) 业务上下文 → 走 [generate.md](references/generate.md) 生成 → (wrapper) post-hook 写 video task 到 DB
250
+ - (wrapper) 改段触发 → 走 [edit.md](references/edit.md) → (wrapper) post-hook 更新 DB
251
+
252
+ LLM 跟 wrapper SKILL 主流程,**穿插**调用 digital-human 各 reference。digital-human 内部不感知 wrapper 存在。复用完整逻辑(不重写),只插 hook;升级 digital-human 自动获益。
253
+
254
+ **Mode B: Direct CLI primitives orchestration**
255
+
256
+ wrapper 不 reference digital-human SKILL,直接编排 CLI:`gen avatar onboard ... → gen voice preview ...(逐段)→ gen voice concat ... → gen avatar video --audio ...`。wrapper 自己定义文件布局 / state。适用流程跟 digital-human 差异大的场景。
257
+
258
+ ### 不稳定项(wrapper 不应 depend on)
259
+
260
+ - 内部 bash 步骤编号 / 章节号(`§6.5` 等):随实施迭代会变
261
+ - `history.md` 行格式 / `[audio-review]` / `[v1]` 标签:可能演化
262
+ - 错误码 `code` 稳定,`message` 文案可能改
263
+ - `segments/seg-NN.wav` / `full-audio.mp3` 内部码率 / 编码参数:跟上游返回 + concat 编码有关
264
+ - LLM user-facing 话术:产品 UX 调整时会改
265
+
266
+ ### Versioning
267
+
268
+ - `@optima-chat/gen-cli`:semver,major 破坏性变更才升(删 flag);minor 加 primitive / flag(向后兼容);patch 修 bug
269
+ - workspace 布局 + meta.md schema:跟随 optima-agent skill 包版本;major 升才破坏,minor 只加不删
270
+
271
+ wrapper SKILL 应在自己 SKILL.md 声明:
272
+ ```
273
+ 依赖:
274
+ - @optima-chat/gen-cli ≥ 2.3.0 (含 voice preview / voice concat / avatar video --audio)
275
+ - @optima-chat/optima-agent ≥ <含 digital-human 2.0.0 的包版本> (音频先行 + wrapper contract)
276
+ ```
277
+
169
278
  ## 相关 / 参考
170
279
 
171
280
  - **Backend**:`optima-gen/packages/generation`(adapters / routes / db)
172
- - **CLI**:`@optima-chat/gen-cli ≥ 2.0.0`,命名空间分两层:
173
- - **高层 facade**(本 skill 主用):`gen avatar onboard / video / profile`(一次性训 voice+avatar + 用训练分身合成 + profile)
281
+ - **CLI**:`@optima-chat/gen-cli ≥ 2.3.0`(含 `gen voice preview` / `gen voice concat` / `gen avatar video --audio`),命名空间分层:
282
+ - **高层 facade**(本 skill 主用):`gen avatar onboard / profile`( + 查) + `gen avatar video --audio`(音频驱动单条出片)
283
+ - **音频先行**:`gen voice preview`(逐段试听音频)/ `gen voice concat`(拼整条音频),本 skill `generate.md §6 / §7` 和 `edit.md §5 / §6` 用
284
+ - **视频处理**:`gen video --provider heygen`(任意 heygen avatar 单条合成,含公共 avatar 路径)/ `gen video stitch`(多段 mp4 拼接,仍在但 digital-human v2 不用)
174
285
  - **低层**(单独操作):`gen avatar train`(仅训 avatar 不训 voice) / `gen voice clone`(仅训 voice)
175
286
  - **辅助**:`gen task get/cancel/list`
176
287
  - **兼容性别名**:`gen doctor onboard / video / profile` 仍可用,跟 `gen avatar *` 行为等价;本 skill 统一用新名
177
288
  - **Spec**:[optima-doctor-avatar §13](https://github.com/Optima-Chat/optima-doctor-avatar/blob/main/docs/superpowers/specs/2026-05-07-doctor-avatar-poc-design.md#13-phase-1-probe-findings-2026-05-08)
178
289
  - **v1.1 follow-up**:[optima-gen issue #54](https://github.com/Optima-Chat/optima-gen/issues/54)
179
290
  - **设计 SPEC**:
180
- - [skill 归并](https://github.com/Optima-Chat/optima-agent/blob/main/docs/superpowers/specs/2026-05-14-digital-human-skill-consolidation.md)
181
- - [workspace 命名](https://github.com/Optima-Chat/optima-agent/blob/main/docs/superpowers/specs/2026-05-14-digital-human-workspace-naming.md)
182
- - [segment 持久化](https://github.com/Optima-Chat/optima-agent/blob/main/docs/superpowers/specs/2026-05-15-digital-human-segment-persistence.md)
183
- - **相关 skill**:`video-gen`(产品视频;与本 skill 互斥 — 见两边 description)
291
+ - **[音频先行流程 (v2,当前权威)](docs/2026-05-30-digital-human-audio-first-flow.md)** —— 逐段音频试听 + 单条音频驱动渲染,取代旧的逐段视频 + stitch(在 optima-gen 仓)
292
+ - [skill 归并](../../../docs/superpowers/specs/2026-05-14-digital-human-skill-consolidation.md)
293
+ - [workspace 命名](../../../docs/superpowers/specs/2026-05-14-digital-human-workspace-naming.md)
294
+ - [segment 持久化](../../../docs/superpowers/specs/2026-05-15-digital-human-segment-persistence.md)
295
+ - [review checkpoint](../../../docs/superpowers/specs/2026-05-16-digital-human-segment-review-checkpoint.md)
296
+ - [gen video stitch primitive + wrapper contract](../../../docs/superpowers/specs/2026-05-16-gen-video-stitch-primitive.md)
297
+ - **相关 skill**:`video-gen`(产品视频;与本 skill 互斥 — 见两边 description)
184
298
 
185
299
  ## 磁盘提示
186
300
 
@@ -1,19 +1,24 @@
1
- # Edit Segment(段落编辑)
1
+ # Edit Segment(出片后改某段)
2
2
 
3
- 只重生某一段,不重做整条视频。**核心价值**:省 70-90% credits + 时间。
3
+ 改**已出片**视频的某一句:重生那段音频 重拼整条 整条重渲染。
4
+
5
+ > ⚠️ **沙箱 bash 须知**(同 [generate.md §0](generate.md)):agent-runtime 的 bash 是安全沙箱,**软拒绝** `$( )` 命令替换、`for`/`while` 循环、命令内换行、`;`/`&&`/`||` 串多条、`> file` 重定向、`| jq`/`| sed`/`| grep` 管道、`nohup &`。所以本文档的所有"取值 / 判断 / 写文件"都靠 **LLM tool_use**(Read / Write / Edit tool + 单条命令读 JSON 输出),**不靠 shell 拼**。下面 bash 块都是**单条命令**;凡需要变量 / 文件内容,LLM 先用 Read tool 读、把值**内联**进命令,不存 `$VAR`。
6
+
7
+ > **音频先行下的成本模型**(跟旧版不同,务必理解):
8
+ > - 改音频(重生那段 `seg-NN.wav`)= **免费 / 极廉**(voice preview 不烧视频额度)。
9
+ > - 但成片是**单条连续渲染**的整条视频,**没法只换一段画面**——改任意一句都要**整条重渲一次**(烧一次视频额度)。
10
+ > - 所以**绝大多数修改应该在出片前的音频试听阶段**([generate.md §6.5](generate.md))就改掉(那时改音频免费、还没渲染)。走到 edit.md 说明**成片后才发现要改**——能做,但要重渲一次,提醒用户。
4
11
 
5
12
  ## 触发
6
13
 
7
14
  用户说类似:
8
15
  - "把刚才那条视频的第 2 段改成 X"
9
- - "重做第 2 段"
16
+ - "重做第 2 段" / "seg-3 重新生成"
10
17
  - "刚才那条改一句话"
11
- - "seg-3 重新生成"
12
- - "第 N 段不对,改成 ..."
13
18
 
14
19
  不在范围(走别的流程):
15
- - "换 avatar"(换分身)→ 这等于新视频,走 [generate.md](generate.md)
16
- - "改总文本"(全部重写)→ 反问"要全新生成还是只改某段?"
20
+ - "换 avatar"(换分身)→ 等于新视频,走 [generate.md](generate.md)
21
+ - "改总文本"(全部重写)→ 反问"要全新生成还是只改某句?"
17
22
  - "video 比例 / 时长 / 字幕等元参数"→ trained avatar 不支持,反问
18
23
 
19
24
  ## 流程
@@ -23,23 +28,55 @@
23
28
  - 用户说"刚才那条" / "刚做的" / 不指定 → `ls -t ~/digital-human/videos/ | head -1` 取最新
24
29
  - 用户给了 video-id(例 `20260515-1430`)→ 直接定位
25
30
  - 用户给文本片段或主题 → 遍历 `videos/*/meta.md` "总文本"段模糊匹配
26
- - 0 个匹配反问 "没找到对应视频,你要找哪条?(列最近 3 条让用户选)"
27
- - 多个匹配 → 反问消歧
31
+ - 0 / 多个匹配 反问消歧
28
32
 
29
33
  如果 video 是 SPEC 落地前(无 workspace 目录)生成的 → 反问 "这条视频没有 workspace 记录,只能重新生成整条。要走 [generate.md](generate.md) 全新创建吗?"
30
34
 
35
+ ### 1.5 State check(找到 video-id 后必走)
36
+
37
+ **Step 0: 旧流程 / 无分段视频检测(必须在 state 路由之前跑)**
38
+
39
+ v2(音频先行)跟 v1(逐段视频 + stitch)**不兼容**。prod 上可能有 v1 时期生成的视频(#118 的卡顿就是 v1 在用)。镜像升到 2.0.0 后,这些旧视频若流进新的音频改段路径会**静默出错**:`gen voice concat` 只扫 `seg-*.{wav,mp3}`,旧的 `seg-*.mp4` 段被排除 → 只剩重生那一段 → 渲出**只含被改段**的残片。所以先挡。
40
+
41
+ **两条单命令查 + LLM 判断**(别写 `if`/`grep -q`/`$()`,沙箱拦):
42
+
43
+ ```bash
44
+ ls ~/digital-human/videos/<VIDEO_ID>/segments/
45
+ ```
46
+
47
+ 再 Read `~/digital-human/videos/<VIDEO_ID>/meta.md` 看"当前版本"字段。然后 LLM **自己判断**(不靠 shell 条件):
48
+
49
+ - **`IS_OLD_FLOW`**(命中任一):`segments/` 里出现 `seg-*.mp4` 段文件,**或** meta.md "当前版本"是旧字面量 `(generating)` / `(review pending)`。
50
+ - **`NO_SEGMENTS`**:没有 `segments/` 目录,或目录里没有任何 `seg-*.txt`(公共形象单条渲染不产分段)。
51
+
52
+ | 命中 | 行为(user-facing,不报 §-编号) |
53
+ |---|---|
54
+ | `IS_OLD_FLOW` | 反问:"这条是旧流程(逐段视频)生成的,改某句得整条重新生成——新流程是音频驱动单条渲染,跟旧的逐段视频不兼容。要用 `<meta 里的分身>` + 原文重新生成吗?" 用户同意 → 走 [generate.md](generate.md)(复用 meta.md "总文本",不再问 text)。**绝不**让它流进下面的 `final-vN.mp4` 改段分支。 |
55
+ | `NO_SEGMENTS`(且非 old-flow) | 反问:"这条是公共形象单条生成的,没有分段,改内容要整条重新生成,要吗?" 同意 → [generate.md](generate.md) 公共分支。 |
56
+
57
+ 两者都不命中 → 继续下面 state 路由。
58
+
59
+ Read `meta.md` 取"当前版本"字段,**按 state machine 分支**(state 即路由 source-of-truth,不靠用户 phrasing 猜):
60
+
61
+ | "当前版本" 值 | 行为 |
62
+ |---|---|
63
+ | `final-vN.mp4` | ✅ 已出片,走本文档 §2-§8 主流程(重渲产 final-v<N+1>.mp4) |
64
+ | `(audio-review pending)` | ⏳ **跳出 edit.md,跳转 [generate.md §6.5 checkpoint](generate.md)** —— 还没出片,用户说"改 seg-N"是 [§6.6 出片前改音频](generate.md)(免费、不渲染) |
65
+ | `(audio generating)` | 🚧 音频段还没生完。**反问**:"这条还在生成音频,要先把剩余段生完吗?(生成中不稳定,没法改)" 用户同意 → 跳 [generate.md §6.8](generate.md) 续生。**话术不出现内部 §-编号** |
66
+ | `(rendering)` | 🎬 视频正在渲染。反问"这条正在出片,等渲染完再改好吗?"——渲染中改段没意义 |
67
+
68
+ **为什么硬分支**:同一句"改 seg-N",在不同 state 下正确动作完全不同。出片前(audio-review pending)改音频免费、不渲染;出片后(final-vN)改要整条重渲。不 state-aware 会乱花额度或产出不存在的版本引用。
69
+
31
70
  ### 2. 读现状
32
71
 
72
+ 用 **Read tool** 读 `~/digital-human/videos/<VIDEO_ID>/meta.md` 和 `~/digital-human/videos/<VIDEO_ID>/history.md`(别 `cat`),再单条 `ls` 看分段:
73
+
33
74
  ```bash
34
- cd ~/digital-human/videos/$VIDEO_ID
35
- cat meta.md
36
- cat history.md
37
- ls segments/
75
+ ls ~/digital-human/videos/<VIDEO_ID>/segments/
38
76
  ```
39
77
 
40
78
  LLM 给用户回顾:
41
- > "找到 `<VIDEO_ID>`(分身: 张医生,4 段,当前版本 final-v2.mp4)
42
- > 想改哪一段?现有:
79
+ > "找到 `<VIDEO_ID>`(分身: 张医生,4 句,当前 final-v2.mp4)。想改哪一句?现有:
43
80
  > - seg-01: '大家好,我是张医生。'
44
81
  > - seg-02: '今天介绍一款 ...'
45
82
  > - seg-03: '它能帮你 ...'
@@ -47,14 +84,12 @@ LLM 给用户回顾:
47
84
 
48
85
  ### 3. 判断改的是什么
49
86
 
50
- 用户给意图后,分支:
51
-
52
87
  | 改的是 | 处理 |
53
88
  |---|---|
54
- | 单段 text | 走 §4 (主流程) |
55
- | 多段 text | 逐段确认 + 逐段走 §4 |
89
+ | 单段 text | 走 §4(主流程) |
90
+ | 多段 text | 逐段确认 + 逐段走 §4 的重生音频,最后**只重渲一次**(§6 合并重渲) |
56
91
  | 换 avatar / voice | 反问 "这等于新视频,本段流程不支持。要走 [generate.md](generate.md) 用 <新分身> 全新生成吗?" |
57
- | 段间顺序 | 反问 "重排顺序需要重 concat,但本 skill v1 不支持此功能。要手动 mv segments/* 改名后我帮你重 concat 吗?" |
92
+ | 段间顺序 | 反问 "重排需要重拼音频 + 重渲,本 skill v1 不支持。要手动 mv segments/* 改名后我帮你重拼重渲吗?" |
58
93
  | 段时长 / 字幕 | trained avatar 不支持调时长 / 字幕参数,reply 不支持 |
59
94
 
60
95
  ### 4. text 改动(主流程)
@@ -65,123 +100,107 @@ LLM 给用户回顾:
65
100
  # 伪代码
66
101
  old_text = read("segments/seg-<N>.txt")
67
102
  new_text = <用户给的新 text>
68
-
69
103
  edit_ratio = levenshtein(old_text, new_text) / max(len(old_text), len(new_text))
70
104
 
71
105
  if edit_ratio > 0.3:
72
- # 大改 → 反问确认
73
- ask_user(f"确认改 seg-<N>?\n 旧: {old_text}\n 新: {new_text}\n (credits 花了不可逆)")
106
+ # 大改 → 反问确认(注意:这里要重渲整条视频,会烧一次额度)
107
+ ask_user(f"确认改 seg-<N>?\n 旧: {old_text}\n 新: {new_text}\n (改音频免费,但成片要整条重渲一次)")
74
108
  if not confirmed:
75
109
  return
76
- # else (≤30%): typo/标点微调,静默继续不打断 flow
110
+ # else (≤30%): typo/标点微调,静默继续
77
111
  ```
78
112
 
79
- `edit_ratio` 计算:Levenshtein 距离 / max(旧长度, 新长度)。30% 阈值的依据:
80
- - 改一个错别字 / 加个标点 / 换"了"为"啦" → < 5%,静默
81
- - 改半句话或换语序 20-40%,触发确认
82
- - 整段重写 → > 50%,肯定确认
113
+ 30% 阈值依据:错别字 / 标点 < 5% 静默;半句话 / 换语序 20-40% 触发确认;整段重写 > 50% 肯定确认。无 levenshtein 工具时用粗估 fallback。
114
+
115
+ ### 5. 覆盖 segments/seg-NN.txt + 重生 seg-NN.wav(只重生音频,免费)
116
+
117
+ 设改的是第 N 段(2 位零填充,例 `02`)。三步,全走 tool_use / 单条命令:
118
+
119
+ 1. **覆盖 text**:用 **Write tool** 把新文本写进 `~/digital-human/videos/<VIDEO_ID>/segments/seg-02.txt`(别 `echo >`)。
120
+ 2. **反查 voice_id**(meta.md 不存 UUID):Read `meta.md` 自己读出 external_id(形如 `dh-xxxxxxxx`,或老格式 `external_id: ...` 行),**不用 grep/sed**。再单条:
121
+ ```bash
122
+ gen avatar profile dh-xxxxxxxx
123
+ ```
124
+ 从它 stdout 的 JSON 里读 `heygen_voice_id`(出片要用的 `heygen_avatar_look_ids[0]` 也一并记下,§6 要),**别 `$()`/`jq` 截**。
125
+ 3. **只重生这一段音频**(免费 / 极廉,不烧视频额度;新文本**内联**进 `--text`,你手里就有):
126
+ ```bash
127
+ gen voice preview --voice <heygen_voice_id> --text "<新 text>" --output ~/digital-human/videos/<VIDEO_ID>/segments/seg-02.wav
128
+ ```
83
129
 
84
- 如果 LLM 没有 levenshtein 工具,可以用粗估:`abs(len(old) - len(new)) / max(len) + diff_count / len > 0.3` 当 fallback。
130
+ **其他段音频不动**。多段改:对每个改的段各发**一条** `gen voice preview`(LLM 并行多条,不写 `for`/`nohup`),然后 §6 **只重渲一次**。
85
131
 
86
- ### 5. 覆盖 segments/seg-NN.txt + 重生 seg-NN.mp4
132
+ > 可选:让用户先**听**新的 seg-N 音频确认,再 §6 重渲(省得渲完又不满意)。强烈推荐多段改 / 大改时先听。
133
+
134
+ ### 6. 重拼音频 → 整条重渲 → final-v<下一版本>.mp4
135
+
136
+ **版本号计算 + 上限清理**(LLM 看 `ls` 输出自己数,别 `sed`/`sort`/`wc`/`$(())`):
87
137
 
88
138
  ```bash
89
- N="02" # 用户改的段号,2 位零填充
90
- echo "<新 text>" > segments/seg-$N.txt
91
-
92
- # 从 meta.md 提取 external_id (avatar 字段含 "external_id: dh-XXXXXXXX")
93
- EXTERNAL_ID=$(grep -oE 'dh-[a-z0-9]{8}' meta.md | head -1)
94
- if [ -z "$EXTERNAL_ID" ]; then
95
- # 存量 slug 兜底 (例 dr-wang),从 meta.md 表里抽
96
- EXTERNAL_ID=$(grep -oE 'external_id: [a-z0-9_-]+' meta.md | sed 's/external_id: //' | head -1)
97
- fi
98
-
99
- # 反查 voice_id + avatar_id (meta.md 不存 UUID,profile 反查)
100
- PROFILE=$(gen avatar profile "$EXTERNAL_ID")
101
- VOICE_ID=$(echo "$PROFILE" | jq -r '.heygen_voice_id')
102
- AVATAR_ID=$(echo "$PROFILE" | jq -r '.heygen_avatar_look_ids[0]')
103
-
104
- # 只重生这一段
105
- gen avatar video --avatar "$AVATAR_ID" --voice "$VOICE_ID" \
106
- --text "$(cat segments/seg-$N.txt)" \
107
- -o "segments/seg-$N.mp4"
139
+ ls ~/digital-human/videos/<VIDEO_ID>/final-v*.mp4
108
140
  ```
109
141
 
110
- **其他段的 mp4 不动**(mtime 保持原样,credits 0 消耗)。
142
+ - LLM 从输出里找最大版本号 `LAST`(例已有 final-v1/v2 LAST=2),下一版 `NEXT = LAST + 1`(=3)
143
+ - **决策表 #12 上限 10**:输出里 final-v*.mp4 已有 **≥ 10 个** → 删最老的那个(单条命令、路径写全):
144
+ ```bash
145
+ rm ~/digital-human/videos/<VIDEO_ID>/final-v1.mp4
146
+ ```
111
147
 
112
- ### 6. 重 concat → final-v<下一版本>.mp4
148
+ **重拼音频 + 单条重渲**(两条单命令,值内联):
113
149
 
114
- **版本号计算 + 上限清理**:
150
+ ```bash
151
+ gen voice concat --workspace-dir ~/digital-human/videos/<VIDEO_ID>/ --out ~/digital-human/videos/<VIDEO_ID>/full-audio.mp3
152
+ ```
115
153
 
116
154
  ```bash
117
- # 找当前 final-v* 的最高版本号
118
- LAST_V=$(ls final-v*.mp4 2>/dev/null | sed 's/final-v\(.*\).mp4/\1/' | sort -n | tail -1)
119
- NEXT_V=$((LAST_V + 1))
120
-
121
- # 决策表 #12: 上限 10,第 11 次写 → 静默删 final-v1.mp4
122
- COUNT=$(ls final-v*.mp4 2>/dev/null | wc -l)
123
- if [ "$COUNT" -ge 10 ]; then
124
- OLDEST=$(ls final-v*.mp4 | sort -V | head -1)
125
- rm "$OLDEST"
126
- fi
155
+ gen avatar video --avatar <heygen_avatar_look_ids[0]> --audio ~/digital-human/videos/<VIDEO_ID>/full-audio.mp3 -o ~/digital-human/videos/<VIDEO_ID>/final-v<NEXT>.mp4
127
156
  ```
128
157
 
129
- **ffmpeg concat 两步走 + 透明告知**:跟 [generate.md §7](generate.md) 同款,输出文件改成 `final-v$NEXT_V.mp4`。**单源真值在 generate.md §7**,本段不复述以免 drift —— 实施时直接复用同一段 bash if/else 代码,把 `final-v1.mp4` 改成 `final-v$NEXT_V.mp4` 即可。
158
+ - `gen voice concat` 重拼整条音频(各段已含新的 seg-NN.wav,自动扫 `segments/seg-*` 排序拼接)。**🔴 严禁手搓 `ffmpeg -f concat`**——即使撞 soft-deny 也绝不回退(同 [generate.md §7](generate.md))。
159
+ - `<heygen_avatar_look_ids[0]>` 用 §5 第 2 步 `gen avatar profile` 输出里读到的值内联;`<NEXT>` 用上面算出的下一版本号。
130
160
 
131
- fallback 重编码时,后续 §8 报告也必须明示"段间 codec 兼容问题,自动重编码合成"
161
+ > 没有 `gen video stitch` 那一步了——音频先行下成片是单条渲染,不是拼接多段 mp4
162
+
163
+ **失败处理**:`gen avatar video --audio` 返回 `音频驱动 doctor-video 暂未开放` → 后端音频驱动未启用(灰度状态,不是用户错误)→ 告知"稍后可用",不重试。其他失败看 `error_message`,音频已拼好可直接重跑渲染。
132
164
 
133
165
  ### 7. 更新 meta.md + history.md
134
166
 
135
- **meta.md** 改两处:
136
- - 表头"当前版本"行:`final-v$NEXT_V.mp4`
137
- - Segments 表第 N 行"最后更新":`<当前时间> (v$NEXT_V)`
167
+ 都用 **Edit tool** 改(别 `echo >>` / `cat <<EOF`,沙箱拦;时间用你 LLM 知道的当前时间内联,别 `$(date)`)。
168
+
169
+ **meta.md**:表头"当前版本"→ `final-v<NEXT>.mp4`;Segments 表第 N 行"最后更新" `<当前时间> (v<NEXT>)`。
138
170
 
139
- **history.md** 末尾追加:
171
+ **history.md** 末尾用 Edit 追加一行(保留旧记录):
140
172
  ```markdown
141
- - **<当前时间>** [v$NEXT_V] 改了 seg-$N: "<旧 text>" → "<新 text>" (<编辑距离比例 X%>)
173
+ - **<当前时间>** [v<NEXT>] 改了 seg-<N>: "<旧 text>" → "<新 text>" (<编辑距离比例 X%>,重渲整条)
142
174
  ```
143
175
 
144
176
  ### 8. 报告
145
177
 
146
- > "✅ 视频更新完成: $VIDEO_DIR/final-v$NEXT_V.mp4
147
- > 改动:seg-$N 的 text 从 '<旧>' 改成 '<新>'
148
- > 其他段(seg-01/03/04)未动,只重生了 seg-$N + 重 concat。
149
- > 历史版本 final-v1 ~ final-v$LAST_V 仍在,想回退直接用旧版本。"
178
+ > "✅ 视频更新完成: ~/digital-human/videos/<VIDEO_ID>/final-v<NEXT>.mp4
179
+ > 改动:seg-<N> 从 '<旧>' 改成 '<新>',重生了该段音频 + 整条重渲一次。
180
+ > 历史版本 final-v1 ~ final-v<LAST> 仍在,想回退直接用旧版本。"
150
181
 
151
- 如果走了 ffmpeg fallback,加一句"⚠️ 段间 codec 兼容问题,自动重编码合成"。
152
- 如果触发了 final-vN.mp4 删最老的逻辑(数量 ≥ 10),加一句"📦 历史版本已满 10,自动清理了最早的 final-v$DELETED.mp4"。
182
+ 如果触发了 final-vN.mp4 删最老逻辑(数量 ≥ 10),加一句"📦 历史版本已满 10,自动清理了最早的 final-v<被删版本>.mp4"。
153
183
 
154
184
  ---
155
185
 
156
186
  ## Edge cases
157
187
 
158
188
  ### 1. 用户说"改第 N 段"但 N 不存在
159
-
160
- 例:视频只有 4 段,用户说"改第 7 段"。
161
-
162
- 反问:"这条视频只有 4 段(seg-01 ~ seg-04),没有第 7 段。要改哪一段?"
189
+ 例:只有 4 段,用户说"改第 7 段"。反问:"这条只有 4 句(seg-01 ~ seg-04),没有第 7 句。要改哪一句?"
163
190
 
164
191
  ### 2. final-v* 数量已达上限 10 + 用户改段
165
-
166
- 按 §6 自动删 final-v1.mp4(最老),保留 latest + 9 个历史。**不问用户**,在 §8 报告里告知"自动清理了 final-vX.mp4"。
192
+ 按 §6 自动删最老 final-v1.mp4,保留 latest + 9 个历史。**不问用户**,在 §8 报告里告知。
167
193
 
168
194
  ### 3. 用户改的 text 跟旧 text 完全一样(edit_ratio = 0)
169
-
170
- 反问:"新旧 text 一样,确认要重生 seg-N 吗?(会花一次 credits,但 mp4 结果可能跟旧版有细微 heygen 随机差异)"
171
- 默认推荐"不重生"。
195
+ 反问:"新旧 text 一样,确认要重渲吗?(音频会重生,韵律可能有细微差异,但要烧一次重渲额度)" 默认推荐"不改"。
172
196
 
173
197
  ### 4. 用户说"改最后一段"/"改第一段"(相对引用)
174
-
175
- LLM 解析:"最后一段"= `seg-$(ls segments/seg-*.txt | wc -l | xargs printf "%02d")`,"第一段"= seg-01。
198
+ 单条 `ls ~/digital-human/videos/<VIDEO_ID>/segments/` 看分段,LLM **数** `seg-*.txt` 的个数得到最后一段号(例 4 个 → 最后是 `seg-04`),"第一段"= `seg-01`。别用 `$(ls ... | wc -l)` 拼。
176
199
 
177
200
  ### 5. 用户改完后说"算了,用回旧版本"
178
-
179
- 回退路径:
180
- - final-v$NEXT_V.mp4 留着(磁盘占用接受)
181
- - 用户用旧版本:直接告诉 ta `~/digital-human/videos/$VIDEO_ID/final-v$LAST_V.mp4` 还在
182
- - 不做"标记 active 版本"功能(SPEC §决策表 #3:final-vN 并存)
183
-
184
- 如果用户问"怎么撤销刚才的改动" → 告诉文件路径 + 提示"meta.md 当前版本字段可以手改回旧 v 号,或者再让我改回去"。
201
+ - final-v<NEXT>.mp4 留着(磁盘占用接受)
202
+ - 告诉用户旧版本 `~/digital-human/videos/<VIDEO_ID>/final-v<LAST>.mp4` 还在
203
+ - 不做"标记 active 版本"功能(final-vN 并存)
185
204
 
186
205
  ---
187
206
 
@@ -189,10 +208,12 @@ LLM 解析:"最后一段"= `seg-$(ls segments/seg-*.txt | wc -l | xargs printf "
189
208
 
190
209
  | ❌ Mistake | ✅ 正确做法 | 为什么 |
191
210
  |---|---|---|
192
- | 改一段时重生所有段 | `gen avatar video` 一次(改的那段) | 核心价值是 credits 省 70-90%,重生所有段直接抵消 |
193
- | 改段时静默不二次确认 | edit_ratio > 30% 反问;≤30% 静默 | 大改不可逆 + credits 花了回不来 + 30% 阈值平衡频繁微调 flow |
194
- | final 覆盖旧版本(final.mp4 一直被替换) | final-vN.mp4 并存,最多 10 个 | 用户对比 / 回退;`history.md` 文字记录配套 |
195
- | 改段不更新 meta.md / history.md | 必须同步更新,meta 的"最后更新"和"当前版本"反映真实状态 | meta LLM 后续操作的单一真值,不更新下次会用错版本 |
196
- | 反查 voice/avatar 从 meta.md 读 | 永远 `gen avatar profile <external_id>` 反查 | meta.md 不存 UUID(UX 原则);profile 反查保证拿到最新 IDs |
197
- | 改段时把 video-id 改成新的 | 同一条视频改段必须复用原 video-id | video-id 是用户对这条视频的"锚",改了用户找不到原 final |
198
- | concat fallback 静默切换 | 必须在报告里说明 "自动重编码,画质轻微损失" | 用户看到画质变了会困惑,透明告知避免认知负担 |
211
+ | 出片后才让用户发现要改 | 引导用户在出片前 [音频试听阶段](generate.md) (免费、不渲染) | edit.md 改一句要整条重渲烧额度;音频阶段改免费 |
212
+ | 改一句重生所有段音频 | 仅重生改的那段 `seg-NN.wav`,其他不动 | 其他段音频不变,重拼时复用 |
213
+ | 多段改时每段都重渲一次 | 逐段重生音频,最后**只重渲一次**整条 | 视频是单条渲染,改 N 段也只渲一次 |
214
+ | `gen video stitch` / `gen avatar video --text` 出片 | `gen voice concat` + `gen avatar video --audio` | 音频先行架构下成片是单条音频驱动渲染 |
215
+ | 改段时静默不二次确认 | edit_ratio > 30% 反问(且提示要重渲);≤30% 静默 | 重渲烧额度不可逆 |
216
+ | final 覆盖旧版本 | final-vN.mp4 并存,最多 10 | 用户对比 / 回退 |
217
+ | 反查 voice/avatar meta.md 读 | 永远 `gen avatar profile <external_id>` 反查 | meta.md 不存 UUID(UX 原则) |
218
+ | 改段时把 video-id 改成新的 | 同一条视频改段复用原 video-id | video-id 是用户对这条视频的锚 |
219
+ | `--audio` 报"暂未开放"当 bug 反复重试 | 后端灰度状态,告知稍后可用 | 音频驱动有 feature flag |