@dsh-gis/dsh-video-studio-skill 1.0.2 → 1.0.4
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.
- package/CHANGELOG.md +49 -0
- package/README.md +84 -108
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/package.json +1 -1
- package/schemas/design.schema.json +5 -5
- package/schemas/film.schema.json +31 -31
- package/skills/video-studio/SKILL.md +168 -268
- package/skills/video-studio/assets/film.json +2 -3
- package/skills/video-studio/assets/golden_frames.json +11 -11
- package/skills/video-studio/assets/scenes.py +92 -49
- package/skills/video-studio/references/{style-packs.md → appearance.md} +17 -15
- package/skills/video-studio/references/audio-variation.md +73 -96
- package/skills/video-studio/references/audio.md +40 -44
- package/skills/video-studio/references/change-impact.md +2 -2
- package/skills/video-studio/references/{v08-quality.md → delivery-gates.md} +9 -11
- package/skills/video-studio/references/frame-qa.md +47 -15
- package/skills/video-studio/references/pipeline.md +4 -4
- package/skills/video-studio/references/pitfalls.md +22 -3
- package/skills/video-studio/references/proposal.md +19 -36
- package/skills/video-studio/references/quality-gates.md +11 -11
- package/skills/video-studio/references/scene-authoring.md +2 -2
- package/skills/video-studio/references/scene-grammar.md +17 -24
- package/skills/video-studio/references/sfx-design.md +7 -5
- package/skills/video-studio/references/web-engine.md +7 -7
- package/skills/video-studio/references/word-timeline.md +7 -7
- package/skills/video-studio/requirements.txt +9 -13
- package/skills/video-studio/scripts/designkit.py +84 -89
- package/skills/video-studio/scripts/filmcheck.py +19 -19
- package/skills/video-studio/scripts/filmkit.py +2949 -2948
- package/skills/video-studio/scripts/gates.py +4 -4
- package/skills/video-studio/scripts/grammar.py +10 -10
- package/skills/video-studio/scripts/musicgen.py +3 -4
- package/skills/video-studio/scripts/preflight.py +5 -5
- package/skills/video-studio/scripts/previewkit.py +11 -12
- package/skills/video-studio/scripts/propose.py +11 -11
- package/skills/video-studio/scripts/qa_frames.py +34 -35
- package/skills/video-studio/scripts/qaasr.py +26 -7
- package/skills/video-studio/scripts/qavmaf.py +19 -19
- package/skills/video-studio/scripts/render.py +137 -141
- package/skills/video-studio/scripts/scorekit.py +21 -23
- package/skills/video-studio/scripts/selfcheck.py +208 -135
- package/skills/video-studio/scripts/sfxkit.py +27 -26
- package/skills/video-studio/scripts/stagewatch.py +4 -4
- package/skills/video-studio/scripts/stylekit.py +77 -282
- package/skills/video-studio/scripts/voicekit.py +43 -44
- package/skills/video-studio/scripts/webengine.py +62 -64
- package/tools/audio_similarity.py +12 -12
- package/tools/ci_check.py +16 -16
- package/tools/estimate.py +24 -25
- package/tools/golden_frames.py +17 -18
- package/tools/silhouette_qa.py +13 -14
|
@@ -1,42 +1,34 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""stylekit.py —
|
|
2
|
+
"""stylekit.py — 视觉/音频原语库:颜色数学、版式几何、主题体检、视觉语言体检。
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
本模块是**纯数据 + 纯函数**的技术底座,不提供任何外观成品。
|
|
5
|
+
外观从哪来只有一条路:`work/design.json`——由 `propose.py` 按 brief 现场采样、
|
|
6
|
+
用户 `--confirm` 之后冻结,再由 `designkit.design_to_plan()` 转成同形的计划字典。
|
|
6
7
|
|
|
7
8
|
设计原则
|
|
8
9
|
--------
|
|
9
|
-
1.
|
|
10
|
-
|
|
11
|
-
2.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
4. 向后兼容:不写 style 字段 = legacy,此时 render/scorekit/scenes 走 v0.4 原路径,
|
|
17
|
-
配乐逐样本一致(selfcheck 有断言)。
|
|
18
|
-
|
|
19
|
-
三级指纹(专门为了「改个错别字不要换整首曲子」)
|
|
20
|
-
---------------------------------------------
|
|
21
|
-
full 标题 + 所有台词 -> 决定音色细节 / 噪声纹理(seed 派生)
|
|
22
|
-
tempo 标题 + kind 序列 + 体量桶 -> 决定 BPM 与调性
|
|
23
|
-
style 关键词评分 + 指纹破平 -> 决定风格 id
|
|
10
|
+
1. 只依赖标准库,不 import filmkit / scorekit / scenes(它们反过来 import 本模块,
|
|
11
|
+
成环就炸)。因此可以单独测、结果可复现。
|
|
12
|
+
2. 阈值只有一份:对比度门槛(`LINT_LIMITS`)、视觉语言边界都在这里,
|
|
13
|
+
`designkit` 直接复用,不抄第二份。
|
|
14
|
+
3. 确定性:只用 sha1 派生,禁 random / time / hash()(str hash 每次进程带随机盐)。
|
|
15
|
+
4. 模板不存在:`STYLES` / `THEMES` / `VISUAL_LANGUAGES` 恒为空表,
|
|
16
|
+
写任何 style id 都会被明确拒绝并给出迁移指引(见 `resolve_script_style`)。
|
|
24
17
|
|
|
25
18
|
用法
|
|
26
19
|
----
|
|
27
20
|
import stylekit as ST
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ST.
|
|
21
|
+
ST.theme_lint(theme) # 对比度/明暗/结构体检,返回问题列表(空 = 过)
|
|
22
|
+
ST.visual_lint_language(lang) # 视觉语言包的数值边界体检
|
|
23
|
+
ST.LAYOUTS["left-stack"] # 版式几何(相对 W/H 与 SAFE,不写死魔法数)
|
|
24
|
+
ST.is_dark(theme) # 明暗族判定(同片不许跨族)
|
|
31
25
|
|
|
32
|
-
python stylekit.py
|
|
33
|
-
python stylekit.py --
|
|
34
|
-
python stylekit.py --lint # 8 套主题全部走对比度门禁
|
|
26
|
+
python stylekit.py # 打印本模块的职责说明
|
|
27
|
+
python stylekit.py --list # 已移除:没有可选外观清单,exit 2
|
|
35
28
|
"""
|
|
36
29
|
from __future__ import annotations
|
|
37
30
|
|
|
38
31
|
import argparse
|
|
39
|
-
import hashlib
|
|
40
32
|
import re
|
|
41
33
|
import sys
|
|
42
34
|
|
|
@@ -128,9 +120,9 @@ _HEX_RE = re.compile(r"^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
|
|
|
128
120
|
def _color_of(v, key):
|
|
129
121
|
"""把 theme_overrides 里的颜色值统一成 (r,g,b):支持 [r,g,b] 和 "#rrggbb" / "#rgb"。
|
|
130
122
|
|
|
131
|
-
为什么必须支持 hex:schemas/film.schema.json 与 references/
|
|
123
|
+
为什么必须支持 hex:schemas/film.schema.json 与 references/appearance.md 的示例
|
|
132
124
|
就是 {"accent": "#ff8a3d"},而 JSON 没有元组,照着文档写 hex 是必然的。
|
|
133
|
-
|
|
125
|
+
字符串必须在**这里**转成元组:原样塞进主题的话,scenes.py 会在渲染期崩
|
|
134
126
|
(实测:ink="#ffffff" -> ValueError: invalid literal for int() with base 10: '#')。
|
|
135
127
|
"""
|
|
136
128
|
if isinstance(v, str):
|
|
@@ -157,13 +149,13 @@ _THEME_COLOR_KEYS = frozenset(("bg", "bg2", "surface", "ink", "muted", "dim",
|
|
|
157
149
|
def apply_theme_overrides(theme, overrides):
|
|
158
150
|
"""把 film.json 的 visual.theme_overrides 合并进主题**并做类型校验**。
|
|
159
151
|
|
|
160
|
-
契约(schemas/film.schema.json:只接受主题里已存在的键;references/
|
|
152
|
+
契约(schemas/film.schema.json:只接受主题里已存在的键;references/appearance.md §3):
|
|
161
153
|
- 未知键忽略:以后主题加字段时老剧本不因为它不认识就炸;
|
|
162
154
|
- 颜色键(值本来是三色元组的那些)接受 [r,g,b] / "#rrggbb" / "#rgb",统一成元组;
|
|
163
155
|
- 数值键接受数字或数字字符串;字符串键必须是字符串;
|
|
164
|
-
- 嵌套结构(card / scale / qa
|
|
165
|
-
|
|
166
|
-
校验只在"
|
|
156
|
+
- 嵌套结构(card / scale / qa …)不支持覆盖,静默忽略(避免破坏已有配置);
|
|
157
|
+
真要改嵌套字段请在 designkit 里改生成逻辑。
|
|
158
|
+
校验只在"值类型不对、会在渲染期崩"的场合抛 ValueError(提前到 --stage style)。
|
|
167
159
|
返回新的 theme dict(不改入参)。
|
|
168
160
|
"""
|
|
169
161
|
out = dict(theme or {})
|
|
@@ -189,9 +181,9 @@ def apply_theme_overrides(theme, overrides):
|
|
|
189
181
|
out[k] = int(round(num)) if isinstance(base, int) else float(num)
|
|
190
182
|
continue
|
|
191
183
|
if isinstance(base, dict):
|
|
192
|
-
# 嵌套结构(card / scale / qa
|
|
193
|
-
#
|
|
194
|
-
#
|
|
184
|
+
# 嵌套结构(card / scale / qa)不支持覆盖:静默忽略而不是报错。
|
|
185
|
+
# 配置里写了这种覆盖时应视为"没生效"而不是"配置坏了",升级不该突然报错。
|
|
186
|
+
# 想改嵌套字段要在 designkit 的 _theme_of_design 里改,见 references/appearance.md §7。
|
|
195
187
|
continue
|
|
196
188
|
if isinstance(base, str):
|
|
197
189
|
if not isinstance(v, str):
|
|
@@ -251,22 +243,20 @@ def _theme(style_id, label, keywords, palette, material, typ, layout, motion, hu
|
|
|
251
243
|
|
|
252
244
|
|
|
253
245
|
# ============================================================================
|
|
254
|
-
#
|
|
246
|
+
# 二、主题:没有内置表,由 designkit 现场构造
|
|
255
247
|
# ============================================================================
|
|
256
248
|
#
|
|
257
|
-
#
|
|
258
|
-
# 深林绿 / 暖纸白 / 墨纸白)。它们是**模板**:用户只能在这 8 种外观里选,
|
|
259
|
-
# 第 9 种永远不会出现。
|
|
249
|
+
# `THEMES` 恒为空:本技能不提供任何内置外观,写 style id 一律被拒绝(见 resolve_script_style)。
|
|
260
250
|
#
|
|
261
|
-
#
|
|
251
|
+
# 主题由 designkit 从连续空间现场构造:
|
|
262
252
|
# · 底色 = (色相 h∈[0,360), 饱和 s, 明度 v) 连续取值,经 _palette_from_hue 算出三段底色;
|
|
263
253
|
# · 其余 8 个语义色由对比度网格求解(每套都必须过 theme_lint);
|
|
264
|
-
# ·
|
|
254
|
+
# · 产出是同形的 35 字段主题字典,scenes.py 不需要知道它从哪来。
|
|
265
255
|
#
|
|
266
|
-
#
|
|
267
|
-
# luminance / contrast /
|
|
268
|
-
# _theme(构造器) / hsv_to_rgb / rgb_to_hsv / LINT_LIMITS / theme_lint /
|
|
269
|
-
#
|
|
256
|
+
# 本模块提供的是**造东西的工具**,不是外观目录:
|
|
257
|
+
# luminance / contrast / _rgb / _color_of / apply_theme_overrides /
|
|
258
|
+
# _theme(构造器) / hsv_to_rgb / rgb_to_hsv / LINT_LIMITS / theme_lint /
|
|
259
|
+
# bg_contrast / is_dark / family_compatible / LAYOUTS(几何参数,不是外观目录)
|
|
270
260
|
THEMES = {}
|
|
271
261
|
# 三、版式骨架(LAYOUTS):3 种,scenes.py 按名字取几何参数
|
|
272
262
|
# ============================================================================
|
|
@@ -309,27 +299,18 @@ def layout(name):
|
|
|
309
299
|
|
|
310
300
|
|
|
311
301
|
# ============================================================================
|
|
312
|
-
#
|
|
302
|
+
# 四、风格表:恒为空(本技能不提供模板)
|
|
313
303
|
# ============================================================================
|
|
314
304
|
#
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
|
|
318
|
-
# ============================================================================
|
|
319
|
-
# 四、风格包(已删除:v1.0.1 起不提供任何模板)
|
|
320
|
-
# ============================================================================
|
|
305
|
+
# 一个设计从哪来只有一条路:work/design.json —— `propose.py` 按 brief 现场采样、
|
|
306
|
+
# 人 `--confirm` 之后冻结。表为空是**契约**,不是没填:selfcheck 断言它们必须为空,
|
|
307
|
+
# 有内容就意味着有人偷偷恢复了外观清单。
|
|
321
308
|
#
|
|
322
|
-
#
|
|
323
|
-
#
|
|
324
|
-
#
|
|
325
|
-
# 现在没有任何 style id:一个设计从哪来只有一条路——work/design.json,
|
|
326
|
-
# 由 propose.py 按 brief 现场采样、人确认后冻结。
|
|
327
|
-
#
|
|
328
|
-
# 保留的:LEGACY_STYLE 常量(只为了让"写 style=legacy"能被明确拒绝并给迁移指引,
|
|
329
|
-
# 而不是抛 AttributeError)。
|
|
309
|
+
# `LEGACY_STYLE` 常量只服务于一件事:让 `style="legacy"` 这类写法被**明确拒绝**并
|
|
310
|
+
# 给出迁移指引,而不是抛 AttributeError。
|
|
330
311
|
STYLE_IDS = ()
|
|
331
312
|
DEFAULT_STYLE = None
|
|
332
|
-
LEGACY_STYLE = "legacy"
|
|
313
|
+
LEGACY_STYLE = "legacy"
|
|
333
314
|
STYLES = {}
|
|
334
315
|
|
|
335
316
|
# ---- 调式池:技术原语,不是模板 ----
|
|
@@ -339,65 +320,37 @@ MINOR_KEYS = ("Dm", "Am", "Em", "Cm")
|
|
|
339
320
|
MAJOR_KEYS = ("C", "G", "D", "F", "A", "Bb")
|
|
340
321
|
|
|
341
322
|
|
|
342
|
-
# ---- 兼容壳:模板表删掉后,老调用点不该 AttributeError ----
|
|
343
|
-
# 这些函数在 v1.0.1 之前用于"按 style id 取模板"。现在没有模板,它们**必须明确报错**,
|
|
344
|
-
# 而不是返回一个假的默认值(那等于偷偷恢复模板)。
|
|
345
|
-
def style_ids(include_legacy=True):
|
|
346
|
-
return []
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def get(style_id):
|
|
350
|
-
raise KeyError(
|
|
351
|
-
"没有风格 %r:v1.0.1 起本技能不提供内置风格(模板已删除)。\n"
|
|
352
|
-
" 每一版外观都必须现场生成:python propose.py --brief \"题材\" --script film.json\n"
|
|
353
|
-
" 再用 --confirm 冻结 design.json" % (style_id,))
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def theme_of(style_id):
|
|
357
|
-
raise KeyError(
|
|
358
|
-
"没有主题 %r:v1.0.1 起没有内置主题表(模板已删除)。\n"
|
|
359
|
-
" 主题由 designkit 从 (色相,饱和,明度) 连续空间现场构造,见 work/design.json"
|
|
360
|
-
% (style_id,))
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def is_legacy(style_id):
|
|
364
|
-
return str(style_id or "") == LEGACY_STYLE
|
|
365
323
|
VISUAL_MOTIFS = ("brackets", "hairlines", "handdrawn", "slabs",
|
|
366
324
|
"minimal", "letterbox", "signal", "rays")
|
|
367
325
|
VISUAL_TRANSITIONS = ("dip", "wash", "paper", "wipe", "cut", "iris")
|
|
368
326
|
NUMBER_STYLES = ("dim", "serif", "cn", "block", "roman", "timecode", "dot", "gilt")
|
|
369
327
|
|
|
370
328
|
# ============================================================================
|
|
371
|
-
#
|
|
329
|
+
# 五、视觉语言包:名字是绘制原语,不是外观目录
|
|
372
330
|
# ============================================================================
|
|
373
331
|
#
|
|
374
|
-
#
|
|
375
|
-
#
|
|
332
|
+
# `VISUAL_LANGUAGES` 恒为空。视觉语言由 `designkit._language_of(design)` 从设计字段
|
|
333
|
+
# 直接拼出来(母题/转场/字距/缓动/重心/layout_cycle 五组),再交给
|
|
334
|
+
# `visual_lint_language` 做体检。
|
|
376
335
|
#
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
# 保留的:VISUAL_MOTIFS / VISUAL_TRANSITIONS / NUMBER_STYLES(**绘制原语的名字**,
|
|
381
|
-
# scenes.py 按名字分发到具体画法;这是"能画什么",不是"长什么样")、
|
|
382
|
-
# visual_lint_language(lang)(对任意 language 字典做体检的纯函数)。
|
|
336
|
+
# 这里保留的是 VISUAL_MOTIFS / VISUAL_TRANSITIONS / NUMBER_STYLES —— **绘制原语的名字**:
|
|
337
|
+
# scenes.py 按名字分发到具体画法。这是"能画什么",不是"长什么样"。
|
|
383
338
|
VISUAL_LANGUAGES = {}
|
|
384
339
|
|
|
385
340
|
|
|
386
341
|
def visual_language(lang=None):
|
|
387
|
-
"""
|
|
342
|
+
"""返回一份可用的视觉语言包:传入字典就原样返回,否则给空壳。
|
|
388
343
|
|
|
389
|
-
|
|
390
|
-
|
|
344
|
+
真正的语言包由 `designkit._language_of()` 生成并挂在 plan["language"] 上;
|
|
345
|
+
这个函数只负责让"没有语言包"的调用点拿到一个合法空壳而不是 None。"""
|
|
391
346
|
return lang if isinstance(lang, dict) else {}
|
|
392
347
|
|
|
393
348
|
|
|
394
349
|
def visual_lint_language(vl):
|
|
395
|
-
"""
|
|
350
|
+
"""视觉语言体检:对**任意** language 字典,不要求它属于某个命名风格。
|
|
396
351
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
但仍然需要"这套语言参数越界没有"的检查(否则转场 1 秒、push 0.3 这类
|
|
400
|
-
会让整片观感崩掉的错误要等渲完 300 帧才发现)。
|
|
352
|
+
没有这条检查,转场 1 秒、push 0.3 这类会让整片观感崩掉的错误要等渲完 300 帧
|
|
353
|
+
才发现——所以它在 design 校验阶段就要跑。
|
|
401
354
|
"""
|
|
402
355
|
bad = []
|
|
403
356
|
if not isinstance(vl, dict) or not vl:
|
|
@@ -457,159 +410,32 @@ def visual_lint_language(vl):
|
|
|
457
410
|
|
|
458
411
|
|
|
459
412
|
def visual_lint(lang=None):
|
|
460
|
-
"""对**任意** language
|
|
413
|
+
"""对**任意** language 字典做体检;传字符串(风格 id)时明确报错而不是静默通过。"""
|
|
461
414
|
if isinstance(lang, str):
|
|
462
|
-
|
|
463
|
-
return ["没有命名视觉语言 %r(v1.0.1 起视觉语言由 design 现场生成)" % lang]
|
|
415
|
+
return ["没有命名视觉语言 %r(视觉语言由 design 现场生成)" % lang]
|
|
464
416
|
return visual_lint_language(lang or {})
|
|
465
417
|
|
|
466
418
|
|
|
467
|
-
def visual_lint_all():
|
|
468
|
-
"""v1.0.1 起没有内置语言可逐一检查,保留空实现只为兼容。"""
|
|
469
|
-
return {}
|
|
470
419
|
# ============================================================================
|
|
471
|
-
#
|
|
420
|
+
# 六、解析:任何"按 style id 取外观"的调用都必须明确失败
|
|
472
421
|
# ============================================================================
|
|
473
422
|
|
|
474
|
-
FP_VERSION = "style-fp-v1"
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
def _digest(*parts):
|
|
478
|
-
"""sha1 派生的稳定 32 位无符号整数(禁 hash():str hash 每次进程带随机盐)。"""
|
|
479
|
-
h = hashlib.sha1(("|".join(str(p) for p in parts)).encode("utf-8")).hexdigest()
|
|
480
|
-
return int(h[:8], 16)
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
def _text_of(script):
|
|
484
|
-
"""把剧本里所有可读文字摊平(标题权重最高)。"""
|
|
485
|
-
title = str(script.get("title") or "")
|
|
486
|
-
chunks = [title, title] # 标题 x2
|
|
487
|
-
for ln in script.get("lines") or []:
|
|
488
|
-
chunks.append(str(ln.get("chapter") or ln.get("kicker") or ""))
|
|
489
|
-
chunks.append(str(ln.get("text") or ""))
|
|
490
|
-
chunks.append(str(ln.get("sub") or ""))
|
|
491
|
-
for it in ln.get("items") or []:
|
|
492
|
-
if isinstance(it, dict):
|
|
493
|
-
chunks.append(str(it.get("text") or ""))
|
|
494
|
-
else:
|
|
495
|
-
chunks.append(str(it))
|
|
496
|
-
return "".join(chunks)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
def fingerprint(script):
|
|
500
|
-
"""三级内容指纹。同剧本 -> 同指纹;换一部片 -> 三个都变。"""
|
|
501
|
-
title = str(script.get("title") or "")
|
|
502
|
-
lines = script.get("lines") or []
|
|
503
|
-
texts = [str(ln.get("text") or "") for ln in lines]
|
|
504
|
-
kinds = [str(ln.get("kind") or "body") for ln in lines]
|
|
505
|
-
dur_bucket = int(len("".join(texts)) // 120) # 体量桶:改几个字不动它
|
|
506
|
-
return {
|
|
507
|
-
"version": FP_VERSION,
|
|
508
|
-
"full": "%08x" % _digest(FP_VERSION, "full", title, *texts),
|
|
509
|
-
"tempo": "%08x" % _digest(FP_VERSION, "tempo", title, *kinds, dur_bucket),
|
|
510
|
-
"style": "%08x" % _digest(FP_VERSION, "style", title),
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
# —— 自动选风格(score_styles / pick_style / _kind_profile)已删除 ——
|
|
515
|
-
#
|
|
516
|
-
# 这三个函数是"机器替你挑一套内置风格"的实现:按关键词给 8 套 STYLES 打分、同分用指纹破平。
|
|
517
|
-
# v1.0.1 把 STYLES/THEMES 清空后它们必然崩(`THEMES[STYLES[sid]["theme"]]` -> KeyError),
|
|
518
|
-
# 而且留着也没有意义 —— 没有"可选集合"就没有"选"这件事。
|
|
519
|
-
#
|
|
520
|
-
# 为什么必须**删掉**而不是留着报错:留下一个"看起来还能调用"的入口,
|
|
521
|
-
# 下一个维护者会以为它是活的,调通了才发现整个语义已经不成立。
|
|
522
|
-
# 现在的唯一路径是 propose.py 现场采样。
|
|
523
|
-
|
|
524
423
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
# ============================================================================
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def _pick_from_pool(pool, fp_hex, salt=""):
|
|
531
|
-
"""从池里按指纹稳定取一个。
|
|
532
|
-
|
|
533
|
-
salt 用来把"风格 id"混进选择:不同风格即便指纹相同,也会落到池里不同位置。
|
|
534
|
-
没有 salt 时会出现 paper 与 lo-fi 都选到 84BPM / G 调这种"风格不同但基础参数撞车",
|
|
535
|
-
听感上就打了折扣 —— 这正是 v0.4 同质化的另一种形态。
|
|
536
|
-
"""
|
|
537
|
-
seq = [p for p in pool if p]
|
|
538
|
-
if not seq:
|
|
539
|
-
return None
|
|
540
|
-
v = int(fp_hex, 16)
|
|
541
|
-
if salt:
|
|
542
|
-
v += _digest("pick", salt)
|
|
543
|
-
return seq[v % len(seq)]
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
def _as_bool(v, default=False):
|
|
547
|
-
if v is None:
|
|
548
|
-
return default
|
|
549
|
-
if isinstance(v, str):
|
|
550
|
-
return v.strip().lower() not in ("0", "false", "no", "off", "")
|
|
551
|
-
return bool(v)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
# persona 名 -> edge voice id 的**兼容映射**(v1.0 / D3)。
|
|
555
|
-
# 这不是"身份表":它不带语速/音高/响度补偿,只是把老 STYLES 表里的字符串翻译成
|
|
556
|
-
# voicekit 现在要求的 edge voice id。真正的声音参数在 design.audio.voice 里。
|
|
557
|
-
_PERSONA_VOICE = {
|
|
558
|
-
"narrator_male": "zh-CN-YunyangNeural",
|
|
559
|
-
"narrator_female": "zh-CN-XiaoxiaoNeural",
|
|
560
|
-
"warm_female": "zh-TW-HsiaoChenNeural",
|
|
561
|
-
"bright_female": "zh-CN-XiaoyiNeural",
|
|
562
|
-
"authority_male": "zh-CN-YunjianNeural",
|
|
563
|
-
"young_male": "zh-CN-YunxiNeural",
|
|
564
|
-
"teen_male": "zh-CN-YunxiaNeural",
|
|
565
|
-
"dialect_ne_female": "zh-CN-liaoning-XiaobeiNeural",
|
|
566
|
-
"dialect_sx_female": "zh-CN-shaanxi-XiaoniNeural",
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
def _persona_to_voice_id(name):
|
|
571
|
-
"""老 persona 名 -> edge voice id;已经是 voice id 就原样返回。"""
|
|
572
|
-
s = str(name or "").strip()
|
|
573
|
-
if not s:
|
|
574
|
-
return s
|
|
575
|
-
return _PERSONA_VOICE.get(s, s)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
_AUTO_WARNED = []
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
def _warn_auto_style_once():
|
|
582
|
-
"""每天每进程只喊一次:自动选型已经不该再用了。"""
|
|
583
|
-
if _AUTO_WARNED:
|
|
584
|
-
return
|
|
585
|
-
_AUTO_WARNED.append(1)
|
|
586
|
-
import sys as _sys
|
|
587
|
-
print("[stylekit][deprecated] style=auto 的自动选型已停用(v1.0 / D3)。"
|
|
588
|
-
"\n 现在的主路径是 work/design.json:先 python propose.py --brief \"...\" 出方案,"
|
|
589
|
-
"\n 再 python propose.py --confirm --candidate A 冻结设计。"
|
|
590
|
-
"\n 想以出厂预设为起点:python propose.py --preset tech-deep ...",
|
|
591
|
-
file=_sys.stderr)
|
|
424
|
+
def resolve_script_style(script):
|
|
425
|
+
"""没有设计时的**唯一失败出口**:明确报错并给两步迁移指引。
|
|
592
426
|
|
|
427
|
+
本函数不再解析任何东西——本技能没有内置风格表,"按 style id 取外观"这件事不存在。
|
|
428
|
+
但它必须**存在且报错**:`filmkit.style_plan` / `scenes.py` / `preflight` / `qavmaf`
|
|
429
|
+
里都有「拿不到 plan 时回退到这里」的调用点。保留一个能给指引的入口,
|
|
430
|
+
那些点会得到"先 propose 再 confirm";删掉函数只会得到 AttributeError。
|
|
593
431
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
历史:v1.0.1 之前本函数按 8 套内置 STYLES 解析出一份风格计划
|
|
598
|
-
(主题 + 配乐 + 声线 + 节奏 + 版式轮换)。模板删掉后它已经没有可解析的对象——
|
|
599
|
-
但 v1.0.1 只删了表,没删这条函数,于是留下三个死分支:
|
|
600
|
-
`pick_style` / `STYLES[sid]` / `THEMES[...]`,任何一个被调用都是 NameError/KeyError,
|
|
601
|
-
连 `style="auto"` 都直接崩(v1.0.2 实测)。
|
|
602
|
-
|
|
603
|
-
为什么保留函数而不是删掉:`filmkit.style_plan` / `scenes.py` / `preflight` /
|
|
604
|
-
`qavmaf` 里还有「拿不到 plan 时回退到这里」的调用点。保留一个明确报错的入口,
|
|
605
|
-
那些点会得到一句能照着做的指引;删掉函数只会得到 AttributeError。
|
|
606
|
-
真正的计划只有两个来源:designkit.design_to_plan(work/design.json)或
|
|
607
|
-
render.resolve_style(已解析好的 script["_style"])。
|
|
432
|
+
真正的计划只有两个来源:`designkit.design_to_plan(work/design.json)`,
|
|
433
|
+
或 `render.resolve_style()` 解析好挂在 `script["_style"]` 上的那一份。
|
|
608
434
|
"""
|
|
609
435
|
raw = script.get("style") if isinstance(script, dict) else None
|
|
610
436
|
extra = ("(收到 style=%r)" % (raw,)) if raw not in (None, "") else ""
|
|
611
437
|
raise ValueError(
|
|
612
|
-
"这个剧本没有可解析的设计%s
|
|
438
|
+
"这个剧本没有可解析的设计%s:本技能不提供任何内置风格/模板,\n"
|
|
613
439
|
" 「写 style 走内置外观」与「不写 style 走老外观」两条路都已删除。\n"
|
|
614
440
|
" 正确做法(两步):\n"
|
|
615
441
|
" 1) python propose.py --brief \"题材/要求\" --script <film.json> --n 3\n"
|
|
@@ -622,8 +448,8 @@ def resolve_script_style(script):
|
|
|
622
448
|
# 七、门禁:主题体检(对比度 / 明暗 / 结构)
|
|
623
449
|
# ============================================================================
|
|
624
450
|
#
|
|
625
|
-
#
|
|
626
|
-
#
|
|
451
|
+
# 阈值来自 WCAG 2.1(inks 取 AAA 正文线,其余取 AA),不是拍脑袋定的。
|
|
452
|
+
# 量法:**按 bg/bg2 两端的最差值**,不看中点——中点会把"一端合格、一端不合格"平均过去。
|
|
627
453
|
|
|
628
454
|
LINT_LIMITS = {
|
|
629
455
|
"ink": 7.0, # 正文级(WCAG AAA 正文线)
|
|
@@ -641,10 +467,8 @@ def bg_contrast(th, role):
|
|
|
641
467
|
"""文字色对背景的**最差**对比度:bg/bg2 是上下两端的主渐变(scenes._bg 按行插值),
|
|
642
468
|
任何一行文字都可能落在两端附近,所以取二者较小值,而不是看中点。
|
|
643
469
|
|
|
644
|
-
|
|
645
|
-
中点平均 4.84 把 AA 4.5 的门槛"平均"
|
|
646
|
-
这一版把门槛改成端点最差值,并同时下调 lo-fi 的 muted 到 (84,98,94)
|
|
647
|
-
(bg 5.44 / bg2 4.56,两端都过)。
|
|
470
|
+
为什么不能只看中点:某次实测的 muted(86,100,96) 对 bg 是 5.28、对 bg2 只有 4.42,
|
|
471
|
+
中点平均 4.84 把 AA 4.5 的门槛"平均"过去了。端点判据不会漏掉这种。
|
|
648
472
|
"""
|
|
649
473
|
return min(contrast(th[role], th["bg"]), contrast(th[role], th["bg2"]))
|
|
650
474
|
|
|
@@ -680,16 +504,6 @@ def theme_lint(th):
|
|
|
680
504
|
return bad
|
|
681
505
|
|
|
682
506
|
|
|
683
|
-
def lint_all():
|
|
684
|
-
"""全部内置主题体检,返回 {style_id: [问题...]}(只有有问题的才在里面)。"""
|
|
685
|
-
out = {}
|
|
686
|
-
for sid in STYLE_IDS:
|
|
687
|
-
bad = theme_lint(THEMES[STYLES[sid]["theme"]])
|
|
688
|
-
if bad:
|
|
689
|
-
out[sid] = bad
|
|
690
|
-
return out
|
|
691
|
-
|
|
692
|
-
|
|
693
507
|
# ============================================================================
|
|
694
508
|
# 八、片内一致性:明暗族判定(段界不许跨族,否则必触发闪烁门禁)
|
|
695
509
|
# ============================================================================
|
|
@@ -704,35 +518,16 @@ def family_compatible(a, b):
|
|
|
704
518
|
return is_dark(a) == is_dark(b)
|
|
705
519
|
|
|
706
520
|
|
|
707
|
-
def blend_theme(a, b, p):
|
|
708
|
-
"""按 p in [0,1] 线性插值两套主题(同族时用于段界缓变,避免整片换底触发闪烁门禁)。"""
|
|
709
|
-
if not family_compatible(a, b):
|
|
710
|
-
raise ValueError("不能混合明暗族不同的主题:%s / %s" % (a.get("id"), b.get("id")))
|
|
711
|
-
q = max(0.0, min(1.0, float(p)))
|
|
712
|
-
out = dict(a)
|
|
713
|
-
for k, va in a.items():
|
|
714
|
-
vb = b.get(k, va)
|
|
715
|
-
if isinstance(va, tuple) and len(va) == 3 and isinstance(vb, tuple) and len(vb) == 3:
|
|
716
|
-
out[k] = tuple(int(round(va[i] * (1 - q) + vb[i] * q)) for i in range(3))
|
|
717
|
-
elif (isinstance(va, (int, float)) and not isinstance(va, bool)
|
|
718
|
-
and isinstance(vb, (int, float))):
|
|
719
|
-
out[k] = type(va)(round(va * (1 - q) + vb * q, 4))
|
|
720
|
-
return out
|
|
721
|
-
|
|
722
|
-
|
|
723
521
|
# ============================================================================
|
|
724
|
-
# 九、CLI
|
|
522
|
+
# 九、CLI:没有可列/可看的风格,只解释能力去了哪
|
|
725
523
|
# ============================================================================
|
|
726
524
|
#
|
|
727
|
-
#
|
|
728
|
-
#
|
|
729
|
-
# --lint 在空表上一条都不检查却 exit 0 —— 后者是最坏的一种:**假通过**。
|
|
730
|
-
# 本版把三个动作改成「明确说明能力已迁移 + 非零退出码」,并把 lint_all / _describe
|
|
731
|
-
# 一并删掉(留着就是死代码,下一个人会以为它还能用)。
|
|
525
|
+
# --list / --show / --lint 三个动作保留**壳**:说明"没有可选外观清单"并以 exit 2 退出。
|
|
526
|
+
# 不能返回 0 —— 在空表上 exit 0 是假通过,脚本会以为"列出来了"。
|
|
732
527
|
|
|
733
528
|
|
|
734
529
|
def _explain_removed(what):
|
|
735
|
-
print("[stylekit] %s
|
|
530
|
+
print("[stylekit] %s:本技能没有可选外观清单(不用内置风格表)。" % what,
|
|
736
531
|
file=sys.stderr)
|
|
737
532
|
print(" 现在没有「可选外观清单」,每一版外观都是现场生成的:", file=sys.stderr)
|
|
738
533
|
print(" python propose.py --brief \"题材/要求\" --script <film.json> --n 3", file=sys.stderr)
|
|
@@ -744,10 +539,10 @@ def _explain_removed(what):
|
|
|
744
539
|
|
|
745
540
|
def main(argv=None):
|
|
746
541
|
ap = argparse.ArgumentParser(
|
|
747
|
-
description="
|
|
748
|
-
ap.add_argument("--list", action="store_true", help="
|
|
749
|
-
ap.add_argument("--show", metavar="ID", help="
|
|
750
|
-
ap.add_argument("--lint", action="store_true", help="
|
|
542
|
+
description="视觉原语库(无内置风格:本命令只说明外观从哪来,不列清单)")
|
|
543
|
+
ap.add_argument("--list", action="store_true", help="(没有此能力)列出外观")
|
|
544
|
+
ap.add_argument("--show", metavar="ID", help="(没有此能力)查看某个外观")
|
|
545
|
+
ap.add_argument("--lint", action="store_true", help="(没有此能力)批量体检内置主题")
|
|
751
546
|
ap.add_argument("--json", action="store_true", help="配合 --show 输出 JSON")
|
|
752
547
|
a = ap.parse_args(argv)
|
|
753
548
|
try:
|