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

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 (45) hide show
  1. package/CHANGELOG.md +1109 -0
  2. package/README.md +76 -10
  3. package/THIRD_PARTY_NOTICES.md +1 -0
  4. package/docs/styles/sheet-8-styles.png +0 -0
  5. package/docs/styles/sheet-before-after.png +0 -0
  6. package/index.js +4 -1
  7. package/package.json +7 -3
  8. package/schemas/film.schema.json +329 -2
  9. package/skills/video-studio/SKILL.md +270 -13
  10. package/skills/video-studio/assets/golden_frames.json +65 -0
  11. package/skills/video-studio/assets/scenes.py +2162 -1190
  12. package/skills/video-studio/assets/web/engine.js +63 -2
  13. package/skills/video-studio/assets/web/film.js +224 -101
  14. package/skills/video-studio/assets/web/index.html +4 -2
  15. package/skills/video-studio/assets/web/qa_text.js +36 -8
  16. package/skills/video-studio/references/audio-variation.md +4 -3
  17. package/skills/video-studio/references/audio.md +2 -2
  18. package/skills/video-studio/references/change-impact.md +1 -1
  19. package/skills/video-studio/references/quality-gates.md +25 -3
  20. package/skills/video-studio/references/scene-grammar.md +306 -0
  21. package/skills/video-studio/references/style-packs.md +108 -3
  22. package/skills/video-studio/references/v08-quality.md +256 -0
  23. package/skills/video-studio/references/web-engine.md +19 -2
  24. package/skills/video-studio/references/word-timeline.md +147 -0
  25. package/skills/video-studio/requirements.txt +14 -0
  26. package/skills/video-studio/scripts/filmcheck.py +218 -0
  27. package/skills/video-studio/scripts/filmkit.py +521 -38
  28. package/skills/video-studio/scripts/gates.py +88 -4
  29. package/skills/video-studio/scripts/grammar.py +559 -0
  30. package/skills/video-studio/scripts/preflight.py +774 -0
  31. package/skills/video-studio/scripts/qa_frames.py +49 -8
  32. package/skills/video-studio/scripts/qaasr.py +1563 -0
  33. package/skills/video-studio/scripts/qavmaf.py +1442 -0
  34. package/skills/video-studio/scripts/render.py +764 -32
  35. package/skills/video-studio/scripts/scorekit.py +16 -30
  36. package/skills/video-studio/scripts/selfcheck.py +483 -0
  37. package/skills/video-studio/scripts/sfxkit.py +130 -14
  38. package/skills/video-studio/scripts/stagewatch.py +19 -6
  39. package/skills/video-studio/scripts/stylekit.py +242 -12
  40. package/skills/video-studio/scripts/voicekit.py +15 -7
  41. package/skills/video-studio/scripts/webengine.py +1119 -72
  42. package/tools/ci_check.py +173 -31
  43. package/tools/estimate.py +130 -19
  44. package/tools/golden_frames.py +664 -0
  45. package/tools/silhouette_qa.py +910 -0
@@ -773,11 +773,19 @@ def _cue_anchor(cue) -> float | None:
773
773
  return None
774
774
 
775
775
 
776
- def _sync_coverage(cues, events) -> dict:
776
+ def _sync_coverage(cues, events, tol=SYNC_TOL) -> dict:
777
777
  """统计"音画同步到底查了多少条":返回 {"cues","anchors","same","judged","src_skipped"}。
778
778
 
779
- judged = 能给出 dt 判定的 cue 数 = 自带锚点的 cue + 至少存在同名 kind 且**同源**事件的 cue。
780
- 这是防"静默全过"的量化指标:judged == 0 而 cues > 0 时,sync 为空没有意义。
779
+ judged = **按命名约定真的判过 dt** 的 cue 数 = 自带视觉锚点的 cue + 同 kind 且落在
780
+ SYNC_SAME_WIN 搜索窗内、src 同基准的 cue。这是防"静默全过"的量化指标:
781
+ judged == 0 而 cues > 0 时,sync 为空没有意义。
782
+ ⚠ 不能只数"存在同名 kind 的 cue":同名事件若都离 cue 超过 SYNC_SAME_WIN 的搜索窗,
783
+ sync_check 一条也不会判,judged 却会记成"查过了"——require_sync 的"没查"守卫
784
+ 就被这个虚高计数骗过去(实测 cues=[impact@0.5] / events=[impact@100]
785
+ -> judged=1、errors=[],门禁根本没判)。所以这里用与 sync_check 同一个 _match_cue 判。
786
+ ⚠ 也**不**把"异 kind 就近确认"(mode="near")算进 judged:它只在 |dt|<=tol 时认可,
787
+ 是运气不是约定;把它算成"判过"会让命名约定失效的场景显示成"已判 1 条"。
788
+ 另外记 judged_any(含异 kind 佐证)只作诊断,不进报告口径。
781
789
  src_skipped = 因为"cue 与事件时间基准不同"(src 不同)而无法参与对照的 cue 数:
782
790
  web 引擎三路合并(film.json/scenes.py 用 PIL 时间轴、页面事件用页面时间轴)时会有,
783
791
  这种情况不能算"同步失败",但也不能静默跳过 —— 由 _load_cues 转成 warning 点名。
@@ -786,18 +794,29 @@ def _sync_coverage(cues, events) -> dict:
786
794
  events = [e for e in (events or []) if isinstance(e, dict)]
787
795
  anchors = sum(1 for c in cues if _cue_anchor(c) is not None)
788
796
  same = 0
797
+ judged = 0
798
+ judged_any = 0
789
799
  src_skipped = 0
790
800
  for c in cues:
791
801
  if _cue_anchor(c) is not None:
802
+ judged += 1
803
+ judged_any += 1
792
804
  continue
793
805
  compat = _compatible_events(c, events)
794
806
  if not compat and events:
795
807
  src_skipped += 1
796
808
  continue
797
- if str(c.get("type") or "").strip().lower() in {_event_kind(e) for e in compat}:
809
+ typ = str(c.get("type") or "").strip().lower()
810
+ if typ in {_event_kind(e) for e in compat}:
798
811
  same += 1
812
+ matched = _match_cue(c, compat, tol)[1] is not None
813
+ if matched:
814
+ judged_any += 1
815
+ # 只有"同名 kind 配对成功"才算按约定判过(锚点上面已单独计入)
816
+ if matched and any(_event_kind(e) == typ for e in compat):
817
+ judged += 1
799
818
  return {"cues": len(cues), "anchors": anchors, "same": same,
800
- "judged": anchors + same, "src_skipped": src_skipped}
819
+ "judged": judged, "judged_any": judged_any, "src_skipped": src_skipped}
801
820
 
802
821
 
803
822
  def _load_cues(cues_json):
@@ -892,18 +911,40 @@ def _load_cues(cues_json):
892
911
  warns.append(msg)
893
912
  info["blockers"].append(msg)
894
913
  elif cues and events:
895
- # 门禁静默失效的最后一道保险:同 kind 配对要求 events.kind == cue.type,
896
- # 且按来源(src)过滤后仍要有可配对项(见 _src_compatible)。
897
- # 若一个都对不上,sync_check 会"零超差"通过——看起来干净,其实一条都没在查。
914
+ # 门禁静默失效的最后两道保险,分别覆盖两种"零超差但一条没查":
915
+ # ① 命名约定失效:cue.type 与任何事件的 kind 都不同名(cov["same"] == 0);
916
+ # ② 同名事件全落在 SYNC_SAME_WIN 搜索窗之外,一条 dt 都算不出来(cov["judged"] == 0)。
917
+ # 两者都会让 sync_check 返回空列表——看起来干净,其实一条都没在查。
918
+ # 判据用 cov["judged"](按约定真的配对成功)而不是 cov["same"](只是同名)。
898
919
  cue_types = {str(c.get("type") or "").strip().lower() for c in cues}
899
920
  ev_kinds = {_event_kind(e) for e in events}
900
921
  if cov["same"] == 0 and cov["anchors"] == 0:
922
+ # 命名约定失效:cue.type 与任何事件的 kind 都不同名。这时 sync_check 可能靠
923
+ # 异 kind 的"就近确认"蒙到几条(dt 恰好在容差内),但**没有任何一条是照约定
924
+ # 配出来的**——对齐了是巧合,错位了也不会报。所以这条与 judged 无关,永远报。
901
925
  msg = ("音画同步检查可能没生效:%d 个 cue 的 type 与 %d 个事件的 kind 没有任何可配对的同名项"
902
926
  "(同 kind 配对要求 events.kind == cue 的配方名,且双方 src 需属同一时间基准)。"
903
927
  "sync 为空不代表同步正确——请按 references/frame-qa.md §2.5 的命名约定登记事件,"
904
928
  "或把某个 cue 故意改错 0.3 s 重跑做反证。" % (len(cue_types), len(ev_kinds)))
905
929
  warns.append(msg)
906
930
  info["blockers"].append(msg)
931
+ elif cov["judged"] == 0 and cov.get("judged_any", 0) == 0:
932
+ # 同名 kind 有,但一条 dt 都算不出来:事件全落在 SYNC_SAME_WIN 搜索窗之外,
933
+ # 或全被 src 过滤掉了。sync_check 同样"零超差"通过——必须让人看见。
934
+ msg = ("音画同步检查可能没生效:%d 条 cue 里没有一条能判定 dt"
935
+ "(同名事件都不在 %.1f s 搜索窗内,或 src 时间基准不同)。"
936
+ "sync 为空不代表同步正确——请核对 events 的时间基准与时刻,"
937
+ "或给 cue 写数值 visual_at。" % (len(cues), SYNC_SAME_WIN))
938
+ warns.append(msg)
939
+ info["blockers"].append(msg)
940
+ else:
941
+ # 「有 cues.json 但里面既没有 cues 也没有 events」:文件解析成功、对照却无从做起。
942
+ # 不记 blocker 的话 require_sync 也拦不住:report() 里把"没查"升级成 error 的条件
943
+ # 要求 cues 非空,而这里 cues 为空 —— 于是空 cues.json 会静默通过整道同步门禁。
944
+ msg = ("cues.json 里既没有 cues 也没有 events:音画同步对照没有执行"
945
+ "(文件是空对象、只写了 version 字段?按 §4.2 补 cues / events)")
946
+ warns.append(msg)
947
+ info["blockers"].append(msg)
907
948
  return cues, events, warns, info
908
949
 
909
950