@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
package/tools/estimate.py
CHANGED
|
@@ -40,11 +40,11 @@ import unicodedata
|
|
|
40
40
|
from pathlib import Path
|
|
41
41
|
|
|
42
42
|
# ============================================================================
|
|
43
|
-
#
|
|
43
|
+
# 基准常量(换机请重测后回填,别把这里的数字当通用真理)
|
|
44
44
|
# ============================================================================
|
|
45
45
|
|
|
46
46
|
W, H, FPS = 1920, 1080, 30 # filmkit.W/H/FPS:本项目写死,film.json 改不了
|
|
47
|
-
PIL_FPS = (40.0, 80.0) # PIL 逐帧渲染速度:中等复杂度 40–54 fps /
|
|
47
|
+
PIL_FPS = (40.0, 80.0) # PIL 逐帧渲染速度:中等复杂度 40–54 fps / 夹具片 68–82 fps
|
|
48
48
|
PARALLEL_EFF = 0.5 # 分块并行的**实测效率**:2 进程只快 1.5×,不是 2×(见 CALIBRATION)
|
|
49
49
|
WEB_SEC_PER_FRAME = (0.080, 0.130) # web 引擎:Edge 无头 PNG 截图 80–130 ms/帧
|
|
50
50
|
WEB_STARTUP_SEC = 6.0 # 浏览器冷启动 + CDP 连接(12 s 片 28–30 s 含启动)
|
|
@@ -52,14 +52,13 @@ X264_FPS = 141.0 # libx264 -preset medium -crf 17 ≈ 4.7×
|
|
|
52
52
|
TTS_SEC_PER_LINE = (2.0, 4.0) # edge-tts:每句每 worker 的墙钟(联网,波动大):实测 2.2 s,留到 4.0
|
|
53
53
|
# 配乐耗时锚点 (片长 s, 耗时低, 耗时高)。
|
|
54
54
|
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# 是单遍事件合成,实测抖动也小(同参数逐采样确定,耗时只随片长线性涨)。
|
|
55
|
+
# musicgen 实测值:30 s / 60 s / 120 s 分别 0.94 / 1.93 / 3.68 s(≈31× 实时)。
|
|
56
|
+
# 参数化编排器是单遍事件合成,实测抖动也小(同参数逐采样确定,耗时只随片长线性涨);
|
|
57
|
+
# 早期逐层展开事件、多次 IIR 的配乐引擎实测 29 s 片要 3.6–6.5 s。
|
|
59
58
|
# 区间仍给 0.7–1.5 系数包络:CI 机器与共享 CPU 会慢,报小了会让人以为卡住。
|
|
60
59
|
SCORE_REFS = ((29.0, 0.9, 1.6), (58.0, 1.8, 3.0), (389.0, 11.5, 19.0))
|
|
61
60
|
SFX_SEC_PER_CUE = 0.20 # 单条短音效 合成+落位(24 配方单次 ≤1.5 s 是上限口径)
|
|
62
|
-
SFX_MIN_SEC = 0.8 # 实测 18 条 cue
|
|
61
|
+
SFX_MIN_SEC = 0.8 # 实测 18 条 cue 的夹具片 sfx 墙钟 0.8 s(含 ffmpeg 写盘)
|
|
63
62
|
LAYOUT_BASE, LAYOUT_PER_LINE = 0.05, 0.01 # 排轴 + srt/ass + 安全区/字形自查(实测 29 s 片 <0.15 s)
|
|
64
63
|
CUES_BASE, CUES_PER_LINE = 0.05, 0.005 # 提示点落盘 + 排序(实测 29 s 片 <0.15 s)
|
|
65
64
|
AUDIO_BASE, AUDIO_PER_SEC = 0.6, 0.012 # numpy 总线按采样点叠加 + 写 wav
|
|
@@ -70,12 +69,12 @@ MUX_PER_SEC = 0.045 # 每片长秒(两遍 loudnorm 解码音
|
|
|
70
69
|
QA_BASE_SEC = (1.0, 3.0) # 固定开销:联系表 + 参考帧重渲染
|
|
71
70
|
QA_SEC_PER_SEC = (0.12, 0.25) # 随片长:帧级深检流式解码 + 响度曲线
|
|
72
71
|
UNMEASURED_BAND = 0.30 # 没有实测的阶段(layout/cues/sfx/audio)给 ±30% 的区间,别假装精确
|
|
73
|
-
#
|
|
72
|
+
# G11(全片 VMAF/SSIM):参考帧由场景函数**重渲一遍**再喂 libvmaf,成本跟帧数走、
|
|
74
73
|
# 与 frames 阶段同量级。实测口径差异极大:qavmaf 作者 29 s 片 12.0 s(≈0.40× 片长);
|
|
75
|
-
# SKILL.md §12
|
|
74
|
+
# SKILL.md §12 同一部夹具片 14.2 s;同机另一轮负载下 28.8 s。这里取 0.40–1.00× 片长
|
|
76
75
|
# (下限=干净机、上限=争 CPU 的长片外推),固定开销单独列 1–2 s。
|
|
77
|
-
# 注:
|
|
78
|
-
#
|
|
76
|
+
# 注:1.0–1.6× 这类没有实测支撑的系数会把 29 s 片算成 31–50 s(实际 12–29 s),
|
|
77
|
+
# 预演总量系统性偏大近 2 倍——"拍脑袋的悲观系数"比低估更糟:排期会按假的数字走。
|
|
79
78
|
VMAF_BASE_SEC = (1.0, 2.0) # 起渲染进程 + 参考帧管线搭建
|
|
80
79
|
VMAF_SEC_PER_SEC = (0.40, 1.00) # 每片长秒(参考帧重渲 + libvmaf 逐帧比)
|
|
81
80
|
|
|
@@ -85,7 +84,7 @@ CALIBRATION = dict(
|
|
|
85
84
|
"SKILL.md §12 记录的作者机是 Ryzen AI 9 HX 370 / 12C24T / Radeon 890M"
|
|
86
85
|
"(是否同一台未核实,只保证这些数字是在上述 24 逻辑核机器上测的)"),
|
|
87
86
|
measured_at="2026-11-10",
|
|
88
|
-
sdk="video-studio
|
|
87
|
+
sdk="video-studio scripts(ffmpeg 自带 x264)",
|
|
89
88
|
film="29.011 s / 870 帧 / 测试样片(assets/film.json + assets/scenes.py,仅用于基准校准)",
|
|
90
89
|
method="render.py --stage all --jobs 2,逐 stage 取 [time] 行墙钟;同片重复 2 次",
|
|
91
90
|
observed=dict(tts=2.2, layout=0.0, cues=0.0, score=6.3, sfx=0.8, audio=0.7,
|
|
@@ -94,14 +93,14 @@ CALIBRATION = dict(
|
|
|
94
93
|
" score 58 s 片 4.5–5.0 s; qa 6.2–6.8 s",
|
|
95
94
|
)
|
|
96
95
|
CPS = 4.2 # 每秒约 4.2 字(voicekit 校准 3.84–4.61 cps 的中位口径)
|
|
97
|
-
DEFAULT_LEAD, DEFAULT_TAIL, DEFAULT_MIN_DUR = 0.62, 0.92, 3.0 #
|
|
96
|
+
DEFAULT_LEAD, DEFAULT_TAIL, DEFAULT_MIN_DUR = 0.62, 0.92, 3.0 # body 基准
|
|
98
97
|
CORES = 12 # HX 370:12C/24T;jobs > 12 不再线性加速
|
|
99
98
|
TTS_BYTES_PER_LINE = 32 * 1024 # 单句 mp3 缓存(4 s 语音约 24 KB,留余量)
|
|
100
99
|
WAV_BYTES_PER_SEC = 48000 * 2 * 2 # 48 kHz 立体声 PCM16 = 192 KB/s
|
|
101
100
|
VIDEO_MBPS = (1.2, 3.0) # crf17 @1080p30 图文内容(暗底大字 0.5–1 Mbps 属正常)
|
|
102
101
|
AUDIO_MBPS = 0.192 # AAC 192 kbps
|
|
103
102
|
QA_DISK_BYTES = 12 * 1024 * 1024 # 联系表 + 静帧 + deep.png
|
|
104
|
-
BASELINE_NOTE = ("
|
|
103
|
+
BASELINE_NOTE = ("区间来自本机实测(机型/样本见下方 calibration;"
|
|
105
104
|
"SKILL.md §12 记录的作者机是 Ryzen AI 9 HX 370 / 12C24T);换机请先重测再采信")
|
|
106
105
|
|
|
107
106
|
STAGE_LABELS = {
|
|
@@ -114,7 +113,7 @@ STAGE_LABELS = {
|
|
|
114
113
|
"frames": "逐帧渲染+编码",
|
|
115
114
|
"mux": "合成成片",
|
|
116
115
|
"qa": "质检+抽帧",
|
|
117
|
-
#
|
|
116
|
+
# 交付前的三道门禁单独列(它们的成本不能藏进 qa 里)
|
|
118
117
|
"preflight": "渲染前预检",
|
|
119
118
|
"vmaf": "全片VMAF/SSIM",
|
|
120
119
|
"asr": "ASR回读",
|
|
@@ -314,7 +313,7 @@ def score_seconds(total):
|
|
|
314
313
|
def _speedup(jobs):
|
|
315
314
|
"""分块并行的**有效加速比**:不是 jobs 倍。
|
|
316
315
|
|
|
317
|
-
实测(870
|
|
316
|
+
实测(870 帧夹具片):jobs=1 → 14.37 s、jobs=2 → 9.53 s,只有 1.51×。
|
|
318
317
|
原因很实在:父进程要 spawn 子进程、每段都要重新 import scenes.py / 解析主题、
|
|
319
318
|
落盘 mux 串行、磁盘写带宽争用。按 jobs 线性折算会持续低估长片(越长的片分块越多)。
|
|
320
319
|
这里用 1 + (jobs-1)*PARALLEL_EFF 的线性模型:比理论值保守,实测 2 进程刚好对上。
|
|
@@ -426,7 +425,7 @@ def build_report(script_path, script, args):
|
|
|
426
425
|
_warn("engine=%r 不认识(只能是 pil / web),按 pil 预演" % engine)
|
|
427
426
|
engine = "pil"
|
|
428
427
|
|
|
429
|
-
#
|
|
428
|
+
# 画幅/帧率可以按片配置(只允许 16:9 横屏 + 下采样输出,见 filmkit.video_plan)。
|
|
430
429
|
# 预演必须跟着走,否则 720p 片会被按 1080p 估帧数与磁盘。
|
|
431
430
|
try:
|
|
432
431
|
import sys as _sys
|
|
@@ -455,7 +454,7 @@ def build_report(script_path, script, args):
|
|
|
455
454
|
source_note = "真实帧数:%s" % tl_path
|
|
456
455
|
else:
|
|
457
456
|
total, _scenes = estimate_timeline(script)
|
|
458
|
-
#
|
|
457
|
+
# 没有 timeline.json 时也必须用 film.json 的 video.fps —— 定死 30 会让
|
|
459
458
|
# 24/25/50/60 fps 的片子帧数、磁盘、分块全部按 30 fps 算(24 fps 高估 25%,
|
|
460
459
|
# 60 fps 低估一半),而 render.py 的 build_timeline(fps=vp["fps"]) 用的是真实值。
|
|
461
460
|
fps = int(_vp["fps"])
|
|
@@ -516,8 +515,8 @@ def build_report(script_path, script, args):
|
|
|
516
515
|
mux_lo, mux_hi = mux_seconds_pair(frames, jobs_eff, total, script)
|
|
517
516
|
qa_lo = max(0.5, QA_BASE_SEC[0] + total * QA_SEC_PER_SEC[0])
|
|
518
517
|
qa_hi = max(qa_lo, QA_BASE_SEC[1] + total * QA_SEC_PER_SEC[1])
|
|
519
|
-
#
|
|
520
|
-
# 实测口径(29.0 s
|
|
518
|
+
# 三个可选门禁:预检(固定小开销)、全片 VMAF/SSIM、ASR 回读。
|
|
519
|
+
# 实测口径(29.0 s 夹具片):preflight 0.4 s;VMAF full 12–29 s(0.40–1.00× 片长,
|
|
521
520
|
# 参考帧重渲与 libvmaf 都在里面,见 VMAF_* 常量注释);ASR small ≈ 6–10 s。
|
|
522
521
|
# 这三项全在**交付前**跑,不写进预演就等于"预演说 40 s、实际 90 s"——所以单独列 stage。
|
|
523
522
|
qcfg = script.get("qa") or {}
|
|
@@ -630,7 +629,7 @@ def build_report(script_path, script, args):
|
|
|
630
629
|
"source": source,
|
|
631
630
|
"source_note": source_note,
|
|
632
631
|
"video": {"title": script.get("title"),
|
|
633
|
-
#
|
|
632
|
+
# 内部构图恒为 1080p,交付尺寸可以更小(_vp 从 film.json 的 video 块来)
|
|
634
633
|
"width": _vp["w"], "height": _vp["h"],
|
|
635
634
|
"out_width": _vp["out_w"], "out_height": _vp["out_h"],
|
|
636
635
|
"fps": fps, "frames": frames,
|
|
@@ -660,8 +659,8 @@ def build_report(script_path, script, args):
|
|
|
660
659
|
"advice": bottleneck_advice(worst["stage"], engine, jobs_eff)},
|
|
661
660
|
"assumptions": [
|
|
662
661
|
"内部构图恒为 1920x1080;交付尺寸/帧率可写 film.json 的 video 块"
|
|
663
|
-
"
|
|
664
|
-
"lead=%.2f / tail=%.2f / min_dur=%.1f(
|
|
662
|
+
"(只允许 16:9 横屏且只能下采样;本次按 %d fps 估)" % fps,
|
|
663
|
+
"lead=%.2f / tail=%.2f / min_dur=%.1f(body 基准;剧本写了就以剧本为准)"
|
|
665
664
|
% (DEFAULT_LEAD, DEFAULT_TAIL, DEFAULT_MIN_DUR),
|
|
666
665
|
"tts 耗时按 %d 句 ÷ workers × %.1f–%.1f s(每句每 worker);实际受网络波动影响"
|
|
667
666
|
% (n, TTS_SEC_PER_LINE[0], TTS_SEC_PER_LINE[1]),
|
|
@@ -669,7 +668,7 @@ def build_report(script_path, script, args):
|
|
|
669
668
|
% (PARALLEL_EFF, CORES),
|
|
670
669
|
"mux 默认 -c:v copy,只跟片长有关;reencode_video=true 才按帧数重编",
|
|
671
670
|
"layout/cues/audio/sfx 给 ±%d%% 经验区间(无单阶段实测)" % int(UNMEASURED_BAND * 100),
|
|
672
|
-
"
|
|
671
|
+
"可选门禁口径:preflight 0.4 s 固定;vmaf ≈ %.2f–%.2f× 片长 + %.1f–%.1f s"
|
|
673
672
|
"(参考帧重渲染 + libvmaf;同机负载不同实测差近 2×);asr ≈ 0.18–0.35× 片长 + "
|
|
674
673
|
"3.5–6.5 s(faster-whisper small / CPU int8)"
|
|
675
674
|
% (VMAF_SEC_PER_SEC[0], VMAF_SEC_PER_SEC[1], VMAF_BASE_SEC[0], VMAF_BASE_SEC[1]),
|
|
@@ -695,7 +694,7 @@ def print_report(rep):
|
|
|
695
694
|
"会自动改用真实帧数。")
|
|
696
695
|
style = v.get("style")
|
|
697
696
|
if style is None:
|
|
698
|
-
style = "(没写 style 字段 =
|
|
697
|
+
style = "(没写 style 字段 = legacy 行为)"
|
|
699
698
|
print(" 《%s》 片长 %.1f s / %d 帧 / %dx%d@%d / engine=%s / crf=%s preset=%s / style=%s"
|
|
700
699
|
% (v.get("title") or "(未写 title)", v["duration_s"], v["frames"], v["width"], v["height"],
|
|
701
700
|
v["fps"], v["engine"], v["crf"], v["preset"], style))
|
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
|
|
@@ -72,7 +71,7 @@ DEFAULT_SCRIPT = os.path.join(ASSETS, "film.json")
|
|
|
72
71
|
# v1.0 / D3:金帧基线绑"固定 fixture 设计"而不是内置风格。
|
|
73
72
|
# 为什么:金帧要守的是"同一份设计 + 同一份场景代码,像素不许漂",
|
|
74
73
|
# 不是"某套内置风格长什么样"——后者会随风格表下沉到 presets 而失去意义。
|
|
75
|
-
#
|
|
74
|
+
# 没有这个文件时回落到 stylekit 老口径(它现在只会明确报错),不阻塞诊断。
|
|
76
75
|
DEFAULT_DESIGN = os.path.join(ASSETS, "design_fixture.json")
|
|
77
76
|
DEFAULT_SCENES = os.path.join(ASSETS, "scenes.py")
|
|
78
77
|
DEFAULT_GOLDEN = os.path.join(ASSETS, "golden_frames.json")
|
|
@@ -84,7 +83,7 @@ FAKE_VOICE_DUR = 2.0
|
|
|
84
83
|
HOOK_LOCAL = 1.2
|
|
85
84
|
BODY_LOCAL = 1.6
|
|
86
85
|
OUTRO_LOCAL = 1.5
|
|
87
|
-
#
|
|
86
|
+
# 参与字体指纹的字重(内置绘制器只用到 bold / reg / light)
|
|
88
87
|
FONT_WEIGHTS = ("bold", "reg", "light")
|
|
89
88
|
|
|
90
89
|
RESULT_KEYS = ("status", "frames", "font_fp")
|
|
@@ -191,7 +190,7 @@ def build_timeline(K, script):
|
|
|
191
190
|
与 render.st_layout 的差别只在 dur 的来源:这里每句固定 FAKE_VOICE_DUR 秒,
|
|
192
191
|
其余(lead/tail/min_dur/hold/bar)照常走 filmkit.build_timeline,结果可复现。
|
|
193
192
|
|
|
194
|
-
但 **fps 必须跟 render.py 一样取自 filmkit.video_plan**(
|
|
193
|
+
但 **fps 必须跟 render.py 一样取自 filmkit.video_plan**(fps 可按片配置):
|
|
195
194
|
build_timeline 的默认 fps=30 会让 24/25/50/60 fps 的片子按 30 fps 编号,
|
|
196
195
|
于是基线里的 frame 号既不是成片的真实帧号,t=frame/fps 也不是真实时刻——
|
|
197
196
|
那份基线守的是"另一部片子"的画面,回放/比对都错位(且不会报错)。
|
|
@@ -299,7 +298,7 @@ def env_fingerprint():
|
|
|
299
298
|
口径细节:**Python 只按 major.minor 参与指纹**,补丁号只记录不参与比较。
|
|
300
299
|
理由是真正决定抗锯齿像素的是 Pillow(FreeType)与 numpy,不是 CPython 的补丁版本;
|
|
301
300
|
而按补丁号比较会让"作者机器 3.12.16 / 用户机器 3.12.14"这种正常差异一律 skipped——
|
|
302
|
-
|
|
301
|
+
实测踩过:金帧回归因此显示"0 帧像素一致"却记 PASS,等于这条门禁从没真的比过。
|
|
303
302
|
真要跨 Pillow/numpy 版本比,仍然会 skipped,那才是像素不可比的情形。
|
|
304
303
|
"""
|
|
305
304
|
import numpy
|
|
@@ -314,7 +313,7 @@ def env_fingerprint():
|
|
|
314
313
|
def _env_fp_from(env):
|
|
315
314
|
"""按"参与比较的口径"给 env 字典算指纹(Python 只取 major.minor)。
|
|
316
315
|
|
|
317
|
-
|
|
316
|
+
单独抽出来是为了**兼容老基线**:早期 golden_frames.json 里记的是完整补丁号
|
|
318
317
|
(如 3.12.16),如果直接拿新口径算出的指纹去比,老基线会永远 skipped——那等于把
|
|
319
318
|
"补丁号差异"永久固化成"这条门禁不用跑"。这里对**存的 env** 和**当前 env** 走同一个
|
|
320
319
|
归一化函数,所以 3.12.14 与 3.12.16 视为同一环境,而 Pillow/numpy 的差异照旧拦住。
|
|
@@ -425,9 +424,9 @@ def _golden_payload(script_path, scenes_path, K, plan, tl, rendered, design_path
|
|
|
425
424
|
"generator": "golden_frames.py v%s" % __version__,
|
|
426
425
|
"comment": "金帧基线:字体指纹+环境指纹一致时,每一帧像素 sha256 必须相同;"
|
|
427
426
|
"换机/字体/Pillow 版本不同判 skipped(不误报),同环境像素变了才是 fail。"
|
|
428
|
-
"
|
|
427
|
+
"改动夹具(film.json / scenes.py / 设计)后请 --write 重录并提交本文件。",
|
|
429
428
|
"canvas": [int(K.W), int(K.H)],
|
|
430
|
-
# 记时间轴自己的 fps(不是写死的 K.FPS):
|
|
429
|
+
# 记时间轴自己的 fps(不是写死的 K.FPS):film.json 可以设 24/25/50/60
|
|
431
430
|
"fps": int(tl["fps"]),
|
|
432
431
|
"script": _rel(script_path),
|
|
433
432
|
"scenes": _rel(scenes_path),
|
|
@@ -548,7 +547,7 @@ def check_golden(script_path, scenes_path, golden_path, design_path=None):
|
|
|
548
547
|
reason="skipped: font differs(字体指纹 %s)——换机/字体版本不同,"
|
|
549
548
|
"像素比对不成立;确认画面后在当前机器 --write 重录"
|
|
550
549
|
% _fmt_fp_pair(font_fp, golden.get("font_fp")))
|
|
551
|
-
# 比指纹时对**存的 env** 走同一个归一化(见 _env_fp_from
|
|
550
|
+
# 比指纹时对**存的 env** 走同一个归一化(见 _env_fp_from):这样早期那些
|
|
552
551
|
# 记着完整补丁号的老基线不会因为 3.12.14 vs 3.12.16 永久 skipped。
|
|
553
552
|
golden_env_fp = _env_fp_from(golden.get("env") or {})
|
|
554
553
|
if golden_env_fp != env_fp:
|
|
@@ -647,15 +646,15 @@ def main(argv=None):
|
|
|
647
646
|
g = ap.add_mutually_exclusive_group()
|
|
648
647
|
g.add_argument("--check", action="store_true", help="校验(默认)")
|
|
649
648
|
g.add_argument("--write", action="store_true", help="生成/更新基线")
|
|
650
|
-
ap.add_argument("--script", default=DEFAULT_SCRIPT, help="film.json
|
|
649
|
+
ap.add_argument("--script", default=DEFAULT_SCRIPT, help="film.json(默认夹具片)")
|
|
651
650
|
ap.add_argument("--design", default=DEFAULT_DESIGN,
|
|
652
651
|
help="固定 fixture 设计(默认 assets/design_fixture.json);"
|
|
653
652
|
"传空串回落到老的 stylekit 口径")
|
|
654
|
-
ap.add_argument("--scenes", default=DEFAULT_SCENES, help="scenes.py
|
|
653
|
+
ap.add_argument("--scenes", default=DEFAULT_SCENES, help="scenes.py(默认内置场景)")
|
|
655
654
|
ap.add_argument("--golden", default=DEFAULT_GOLDEN,
|
|
656
655
|
help="基线 JSON(默认 skills/video-studio/assets/golden_frames.json)")
|
|
657
656
|
ap.add_argument("--frames", default="",
|
|
658
|
-
help="--write
|
|
657
|
+
help="--write 时指定帧号(逗号分隔);缺省取夹具片 3 个时刻")
|
|
659
658
|
ap.add_argument("--json", action="store_true", help="stdout 只输出 JSON(机读)")
|
|
660
659
|
ap.add_argument("--strict", action="store_true",
|
|
661
660
|
help="skipped 用退出码 2 表示(默认 skipped 退出码 0,但绝不写 pass)")
|
package/tools/silhouette_qa.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""silhouette_qa.py — 跨设计剪影可区分门禁(G13
|
|
2
|
+
"""silhouette_qa.py — 跨设计剪影可区分门禁(G13)。
|
|
3
3
|
|
|
4
4
|
解决什么问题
|
|
5
5
|
------------
|
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
灰度三值化后的**构图骨架**(前景占比、重心、边缘占用、左右对称、纵向明暗剖面)是否真的
|
|
9
9
|
变了?本工具把"骨架到底有没有变"变成可复核的数字,而不是审美口水。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
因此本工具改成:**N 个设计之间两两比较**——这才对应"不同视频会不会长得一样"。
|
|
11
|
+
输入从哪来
|
|
12
|
+
----------
|
|
13
|
+
技能里没有内置风格表(stylekit.STYLE_IDS == ()),"同一剧本换 style"这种输入不存在。
|
|
14
|
+
"多部片"来自 **designkit 现场采样**:同一 brief 不同 seed(或不同 brief)得到 N 个设计,
|
|
15
|
+
每个设计是一整条独立外观(配色/母题/版式/HUD/字体族/字号比例/字距/行距/重心偏移/转场/缓动/编号写法…)。
|
|
16
|
+
所以本工具比的是 **N 个设计之间两两比较**——这才对应"不同视频会不会长得一样"。
|
|
18
17
|
|
|
19
18
|
算法(验收口径;改阈值 / 改指标之前先读这一节)
|
|
20
19
|
----------------------------------------------
|
|
@@ -71,7 +70,7 @@ B 的基准是四角中位数:浅色主题整屏 bg→bg2 渐变本身就可
|
|
|
71
70
|
跳过 = 帧渲不出来 / 指标缺值,**skipped != 通过**:它只说明这次没证据,不说明
|
|
72
71
|
骨架相同;--strict 用于 CI 里把"没跑成"也当红。
|
|
73
72
|
|
|
74
|
-
CLI
|
|
73
|
+
CLI(没有 --styles:STYLE_IDS 是空元组,按风格 ID 选已经无意义)
|
|
75
74
|
------------------------------------------------------------------------------------------
|
|
76
75
|
python tools/silhouette_qa.py [--designs 3] [--seed 1] [--brief "题材/要求"]
|
|
77
76
|
[--design-json PATH]
|
|
@@ -102,7 +101,7 @@ CLI(v2.0.0;旧的 --styles 已删除——STYLE_IDS 在 v1.0.1 是空元组
|
|
|
102
101
|
"""
|
|
103
102
|
from __future__ import annotations
|
|
104
103
|
|
|
105
|
-
__version__ = "
|
|
104
|
+
__version__ = "1.0.4"
|
|
106
105
|
|
|
107
106
|
import argparse
|
|
108
107
|
import copy
|
|
@@ -147,7 +146,7 @@ TIER_BG_MAX = 15 # d < 15 -> 背景档 0
|
|
|
147
146
|
TIER_FG_MIN = 60 # d >= 60 -> 前景档 2
|
|
148
147
|
EDGE_BAND = 0.06 # 四边各 6% 带宽
|
|
149
148
|
METRIC_ORDER = ("F", "Cx", "Cy", "B", "S", "P")
|
|
150
|
-
#
|
|
149
|
+
# 达标线:F>=6pp / Cx,Cy>=8 / B>=14 / S>=0.20 / P>=6.0。
|
|
151
150
|
# 改这里等于改验收口径,需说明理由;v2.0.0 只换输入(设计而不是风格),不放宽任何一条。
|
|
152
151
|
THRESHOLDS = {"F": 6.0, "Cx": 8.0, "Cy": 8.0, "B": 14.0, "S": 0.20, "P": 6.0}
|
|
153
152
|
METRIC_LABELS = {
|
|
@@ -346,7 +345,7 @@ def load_designkits(scripts_dir=SCRIPTS):
|
|
|
346
345
|
def sample_designs(DK, n, seed, brief=None):
|
|
347
346
|
"""现场采样 n 个设计(不同 brief 或同 brief 不同 seed,跨设计两两比较)。
|
|
348
347
|
|
|
349
|
-
为什么要"现场采样"
|
|
348
|
+
为什么要"现场采样"而不是读一份写死的清单:技能里没有任何内置外观表,
|
|
350
349
|
多部片的差异全部来自 designkit 的连续采样;门禁必须量**采样器真的会产出的东西**。
|
|
351
350
|
seed=None 表示每次新设计(采样器的默认语义);本工具缺省固定 seed 以保结论可复现。
|
|
352
351
|
"""
|
|
@@ -416,7 +415,7 @@ def _family_of(ST, plan):
|
|
|
416
415
|
def build_script(design_id, scene_types):
|
|
417
416
|
"""造一份"只有设计不同"的合成剧本(title/lines 固定,dur 固定 4.0s)。
|
|
418
417
|
|
|
419
|
-
style
|
|
418
|
+
style 字段不写任何风格 id(没有内置风格表);渲染用的计划由
|
|
420
419
|
run_gate 塞进 script["_style"],scenes.draw_frame 优先读它。
|
|
421
420
|
"""
|
|
422
421
|
lines = []
|
|
@@ -1156,7 +1155,7 @@ def main(argv=None):
|
|
|
1156
1155
|
_setup_stdio()
|
|
1157
1156
|
ap = argparse.ArgumentParser(
|
|
1158
1157
|
description="跨设计剪影可区分门禁 G13:同一内容换设计(designkit 现场采样)渲帧,"
|
|
1159
|
-
"
|
|
1158
|
+
"三值化后比结构指标。没有 --styles:技能不提供内置"
|
|
1160
1159
|
"风格表(stylekit.STYLE_IDS 为空),输入改为现场采样的设计。",
|
|
1161
1160
|
epilog="例:python tools/silhouette_qa.py --designs 4 --json;"
|
|
1162
1161
|
"python tools/silhouette_qa.py --selftest")
|