@dsh-gis/dsh-video-studio-skill 0.9.1 → 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 +473 -0
- package/README.md +263 -327
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/package.json +9 -10
- package/schemas/design.schema.json +722 -0
- package/schemas/film.schema.json +31 -42
- package/schemas/proposal.schema.json +133 -0
- package/skills/video-studio/SKILL.md +1216 -1123
- package/skills/video-studio/assets/design_fixture.json +141 -0
- package/skills/video-studio/assets/film.json +2 -4
- package/skills/video-studio/assets/golden_frames.json +13 -10
- package/skills/video-studio/assets/scenes.py +153 -65
- package/skills/video-studio/assets/voice_loudness.json +16 -0
- package/skills/video-studio/references/appearance.md +123 -0
- package/skills/video-studio/references/audio-variation.md +74 -93
- package/skills/video-studio/references/audio.md +64 -66
- package/skills/video-studio/references/change-impact.md +5 -5
- package/skills/video-studio/references/{v08-quality.md → delivery-gates.md} +16 -18
- package/skills/video-studio/references/frame-qa.md +48 -16
- 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 +160 -0
- 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 +40 -42
- package/skills/video-studio/references/sfx-design.md +8 -6
- package/skills/video-studio/references/web-engine.md +8 -8
- package/skills/video-studio/references/word-timeline.md +9 -9
- package/skills/video-studio/requirements.txt +9 -13
- package/skills/video-studio/scripts/designkit.py +2115 -0
- package/skills/video-studio/scripts/filmcheck.py +22 -48
- package/skills/video-studio/scripts/filmkit.py +2980 -2904
- package/skills/video-studio/scripts/gates.py +4 -4
- package/skills/video-studio/scripts/grammar.py +73 -13
- package/skills/video-studio/scripts/musicgen.py +2042 -0
- package/skills/video-studio/scripts/preflight.py +14 -7
- package/skills/video-studio/scripts/previewkit.py +848 -0
- package/skills/video-studio/scripts/propose.py +825 -0
- package/skills/video-studio/scripts/qa_frames.py +73 -39
- package/skills/video-studio/scripts/qaasr.py +26 -7
- package/skills/video-studio/scripts/qavmaf.py +89 -23
- package/skills/video-studio/scripts/render.py +545 -231
- package/skills/video-studio/scripts/scorekit.py +55 -1755
- package/skills/video-studio/scripts/selfcheck.py +815 -215
- package/skills/video-studio/scripts/sfxkit.py +27 -26
- package/skills/video-studio/scripts/stagewatch.py +24 -8
- package/skills/video-studio/scripts/stylekit.py +207 -888
- package/skills/video-studio/scripts/voicekit.py +508 -387
- package/skills/video-studio/scripts/webengine.py +71 -72
- package/tools/audio_similarity.py +64 -37
- package/tools/ci_check.py +20 -14
- package/tools/estimate.py +34 -29
- package/tools/golden_frames.py +72 -28
- package/tools/silhouette_qa.py +520 -168
- package/docs/demo-pil-sheet.png +0 -0
- package/docs/styles/demo-results.json +0 -76
- package/docs/styles/sheet-8-styles.png +0 -0
- package/docs/styles/sheet-ai.png +0 -0
- package/docs/styles/sheet-before-after.png +0 -0
- package/docs/styles/sheet-craft.png +0 -0
- package/docs/styles/sheet-night.png +0 -0
- package/skills/video-studio/assets/score_demo.json +0 -307
- package/skills/video-studio/references/style-packs.md +0 -283
package/tools/golden_frames.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""golden_frames.py —
|
|
2
|
+
"""golden_frames.py — 金帧像素回归。
|
|
3
3
|
|
|
4
4
|
解决什么问题
|
|
5
5
|
------------
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
未必报——只有"同一帧的像素哈希"能当场抓住。本工具就是那条基线。
|
|
6
|
+
改一行绘制代码、换一套设计、动字幕排版,肉眼看不出来、qa_frames 的统计指标也未必报——
|
|
7
|
+
只有"同一帧的像素哈希"能当场抓住。本工具就是那条基线。
|
|
9
8
|
|
|
10
9
|
做什么
|
|
11
10
|
------
|
|
12
11
|
渲染指定帧号 -> 每帧算 SHA-256 -> 与 skills/video-studio/assets/golden_frames.json 比对。
|
|
13
|
-
* --write
|
|
12
|
+
* --write 生成/更新基线(绘制代码有意改动后重录,并把 JSON 一起提交)
|
|
14
13
|
* --check 校验(默认):同环境像素变了 = fail,并列出哪几帧变了
|
|
15
14
|
|
|
16
15
|
三档语义("没查"绝不能写成"通过")
|
|
@@ -30,7 +29,7 @@ sha1),指纹不同直接 skipped(跨机不误报);指纹相同还变
|
|
|
30
29
|
基线怎么来的(确定性)
|
|
31
30
|
----------------------
|
|
32
31
|
不联网、不依赖 TTS 产物:用固定的假语音时长(FAKE_VOICE_DUR=2.0s)排一条合成时间轴,
|
|
33
|
-
|
|
32
|
+
取夹具片的 3 个时刻(hook / body / outro)作为默认帧号。这样同一份 film.json + scenes.py
|
|
34
33
|
在任何机器上都能立刻重算基线;真实旁白时长只影响"哪一帧是哪一句",不影响"这一帧画成
|
|
35
34
|
什么样",后者才是本工具要守的东西。帧号与场景 id、段内时刻都记进基线,便于人工复核。
|
|
36
35
|
|
|
@@ -51,7 +50,7 @@ CLI
|
|
|
51
50
|
"""
|
|
52
51
|
from __future__ import annotations
|
|
53
52
|
|
|
54
|
-
__version__ = "0.
|
|
53
|
+
__version__ = "1.0.4"
|
|
55
54
|
|
|
56
55
|
import argparse
|
|
57
56
|
import hashlib
|
|
@@ -69,6 +68,11 @@ SCRIPTS = os.path.join(SKILL, "scripts")
|
|
|
69
68
|
ASSETS = os.path.join(SKILL, "assets")
|
|
70
69
|
|
|
71
70
|
DEFAULT_SCRIPT = os.path.join(ASSETS, "film.json")
|
|
71
|
+
# v1.0 / D3:金帧基线绑"固定 fixture 设计"而不是内置风格。
|
|
72
|
+
# 为什么:金帧要守的是"同一份设计 + 同一份场景代码,像素不许漂",
|
|
73
|
+
# 不是"某套内置风格长什么样"——后者会随风格表下沉到 presets 而失去意义。
|
|
74
|
+
# 没有这个文件时回落到 stylekit 老口径(它现在只会明确报错),不阻塞诊断。
|
|
75
|
+
DEFAULT_DESIGN = os.path.join(ASSETS, "design_fixture.json")
|
|
72
76
|
DEFAULT_SCENES = os.path.join(ASSETS, "scenes.py")
|
|
73
77
|
DEFAULT_GOLDEN = os.path.join(ASSETS, "golden_frames.json")
|
|
74
78
|
|
|
@@ -79,7 +83,7 @@ FAKE_VOICE_DUR = 2.0
|
|
|
79
83
|
HOOK_LOCAL = 1.2
|
|
80
84
|
BODY_LOCAL = 1.6
|
|
81
85
|
OUTRO_LOCAL = 1.5
|
|
82
|
-
#
|
|
86
|
+
# 参与字体指纹的字重(内置绘制器只用到 bold / reg / light)
|
|
83
87
|
FONT_WEIGHTS = ("bold", "reg", "light")
|
|
84
88
|
|
|
85
89
|
RESULT_KEYS = ("status", "frames", "font_fp")
|
|
@@ -186,7 +190,7 @@ def build_timeline(K, script):
|
|
|
186
190
|
与 render.st_layout 的差别只在 dur 的来源:这里每句固定 FAKE_VOICE_DUR 秒,
|
|
187
191
|
其余(lead/tail/min_dur/hold/bar)照常走 filmkit.build_timeline,结果可复现。
|
|
188
192
|
|
|
189
|
-
但 **fps 必须跟 render.py 一样取自 filmkit.video_plan**(
|
|
193
|
+
但 **fps 必须跟 render.py 一样取自 filmkit.video_plan**(fps 可按片配置):
|
|
190
194
|
build_timeline 的默认 fps=30 会让 24/25/50/60 fps 的片子按 30 fps 编号,
|
|
191
195
|
于是基线里的 frame 号既不是成片的真实帧号,t=frame/fps 也不是真实时刻——
|
|
192
196
|
那份基线守的是"另一部片子"的画面,回放/比对都错位(且不会报错)。
|
|
@@ -216,11 +220,39 @@ def build_timeline(K, script):
|
|
|
216
220
|
return tl
|
|
217
221
|
|
|
218
222
|
|
|
219
|
-
def
|
|
220
|
-
"""
|
|
223
|
+
def _design_plan(design_path):
|
|
224
|
+
"""读 fixture 设计并转成计划;读不到返回 None(回落到 stylekit 老口径)。"""
|
|
225
|
+
if not design_path or not os.path.exists(str(design_path)):
|
|
226
|
+
return None
|
|
227
|
+
try:
|
|
228
|
+
with open(design_path, encoding="utf-8") as fh:
|
|
229
|
+
design = json.load(fh)
|
|
230
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
231
|
+
raise RuntimeError("fixture 设计读不出来(%s):%s" % (design_path, e)) from e
|
|
232
|
+
sys.path.insert(0, SCRIPTS)
|
|
233
|
+
import designkit as DK
|
|
234
|
+
bad = DK.validate_design(design)
|
|
235
|
+
if bad:
|
|
236
|
+
raise RuntimeError("fixture 设计没过校验(%s):%s" % (design_path, ";".join(bad)))
|
|
237
|
+
return DK.design_to_plan(design)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def resolve_plan(ST, script, design_path=None):
|
|
241
|
+
"""解析计划并挂到 script["_style"]。
|
|
242
|
+
|
|
243
|
+
v1.0 / D3 起优先用 **fixture 设计**(design_path,默认 assets/design_fixture.json):
|
|
244
|
+
金帧守的是"固定设计 + 固定场景代码 -> 像素不漂",与"哪套内置风格"无关。
|
|
245
|
+
没有 fixture 时回落到 stylekit 老口径,不阻塞老项目。
|
|
221
246
|
|
|
222
247
|
主题体检失败在这里就抛错(不要等渲到第 3 帧才炸)。
|
|
223
248
|
"""
|
|
249
|
+
plan = _design_plan(design_path)
|
|
250
|
+
if plan is not None:
|
|
251
|
+
script["_style"] = plan
|
|
252
|
+
bad = ST.theme_lint(plan.get("theme") or {})
|
|
253
|
+
if bad:
|
|
254
|
+
raise RuntimeError("fixture 设计主题体检失败:%s" % ";".join(bad))
|
|
255
|
+
return plan
|
|
224
256
|
plan = ST.resolve_script_style(script)
|
|
225
257
|
bad = ST.theme_lint(plan.get("theme") or ST.THEMES[plan["theme_id"]])
|
|
226
258
|
if bad:
|
|
@@ -266,7 +298,7 @@ def env_fingerprint():
|
|
|
266
298
|
口径细节:**Python 只按 major.minor 参与指纹**,补丁号只记录不参与比较。
|
|
267
299
|
理由是真正决定抗锯齿像素的是 Pillow(FreeType)与 numpy,不是 CPython 的补丁版本;
|
|
268
300
|
而按补丁号比较会让"作者机器 3.12.16 / 用户机器 3.12.14"这种正常差异一律 skipped——
|
|
269
|
-
|
|
301
|
+
实测踩过:金帧回归因此显示"0 帧像素一致"却记 PASS,等于这条门禁从没真的比过。
|
|
270
302
|
真要跨 Pillow/numpy 版本比,仍然会 skipped,那才是像素不可比的情形。
|
|
271
303
|
"""
|
|
272
304
|
import numpy
|
|
@@ -281,7 +313,7 @@ def env_fingerprint():
|
|
|
281
313
|
def _env_fp_from(env):
|
|
282
314
|
"""按"参与比较的口径"给 env 字典算指纹(Python 只取 major.minor)。
|
|
283
315
|
|
|
284
|
-
|
|
316
|
+
单独抽出来是为了**兼容老基线**:早期 golden_frames.json 里记的是完整补丁号
|
|
285
317
|
(如 3.12.16),如果直接拿新口径算出的指纹去比,老基线会永远 skipped——那等于把
|
|
286
318
|
"补丁号差异"永久固化成"这条门禁不用跑"。这里对**存的 env** 和**当前 env** 走同一个
|
|
287
319
|
归一化函数,所以 3.12.14 与 3.12.16 视为同一环境,而 Pillow/numpy 的差异照旧拦住。
|
|
@@ -384,7 +416,7 @@ def default_frame_specs(tl):
|
|
|
384
416
|
|
|
385
417
|
|
|
386
418
|
# ---------------------------------------------------------------- 基线读写
|
|
387
|
-
def _golden_payload(script_path, scenes_path, K, plan, tl, rendered):
|
|
419
|
+
def _golden_payload(script_path, scenes_path, K, plan, tl, rendered, design_path=None):
|
|
388
420
|
font_fp, fonts = font_fingerprint(K, plan)
|
|
389
421
|
env_fp, env = env_fingerprint()
|
|
390
422
|
return {
|
|
@@ -392,12 +424,13 @@ def _golden_payload(script_path, scenes_path, K, plan, tl, rendered):
|
|
|
392
424
|
"generator": "golden_frames.py v%s" % __version__,
|
|
393
425
|
"comment": "金帧基线:字体指纹+环境指纹一致时,每一帧像素 sha256 必须相同;"
|
|
394
426
|
"换机/字体/Pillow 版本不同判 skipped(不误报),同环境像素变了才是 fail。"
|
|
395
|
-
"
|
|
427
|
+
"改动夹具(film.json / scenes.py / 设计)后请 --write 重录并提交本文件。",
|
|
396
428
|
"canvas": [int(K.W), int(K.H)],
|
|
397
|
-
# 记时间轴自己的 fps(不是写死的 K.FPS):
|
|
429
|
+
# 记时间轴自己的 fps(不是写死的 K.FPS):film.json 可以设 24/25/50/60
|
|
398
430
|
"fps": int(tl["fps"]),
|
|
399
431
|
"script": _rel(script_path),
|
|
400
432
|
"scenes": _rel(scenes_path),
|
|
433
|
+
"design": _rel(design_path) if design_path else "",
|
|
401
434
|
"font_fp": font_fp,
|
|
402
435
|
"fonts": fonts,
|
|
403
436
|
"env_fp": env_fp,
|
|
@@ -407,6 +440,9 @@ def _golden_payload(script_path, scenes_path, K, plan, tl, rendered):
|
|
|
407
440
|
"scenes_sha1": _sha1_file(scenes_path),
|
|
408
441
|
"filmkit_sha1": _sha1_file(os.path.join(SCRIPTS, "filmkit.py")),
|
|
409
442
|
"stylekit_sha1": _sha1_file(os.path.join(SCRIPTS, "stylekit.py")),
|
|
443
|
+
# 设计 fixture 进指纹:改了基准设计却不重录基线 = "改了但基线不报警"
|
|
444
|
+
"design_sha1": _sha1_file(design_path) if design_path else "",
|
|
445
|
+
"designkit_sha1": _sha1_file(os.path.join(SCRIPTS, "designkit.py")),
|
|
410
446
|
"golden_tool_sha1": _sha1_file(os.path.abspath(__file__)),
|
|
411
447
|
},
|
|
412
448
|
"frames": [dict(sp, sha256=sha, scene=scene_id, frame=int(f))
|
|
@@ -414,13 +450,13 @@ def _golden_payload(script_path, scenes_path, K, plan, tl, rendered):
|
|
|
414
450
|
}
|
|
415
451
|
|
|
416
452
|
|
|
417
|
-
def write_golden(script_path, scenes_path, golden_path, frames=None):
|
|
453
|
+
def write_golden(script_path, scenes_path, golden_path, frames=None, design_path=None):
|
|
418
454
|
"""--write:渲染金帧并写基线。返回 result(status=pass,written=True)。"""
|
|
419
455
|
K, ST = _modules()
|
|
420
456
|
script = _load_script(script_path)
|
|
421
457
|
scenes_mod = _load_scenes(scenes_path)
|
|
422
458
|
tl = build_timeline(K, script)
|
|
423
|
-
plan = resolve_plan(ST, script)
|
|
459
|
+
plan = resolve_plan(ST, script, design_path=design_path)
|
|
424
460
|
if frames:
|
|
425
461
|
specs = [dict(frame=int(f), scene=str(_scene_at(tl, int(f) / float(tl["fps"]))[0]
|
|
426
462
|
.get("id") or ""),
|
|
@@ -432,7 +468,8 @@ def write_golden(script_path, scenes_path, golden_path, frames=None):
|
|
|
432
468
|
specs = default_frame_specs(tl)
|
|
433
469
|
rendered = render_frames(script, scenes_mod, plan, tl, [sp["frame"] for sp in specs])
|
|
434
470
|
plan = dict(plan, specs=specs)
|
|
435
|
-
data = _golden_payload(script_path, scenes_path, K, plan, tl, rendered
|
|
471
|
+
data = _golden_payload(script_path, scenes_path, K, plan, tl, rendered,
|
|
472
|
+
design_path=design_path)
|
|
436
473
|
golden_path = os.path.abspath(str(golden_path))
|
|
437
474
|
d = os.path.dirname(golden_path)
|
|
438
475
|
if d:
|
|
@@ -475,7 +512,7 @@ def _fmt_fp_pair(actual, expect):
|
|
|
475
512
|
return "%s… vs %s…" % (str(actual)[:12], str(expect)[:12])
|
|
476
513
|
|
|
477
514
|
|
|
478
|
-
def check_golden(script_path, scenes_path, golden_path):
|
|
515
|
+
def check_golden(script_path, scenes_path, golden_path, design_path=None):
|
|
479
516
|
"""--check:渲染基线里的帧号并与基线比对(默认命令)。"""
|
|
480
517
|
golden, why = _load_golden(golden_path)
|
|
481
518
|
if golden is None:
|
|
@@ -498,7 +535,7 @@ def check_golden(script_path, scenes_path, golden_path):
|
|
|
498
535
|
script = _load_script(script_path)
|
|
499
536
|
scenes_mod = _load_scenes(scenes_path)
|
|
500
537
|
tl = build_timeline(K, script)
|
|
501
|
-
plan = resolve_plan(ST, script)
|
|
538
|
+
plan = resolve_plan(ST, script, design_path=design_path)
|
|
502
539
|
font_fp, fonts = font_fingerprint(K, plan)
|
|
503
540
|
env_fp, env = env_fingerprint()
|
|
504
541
|
# 先做"能不能比"的判定,再渲(省得换机时白渲三帧)
|
|
@@ -510,7 +547,7 @@ def check_golden(script_path, scenes_path, golden_path):
|
|
|
510
547
|
reason="skipped: font differs(字体指纹 %s)——换机/字体版本不同,"
|
|
511
548
|
"像素比对不成立;确认画面后在当前机器 --write 重录"
|
|
512
549
|
% _fmt_fp_pair(font_fp, golden.get("font_fp")))
|
|
513
|
-
# 比指纹时对**存的 env** 走同一个归一化(见 _env_fp_from
|
|
550
|
+
# 比指纹时对**存的 env** 走同一个归一化(见 _env_fp_from):这样早期那些
|
|
514
551
|
# 记着完整补丁号的老基线不会因为 3.12.14 vs 3.12.16 永久 skipped。
|
|
515
552
|
golden_env_fp = _env_fp_from(golden.get("env") or {})
|
|
516
553
|
if golden_env_fp != env_fp:
|
|
@@ -551,7 +588,9 @@ def check_golden(script_path, scenes_path, golden_path):
|
|
|
551
588
|
cur_inputs = dict(script_sha1=_sha1_file(script_path),
|
|
552
589
|
scenes_sha1=_sha1_file(scenes_path),
|
|
553
590
|
filmkit_sha1=_sha1_file(os.path.join(SCRIPTS, "filmkit.py")),
|
|
554
|
-
stylekit_sha1=_sha1_file(os.path.join(SCRIPTS, "stylekit.py"))
|
|
591
|
+
stylekit_sha1=_sha1_file(os.path.join(SCRIPTS, "stylekit.py")),
|
|
592
|
+
design_sha1=(_sha1_file(design_path) if design_path else ""),
|
|
593
|
+
designkit_sha1=_sha1_file(os.path.join(SCRIPTS, "designkit.py")))
|
|
555
594
|
stale = [k for k, v in cur_inputs.items()
|
|
556
595
|
if inputs.get(k) and inputs.get(k) != v]
|
|
557
596
|
if changed:
|
|
@@ -607,12 +646,15 @@ def main(argv=None):
|
|
|
607
646
|
g = ap.add_mutually_exclusive_group()
|
|
608
647
|
g.add_argument("--check", action="store_true", help="校验(默认)")
|
|
609
648
|
g.add_argument("--write", action="store_true", help="生成/更新基线")
|
|
610
|
-
ap.add_argument("--script", default=DEFAULT_SCRIPT, help="film.json
|
|
611
|
-
ap.add_argument("--
|
|
649
|
+
ap.add_argument("--script", default=DEFAULT_SCRIPT, help="film.json(默认夹具片)")
|
|
650
|
+
ap.add_argument("--design", default=DEFAULT_DESIGN,
|
|
651
|
+
help="固定 fixture 设计(默认 assets/design_fixture.json);"
|
|
652
|
+
"传空串回落到老的 stylekit 口径")
|
|
653
|
+
ap.add_argument("--scenes", default=DEFAULT_SCENES, help="scenes.py(默认内置场景)")
|
|
612
654
|
ap.add_argument("--golden", default=DEFAULT_GOLDEN,
|
|
613
655
|
help="基线 JSON(默认 skills/video-studio/assets/golden_frames.json)")
|
|
614
656
|
ap.add_argument("--frames", default="",
|
|
615
|
-
help="--write
|
|
657
|
+
help="--write 时指定帧号(逗号分隔);缺省取夹具片 3 个时刻")
|
|
616
658
|
ap.add_argument("--json", action="store_true", help="stdout 只输出 JSON(机读)")
|
|
617
659
|
ap.add_argument("--strict", action="store_true",
|
|
618
660
|
help="skipped 用退出码 2 表示(默认 skipped 退出码 0,但绝不写 pass)")
|
|
@@ -627,12 +669,14 @@ def main(argv=None):
|
|
|
627
669
|
raise SystemExit("--frames 必须是逗号分隔的整数帧号,收到 %r" % a.frames)
|
|
628
670
|
if not frames:
|
|
629
671
|
raise SystemExit("--frames 里没有有效帧号")
|
|
630
|
-
res = write_golden(a.script, a.scenes, a.golden, frames
|
|
672
|
+
res = write_golden(a.script, a.scenes, a.golden, frames,
|
|
673
|
+
design_path=(a.design or None))
|
|
631
674
|
else:
|
|
632
675
|
if a.frames:
|
|
633
676
|
raise SystemExit("--frames 只用于 --write;--check 的帧号由基线决定"
|
|
634
677
|
"(要换帧号请 --write --frames ... 重录)")
|
|
635
|
-
res = check_golden(a.script, a.scenes, a.golden
|
|
678
|
+
res = check_golden(a.script, a.scenes, a.golden,
|
|
679
|
+
design_path=(a.design or None))
|
|
636
680
|
except SystemExit:
|
|
637
681
|
raise
|
|
638
682
|
except Exception as e: # noqa: BLE001
|