@dsh-gis/dsh-video-studio-skill 0.4.0 → 0.6.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.
@@ -137,7 +137,35 @@ trimmed = K.trim_silence(pcm, thresh_db=-42.0, keep_ms=80) # 不是 trim_tail
137
137
  # 轻 EQ:200 Hz 以下衰减 2 dB(去闷),3 kHz 提 1 dB(清晰度)
138
138
  ```
139
139
 
140
- 角色多时固定音色映射:少年 YunxiaNeural、成年男 YunjianNeural、叙述 YunyangNeural、女声 XiaoxiaoNeural;同一角色全片不换。
140
+ ### 7.1 声线(persona)与语气(tone)—— v0.5.0
141
+
142
+ v0.4 及以前全片只有一个 `voice/rate/pitch`,于是"每部片的旁白都差不多"。
143
+ v0.5 引入 **persona(声线)× tone(语气档)**,由 `voicekit.py` 解析成逐句 spec:
144
+
145
+ ```json
146
+ {"voice": {"map": {"hook": "bright_female", "body": "narrator_female", "quote": "authority_male"},
147
+ "jitter": 0.35, "max_distinct": 3}}
148
+ ```
149
+
150
+ ```bash
151
+ python voicekit.py --list # 9 个 persona + 6 个 tone(含实测响度补偿 gain_db)
152
+ python voicekit.py --plan film.json # 打印某份剧本的逐句声线计划(不发请求)
153
+ ```
154
+
155
+ - **persona** 决定 edge-tts 音色 id + 基线 rate/pitch/volume/gain_db;
156
+ - **tone** 决定相对偏移与句前/句后停顿:hook / body / quote / outro / emphasis / calm
157
+ (`body` 严格等于 v0.4 基准:lead 0.62 / tail 0.92 / 所有 delta=0);
158
+ - **内容规则**会自动升级语气:引号句 -> quote(并换到 quote 声线)、数字密集 -> emphasis、
159
+ 问句/感叹 -> 稍快稍亮、长句 -> 降速加尾部留白;显式 `lines[].voice/tone` 优先级最高;
160
+ - **jitter**(默认 0.35)用 sha1 派生 ±1.5% / ±1Hz / ±0.04s 的微扰,去掉"每句等距等音量"的机械感;
161
+ - **governor** 限制全片最多 `max_distinct` 把声音、相邻句不换人 —— 超过就变成广播剧,不是旁白。
162
+
163
+ **响度校准**:不同音色的响度天然差 3–7 dB(实测 narrator_female 比 narrator_male 响约 5.7 dB),
164
+ persona 表里的 `gain_db` 是**实测回填**的(`voicekit.py --calibrate` 可复测)。
165
+ 混音侧每句只应用一次增益(v0.5 顺手修掉了 v0.4 `voice_gain` 被乘两次的静默 bug)。
166
+
167
+ **缓存键 v2**:`sha1(v2|text|voice|rate|pitch|volume|trim|trim_db)`。v0.4 的 key 不含 trim,
168
+ 于是把 `trim_silence` 改成 false 仍会命中旧的裁剪版 —— "关了裁剪其实没关"。v1 key 仍然保留给老缓存。
141
169
 
142
170
  ## 8. 混音自检(必做)
143
171
 
@@ -0,0 +1,54 @@
1
+ # 改了东西要重跑什么(改动影响表)
2
+
3
+ > 这张表的存在理由:**"我就改一句台词,要重渲全片吗?"** 这个问题以前只能靠 agent 猜。
4
+ > 现在有两个更硬的依据:`render.py --stage explain`(机器判断哪些阶段过时)与本表(人读的因果)。
5
+ > 两者不一致时**以 `--stage explain` 为准**——它看的是文件的真实指纹,不是经验。
6
+
7
+ ## 0. 先记住三条
8
+
9
+ 1. **时间轴是唯一的地基**:凡改了影响"每句多长"的东西(台词、语速、停留),
10
+ 下游 `cues → score → sfx → audio → frames → mux → qa` 全部要重跑。
11
+ 改字幕文案(`sub`)不影响语音时长,但会改字幕窗口 → 也要重跑 layout 及之后。
12
+ 2. **画面代码(scenes.py)变了,音轨可以复用**,但 `frames` 与 `qa` 必重跑
13
+ (qa 的锐度参考帧是场景函数重新渲染的,画面一变参考帧就失效)。
14
+ 3. **分块缓存会自己拒绝复用**:改过剧本/时长/分块参数后再 `--resume`,
15
+ `_frames_plan_stamp` 会发现指纹不符并拒绝拼接——**别手工删 `_plan.json` 绕过它**,
16
+ 按提示 `rm -rf work/segments` 重渲。
17
+
18
+ ## 1. 影响表(本机 1080p30 口径)
19
+
20
+ | 改了什么 | 失效产物 | 最小重跑 | 成本量级 |
21
+ |---|---|---|---|
22
+ | `lines[].text`(台词) | tts(该句) / timeline / cues / sfx / audio / frames / mux / qa | `tts → layout → cues → sfx → audio → frames → mux → qa` | 秒级(TTS) + 帧数÷40 s |
23
+ | `lines[].sub`(字幕文案) | sub.srt / sub.ass / timeline 的字幕窗口 / frames / mux / qa | `layout → frames → mux → qa`(TTS 可复用) | 帧数÷40 s |
24
+ | `lines[].hold` / `lead` / `tail` / `bar` | timeline / cues / frames / mux / qa | 同上 | 帧数÷40 s |
25
+ | `lines[].kind` | 声线计划 / 配乐分段 / 画面版式(scenes 按 kind 选绘制函数) | `style → tts → layout → …` 全链 | 全片 |
26
+ | `style`(含 `"auto"` 结果变了) | style.json / voice_plan / tts(换声线时) / score / audio / frames / qa | 看 `--stage explain`:通常是全链 | 全片 |
27
+ | `bpm` / `music.*` | score / audio / mux | `score → audio → mux → qa` | 秒级 |
28
+ | `sfx.cues` / `scenes.py 的 cues()` | cues.json / sfx.wav / audio.wav / mux | `cues → sfx → audio → mux → qa` | 秒级 |
29
+ | `scenes.py`(画面代码) | frames / qa(参考帧失效) | `frames → mux → qa`(音轨可复用) | 帧数÷40 s |
30
+ | `crf` / `preset` | 只影响 mux | `mux → qa` | 1–3 s |
31
+ | `engine`(pil ↔ web) | frames / qa / 分块(两条渲染路径不留复用) | `frames → mux → qa` | 帧数×(1÷fps 或 0.08–0.13 s) |
32
+ | `tts_workers` / `chunk_frames` / `--jobs` | 只影响调度 | 无需重跑 | — |
33
+
34
+ ## 2. 三个常见错误(都真的发生过)
35
+
36
+ | 错误做法 | 后果 | 正确做法 |
37
+ |---|---|---|
38
+ | 改了 `text` 只重跑 `tts` | 新语音配旧时间轴 → 字幕漂移、音效错位 | 跑 `--stage explain`,按它给的最小重跑命令 |
39
+ | 改了 `style` 只重跑 `frames` | 画风换了但配乐/声线还是旧的(或反之) | `style` 是所有下游的上游,通常整链重跑 |
40
+ | 手工删 `work/_plan.json` 绕过指纹 | 拼接出的片子画面和旁白对不上,且**不报错** | 删 `work/segments` 整目录重渲 |
41
+
42
+ ## 3. 一条命令代替这张表
43
+
44
+ ```bash
45
+ python render.py --stage explain # 哪些阶段过时了、为什么、最小重跑命令
46
+ python render.py --stage explain --json # 机读(给脚本/agent 用)
47
+ python render.py --stage qa --strict-stale # 有过时就失败(CI / 交付前)
48
+ ```
49
+
50
+ ## 4. 和人工签核的关系
51
+
52
+ 签核(`--stage accept`)绑的是**内容指纹**(剧本 + scenes.py + 引擎)。
53
+ 所以:**改了上面表里任何一项,之前的签核都会自动失效**,需要重新看、重新签。
54
+ 这不是麻烦,是这套机制存在的意义——否则"我签过了"会变成一张永远有效的空头支票。
@@ -221,5 +221,5 @@ python webengine.py textqa ./web/index.html --step 0.2
221
221
  信号:-16.3 LUFS / 真峰 -1.2 dBTP / 锐度比 0.94 / 黑帧 0 / 字幕 86 条 = 剧本 86 句
222
222
  时间:单帧闪烁 0 / 画面空窗 0 / 音画同步最大偏差 38 ms
223
223
  页面:文字重叠 0 / 出画 0 / 一闪而过 0 / 从没露面 0
224
- 自检:python selfcheck.py -> PASS 24 / FAIL 0 / SKIP 0
224
+ 自检:python selfcheck.py -> PASS 29 / FAIL 0 / SKIP 0
225
225
  ```
@@ -0,0 +1,178 @@
1
+ # 风格包(Style Packs):让每部片的画风 / 配乐 / 旁白都不一样
2
+
3
+ > v0.5.0 新增。读这一篇之前先读 SKILL.md 的 §5.6。
4
+ > 一句话:**一个 style id 同时决定画风、配乐、旁白声音和节奏**,不能三样各自乱配。
5
+
6
+ ## 0. 为什么需要它(v0.4 的病)
7
+
8
+ v0.4 及以前,所有"身份参数"都是模块级常量:
9
+
10
+ | 层 | 被写死的地方 | 结果 |
11
+ |---|---|---|
12
+ | 画风 | assets/scenes.py 的 THEME 字典 + 3 个绘制函数 | 每部片都是深蓝底 + 橙强调 |
13
+ | 配乐 | scorekit 的 4 个小调 + 3 套和弦进行 + 固定动机 | 每部片都是 Dm / 120BPM 那一套 |
14
+ | 旁白 | render.py 的单个 voice/rate/pitch | 每部片都是同一个声音、同一语速 |
15
+ | 流程 | SKILL.md 教的是 "cp 模板 -> 改 lines[].text" | 模型自然只会改文案 |
16
+
17
+ 结果就是:**内容换了,气质没换**。用户的原话是"背景音乐风格都差不多、旁白声音都差不多、整体画风也差不多"。
18
+
19
+ ## 1. 三层结构
20
+
21
+ film.json style: "auto" | "tech-deep" | ... | "legacy"
22
+ |
23
+ v
24
+ stylekit.resolve_script_style(script) <- 纯函数,确定性
25
+ | +- 关键词打分(title/chapter/text/items)
26
+ | +- 三级内容指纹(full / tempo / style)
27
+ | +- 显式覆盖(visual.theme / music.style / voice.map / pacing)
28
+ v
29
+ work/style.json <- 审计产物:能看到"为什么是这套风格"
30
+ | +- style / theme_id / chapter_layout
31
+ | +- music{bpm,key,mood,seed_effective}
32
+ | +- voice{map,policy}
33
+ | +- pacing{lead,tail,min_dur,item_stagger}
34
+ v
35
+ 画面(scenes.py 读主题) 配乐(scorekit 读风格) 旁白(voicekit 读 persona/tone)
36
+
37
+ ## 2. 8 套内置风格
38
+
39
+ | id | 中文 | 画风 | 配乐 | 旁白 | 适合内容 |
40
+ |---|---|---|---|---|---|
41
+ | tech-deep | 科技深蓝 | 深蓝渐变 + 橙 | 小调 100–124 / 琶音 + 铃声 | 男声叙述 + 青年开场 | AI、工程、数据、科普 |
42
+ | magazine | 杂志暖白 | 米白 + 朱红,衬线体 | 大调 88–100 / 电钢 | 女声叙述 + 权威男声引用 | 报告、复盘、研究 |
43
+ | paper | 手绘纸感 | 牛皮纸 + 靛青,楷体 | 大调 84–92 / 马林巴 | 软糯女声 + 男声引用 | 手工、自然、历史 |
44
+ | vivid | 活力撞色 | 深紫 + 柠檬黄,大字 | 大调 120–132 / 短促和弦 | 活泼女声开场 | 活动、营销、上新 |
45
+ | mono | 极简黑白 | 纯白 + 黑,超宽字距 | 极简 60–68 / 钢片琴 | 沉稳男声 + 女声引用 | 观点、演讲、思辨 |
46
+ | cinema | 电影青橙 | 近黑 + 青橙,扫描线 | 小调 76–84 / 弦乐铺底 | 纪录片男声 + 女声引用 | 纪录、叙事、城市 |
47
+ | lo-fi | 暖灰薄荷 | 暖灰 + 薄荷绿 | 大调 72–84 / 电钢 + 轻鼓 | 温柔女声 | 日常、学习、专注 |
48
+ | epic | 深黑鎏金 | 近黑 + 金,衬线体 | 小调 88–96 / 铜管 + 人声铺 | 有力男声 + 男声开场 | 史诗、宣传、高潮 |
49
+
50
+ 外加 legacy:不写 style 字段时的兼容档,画风/配乐/旁白全部等于 v0.4(配乐逐样本一致)。
51
+
52
+ 查表:
53
+
54
+ python stylekit.py --list # 8 套风格 + 主题 + 对比度
55
+ python stylekit.py --show cinema # 单套风格的全部字段
56
+ python stylekit.py --lint # 主题对比度门禁(CI 会跑)
57
+
58
+ ## 3. film.json 怎么配
59
+
60
+ 最小配置(推荐,几乎不用写):
61
+
62
+ {"style": "auto"}
63
+
64
+ 自动模式:命中题材关键词就锁定对应风格;零命中时按内容指纹稳定落桶
65
+ (同一份稿子永远同一个风格,不同稿子大概率不同)。
66
+
67
+ 想锁定:
68
+
69
+ {"style": "cinema"}
70
+
71
+ 想只改某一块(其余跟着风格走):
72
+
73
+ {
74
+ "style": "magazine",
75
+ "visual": {
76
+ "theme": "paper",
77
+ "layout": "split-right",
78
+ "theme_overrides": {"vignette": 0.18, "grain": 8.0}
79
+ },
80
+ "music": {
81
+ "style": "lo-fi",
82
+ "bpm": "auto", "key": "auto", "mood": "calm",
83
+ "seed": 7, "seed_from_content": true
84
+ },
85
+ "voice": {
86
+ "map": {"hook": "bright_female", "body": "narrator_female"},
87
+ "jitter": 0.35,
88
+ "max_distinct": 3
89
+ },
90
+ "pacing": {"lead": 0.6, "tail": 0.9, "item_stagger": 0.3}
91
+ }
92
+
93
+ 逐句覆盖(最高优先级):
94
+
95
+ {"id": "l03", "kind": "body", "text": "他说:「这不可能」",
96
+ "voice": "authority_male", "tone": "quote", "lead": 0.8}
97
+
98
+ ### 字段速查
99
+
100
+ | 字段 | 默认 | 说明 |
101
+ |---|---|---|
102
+ | style | 无(= legacy) | 风格 id / "auto" / "legacy" |
103
+ | visual.theme | — | 等价于锁 style;与 style 1:1 |
104
+ | visual.layout | 风格默认 | left-stack / center-stack / split-right |
105
+ | visual.theme_overrides | — | 深合并覆盖主题单字段 |
106
+ | music.style | 跟随 style | 只换配乐风格 |
107
+ | music.bpm | "auto" | 数字或 "auto"(按风格梯度 + 指纹) |
108
+ | music.key | "auto" | 调名或 "auto"(按风格调池 + 指纹) |
109
+ | music.mood | 风格默认 | calm/reflective/hopeful/steady/driving/tense/uplifting |
110
+ | music.seed_from_content | 非 legacy 时 true | 音色细节由内容指纹派生 |
111
+ | voice.map | 风格默认 | kind -> persona 名 |
112
+ | voice.jitter | 0.35 | 语速/音高微扰,0 = 关 |
113
+ | voice.max_distinct | 3 | 全片最多几把声音 |
114
+ | pacing.* | 风格默认 | 语前留白/语后留白/最短段长/条目落位步长 |
115
+ | lines[].voice/tone/rate/pitch/volume/lead/tail | — | 逐句覆盖(最高优先级) |
116
+
117
+ ## 4. 三级指纹:改错别字不会换整首曲子
118
+
119
+ | 指纹 | 由什么算 | 决定 |
120
+ |---|---|---|
121
+ | full | 标题 + 全部台词 | 音色细节(seed 派生) |
122
+ | tempo | 标题 + kind 序列 + 体量桶 | BPM 与调性 |
123
+ | style | 标题(配合关键词评分破平) | 风格 id |
124
+
125
+ 所以:改一个错别字 -> full 变(音色纹理微变),tempo/style 不变(还是同一首曲子、同一个调);
126
+ 换一部片 -> 三个都变。要完全锁死就写 music.seed + music.seed_from_content: false。
127
+
128
+ ## 5. 片内变化:整体是一套,但每段有呼吸
129
+
130
+ **锁三件事**(全片一致,否则会"花"):
131
+
132
+ 1. 明暗族(全深色 or 全浅色)
133
+ 2. accent 色相
134
+ 3. 字体族与字号阶梯
135
+
136
+ **其余可以按章节变**:
137
+
138
+ - 版式骨架轮换(同一 chapter 内不变):left-stack / center-stack / split-right
139
+ - 背景明度微移、纹理强度、accent 出现的面积
140
+ - 配乐:逐小节能量弧线、配器进出(1 小节渐入渐出)、主奏轮换、动机变形
141
+ - 旁白:hook/body/quote/outro 的语气档(语速、音高、停顿)
142
+
143
+ **禁止**:同一部片里跨明暗族换主题(深蓝段直接切暖白段)。实测这会精确触发
144
+ qa_frames 的单帧闪烁门禁(阈值 6.0 灰度,整屏换底可达 100+ 级),而且观众看着像"换了一部片"。
145
+ 真需要强对比,把它做成两个片子,或登记成带 visual_at 的视觉事件。
146
+
147
+ ## 6. 门禁:主题必须过对比度体检
148
+
149
+ stylekit.theme_lint(theme) 在 --stage style 就拦,不用等渲完 300 帧:
150
+
151
+ | 角色 | 对背景的最低对比度 | 理由 |
152
+ |---|---|---|
153
+ | ink(正文/标题) | 7.0 | WCAG AAA 正文线 |
154
+ | muted(副标) | 4.5 | WCAG AA |
155
+ | dim(HUD 小字) | 3.0 | 装饰级 |
156
+ | accent / accent2 | 4.5 | 强调色常当文字用 |
157
+ | good / bad | 3.0(对卡片底) | 只承载图形,靠位置+颜色双编码 |
158
+ | bg 与 bg2 亮度差 | >= 0.002 | 防纯色底 |
159
+
160
+ 加第 9 套风格时:先写 _theme(...),再跑 python stylekit.py --lint,过了再进 STYLES。
161
+
162
+ ## 7. 加一套自己的风格(5 步)
163
+
164
+ 1. stylekit.THEMES["my-style"] = _theme(...)(配色 + 材质 + 排版 + 版式 + 动态 + HUD)
165
+ 2. stylekit.STYLES["my-style"] = {...}(theme / layouts / music{bpm_ladder,keys,mood} / voice / pacing)
166
+ 3. STYLE_IDS 元组末尾追加(只追加不改顺序,否则指纹映射会漂)
167
+ 4. python stylekit.py --lint 全绿;配乐侧在 scorekit.MUSIC_STYLES 里加同名条目
168
+ 5. 用 3 段样片跑 --stage stills 亲眼看(铁律 6:能出图 != 没问题)
169
+
170
+ ## 8. 常见误用
171
+
172
+ | 现象 | 原因 | 修法 |
173
+ |---|---|---|
174
+ | 画面换了、配乐还是老的 | 只改了 visual.theme 没改 music.style | 直接改 style,或两个都写 |
175
+ | 同一部片前后像两部片 | 段界跨了明暗族 | 用同族主题;或拆成两部 |
176
+ | 改台词后配乐变了 | 改了标题或 kind 结构 -> tempo 指纹变 | 写死 music.key/music.bpm,或 seed_from_content:false |
177
+ | 旁白听感像广播剧 | max_distinct 太大 / 相邻句频繁换人 | 降到 2,靠 tone 而不是换人做变化 |
178
+ | 字幕在浅色主题上看不清 | 用了 v0.4 的黑底白字 | 传 theme= 给 draw_subtitle(render.py 已自动传) |
@@ -23,7 +23,18 @@ opencv-python-headless>=4.8
23
23
  # 装完还要下载浏览器:python -m playwright install chromium
24
24
  # playwright>=1.40
25
25
 
26
+ # ---- v0.6.0 新增文件:都是纯标准库,**不需要**任何 pip 包 ----
27
+ # scripts/gates.py 门禁账本 / QC 报告 / 人工签核(必需:render.py 直接 import)
28
+ # scripts/stagewatch.py 阶段新鲜度(必需:同上)
29
+ # scripts/filmcheck.py film.json 契约校验(可选:ci_check 第 12 项会调)
30
+ # tools/estimate.py 开渲前成本预演(可选)
31
+ # 若你从旧版本升级,记得把 gates.py / stagewatch.py 一起拷到工作目录,
32
+ # 否则 render.py 会 ModuleNotFoundError。
33
+
26
34
  # ---- 不需要 ----
27
35
  # moviepy / pydub / soundfile / librosa / manim:本技能一行都不 import。
28
36
  # ffmpeg 不是 pip 包:系统 PATH 里要有(含 libx264 / aac / loudnorm),
29
37
  # 或用环境变量 FILMKIT_FFMPEG 指向可执行文件。
38
+ # v0.6.0 起探测是 fail-closed 的:每个候选真跑 `-version`,跑不起来的会被跳过
39
+ # (防的是"存在但缺 xfade 的旧版 ffmpeg 被静默选中")。`FILMKIT_FFMPEG_STRICT=1`
40
+ # 可把"找不到可用 ffmpeg"从回退裸命令改成直接报错。