@optima-chat/gen-cli 2.0.0 → 2.2.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.
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: digital-human
3
+ description: "数字人(avatar + voice clone)一站式:训练 + 生成 + 段落编辑 + 管理。触发场景:(a) 训练分身——\"训这个人/克隆这个人/做 AI 老师/做 AI 主播\";(b) 生成口播视频——\"用张医生说欢迎语/用我训的分身介绍产品\";(c) 编辑某段——\"改第 N 段 / 重做第 2 段 / 刚才那条改一句话 / seg-3 重新生成\";(d) 管理——\"我的分身有哪些 / 我的视频有哪些 / 张医生训过没 / 列我的视频\"。范围:所有\"画面里出现真人/数字人讲话\"的视频。"
4
+ version: 1.0.3
5
+ owner_repo: Optima-Chat/optima-gen
6
+ ---
7
+
8
+ # Digital Human Skill
9
+
10
+ 数字分身一站式:训练用户上传的真人视频得到 voice + avatar,后续用名字引用做口播视频。
11
+
12
+ > **用户向输出原则**:对话 / status / 报告里**不出现具体上游品牌 / 服务名**,统一用"训练服务" / "数字分身训练" / "上游" 等中性词。skill 内部 reasoning 引用(spec / probe / source code)可保留具体名称用于 LLM 思考。
13
+
14
+ ## 路由
15
+
16
+ | 用户输入 | 走哪段 |
17
+ |---|---|
18
+ | 有视频 + 训练意图("训这个人"/"克隆"/"做 AI X") | [references/train.md](references/train.md) |
19
+ | 已有分身 + 要做视频("用张医生说..."/"用我训的分身...") | [references/generate.md](references/generate.md) |
20
+ | 没指定主体但要"数字人/口播/真人讲解" | [references/generate.md](references/generate.md) → "选公共 avatar" 分支 |
21
+ | **要改已有视频的某段**("改第 N 段 / 重做第 2 段 / 刚才那条改一句话") | [references/edit.md](references/edit.md) |
22
+ | "我的分身有哪些"/"我的视频有哪些"/"X 训过吗"/"改名"/"删了 X" | [references/manage.md](references/manage.md) |
23
+
24
+ 公共 avatar / voice 清单见 [references/avatar-catalog.md](references/avatar-catalog.md)。
25
+
26
+ ## 工作空间
27
+
28
+ 所有用户私有状态在 `~/digital-human/`,分 3 部分:
29
+
30
+ ```
31
+ ~/digital-human/
32
+ ├── avatars.md # 分身索引(用户能直接看 + 改)
33
+ ├── training/<external_id>/ # 每个分身的训练素材归档
34
+ │ ├── source-video.mp4 # 用户上传的原视频副本
35
+ │ ├── source-audio.wav # 提取的 90s 单声道
36
+ │ ├── train-meta.md # 训练元数据(背景决策 / task ids / specs)
37
+ │ └── preview.jpg # 缓存的 preview 图(可选)
38
+ └── videos/<YYYYMMDD-HHMM>/ # 每条生成视频的 workspace
39
+ ├── meta.md # 这条视频的 avatar / 总文本 / segments 表
40
+ ├── segments/
41
+ │ ├── seg-NN.txt # 这一段的文本
42
+ │ └── seg-NN.mp4 # 这一段的成片
43
+ ├── final-v1.mp4 # concat 出来的初版
44
+ ├── final-vN.mp4 # 改段后重 concat 的新版(最多 10 个)
45
+ └── history.md # 迭代记录
46
+ ```
47
+
48
+ **首次训练**时 LLM 自动 `mkdir -p ~/digital-human/{training,videos}` + 创建 `avatars.md`,模板:
49
+
50
+ ```markdown
51
+ # 我的数字分身
52
+
53
+ | 名字 | external_id | 训练时间 | preview |
54
+ |---|---|---|---|
55
+
56
+ <!-- 使用说明:
57
+ - "名字"是你叫它的方式,可以改(直接改这一列就行)。
58
+ - "external_id"是系统内部 ID,格式为 `dh-` + 8 个小写字母数字,**不要改**;改了 agent 找不到对应分身,你也无法通过运维找回(因为只有这个 ID 能从后端查)。
59
+ - 训完一个分身后,这里会自动加一行。
60
+ - 删除某一行 = **对你视角的硬删除**:你失去所有引用路径,后端 orphan 数据需要联系运维直接查 DB 才能找回。删除前 agent 会二次确认。
61
+ - voice_id / avatar_id 这些底层标识不在这里,需要时 agent 会自动从后端查。
62
+ -->
63
+ ```
64
+
65
+ ### `videos/<id>/meta.md` 模板
66
+
67
+ 每次生成视频时创建:
68
+
69
+ ```markdown
70
+ # Video: <slug from text or topic>
71
+
72
+ | 字段 | 值 |
73
+ |---|---|
74
+ | video-id | 20260515-1430 |
75
+ | avatar | 张医生 (external_id: dh-a7f2k9m1) |
76
+ | 创建时间 | 2026-05-15 14:30 |
77
+ | 当前版本 | final-v1.mp4 |
78
+
79
+ ## 总文本
80
+
81
+ > <用户给的完整 text>
82
+
83
+ ## Segments
84
+
85
+ | seg | text | 时长 | 文件 | 最后更新 |
86
+ |---|---|---|---|---|
87
+ | 01 | "..." | ~Xs | segments/seg-01.mp4 | <时间> |
88
+ ```
89
+
90
+ **不写 UUID** —— meta.md 是用户视野;voice_id / avatar_id 需要时 LLM `gen avatar profile` 反查。
91
+
92
+ ### `training/<external_id>/train-meta.md` 模板
93
+
94
+ ```markdown
95
+ # Training: 张医生 (external_id: dh-a7f2k9m1)
96
+
97
+ | 字段 | 值 |
98
+ |---|---|
99
+ | display_name | 张医生 |
100
+ | external_id | dh-a7f2k9m1 |
101
+ | 训练时间 | 2026-05-15 |
102
+ | voice_task_id | uuid-xxx |
103
+ | avatar_task_id | uuid-yyy |
104
+ | 背景决策 | --clean-bg |
105
+ | 源视频时长 | 145s |
106
+ | 源视频分辨率 | 1080×1920 |
107
+ | preview_image_url | https://...preview.jpg |
108
+ ```
109
+
110
+ 用于:重训(可重用 source-video / source-audio)、debug、运维 orphan 找回。
111
+
112
+ **LLM 读 `avatars.md` 时的健全性校验**(防用户手抖改坏):
113
+
114
+ | 检查 | 错误处理 |
115
+ |---|---|
116
+ | external_id 不符 `^dh-[a-z0-9]{8}$` 且不符存量 ASCII slug 模式(`^[a-z0-9_-]+$`) | 警告"第 N 行的 external_id 看起来损坏,可能无法引用:[名字]" |
117
+ | 名字列为空 | 警告 + 建议补名字 |
118
+ | 名字列重复 | 警告"两个分身都叫 'X',下次引用会有歧义" |
119
+
120
+ 校验失败**不阻止**继续操作(用户的文件,LLM 不越界改),只发警告。
121
+
122
+ ## LLM 如何区分用户输入是名字还是 external_id
123
+
124
+ 当用户说"用 X 做视频"时:
125
+
126
+ | X 形态 | 判断 | 走流程 |
127
+ |---|---|---|
128
+ | 含中文 / 非 ASCII | 必是名字 | 在 `avatars.md` "名字"列模糊匹配 |
129
+ | 匹配 `avatars.md` "名字"列任一行 | 名字 | 取该行 external_id |
130
+ | 纯 ASCII + 形如 `dh-[a-z0-9]{8}` | external_id(新格式) | 直接走 backend profile 查 |
131
+ | 纯 ASCII + 含 `-` + 不在"名字"列 | 大概率存量 external_id(旧 slug,例 `dr-wang`) | 走 [manage.md "存量分身"](references/manage.md) 流程 |
132
+ | 纯 ASCII + 不含 `-` + 不在"名字"列 | 歧义 | 反问"是分身名字还是 ID?" |
133
+
134
+ ## Global Rules
135
+
136
+ 优先级高于任何 pipeline 步骤。违反会导致泄漏、超支或数据错误。
137
+
138
+ 1. **触发严格**:必须有视频 + 训练意图二者。**只有视频没意图 → 不触发**(可能用户只想让你看视频)。**只有意图没视频 → 反问**"要训练谁的分身,把视频发我"。
139
+ 2. **背景必须显式决策**:训练系统会把视频背景 baked 进 avatar look,**video gen 时无法替换**。`--clean-bg` 或 `--accept-baked-bg` 二选一,不能 silent 默认。
140
+ 3. **名字由用户起、external_id 由 LLM 内部生成**:用户只面对一个名字(中英文皆可),UUID(voice_id / avatar_id / external_id)全程不冒泡。
141
+ 4. **Anti-fabrication**:命令 / flag / 端点必须用本 skill 列出的精确版本。CLI sub-command / API 端点不允许凭印象拼。**特别注意**:`gen avatar video` 要 `--avatar` + `--voice` 两个 UUID(不支持 `--external-id` 自动反查,必须先 `gen avatar profile` 拿 IDs)。
142
+ 5. **用户向不暴露上游品牌**:见上面"用户向输出原则"。
143
+ 6. **限制硬约束**(来自 spec §13):
144
+ - 分身合成视频 text ≤ 50 字 / 输出 ≤ 10 秒(spec §13.3)
145
+ - 视频 ≥ 30 秒(voice 训练用),建议 2-5 分钟(avatar 质量,spec §13.5)
146
+ - 单声道音频质量好(噪声 / 多人混杂会 fail)
147
+
148
+ ## 限制 + 失败模式(来自 spec §13)
149
+
150
+ | 限制 | 说明 |
151
+ |---|---|
152
+ | text ≤ 50 字 / 输出 ≤ 10 秒 | 长 prompt 在 trained avatar 上挂死,client 端拆段(spec §13.3) |
153
+ | 单视频 4 looks 只用第一个 | 上游服务未暴露 list-looks endpoint(spec §13.5,v1.1 issue #54) |
154
+ | trained look "still processing" 短期 race | adapter 自动 retry,30min 失败降级公共 avatar(spec §13.2) |
155
+ | 背景 baked | video gen `background` 参数无效,训练时必须干净背景(spec §13.4) |
156
+ | 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 时的恢复路径) |
158
+
159
+ ## 不在范围
160
+
161
+ - ❌ Multi-persona 一次训(一个 external_id 对应一次 onboard)
162
+ - ❌ Background remove / matting(让用户重拍干净背景或自己 preprocess)
163
+ - ❌ Long video gen(text > 50 字让用户拆段)
164
+ - ❌ License / consent 流程(产品层做,不在 skill 内)
165
+ - ❌ 跨设备 workspace 同步
166
+ - ❌ 团队共享 / 跨用户分身
167
+ - ❌ mix-and-match 主流程支持(见 [generate.md "advanced 参考实现"](references/generate.md))
168
+
169
+ ## 相关 / 参考
170
+
171
+ - **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)
174
+ - **低层**(单独操作):`gen avatar train`(仅训 avatar 不训 voice) / `gen voice clone`(仅训 voice)
175
+ - **辅助**:`gen task get/cancel/list`
176
+ - **兼容性别名**:`gen doctor onboard / video / profile` 仍可用,跟 `gen avatar *` 行为等价;本 skill 统一用新名
177
+ - **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
+ - **v1.1 follow-up**:[optima-gen issue #54](https://github.com/Optima-Chat/optima-gen/issues/54)
179
+ - **设计 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)
184
+
185
+ ## 磁盘提示
186
+
187
+ workspace 持久化每条视频的 segments + final + 训练源视频,长期重度用户(年频次 100+ 视频)可能积累几 GB。v1 不自动清理,用户嫌占空间:
188
+
189
+ ```bash
190
+ du -sh ~/digital-human/ # 总占用
191
+ du -sh ~/digital-human/training/*/ # 各分身训练素材占用
192
+ du -sh ~/digital-human/videos/*/ # 各视频占用
193
+ ```
194
+
195
+ 清理:`rm -rf ~/digital-human/videos/<old-id>` 删旧视频目录,`rm -f ~/digital-human/training/<external_id>/source-video.mp4` 单独删源视频文件(保留 train-meta.md 当存根)。
@@ -0,0 +1,47 @@
1
+ # 公共 avatar / voice 清单
2
+
3
+ 无需训练即可用的公共池。LLM 在用户说"做个口播视频"但没指定主体时,用本表给用户挑选。
4
+
5
+ > **维护**:本文件硬编码 v1,长期目标是后端开 `gen avatar list --public` / `gen voice list --public` 端点拉真值。如有更新走 PR 改这里。
6
+
7
+ ## 公共 Avatar
8
+
9
+ | avatar_id | 名称 |
10
+ |---|---|
11
+ | `Abigail_expressive_2024112501` | Abigail(上半身,表情丰富) |
12
+ | `Abigail_standing_office_front` | Abigail 办公室正面 |
13
+ | `Aditya_public_4` | Aditya 棕色西装 |
14
+ | `Adrian_public_3_20240312` | Adrian 蓝色衬衫 |
15
+
16
+ ## 公共 Voice
17
+
18
+ | voice_id | 名称 |
19
+ |---|---|
20
+ | `f38a635bee7a4d1f9b0a654a31d050d2` | Chill Brian(男) |
21
+ | `cef3bc4e0a84424cafcde6f2cf466c97` | Ivy(女) |
22
+ | `b966c31caf124c2a99f19ff1479c964f` | Jessica Anne Bogart(女) |
23
+ | `42d00d4aac5441279d8536cd6b52c53c` | Hope(女) |
24
+
25
+ ## 用法
26
+
27
+ 公共 avatar / voice 用 `gen video --provider heygen`,**不是** `gen avatar video`(后者仅用于已 onboard 训练分身):
28
+
29
+ ```bash
30
+ gen video --provider heygen \
31
+ --avatar Abigail_expressive_2024112501 \
32
+ --voice f38a635bee7a4d1f9b0a654a31d050d2 \
33
+ --text "欢迎来到我们的店铺,今天给大家介绍一款新品" \
34
+ -o clips/public-talking.mp4
35
+ ```
36
+
37
+ 可选参数(见 [generate.md "可选参数"](generate.md)):
38
+ - `--avatar-style normal|circle|closeUp`
39
+ - `--voice-speed 0.5-1.5`
40
+ - `--voice-emotion Excited|Friendly|Serious|Soothing|Broadcaster`
41
+ - `--bg-color "#hex"` / `--bg-image <url>`
42
+ - `--caption`
43
+ - `--title <title>`
44
+
45
+ ## 跟训练分身的混搭
46
+
47
+ 支持但**不是**主流程,见 [generate.md "Advanced: mix-and-match"](generate.md)。
@@ -0,0 +1,198 @@
1
+ # Edit Segment(段落编辑)
2
+
3
+ 只重生某一段,不重做整条视频。**核心价值**:省 70-90% credits + 时间。
4
+
5
+ ## 触发
6
+
7
+ 用户说类似:
8
+ - "把刚才那条视频的第 2 段改成 X"
9
+ - "重做第 2 段"
10
+ - "刚才那条改一句话"
11
+ - "seg-3 重新生成"
12
+ - "第 N 段不对,改成 ..."
13
+
14
+ 不在范围(走别的流程):
15
+ - "换 avatar"(换分身)→ 这等于新视频,走 [generate.md](generate.md)
16
+ - "改总文本"(全部重写)→ 反问"要全新生成还是只改某段?"
17
+ - "video 比例 / 时长 / 字幕等元参数"→ trained avatar 不支持,反问
18
+
19
+ ## 流程
20
+
21
+ ### 1. 找 video-id
22
+
23
+ - 用户说"刚才那条" / "刚做的" / 不指定 → `ls -t ~/digital-human/videos/ | head -1` 取最新
24
+ - 用户给了 video-id(例 `20260515-1430`)→ 直接定位
25
+ - 用户给文本片段或主题 → 遍历 `videos/*/meta.md` "总文本"段模糊匹配
26
+ - 0 个匹配 → 反问 "没找到对应视频,你要找哪条?(列最近 3 条让用户选)"
27
+ - 多个匹配 → 反问消歧
28
+
29
+ 如果 video 是 SPEC 落地前(无 workspace 目录)生成的 → 反问 "这条视频没有 workspace 记录,只能重新生成整条。要走 [generate.md](generate.md) 全新创建吗?"
30
+
31
+ ### 2. 读现状
32
+
33
+ ```bash
34
+ cd ~/digital-human/videos/$VIDEO_ID
35
+ cat meta.md
36
+ cat history.md
37
+ ls segments/
38
+ ```
39
+
40
+ LLM 给用户回顾:
41
+ > "找到 `<VIDEO_ID>`(分身: 张医生,4 段,当前版本 final-v2.mp4)。
42
+ > 想改哪一段?现有:
43
+ > - seg-01: '大家好,我是张医生。'
44
+ > - seg-02: '今天介绍一款 ...'
45
+ > - seg-03: '它能帮你 ...'
46
+ > - seg-04: '谢谢观看。'"
47
+
48
+ ### 3. 判断改的是什么
49
+
50
+ 用户给意图后,分支:
51
+
52
+ | 改的是 | 处理 |
53
+ |---|---|
54
+ | 单段 text | 走 §4 (主流程) |
55
+ | 多段 text | 逐段确认 + 逐段走 §4 |
56
+ | 换 avatar / voice | 反问 "这等于新视频,本段流程不支持。要走 [generate.md](generate.md) 用 <新分身> 全新生成吗?" |
57
+ | 段间顺序 | 反问 "重排顺序需要重 concat,但本 skill v1 不支持此功能。要手动 mv segments/* 改名后我帮你重 concat 吗?" |
58
+ | 段时长 / 字幕 | trained avatar 不支持调时长 / 字幕参数,reply 不支持 |
59
+
60
+ ### 4. text 改动(主流程)
61
+
62
+ 获取新 text + **二次确认带阈值**(决策表 #14):
63
+
64
+ ```python
65
+ # 伪代码
66
+ old_text = read("segments/seg-<N>.txt")
67
+ new_text = <用户给的新 text>
68
+
69
+ edit_ratio = levenshtein(old_text, new_text) / max(len(old_text), len(new_text))
70
+
71
+ if edit_ratio > 0.3:
72
+ # 大改 → 反问确认
73
+ ask_user(f"确认改 seg-<N>?\n 旧: {old_text}\n 新: {new_text}\n (credits 花了不可逆)")
74
+ if not confirmed:
75
+ return
76
+ # else (≤30%): typo/标点微调,静默继续不打断 flow
77
+ ```
78
+
79
+ `edit_ratio` 计算:Levenshtein 距离 / max(旧长度, 新长度)。30% 阈值的依据:
80
+ - 改一个错别字 / 加个标点 / 换"了"为"啦" → < 5%,静默
81
+ - 改半句话或换语序 → 20-40%,触发确认
82
+ - 整段重写 → > 50%,肯定确认
83
+
84
+ 如果 LLM 没有 levenshtein 工具,可以用粗估:`abs(len(old) - len(new)) / max(len) + diff_count / len > 0.3` 当 fallback。
85
+
86
+ ### 5. 覆盖 segments/seg-NN.txt + 重生 seg-NN.mp4
87
+
88
+ ```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"
108
+ ```
109
+
110
+ **其他段的 mp4 不动**(mtime 保持原样,credits 0 消耗)。
111
+
112
+ ### 6. 重 concat → final-v<下一版本>.mp4
113
+
114
+ **版本号计算 + 上限清理**:
115
+
116
+ ```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
127
+ ```
128
+
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` 即可。
130
+
131
+ 走 fallback 重编码时,后续 §8 报告也必须明示"段间 codec 兼容问题,自动重编码合成"。
132
+
133
+ ### 7. 更新 meta.md + history.md
134
+
135
+ **meta.md** 改两处:
136
+ - 表头"当前版本"行:`final-v$NEXT_V.mp4`
137
+ - Segments 表第 N 行"最后更新":`<当前时间> (v$NEXT_V)`
138
+
139
+ **history.md** 末尾追加:
140
+ ```markdown
141
+ - **<当前时间>** [v$NEXT_V] 改了 seg-$N: "<旧 text>" → "<新 text>" (<编辑距离比例 X%>)
142
+ ```
143
+
144
+ ### 8. 报告
145
+
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 仍在,想回退直接用旧版本。"
150
+
151
+ 如果走了 ffmpeg fallback,加一句"⚠️ 段间 codec 兼容问题,自动重编码合成"。
152
+ 如果触发了 final-vN.mp4 删最老的逻辑(数量 ≥ 10),加一句"📦 历史版本已满 10,自动清理了最早的 final-v$DELETED.mp4"。
153
+
154
+ ---
155
+
156
+ ## Edge cases
157
+
158
+ ### 1. 用户说"改第 N 段"但 N 不存在
159
+
160
+ 例:视频只有 4 段,用户说"改第 7 段"。
161
+
162
+ 反问:"这条视频只有 4 段(seg-01 ~ seg-04),没有第 7 段。要改哪一段?"
163
+
164
+ ### 2. final-v* 数量已达上限 10 + 用户改段
165
+
166
+ 按 §6 自动删 final-v1.mp4(最老),保留 latest + 9 个历史。**不问用户**,在 §8 报告里告知"自动清理了 final-vX.mp4"。
167
+
168
+ ### 3. 用户改的 text 跟旧 text 完全一样(edit_ratio = 0)
169
+
170
+ 反问:"新旧 text 一样,确认要重生 seg-N 吗?(会花一次 credits,但 mp4 结果可能跟旧版有细微 heygen 随机差异)"
171
+ 默认推荐"不重生"。
172
+
173
+ ### 4. 用户说"改最后一段"/"改第一段"(相对引用)
174
+
175
+ LLM 解析:"最后一段"= `seg-$(ls segments/seg-*.txt | wc -l | xargs printf "%02d")`,"第一段"= seg-01。
176
+
177
+ ### 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 号,或者再让我改回去"。
185
+
186
+ ---
187
+
188
+ ## Common Mistakes
189
+
190
+ | ❌ Mistake | ✅ 正确做法 | 为什么 |
191
+ |---|---|---|
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 静默切换 | 必须在报告里说明 "自动重编码,画质轻微损失" | 用户看到画质变了会困惑,透明告知避免认知负担 |