@dsh-gis/dsh-video-studio-skill 0.6.0 → 0.7.2

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/README.md +41 -16
  2. package/index.js +20 -7
  3. package/package.json +4 -2
  4. package/schemas/film.schema.json +75 -4
  5. package/skills/video-studio/SKILL.md +95 -23
  6. package/skills/video-studio/assets/film.json +7 -14
  7. package/skills/video-studio/assets/scenes.py +115 -10
  8. package/skills/video-studio/assets/web/engine.js +18 -1
  9. package/skills/video-studio/assets/web/film.js +29 -4
  10. package/skills/video-studio/assets/web/index.html +5 -0
  11. package/skills/video-studio/references/audio-variation.md +158 -0
  12. package/skills/video-studio/references/audio.md +67 -5
  13. package/skills/video-studio/references/change-impact.md +1 -1
  14. package/skills/video-studio/references/quality-gates.md +1 -1
  15. package/skills/video-studio/references/web-engine.md +1 -1
  16. package/skills/video-studio/requirements.txt +2 -1
  17. package/skills/video-studio/scripts/filmcheck.py +81 -12
  18. package/skills/video-studio/scripts/filmkit.py +271 -18
  19. package/skills/video-studio/scripts/gates.py +75 -12
  20. package/skills/video-studio/scripts/qa_frames.py +109 -19
  21. package/skills/video-studio/scripts/render.py +420 -51
  22. package/skills/video-studio/scripts/scorekit.py +289 -46
  23. package/skills/video-studio/scripts/selfcheck.py +302 -2
  24. package/skills/video-studio/scripts/sfxkit.py +132 -30
  25. package/skills/video-studio/scripts/stagewatch.py +141 -31
  26. package/skills/video-studio/scripts/stylekit.py +94 -6
  27. package/skills/video-studio/scripts/voicekit.py +8 -0
  28. package/skills/video-studio/scripts/webengine.py +68 -14
  29. package/tools/audio_similarity.py +279 -0
  30. package/tools/ci_check.py +145 -34
  31. package/tools/estimate.py +133 -54
  32. package/tools/selftest_noscipy.py +70 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @dsh-gis/dsh-video-studio-skill
2
2
 
3
- ![version](https://img.shields.io/badge/version-0.6.0-blue) ![license](https://img.shields.io/badge/license-MIT-green)
3
+ ![version](https://img.shields.io/badge/version-0.7.2-blue) ![license](https://img.shields.io/badge/license-MIT-green)
4
4
 
5
5
  **DeepSeek Harness 技能插件:用代码从零生成中文视频。**
6
6
 
@@ -28,6 +28,14 @@ ffmpeg 编码 → 采样级混音 → **四层质检**(容器 / 信号 / 时
28
28
  > 顺手修掉一个**已发布版本里的真 bug**:`--jobs >1` 并行渲染因主题色元组被 JSON 变成 list 而直接崩溃
29
29
  > ——也就是 README 下面教的"长片分块并行"此前根本跑不通。
30
30
  > 详见 [CHANGELOG.md](CHANGELOG.md)、SKILL.md §7.0 与 references/change-impact.md。
31
+ > **v0.7.0(声音也不再一片一样)**:内容指纹从 seed 推进到**结构层**——和弦替换、进行档位、动机偏移、
32
+ > 鼓型 ghost 都随内容变化;音效 seed 改内容派生并跟随配乐调性(`in_key`);段落边界前 2.2 s 的
33
+ > whoosh/riser 自动 -3 dB,避免与配乐过门撞车;duck 按风格加深到 3.0–5.5 dB。legacy 路径逐样本不变。
34
+ > **v0.7.1(旁白优先)**:配乐基线从 -1.5 降到 **-12 dB**,有人声处再按包络压配乐 6 dB / 音效 3 dB,
35
+ > 混音后按台词活跃区实测旁白/背景差,**不足 12 dB 自动补压背景**(`guard_snr_db`,最多 30 dB)。
36
+ > 实测(真实素材):旧混音 -4.2 dB → 新混音 12.0 dB,`audio_similarity --strict` D 组 12.9 dB;
37
+ > selfcheck 34 → 38 条,其中第 35–38 条专盯这条链路。
38
+ > 详见 [CHANGELOG.md](CHANGELOG.md) 与 SKILL.md §5.6 / §5.7、references/audio-variation.md。
31
39
 
32
40
  ## 安装
33
41
 
@@ -60,23 +68,33 @@ mv ~/.dsh/skills/video-studio ~/.dsh/backup/video-studio.bak-$(date +%Y%m%d)
60
68
  <p align="center"><img src="docs/demo-pil-sheet.png" width="100%" alt="默认模板片联系表:开场 / 正文卡片 / 正文卡片 / 收尾"></p>
61
69
 
62
70
  上面四帧是用仓库自带的 `assets/film.json` + `assets/scenes.py` 从零跑一遍 `python render.py` 的
63
- `work/qa/sheet.png`,未经任何修图。配套实测(同一台机器):
71
+ `work/qa/sheet.png`,未经任何修图。配套实测(**作者机器** Windows 11 / OSGeo4W Python 3.12,
72
+ v0.6 时期口径;TTS 实测时长与 AAC 编码会让这些数字在小范围内浮动,换机请按 SKILL.md §14 重测):
64
73
  **时长 30.10 s / 1920×1080 / 30 fps / h264+aac 48 kHz / -16.0 LUFS / 真峰 -1.2 dBTP / 黑帧 0 /
65
74
  帧级深检:闪烁 0、空窗 0、画面静止 0**(音画同步在 PIL 路线上需要写 `visual_at` 才会真的查,见 SKILL.md §7)。
66
75
 
76
+ 怎么复现:按 [SKILL.md §1](skills/video-studio/SKILL.md) 的五步上手,把自带模板拷到**空目录**里跑一遍即可
77
+ (`cp assets/film.json assets/scenes.py` + 拷 `scripts/*.py` → `python render.py`)。数字每次都会差一点:
78
+ TTS 实测时长、AAC 编码都会让真峰/响度/锐度浮动,**别 chasing 小数点**。本机(另一台)复测同模板得到的是
79
+ 29.00 s / -16.5 LUFS / -1.4 dBTP / 锐度比 1.01(2026-11-10 另一台 Windows 机器复测)——
80
+ **不同机器、不同 TTS 结果,两组都对,别混着引用**。
81
+
67
82
  ## 效果:同一个模板,八种气质(v0.5)
68
83
 
69
84
  同一份文字,只改 `"style"`(画风 / 配乐 / 旁白 / 节奏一起变):
70
85
 
71
86
  <p align="center"><img src="docs/styles/sheet-8-styles.png" width="100%" alt="8 套风格对比:科技深蓝 / 杂志暖白 / 手绘纸感 / 活力撞色 / 极简黑白 / 电影青橙 / 暖灰薄荷 / 深黑鎏金"></p>
72
87
 
73
- 三份**不同题材**的短片(全流程真跑:TTS → 排轴 → 配乐 → 音效 → 混音 → 逐帧 → 编码 → 质检):
88
+ 三份**不同题材**的短片(全流程真跑:TTS → 排轴 → 配乐 → 音效 → 混音 → 逐帧 → 编码 → 质检;数据见
89
+ `docs/styles/demo-results.json`,是 v0.5 时期的实测快照,片源脚本未随仓库分发,**不可直接复现**;
90
+ **别把这张表当成"现在跑出来就是这样"**——BPM/调性由内容指纹从风格梯度里选(v0.7 起指纹还参与配乐结构),
91
+ 同一题材换一版正文就可能落到别的 BPM/调性,当前风格解析结果请以 `--stage style` 的打印为准):
74
92
 
75
93
  | 题材 | 自动选中的风格 | 配乐 | 旁白声线 | 质检 |
76
94
  |---|---|---|---|---|
77
95
  | AI 大模型的三年 | tech-deep 科技深蓝 | 小调 116BPM / Em | 青年开场 → 男声正文 → 女声引用 | PASS(17.3s,-16.0 LUFS) |
78
- | 手工木作的一天 | paper 手绘纸感 | 大调 84BPM / C | 女声 + 软糯女声正文 | PASS(19.9s) |
79
- | 深夜学习指南 | lo-fi 暖灰薄荷 | 大调 84BPM / G | 温柔女声 + 女声开场 | PASS(20.1s) |
96
+ | 手工木作的一天 | paper 手绘纸感 | 大调 88BPM / G | 女声 + 软糯女声正文 | PASS(19.9s) |
97
+ | 深夜学习指南 | lo-fi 暖灰薄荷 | 大调 84BPM / F | 温柔女声 + 女声开场 | PASS(20.1s) |
80
98
 
81
99
  <p align="center"><img src="docs/styles/sheet-ai.png" width="32%" alt="AI 题材"><img src="docs/styles/sheet-craft.png" width="32%" alt="手作题材"><img src="docs/styles/sheet-night.png" width="32%" alt="深夜题材"></p>
82
100
 
@@ -96,8 +114,9 @@ cp <SKILL_DIR>/assets/scenes.py . # 场景模板
96
114
  cp <SKILL_DIR>/scripts/*.py . # filmkit/stylekit/voicekit/render/selfcheck + sfxkit/scorekit/qa_frames/webengine
97
115
  cp -r <SKILL_DIR>/assets/web ./web # 只在走浏览器引擎时需要
98
116
 
99
- python selfcheck.py # 32 条环境/回归断言(20 秒,不联网)
100
- python <REPO>/tools/estimate.py --script film.json # v0.6:开渲前先算耗时/磁盘/分块
117
+ python selfcheck.py # 43 条环境/回归断言(20 秒内,不联网)
118
+ python <REPO>/tools/estimate.py --script film.json # v0.6:开渲前先算耗时/磁盘/分块(仓库/包里的工具,
119
+ # 不在 skills/video-studio/ 内,quickstart 拷出来的目录没有它)
101
120
  python render.py --stage stills --stills "l01:3,l02:5" # 母版预览(缺 tts/layout 会自动补跑)
102
121
  python render.py --script film.json --scenes scenes.py --out out/film.mp4 --work work
103
122
  cat out/QC-REPORT.md # v0.6:机器可读交付结论(pass/fail/skipped)
@@ -122,10 +141,11 @@ tts → layout → cues → score → sfx → audio → frames → mux → qa
122
141
 
123
142
  | 想要 | 怎么做 |
124
143
  |---|---|
125
- | 让 PIL 路线的音画同步门禁真的生效 | cue 加 `"visual_at": 绝对秒`;CI 里再加 `QA_REQUIRE_SYNC=1` 把"没查"变成失败 |
144
+ | 让 PIL 路线的音画同步门禁真的生效 | cue 加 `"visual_at": 绝对秒`;再把 `QA_REQUIRE_SYNC=1` 加进你自己的交付流水线(本仓库的 workflow 没设它) |
126
145
  | 浏览器渲染要跨会话字节级可复现 | `--deterministic`(或 `WEBENGINE_DETERMINISTIC=1`),代价约 +22%/帧 |
127
146
 
128
- `<SKILL_DIR>` 是 `skill` 工具加载时回报的 resourceBase 目录(`.../skills/video-studio/`)。
147
+ `<SKILL_DIR>` 是 `skill` 工具加载时回报的 resourceBase 目录(`.../skills/video-studio/`);
148
+ `<REPO>` 是包根(仓库根,或装好后 profile 里的 `node_modules/@dsh-gis/dsh-video-studio-skill/`),即 `<SKILL_DIR>` 往上两级——`tools/`、`schemas/` 都在 `<REPO>` 下,`cp` 到 `myfilm/` 后那里**没有** `tools/`。
129
149
 
130
150
  分阶段(v0.2 起**每个 stage 只做自己的事**):`--stage style | tts | layout | cues | score | sfx | audio | frames | mux | qa | stills | accept | explain`。
131
151
 
@@ -164,30 +184,33 @@ cordis.patch.yml 组合包 patch(insert: video-studio)
164
184
  icon.svg / locale/*.json 插件页展示元数据
165
185
  skills/video-studio/
166
186
  SKILL.md 主线:上手 / 八阶段 / 引擎选型 / 画质 / 声音 / 提示点 / 停留窗口 / 质检 / 故障速查
167
- references/ pipeline · scene-authoring · audio · sfx-design · web-engine · frame-qa
168
- quality-gates · change-impact · style-packs · pitfalls
187
+ references/ pipeline · scene-authoring · audio · audio-variation · sfx-design · web-engine
188
+ frame-qa · quality-gates · change-impact · style-packs · pitfalls
169
189
  scripts/filmkit.py 生产内核(字体/排版/字幕/音频总线/CueBus/TTS/时间轴/编码/质检)
170
190
  scripts/gates.py v0.6 门禁账本 / QC 报告渲染 / 人工签核(绑内容指纹)
171
191
  scripts/stagewatch.py v0.6 阶段新鲜度(work/manifest.json + --stage explain)
172
192
  scripts/filmcheck.py v0.6 film.json 契约校验(未知键/枚举/跨字段)
173
193
  scripts/render.py 编排器(13 个 stage、pil|web 双引擎、分块并行、断点续跑、母版预览、门禁账本)
174
- scripts/selfcheck.py 回归自检(32 条"曾真的坏过"的断言)
194
+ scripts/selfcheck.py 回归自检(43 条"曾真的坏过"的断言)
175
195
  scripts/sfxkit.py 物理建模音效库(24 配方,scipy 可选)
176
196
  scripts/scorekit.py 程序化配乐(和弦表 / 分段能量 / 吸气)
177
197
  scripts/webengine.py 浏览器逐帧渲染(CDP 截图、分块并行、崩溃重开、cue 导出、文字质检)
178
198
  scripts/qa_frames.py 帧级深检(单帧闪烁 / 画面空窗 / 画面静止 / 音画同步 / 亮度响度曲线)
179
199
  requirements.txt Python 依赖(必需 / 强烈建议 / 可选三档,带注释)
180
- assets/film.json 剧本模板(含 hold / bpm / music / sfx.cues)
200
+ assets/film.json 剧本模板(含 bpm / music / sfx.cues / 逐句 hold;顶层 hold 可选,缺省用风格 pacing)
181
201
  assets/scenes.py 场景模板(开场 / 正文卡片 / 收尾 + cues() 钩子)
182
202
  assets/web/ 浏览器页面模板(原生 Canvas2D 确定性时间轴)
183
203
  tools/ci_check.py 仓库级体检(12 项;语法/JSON/依赖白名单/打包卫生/文档引用/film 契约,不需 ffmpeg)
184
204
  tools/estimate.py 开渲前成本预演(帧数/耗时/磁盘/分块建议,--json 可机读)
205
+ tools/audio_similarity.py 音频差异/相似度验收(--strict 阈值判定,改音频代码后必跑)
206
+ tools/selftest_noscipy.py 无 scipy 兜底路径自检(CI 用;屏蔽 scipy 后跑 sfxkit/scorekit 自检)
185
207
  schemas/film.schema.json film.json 的契约(draft-07 子集)
186
208
  .github/workflows/ci.yml CI:repo 体检(ubuntu+windows)+ 技能自检(ubuntu)
187
209
  ```
188
210
 
189
211
  ## 环境前提
190
212
 
213
+ **插件运行**:Node.js ≥ 20(`package.json` 的 `engines`;这是 DSH 加载插件用的运行时,不必另装)。
191
214
  **必需**:ffmpeg + Python 的 numpy / Pillow / edge-tts。
192
215
  **可选**:cv2(编码锐度比)、scipy(音效滤波加速,缺了走 FFT 卷积)、
193
216
  playwright + Chromium/Edge(只在 `--engine web` 时需要)。
@@ -219,9 +242,11 @@ peer 范围 `@deepseek-ai/dsh-skill-filesystem >=0.1.0-rc.6 <0.3.0-0`,覆盖 D
219
242
  ## 仓库自检(给贡献者与 CI 用,不是给用户用的)
220
243
 
221
244
  ```bash
222
- python tools/ci_check.py -v # 12 项:py 语法 / node --check / JSON / 技能清单 / 依赖白名单 /
223
- # npm pack 打包卫生 / 文档路径引用 / Python 3.9 语法 /
224
- # Markdown 围栏平衡 / CI workflow 语法 / 单文件体积 /
245
+ python tools/ci_check.py -v # 12 项:py 语法(含 tools/,SyntaxWarning 记失败)/ node --check /
246
+ # JSON + 版本号一致性(package.json vs README 徽章)/ 技能清单 /
247
+ # 依赖白名单 / npm pack 打包卫生 / 文档路径 + 正文相对链接 /
248
+ # Python 3.9 语法 / Markdown 围栏(状态机)/ CI workflow
249
+ # (裸冒号 + run 里的 python 脚本是否存在)/ 单文件体积 /
225
250
  # film.json 契约(调 filmcheck.py,规则只留一份)
226
251
  ```
227
252
 
package/index.js CHANGED
@@ -31,11 +31,12 @@ export const inject = ["skills"];
31
31
  /**
32
32
  * 注册 bundled 技能根。
33
33
  *
34
- * 三件事任何一件不成立都只打日志、不抛错——插件本身没有"必须成功"的业务,
34
+ * 四件事任何一件不成立都只打日志、不抛错——插件本身没有"必须成功"的业务,
35
35
  * 挂不上技能是降级,不是崩溃:
36
36
  * 1. 找不到包内 SKILL.md(打包漏了文件 / 被裁剪)
37
37
  * 2. 运行时没有 ctx.skills(profile 没装技能注册表)
38
38
  * 3. 可选 peer 没装(只装了本插件)
39
+ * 4. provider 自己抛错(版本不匹配 / 目录无权限 / 注册表已占用)
39
40
  *
40
41
  * @param ctx - 携带 skills 服务的 Cordis 上下文。
41
42
  */
@@ -63,12 +64,24 @@ export async function apply(ctx) {
63
64
  return;
64
65
  }
65
66
 
66
- applyFilesystemProvider(ctx, {
67
- providerName: "video-studio",
68
- includeDefaultRoots: false,
69
- bundledSkillDir,
70
- watch: false,
71
- });
67
+ // 第 4 件可能失败的事:provider 自己抛错(版本不匹配、目录无权限、注册表已占用……)。
68
+ // 这里必须兜住:插件行的加载失败会让整行从 profile 里消失,而"挂不上技能"本来只是个降级。
69
+ // (v0.7.1 之前没兜:provider 一抛错 apply 就 reject,而且一行日志都没有——stub 实测复现过。)
70
+ try {
71
+ applyFilesystemProvider(ctx, {
72
+ providerName: "video-studio",
73
+ includeDefaultRoots: false,
74
+ bundledSkillDir,
75
+ watch: false,
76
+ });
77
+ } catch (error) {
78
+ warn(
79
+ ctx,
80
+ `挂载 bundled 技能根失败(本次只是空行,不影响 profile 里其它插件):` +
81
+ `${error?.code ?? error?.message ?? error}` +
82
+ `;检查 ${PROVIDER_PKG} 的版本是否满足 peer 范围,以及 ${bundledSkillDir} 是否可读`
83
+ );
84
+ }
72
85
  }
73
86
 
74
87
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-gis/dsh-video-studio-skill",
3
- "version": "0.6.0",
3
+ "version": "0.7.2",
4
4
  "description": "Video Studio skill plugin for DeepSeek Harness: style-pack driven code-generated Chinese videos — one style id decides palette/typography/music style/voice persona/pacing, measured TTS timeline, deterministic PIL/browser rendering, programmatic SFX and score, cue-driven sound sync, ffmpeg encoding, sample-level mixing and multi-layer QA gates.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -34,7 +34,9 @@
34
34
  "docs/styles/demo-results.json",
35
35
  "schemas",
36
36
  "tools/estimate.py",
37
- "tools/ci_check.py"
37
+ "tools/ci_check.py",
38
+ "tools/audio_similarity.py",
39
+ "tools/selftest_noscipy.py"
38
40
  ],
39
41
  "publishConfig": {
40
42
  "access": "public"
@@ -49,7 +49,41 @@
49
49
  "string",
50
50
  "object"
51
51
  ],
52
- "description": "字符串 = edge-tts voice id(如 zh-CN-YunxiNeural,legacy 单声线路径);对象 = v0.5 声线策略(map 按 kind 映射 persona、auto、jitter、max_distinct、rate_range、cps_band),此时忽略 rate/pitch 的单值覆盖语义"
52
+ "description": "字符串 = edge-tts voice id(如 zh-CN-YunxiNeural,legacy 单声线路径);对象 = v0.5 声线策略(map 按 kind 映射 persona、auto、jitter、max_distinct、rate_range、cps_band),此时忽略 rate/pitch 的单值覆盖语义",
53
+ "properties": {
54
+ "map": {
55
+ "type": "object",
56
+ "description": "按 kind 映射 persona:{\"hook\": \"young_male\", \"body\": \"narrator_male\", \"quote\": \"narrator_female\", \"outro\": \"narrator_male\"}(键取 lines[].kind 的 6 个值;值取 voicekit.PERSONA_IDS)"
57
+ },
58
+ "default": {
59
+ "type": "string",
60
+ "description": "没有映射时用的 persona 名(voicekit.PERSONA_IDS 之一)"
61
+ },
62
+ "auto": {
63
+ "type": "boolean",
64
+ "description": "是否按内容自动挑 persona(默认 true;false = 只用 map/default)"
65
+ },
66
+ "jitter": {
67
+ "type": "number",
68
+ "minimum": 0,
69
+ "description": "逐句 rate/pitch/lead/tail 的确定性抖动幅度(默认 0.35;legacy 默认 0)"
70
+ },
71
+ "max_distinct": {
72
+ "type": "integer",
73
+ "minimum": 1,
74
+ "description": "全片最多用几个不同 persona(默认 3;相邻句不换人是防广播剧的关键)"
75
+ },
76
+ "rate_range": {
77
+ "type": "array",
78
+ "description": "语速夹取区间(百分数,例如 [-15, 12])",
79
+ "items": { "type": "number" }
80
+ },
81
+ "cps_band": {
82
+ "type": "array",
83
+ "description": "可接受的每秒字数区间(默认 [3.6, 5.6])",
84
+ "items": { "type": "number" }
85
+ }
86
+ }
53
87
  },
54
88
  "rate": {
55
89
  "type": "string",
@@ -180,7 +214,7 @@
180
214
  "tail": {
181
215
  "type": "number",
182
216
  "minimum": 0,
183
- "description": "句后留白秒数(一般通过 tail_map / voice_plan 控制,这里少见)"
217
+ "description": "注意:**当前不生效**(保留字段)。filmkit.build_timeline 只读 tail_map,逐句句后留白请写顶层 tail_map,或交给 voicekit 的 voice_plan(st_layout 会把它的逐句 tail 并进 tail_map)。filmcheck 对写了 lines[].tail 的剧本会报 WARN 提醒。"
184
218
  },
185
219
  "persona": {
186
220
  "type": "string",
@@ -382,11 +416,35 @@
382
416
  },
383
417
  "gain_db": {
384
418
  "type": "number",
385
- "description": "配乐总线增益 dB(默认 -1.5,混音前)"
419
+ "description": "配乐总线增益 dB(v0.7.1 默认 -12:视频以旁白为主,背景必须明显低于人声;旧版 -1.5 会让背景与台词几乎等高)"
386
420
  },
387
421
  "duck_db": {
388
422
  "type": "number",
389
- "description": "音效响起时配乐让位深度 dB(默认按风格 2.0–4.5)"
423
+ "description": "音效响起时配乐让位深度 dB(v0.7 默认按风格 3.0–5.5)"
424
+ },
425
+ "voice_duck_db": {
426
+ "type": "number",
427
+ "description": "v0.7.1:旁白说话时配乐额外让位深度 dB(默认 6;0 = 关闭旁白侧链)"
428
+ },
429
+ "voice_duck_sfx_db": {
430
+ "type": "number",
431
+ "description": "v0.7.1:旁白说话时音效额外让位深度 dB(默认 3;0 = 关闭)"
432
+ },
433
+ "voice_thresh_db": {
434
+ "type": "number",
435
+ "description": "v0.7.1:语音活跃度阈值 dBFS(默认 -42;低于它算静音间隙,背景恢复)"
436
+ },
437
+ "voice_floor_db": {
438
+ "type": "number",
439
+ "description": "v0.7.1:语音包络的地板 dBFS(默认 -60;与阈值之间做 0~1 映射)"
440
+ },
441
+ "guard_snr_db": {
442
+ "type": "number",
443
+ "description": "v0.7.1:旁白清晰度兜底(默认 12)。混音后实测人声活跃区的人声/背景响度差,不足就整体再压背景;0 = 关闭"
444
+ },
445
+ "guard_cap_db": {
446
+ "type": "number",
447
+ "description": "v0.7.1:SNR 兜底最多补压多少 dB(默认 30,防止把背景压成忽有忽无;补压 >18 dB 会提示检查素材电平)"
390
448
  },
391
449
  "seg": {
392
450
  "type": "number",
@@ -441,10 +499,23 @@
441
499
  "type": "number",
442
500
  "description": "音效总线增益 dB(默认 0)"
443
501
  },
502
+ "base_db": {
503
+ "type": "object",
504
+ "additionalProperties": true,
505
+ "description": "按配方名覆盖基础电平 dB:{\"whoosh\": -6}。render.py 的 st_sfx 原样透传给 filmkit.CueBus.build_audio(base_db=...),只对键名命中的配方生效(未知配方名不报错、也不生效)"
506
+ },
444
507
  "mix_gain_db": {
445
508
  "type": "number",
446
509
  "description": "混音时音效总线额外增益 dB(默认 0)"
447
510
  },
511
+ "content_seed": {
512
+ "type": "boolean",
513
+ "description": "v0.7:true(默认)= 每条 cue 的 seed 由 style/content_fp 派生(不同影片同一位置也不同);false = 回到 v0.6 的 seed_base+i 位置派生(老片逐样本兼容)"
514
+ },
515
+ "duck_transitions": {
516
+ "type": "boolean",
517
+ "description": "v0.7:true(默认)= 段落边界前 2.2s 内的 whoosh/riser/reverse_swell 自动 -3 dB,避免与配乐过门在同一频段撞车;false = 不处理"
518
+ },
448
519
  "cues": {
449
520
  "type": "array",
450
521
  "description": "提示点数组:每条 {at, type, dur, gain, ...};未知参数原样透传给 sfxkit 配方(size/pan/seed/pitch…)",
@@ -26,6 +26,11 @@ description: 用代码从零生成中文视频(科普片/纪录片/宣传片/
26
26
  > 下文的 `<SKILL_DIR>` 指**本次加载技能时 `skill` 工具回报的 `resourceBase.path`**。装成插件后它就是
27
27
  > 本插件包内的 `skills/video-studio/`(profile 的 `node_modules/@dsh-gis/dsh-video-studio-skill` 是链接)。
28
28
  > 找不到时按名字定位:`find ~ -path '*/skills/video-studio/scripts/filmkit.py'`。
29
+ >
30
+ > `<REPO>` 指**包根**(仓库根,或装好后 profile 里的 `node_modules/@dsh-gis/dsh-video-studio-skill/`),
31
+ > 即 `<SKILL_DIR>` 往上两级。**`tools/` 与 `schemas/` 在 `<REPO>` 下,不在 `<SKILL_DIR>` 下**:
32
+ > 本节的 `cp <SKILL_DIR>/scripts/...` 把技能拷成 `myfilm/` 之后,那个工作目录里**没有** `tools/`,
33
+ > 所以 `tools/estimate.py` 这类仓库工具要按 `<REPO>/tools/...` 调用(或从包/仓库里取一份过来)。
29
34
 
30
35
  ```bash
31
36
  # 1) 建工作目录,拷模板
@@ -39,7 +44,7 @@ cp <SKILL_DIR>/scripts/{filmkit.py,stylekit.py,render.py,selfcheck.py,gates.py,s
39
44
  cp <SKILL_DIR>/scripts/{sfxkit.py,scorekit.py,voicekit.py,qa_frames.py} .
40
45
  cp -r <SKILL_DIR>/assets/web ./web
41
46
 
42
- # 2) 先跑一次自检:32 条回归护栏,20 秒内出结论(不联网;可选模块缺失会标 SKIP)
47
+ # 2) 先跑一次自检:43 条回归护栏,20 秒内出结论(不联网;可选模块缺失会标 SKIP)
43
48
  python selfcheck.py
44
49
 
45
50
  # 3) 【v0.5 新步骤】先确认风格,再改文字
@@ -59,7 +64,9 @@ python render.py --script film.json --scenes scenes.py --work work --stage still
59
64
  # 4.5) 【v0.6】先算成本再开渲:帧数 / 各阶段耗时 / 磁盘 / 分块建议
60
65
  # 项目原则是"成本敏感",而在此之前开渲前拿不到任何数字。
61
66
  # (--json 可机读;有 work/timeline.json 时用真实时长,否则按字数粗估并标注)
62
- python <SKILL_DIR>/../../../tools/estimate.py --script film.json --jobs 4
67
+ # 注意:estimate.py 是**仓库/包里的工具**(<REPO>/tools/),不在 skills/video-studio/ 里,
68
+ # quickstart 拷出来的 myfilm/ 也不会有它;想用就按包根路径调用,或从包/仓库里复制过来。
69
+ python <REPO>/tools/estimate.py --script film.json --jobs 4
63
70
 
64
71
  # 5) 一条命令出片(TTS→排版→混音→渲染→合成→质检)
65
72
  python render.py --script film.json --scenes scenes.py --out out/film.mp4 --work work
@@ -206,10 +213,16 @@ python webengine.py textqa ./web/index.html # 文字重叠/出画/一
206
213
  | 配乐 | `scorekit.py`(程序化) | `work/music.wav` | 和弦表 + 律动层 + 分段能量;段落边界对齐小节线 |
207
214
  | 音效 | `sfxkit.py`(物理建模) | `work/sfx.wav` | 按 `cues.json` 里登记的提示点逐条合成 |
208
215
 
209
- 三条总线在 `--stage audio` 里合成:**音效响起时配乐自动让位**(`duck_db`,默认 3 dB),
216
+ 三条总线在 `--stage audio` 里合成:**音效响起时配乐自动让位**(`duck_db`,v0.7 默认 4 dB,
217
+ cinema/epic 5.5 dB;旧版 3 dB 在段落过门时配乐几乎与音效等高),
210
218
  最后交给 ffmpeg 两遍 loudnorm 到 -16 LUFS / 真峰 ≤ -1 dBTP。**不要**自己在 numpy 里做最终响度标准化——
211
219
  你有 `loudnorm`,它比手写归一化准得多,而且真峰才是门禁。
212
220
 
221
+ > **v0.7 片内分离**:配乐在段落交界本来就有过门(scorekit 的 swell/riser/sub_boom),
222
+ > sfx 轨不再在同一个边界重复放 whoosh/riser——`--stage sfx` 会对落在
223
+ > [边界 - 2.2 s, 边界] 内的转场类 cue 自动 -3 dB(`sfx.duck_transitions=false` 可关)。
224
+ > 提示音的音高现在跟随配乐调性(`in_key(freq, key)`),不再硬编码 D 小调五声。
225
+
213
226
  ### 5.2 提示点:让声音跟着画面走
214
227
 
215
228
  **这是 v0.3 最重要的一个概念。** 画面在该响一下的地方登记一个提示点,音频阶段按提示点合成音效:
@@ -344,21 +357,60 @@ python render.py ... --stage style # 用某份 film.json 解析风格并打印"
344
357
  ### 5.6 混音链(照着做,别自创)
345
358
 
346
359
  ```
347
- 旁白(单声道, 居中) ─┐
348
- 配乐(立体声) ───────┼─> duck(音效包络, -3dB) ─> 求和 ─> 峰值保护(0.89) ─> ffmpeg loudnorm 两遍 ─> -16 LUFS / TP<=-1
349
- 音效(立体声) ───────┘
360
+ 旁白(单声道, 居中) ─────────────────────────────┐
361
+ 配乐(立体声, -12dB 基线) ─> 音效duck(-4dB) ─> 旁白duck(-6dB) ─┤─> 求和 ─> 峰值保护(0.89) ─> loudnorm 两遍 ─> -16 LUFS / TP<=-1
362
+ 音效(立体声) ───────────────────> 旁白duck(-3dB) ─────────────┘
350
363
  ```
351
364
 
352
- - **旁白优先**(听清台词是第一优先):`mix_film` 里旁白固定 `voice_gain=1.0` 居中,配乐默认
353
- `music.gain_db=-1.5`、音效按 `CUE_BASE_DB` 逐条定电平。**"高 12 dB"是人耳经验值,代码不测量也不保证**——
354
- 交付前用 `filmkit.loudness` 或回听确认旁白压得住,别把它当门禁。
355
- - 音效响时配乐自动让位 3 dB(`mix_film(duck_db=3.0)`;注意 `duck()` 函数自身默认是 4.0,
356
- `mix_film` 传的是 3.0——看源码时别被两个默认值绕住)。让得太多会"音乐一抖一抖"。
365
+ - **旁白优先是硬行为(v0.7.1)**,不是文档口号:`mix_film` 里旁白固定 `voice_gain=1.0` 居中、不被任何侧链影响;
366
+ 配乐基线 `music.gain_db=-12`(旧版 -1.5,导致背景与台词几乎等高);有人声的地方再按旁白包络压
367
+ 配乐 6 dB、音效 3 dB(`music.voice_duck_db` / `music.voice_duck_sfx_db`,间隙自动恢复)。
368
+ 实测人声段"旁白/背景"响度差 **12.9 dB**(真实配乐素材下;合成信号口径 15 dB),
369
+ selfcheck 第 35/36 条与 `audio_similarity.py --strict` 的 D 组守着这条。
370
+ - **SNR 兜底(v0.7.1 最后一道闸)**:混音后按台词活跃区实测"人声/背景",不足 12 dB 就整体再压背景
371
+ (`music.guard_snr_db`,最多 `guard_cap_db=30`;补压 >18 dB 会提示检查素材电平)。
372
+ 这是唯一一条"混音后自己改电平"的门禁,保证换任何配乐素材都不会把旁白盖住。
373
+ - **BGM 与音效是可选层**:它们的作用是铺情绪与打重音,不该抢字。想更安静就调低 `music.gain_db`
374
+ 或设 `music.enabled=false`;"让观众听清旁白"永远优先于"背景好听"。
375
+ - 音效响时配乐自动让位:v0.7 起风格表默认 4.0 dB(mono 3.0 / lo-fi·magazine 3.5 /
376
+ cinema·epic 5.5),`film.json` 的 `music.duck_db` 显式值永远优先。旧版 `mix_film` 默认 3.0 dB,
377
+ 在段落过门时配乐几乎与音效等高——这是"配乐和音效听着像同一套"的混音侧成因,已由 `duck_default` 抬高。
378
+ 让得太多会"音乐一抖一抖",所以按风格分级而不是一律压狠。
357
379
  - **本项目不做母带压缩**:链路是 求和 → duck → 峰值保护 0.89 → `ffmpeg loudnorm` 两遍。
358
380
  代码里**没有压缩器**(想加请自行在 `mix_film` 之后接),所以也不存在"压太狠"的问题;
359
381
  要让重击前后有能量差,靠的是配乐分段 energy 与 `suck()`,不是压缩。
360
382
  - 成片音轨必须 48 kHz(`mux` 已显式 `-ar 48000`,selfcheck 第 12 条守着)。
361
383
 
384
+ ### 5.7 声音的片间差异与片内分离(v0.7 新增)
385
+
386
+ > v0.5 解决的是"配乐风格都差不多",但**同一风格下每部片的配乐仍像同一首**——
387
+ > 内容指纹只进了 seed(改音色细节),和弦进行/动机/鼓型全固定。
388
+ > 实测:同风格、同 bpm、同调、只换内容指纹,两段配乐相关性 **0.966**。
389
+
390
+ v0.7 把指纹推进到**结构层**(详见 **references/audio-variation.md**):
391
+
392
+ | 层 | v0.6 | v0.7 |
393
+ |---|---|---|
394
+ | 配乐和声 | 风格固定进行 | 指纹决定**每两小节一次的稳定和弦替换**(不影响乐句末与片尾规则) |
395
+ | 配乐进行 | 风格固定一套进行 | 指纹在风格自带的 calm/body/drive 三档里选一档(同风格素材,只换功能顺序) |
396
+ | 配乐动机 | 风格固定动机 | 指纹对级数做 **±1 稳定偏移**(节奏不变,问句/答句独立派生;跨主音带八度修正) |
397
+ | 配乐鼓型 | 固定落点 | 指纹加 ghost kick(不与现有落点重复),clap 位置 ±0.5 拍 |
398
+ | 出片接线 | 指纹只进 seed | `render.py` 的 `st_score` 把 `fingerprint.full` 传进 `scorekit.render()`;legacy 不传 |
399
+ | 音效 seed | `seed_base + i`(只跟 cue 顺序) | `content_seed(seed_base, style, content_fp, type, t)`(跟内容走) |
400
+ | 提示音音高 | 硬编码 D 小调五声 | `in_key(freq, key)` 跟随配乐调性 |
401
+ | 段落边界 | 配乐过门 + sfx 转场各响各的 | 边界前 2.2 s 内的 whoosh/riser **自动 -3 dB**(不与过门撞车) |
402
+ | duck | 3 dB 一刀切 | 按风格 3.0–5.5 dB,配乐让得更彻底 |
403
+ | 旁白优先(v0.7.1) | 配乐 -1.5 dB、无语音侧链 | 配乐 -12 dB 基线 + 人声处再压 6 dB(音效 3 dB),人声段 SNR 实测 15 dB |
404
+
405
+ **向后兼容**:不写 `style` 字段(legacy)或 `sfx.content_seed: false` 时,音效走旧的
406
+ `seed_base + i` 路径,老片逐样本不变(selfcheck 第 33 条守着这条)。
407
+
408
+ 验收工具(改音频相关代码后必须跑):
409
+
410
+ ```bash
411
+ python <REPO>/tools/audio_similarity.py --strict # A/B/C 三组阈值,失败 exit 1(在包根执行)
412
+ ```
413
+
362
414
  ## 6. 停留窗口:让观众看清楚,而不是整片放慢(v0.3.0)
363
415
 
364
416
  > 来自同一部成片的复盘:"部分画面没看清就被切走了"。**解决办法不是把整片放慢,而是按内容加停留。**
@@ -383,7 +435,7 @@ python render.py ... --stage style # 用某份 film.json 解析风格并打印"
383
435
 
384
436
  ```bash
385
437
  python render.py ... --stage qa # 只读质检:分辨率/音轨/时长/响度/真峰/锐度比/黑帧/字幕条数 + 帧级深检
386
- python selfcheck.py # 技能自身回归(32 条曾真的坏过的点)
438
+ python selfcheck.py # 技能自身回归(43 条曾真的坏过的点)
387
439
  python qa_frames.py out/film.mp4 --cues work/cues.json # 单独跑帧级深检,出 JSON + 图
388
440
  # v0.6:qa / all 收尾会自动写 out/QC-REPORT.md(机器可读交付结论)
389
441
  # 并落 work/gates.json(三档 gate matrix)与 work/cost.jsonl(实测耗时台账)
@@ -428,8 +480,9 @@ python render.py ... --stage accept --gate stills --note "看过联系表" #
428
480
 
429
481
  ```bash
430
482
  python render.py ... --stage explain # 哪些阶段过时了、为什么、最小重跑命令
431
- python render.py ... --stage explain --json # 机读
432
483
  python render.py ... --stage qa --strict-stale # 有过时就失败(CI / 交付前)
484
+ # 机读:问 stagewatch(render.py 的 explain 只打人读文本,不接受 --json)
485
+ python scripts/stagewatch.py --work work --json
433
486
  ```
434
487
 
435
488
  机制:每个 stage 结束往 `work/manifest.json` 记一行 `{stage, at, inputs:{名:指纹}, outputs:{名:指纹}}`;
@@ -589,11 +642,13 @@ v0.4 给了两条真正能用的路:
589
642
  - `references/pipeline.md` — 八阶段详细规格、契约 JSON 格式、目录约定、并行分工
590
643
  - `references/scene-authoring.md` — 场景函数写法、构图规则、Canvas/CDP 方案、超采样与后处理
591
644
  - `references/audio.md` — 采样级混音、程序化配乐/音效、响度链路、循环环境音、提示点系统
645
+ - `references/audio-variation.md` — **(v0.7)片间差异**:内容指纹怎么进和声/进行/动机/鼓型,legacy 兼容与验收阈值
592
646
  - `references/sfx-design.md` — **(v0.3)音效设计**:物理拆解流程、配方表、听感诊断表、QC 规则
593
647
  - `references/web-engine.md` — **(v0.3)浏览器引擎**:页面契约、确定性时间轴、分块渲染、崩溃重开
594
648
  - `references/frame-qa.md` — **(v0.3)帧级质检**:单帧闪烁判据、画面空窗、音画同步、报告怎么读
595
649
  - `references/quality-gates.md` — G0–G6 全部门禁、可复制代码、验收清单
596
650
  - `references/change-impact.md` — **(v0.6)改动影响表**:改了 text/sub/hold/style/scenes.py 各要重跑什么
651
+ - `references/style-packs.md` — **(v0.5)风格包**:主题/配乐/声线字段、三级指纹规则、加第 9 套风格的步骤
597
652
  - `references/pitfalls.md` — 20 条最佳实践 + 16 条反模式 + 3 个非确定性 bug 复盘
598
653
 
599
654
  ## 10. 脚本资产
@@ -605,15 +660,15 @@ v0.4 给了两条真正能用的路:
605
660
  | `scripts/gates.py` | **(v0.6)门禁账本**:pass / fail / skipped 三档、`out/QC-REPORT.md` 渲染、人工签核(绑内容指纹)、原子写 |
606
661
  | `scripts/stagewatch.py` | **(v0.6)阶段新鲜度**:每 stage 记录输入/输出指纹到 `work/manifest.json`;上游一改,下游报过时并给出最小重跑命令 |
607
662
  | `scripts/filmcheck.py` | **(v0.6)film.json 契约校验**:未知键提示"是不是想写 X"(以前静默忽略)、枚举与范围、跨字段(cue 引用的 id 必须存在) |
608
- | `scripts/selfcheck.py` | 回归自检:**32 条**"曾真的坏过"的断言,不联网 20 秒出结论(可选模块缺失标 SKIP) |
663
+ | `scripts/selfcheck.py` | 回归自检:**43 条**"曾真的坏过"的断言,不联网 20 秒内出结论(可选模块缺失标 SKIP) |
609
664
  | `scripts/sfxkit.py` | **(v0.3)物理建模音效库**:模态合成/纹理/空间/动力学/母带 + 24 个配方 + `CUE_ALIASES` |
610
665
  | `scripts/scorekit.py` | **(v0.3)程序化配乐**:和弦表/分段能量/律动层/吸气/自动电平,输出 `music.wav` |
611
666
  | `scripts/webengine.py` | **(v0.3)浏览器逐帧渲染**:Edge/Chromium + CDP 截图、分块并行、崩溃重开、cue 导出、文字质检 |
612
667
  | `scripts/qa_frames.py` | **(v0.3)帧级质检**:单帧闪烁/画面空窗/音画同步/亮度与响度曲线(JSON + PNG) |
613
668
  | `assets/scenes.py` | 场景模板(开场/正文卡片/收尾 + **`cues()` 提示点钩子示范**),含缓存排版与预烘焙背景的正确写法 |
614
669
  | `assets/film.json` | 剧本模板(含 `hold`/`bpm`/`music`/`sfx.cues`/`engine` 等新字段) |
615
- | `../../schemas/film.schema.json` | **(v0.6)film.json 的契约**(JSON Schema draft-07 子集)。校验器见 `scripts/filmcheck.py`,模板由 `tools/ci_check.py` 第 12 项守着 |
616
- | `../../tools/estimate.py` | **(v0.6)开渲前预演**:帧数 / 各阶段耗时 / 磁盘峰值 / 分块建议,`--json` 可机读。基准常量集中在文件顶部,换机请重测后回填 |
670
+ | `<REPO>/schemas/film.schema.json` | **(v0.6)film.json 的契约**(JSON Schema draft-07 子集)。校验器见 `scripts/filmcheck.py`,模板由 `tools/ci_check.py` 第 12 项守着 |
671
+ | `<REPO>/tools/estimate.py` | **(v0.6)开渲前预演**:帧数 / 各阶段耗时 / 磁盘峰值 / 分块建议,`--json` 可机读。基准常量集中在文件顶部,换机请重测后回填 |
617
672
  | `assets/web/` | **(v0.3)浏览器页面模板**:原生 Canvas2D 确定性时间轴、场景示范、文字质检脚本 |
618
673
  | `requirements.txt` | **(v0.4)Python 依赖清单**:必需(numpy/Pillow/edge-tts)/ 强烈建议(cv2)/ 可选(scipy、playwright)三档,带注释说明"缺了会怎样"。`pip install -r requirements.txt` 即可 |
619
674
 
@@ -652,7 +707,8 @@ v0.4 给了两条真正能用的路:
652
707
  > 容器:1920x1080 / 30 fps / h264+aac / 48 kHz / 时长 30.10 s / 1.00 Mbps
653
708
  > 信号:-16.0 LUFS / 真峰 -1.2 dBTP / 锐度比 1.01 / 黑帧 0 / 字幕 4 条 = 剧本 4 句
654
709
  > 时间:单帧闪烁 0 / 画面空窗 0 / 音画同步超差 0(本条为 web 引擎或写了 visual_at 时才有意义)
655
- > 自检:selfcheck PASS 32 / FAIL 0 / SKIP 0;tools/ci_check.py PASS 12 / FAIL 0
710
+ > 自检:selfcheck PASS 43 / FAIL 0 / SKIP 0;tools/ci_check.py PASS 12 / FAIL 0
711
+ > 音频:work/score.json 的 variation 非 0(指纹参与了结构变奏)、audio_similarity --strict 全 PASS
656
712
  > 目检:work/qa/sheet.png,四帧分别为开场星射线、五步成片卡片、画质三件事卡片、收尾居中大字;
657
713
  > 字幕黑底白字不压正文,大字边距均匀,无空洞帧。
658
714
  > ```
@@ -694,7 +750,7 @@ v0.4 给了两条真正能用的路:
694
750
  | `scorekit` | 389 s 全片 **4–8 s**(v0.3 首测 6.7–8.2 s,v0.4 复测 3.9–5.8 s,随负载);60 s 实测 2–4 s;输出 -18.5 LUFS / -1.44 dBTP(母带交给 loudnorm)。**selftest 会打印当下实测**,以那个为准 |
695
751
  | `webengine` | **80–130 ms/帧**(1080p30,Edge 无头,PNG 截图);12 s / 360 帧 **28–30 s**(2 workers,含启动);**同一页面会话内**同帧两次截图 byte 完全一致(跨会话默认 GPU 光栅有 ≤4 像素、±1 级抗锯齿差异,要跨会话字节级一致请开 `--deterministic`,代价约 +22%/帧) |
696
752
  | `qa_frames` | 30 s 片全量深检 **约 1 s**(160×90 流式解码 903 帧);闪烁/空窗/静止/同步 |
697
- | 端到端 | 30.10 s 模板片:TTS 4 句 → cues 13 条 → 配乐 3.3 s → 音效 13 条落位 → 混音 → PIL 75 fps 渲染 12 s → mux → 深检 PASS |
753
+ | 端到端 | 30.10 s 模板片:TTS 4 句 → cues 由 scenes.py 按 kind 生成(v0.7,模板片 17 条)→ 配乐 3.3 s → 音效逐条落位 → 混音 → PIL 75 fps 渲染 12 s → mux → 深检 PASS |
698
754
  | Python 依赖 | **playwright 已装**(chromium + msedge 均可用);scipy 1.18 已装(sfxkit 可选,缺了走 FFT 兜底);`soundfile` **没装**(本技能不依赖它) |
699
755
 
700
756
  ### 排期公式(保守)
@@ -713,7 +769,23 @@ Pillow 路线: 墙钟秒数 ≈ 帧数 / 40 / 并行度 + 30% 余量
713
769
 
714
770
  ## 13. 升级须知(从旧版迁移要动的地方)
715
771
 
716
- ### 13.0 v0.3.0 → v0.4.0
772
+ ### 13.0 v0.4 → 当前版本(v0.5 / v0.6 / v0.7 / v0.7.1 / v0.7.2 要做什么)
773
+
774
+ **一句话**:v0.4 的老项目**不写新字段就能继续跑**(不写 `style` = legacy 行为,配乐逐样本不变),
775
+ 但**工作目录要多拷文件**,且有两处默认值变了要重新听。按版本逐条过:
776
+
777
+ | 从 → 到 | 你要做的动作 | 依据 |
778
+ |---|---|---|
779
+ | v0.4 → **v0.5** | 新工作目录多拷 `scripts/stylekit.py`(**必需**,render 直接 import)、`scripts/voicekit.py`(可选,缺了单声线降级);`--stage` 多出 `style`,`--stage all` 顺序变为 `style → tts → …`;改用 `style: "auto"` 才会自动选风格,不写仍是 legacy | CHANGELOG 0.5.0「升级须知(v0.4 -> v0.5)」 |
780
+ | v0.5 → **v0.6** | **必需**多拷 `scripts/gates.py`、`scripts/stagewatch.py`(少一个 render.py 直接 `ModuleNotFoundError`);首次跑会多出 `work/manifest.json` / `work/gates.json` / `out/QC-REPORT.md`,属新增文件 | CHANGELOG 0.6.0「升级须知(v0.5 → v0.6)」 |
781
+ | v0.6 → **v0.7** | 想吃到「配乐/音效随内容变化」就保持 `style: "auto"`,**不要**写死 `music.seed` 或关掉 `seed_from_content`;音效想更丰富就把提示点写进 `scenes.py` 的 `cues()` | CHANGELOG 0.7.0「升级须知(v0.6 → v0.7)」 |
782
+ | v0.7 → **v0.7.1** | 配乐基线默认值从 `music.gain_db=-1.5` 变成 **-12**(模板也已改),有人声处再压配乐 6 dB / 音效 3 dB;**之前手调过 `gain_db`/`duck_db` 的片子要重新听一遍**(现在背景整体更低)。显式写 0 可关掉对应侧链 | CHANGELOG 0.7.1「修了什么」+ `assets/film.json` 的 `music` 默认值 |
783
+ | v0.7.1 → **v0.7.2** | `QC-REPORT.md` 现在**只落在 `--out` 的目录**(以前 `accept` 分支按 `work` 的兄弟目录算,`--out deliver/film.mp4` 时签核会更新到另一份没人交付的文件)。如果你用 `--out` 指到了非 `out/` 的目录,交付时请看新位置;旧的 `out/QC-REPORT.md` 不会被再写 | `render._report_path()` 的 docstring + `selfcheck` 第 40 条(本轮新增) |
784
+
785
+ > 查当前版本:`python <REPO>/tools/ci_check.py -v` 里的 version 断言、或 `cat package.json` 的 `version`。
786
+ > 升级后建议按 §14 重跑一次自检(43 条),再按 §7 重新签核(改了代码/默认值,旧签核会自动失效,这是设计)。
787
+
788
+ ### 13.1 v0.3.0 → v0.4.0
717
789
 
718
790
  **多数项目不用改任何东西**(默认行为向后兼容),但下面几处会**改变你看到的输出或报错**:
719
791
 
@@ -727,7 +799,7 @@ Pillow 路线: 墙钟秒数 ≈ 帧数 / 40 / 并行度 + 30% 余量
727
799
  | `film.json` 校验变严 | 缺 `text` / `lines` 空 / id 重复 / bpm 非法 会直接报错 | 按提示修剧本 |
728
800
  | 可选 peer 改成按需 import | 缺 `@deepseek-ai/dsh-skill-filesystem` 时插件不再整体加载失败,只是不挂载技能并打日志 | 想用技能就按日志里的命令装依赖 |
729
801
 
730
- ### 13.1 v0.2 → v0.3.0
802
+ ### 13.2 v0.2 → v0.3.0
731
803
 
732
804
  **兼容性优先:老项目不做任何改动也能跑**(`engine` 默认 `pil`、`music.mode` 缺省时按老的和弦床走、`whoosh` 行为保留)。
733
805
 
@@ -744,7 +816,7 @@ Pillow 路线: 墙钟秒数 ≈ 帧数 / 40 / 并行度 + 30% 余量
744
816
 
745
817
  **新增文件**:`sfxkit.py` / `scorekit.py` / `webengine.py` / `qa_frames.py` / `assets/web/`(都是可选,缺失时对应能力自动降级并打印 WARN)。
746
818
 
747
- ### 13.2 v0.1 → v0.2(历史)
819
+ ### 13.3 v0.1 → v0.2(历史)
748
820
 
749
821
  | 变更 | v0.1 | v0.2 | 迁移动作 |
750
822
  |---|---|---|---|
@@ -762,7 +834,7 @@ Pillow 路线: 墙钟秒数 ≈ 帧数 / 40 / 并行度 + 30% 余量
762
834
 
763
835
  ```bash
764
836
  cd <SKILL_DIR>/scripts
765
- python selfcheck.py # 32 条断言;FAIL 就照提示修环境
837
+ python selfcheck.py # 43 条断言;FAIL 就照提示修环境
766
838
  python sfxkit.py --selftest # 音效库(可选模块,装了才跑)
767
839
  python scorekit.py --selftest # 配乐
768
840
  python webengine.py --selftest # 浏览器引擎(24 项,会真起无头浏览器,约 15 秒)
@@ -775,7 +847,7 @@ selfcheck 自己造测试信号、现场调 ffmpeg 编码,**不联网、不依
775
847
  **别把"没装可选件"和"坏了"混为一谈**。
776
848
 
777
849
  > **仓库级体检**(v0.4 起)是另一层,别和 selfcheck 搞混:
778
- > `python tools/ci_check.py -v` 只查"这个仓库作为可分发包是否完整"(语法/JSON/依赖白名单/
850
+ > `python <REPO>/tools/ci_check.py -v`(在包根执行)只查"这个仓库作为可分发包是否完整"(语法/JSON/依赖白名单/
779
851
  > 打包卫生/文档路径引用),**不需要 ffmpeg、字体或网络**,任何机器都能跑;
780
852
  > selfcheck 查的是"技能还能不能真的出片"。两层都在 CI 里跑。
781
853
  >
@@ -24,8 +24,11 @@
24
24
  "key": "auto",
25
25
  "seed": 7,
26
26
  "seed_from_content": true,
27
- "gain_db": -1.5,
28
- "duck_db": 3,
27
+ "gain_db": -12,
28
+ "duck_db": 4,
29
+ "voice_duck_db": 6,
30
+ "voice_duck_sfx_db": 3,
31
+ "guard_snr_db": 12,
29
32
  "seg": 4,
30
33
  "amp": 0.05,
31
34
  "gain": 0.9,
@@ -51,24 +54,14 @@
51
54
  "sfx": {
52
55
  "enabled": true,
53
56
  "gain_db": 0,
57
+ "content_seed": true,
58
+ "_comment": "v0.7 去固定化(references/audio-variation.md §3.5):模板只留 1 条转场 whoosh 作示例,具体提示点建议交给 assets/scenes.py 的 cues() 钩子按画面内容生成(hook=impact+riser;body=ui_blip 按条目文本定音高 + answer_chime + 段尾 whoosh;quote=reverse_swell+impact;outro=answer_chime+sub_drop)。content_seed=true(默认)= 每条 cue 的 seed 由 style/content_fp 派生(不同片子同一位置也不同),显式 false 回到 v0.6 的 seed_base+i 旧行为。",
54
59
  "cues": [
55
60
  {
56
61
  "at": "l02-0.15",
57
62
  "type": "whoosh",
58
63
  "dur": 0.9,
59
64
  "gain": -2
60
- },
61
- {
62
- "at": "l03",
63
- "type": "riser",
64
- "dur": 2,
65
- "gain": -4
66
- },
67
- {
68
- "at": "l03+0.05",
69
- "type": "impact",
70
- "size": 1,
71
- "gain": -3
72
65
  }
73
66
  ]
74
67
  },