@oh-story/dsh 0.1.11 → 0.1.13

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 (264) hide show
  1. package/README.md +17 -11
  2. package/lib/client.js +85 -35
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +41 -36
  5. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
  6. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
  7. package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
  8. package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
  9. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
  10. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
  11. package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
  12. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
  13. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
  14. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
  15. package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
  16. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
  17. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
  18. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
  19. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
  20. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  21. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
  22. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
  23. package/lib/index.js +16 -7
  24. package/lib/index.js.map +2 -2
  25. package/lib/novel-to-game/README.md +98 -44
  26. package/lib/novel-to-game/README_ZH.md +100 -46
  27. package/lib/novel-to-game/VERSION +1 -1
  28. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
  29. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
  30. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
  31. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
  32. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
  33. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
  34. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
  35. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
  36. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
  37. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
  38. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
  39. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
  40. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
  41. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
  42. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
  43. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
  44. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
  45. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
  46. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
  47. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
  57. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
  58. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
  59. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
  60. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
  61. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
  62. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
  63. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
  64. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
  65. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
  66. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
  91. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
  92. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
  93. package/lib/novel-to-game/manifest.json +303 -78
  94. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
  95. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
  96. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
  98. package/lib/types/client/drama-production-view.d.ts +11 -0
  99. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  100. package/lib/types/client/file-activity.d.ts +1 -1
  101. package/lib/types/client/file-activity.d.ts.map +1 -1
  102. package/lib/types/client/production-prompts.d.ts +7 -1
  103. package/lib/types/client/production-prompts.d.ts.map +1 -1
  104. package/lib/types/client/production-runtime.d.ts +7 -2
  105. package/lib/types/client/production-runtime.d.ts.map +1 -1
  106. package/lib/types/drama-adapters.d.ts.map +1 -1
  107. package/lib/types/skill-provider.d.ts.map +1 -1
  108. package/lib/types/video-project.d.ts.map +1 -1
  109. package/lib/video-recap/CHANGELOG.md +108 -1
  110. package/lib/video-recap/README.en.md +18 -14
  111. package/lib/video-recap/README.md +18 -14
  112. package/lib/video-recap/manifest.json +297 -321
  113. package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
  114. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
  115. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
  116. package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
  117. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
  118. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
  119. package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
  120. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
  121. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
  122. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
  123. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
  124. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
  125. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
  126. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
  127. package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
  128. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
  129. package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
  130. package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
  131. package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
  132. package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
  133. package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
  134. package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
  135. package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
  136. package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
  137. package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
  138. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
  139. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
  140. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
  141. package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
  142. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
  143. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
  144. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
  145. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
  146. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
  147. package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
  148. package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
  149. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
  150. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
  151. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
  152. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
  153. package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
  154. package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
  155. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
  156. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
  157. package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
  158. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
  159. package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
  160. package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
  161. package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
  162. package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
  163. package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
  164. package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
  165. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
  166. package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
  167. package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
  168. package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
  169. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
  170. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
  171. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
  172. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
  173. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
  174. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
  175. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
  176. package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
  177. package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
  178. package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
  179. package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
  180. package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
  181. package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
  182. package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
  183. package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
  184. package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
  185. package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
  186. package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
  187. package/lib/video-recap/skills/video-script/SKILL.md +9 -7
  188. package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
  189. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
  190. package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
  191. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
  192. package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
  193. package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
  194. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
  195. package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
  196. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
  197. package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
  198. package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
  199. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
  200. package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
  201. package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
  202. package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
  203. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
  204. package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
  205. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
  206. package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
  207. package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
  208. package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
  209. package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
  210. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
  211. package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
  212. package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
  213. package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
  214. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
  215. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
  216. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
  217. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
  218. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
  219. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
  220. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
  221. package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
  222. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
  223. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
  224. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
  225. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
  226. package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
  227. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
  228. package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
  229. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
  230. package/package.json +45 -45
  231. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  232. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  233. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  234. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
  235. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
  236. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
  237. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
  238. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
  239. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
  240. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
  241. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
  242. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
  243. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
  244. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
  245. package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
  246. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
  247. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
  248. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
  249. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
  250. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
  251. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
  252. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
  253. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
  254. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
  255. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
  256. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
  257. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
  258. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
  259. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
  260. package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
  261. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
  262. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
  263. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
  264. /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
@@ -0,0 +1,139 @@
1
+ # 制作参考:字段、枚举与示例
2
+
3
+ 本文件只列字段与枚举;剧作与剪辑手法的具体规则以后续写稿阶段自己的手册为准,这里不复述。
4
+
5
+ ## 标注词表
6
+
7
+ | 字段 | 取值 |
8
+ |---|---|
9
+ | `audio_spans[].owner` | `narration` · `original_dialogue` · `action_sound` · `ambience` · `music` · `silence` |
10
+ | `audio_spans[].narration_job`(可选,仅 narration 段) | `context` · `causal_link` · `foreshadow` · `interpretation` · `transition` |
11
+ | `sections[].function`(可重复出现) | `hook` · `setup` · `turn` · `escalation` · `payoff` |
12
+ | `dimension` | `narrative_structure` · `pacing` · `shots_editing` · `narration_subtitles` · `audio_visual` |
13
+ | `applies_to` | `story_plan` · `visual_audio_board` · `clip_plan` · `narration` · `style_card` |
14
+
15
+ `audio_spans` 记录**谁占有这段声音**(对白下压着的背景乐不算主导;原片自带的画外音、内心独白记 `original_dialogue`);`sections` 记录叙事功能。两者都必须按时间排序、
16
+ 不重叠、间隙不超过 0.5 秒、覆盖整片。
17
+
18
+ ## `reference_breakdown.json`(Agent 唯一要写的文件)
19
+
20
+ 顶层只允许 `schema`、`labels`、`source_facts`、`methods`、`skipped_dimensions`;每一层的键都是封闭集合。
21
+
22
+ ```json
23
+ {
24
+ "schema": "video-reference.breakdown.v1",
25
+ "labels": {
26
+ "audio_spans": [
27
+ {"start": 0, "end": 13.6, "owner": "narration", "narration_job": "context"},
28
+ {"start": 13.6, "end": 30.6, "owner": "original_dialogue"}
29
+ ],
30
+ "sections": [{"start": 0, "end": 30.6, "function": "hook"}],
31
+ "subtitles": {"burned": true, "max_lines": 1, "marks_original": "「」", "evidence_t": [12.0]},
32
+ "basis": "5s ASR 窗口 + 故事板;切点经 frames --review / --longest 5 复核",
33
+ "cut_fixes": {"add": [10.8], "remove": []}
34
+ },
35
+ "source_facts": [
36
+ {"id": "f1", "dimension": "narrative_structure", "statement": "开场旁白交代主角身世后切入原声冲突",
37
+ "t": [0, 13.6], "entities": ["<原片人名>"]},
38
+ {"id": "f2", "dimension": "pacing", "statement": "原声段沿用原片较密的切点,旁白段切得更少",
39
+ "measure": ["derived.by_owner.original_dialogue.cuts_per_min", "derived.by_owner.narration.cuts_per_min"],
40
+ "entities": []}
41
+ ],
42
+ "methods": [
43
+ {"id": "m1", "dimension": "audio_visual",
44
+ "rule": "原声段保留原片自身的剪辑节奏,旁白段换成较长的镜头承载解说,让注意力留在声音上",
45
+ "applies_when": "对白冲突强、需旁白补前情的剧集解说", "avoid_when": "主要靠动作声承载的段落",
46
+ "applies_to": "visual_audio_board", "evidence": ["f2", "measure:derived.switch_on_cut_share"],
47
+ "targets": {"narration_cuts_per_min": {"from": "derived.by_owner.narration.cuts_per_min"}}}
48
+ ],
49
+ "skipped_dimensions": {"narration_subtitles": "ASR 不可用,未测语速"}
50
+ }
51
+ ```
52
+
53
+ - `cut_fixes` 可选,键只有 `add`、`remove`(秒数列表):`remove` 的每个值必须在某个测得切点 ±0.1 秒内,`add` 的值不得在测得切点
54
+ ±0.1 秒内。写了(哪怕是 `{}`)就表示看过待复核窗口;有增删时所有 `shots.*` 与切点相关的 `derived.*` 都按复核后的切点重算。
55
+ - `subtitles` 可选:`burned`(布尔)、`max_lines`(≥1 整数)必填,`marks_original`(字符串)、`evidence_t`(秒数列表)可选。
56
+ - `skipped_dimensions` 的值是一句非空的原因;它会原样导出,同样受 R8 泄漏扫描。
57
+ - fact:`id`、`dimension`、`statement`、`entities` 必填,`t` 与 `measure` 二选一。`entities` 写这条事实涉及的人名、地名、
58
+ 组织名,没有就写空列表——它同时是泄漏扫描的词表。
59
+ - method:`id`、`dimension`、`rule`、`applies_to`、`evidence` 必填;`applies_when`、`avoid_when`、`targets` 可选。
60
+ - target:只写 `from`,路径根只能是 `shots`、`loudness` 或 `derived`,不得带列表下标;解析结果必须是数值叶子,或以下派生对象之一:
61
+ `derived.structure`、`derived.first_original_at`(只导出 `{fraction}`)、`derived.narration_jobs`、`derived.by_owner.<owner>`、
62
+ `derived.by_section.<function>`。`shots.cuts`、`shots.curve`、`loudness.short_term_1s` 这类逐时刻序列不能做 target。
63
+ - fact 的 `measure` 与 method 的 `measure:` 证据也只认这三个根,且不能落在字符串值上。
64
+
65
+ ## 测量与派生值
66
+
67
+ `reference_measurements.json` 由 measure 写(`settings` 记切点规则参数,`scdet_scores` 是 ≥2 分的逐帧分数缓存,`mafd_peaks` 是帧差单侧峰,三者都不能被引用),
68
+ 路径即 target / evidence 可引用的 `shots.*`、`loudness.*`(有 `cut_fixes` 时按复核后的切点):
69
+
70
+ | 路径 | 含义 |
71
+ |---|---|
72
+ | `shots.cuts` · `count` · `mean_s` · `median_s` · `p10_s` · `p90_s` | 切点时间与镜长分布 |
73
+ | `shots.review_windows` | 被压下的疑似切点与不在切点上的帧差单侧峰,合成窗口 `[[开始, 结束], …]`,给 `frames --review` 用 |
74
+ | `shots.share_under_1s` · `share_over_8s` · `cuts_per_min` · `curve` | 短/长镜头占比、切点密度、10 秒窗口切点曲线(每窗按自身长度折算成每分钟) |
75
+ | `loudness.integrated_lufs` · `lra_lu` · `true_peak_dbtp` · `short_term_1s` | 整体响度、动态范围、真峰值、逐秒短期响度(前约 2 秒为预热空值) |
76
+
77
+ `derived.*` 由 check 在内存里计算,export 写入用到的部分:
78
+
79
+ | 路径 | 含义 |
80
+ |---|---|
81
+ | `derived.by_owner.<owner>` | `seconds` · `share` · `blocks` · `block_median_s` · `cuts_per_min` · `mean_short_term_lufs` |
82
+ | `derived.narration_chars_per_s` | 只用被旁白覆盖 ≥80% 的 ASR 窗口;ASR 失败或跳过时 `value` 为 null |
83
+ | `derived.switch_on_cut_share` | 音轨归属切换点落在画面切点 ±0.25 秒内的比例 |
84
+ | `derived.by_section.<function>` | `seconds` · `cuts_per_min` · `narration_share` |
85
+ | `derived.structure` | `[{function, at:[开始占比, 结束占比], lead_owner}]` |
86
+ | `derived.first_original_at` | 第一段 `original_dialogue`(含原片自带画外音)的 `{s, fraction}` |
87
+ | `derived.narration_jobs` | 各 job 占旁白时长的比例(标注了 job 时才有) |
88
+
89
+ ## `production_reference.json`(导出物,消费方只读这个)
90
+
91
+ ```json
92
+ {
93
+ "schema": "video-reference.production.v1",
94
+ "duration_s": 302.66,
95
+ "canvas": {"width": 1280, "height": 676},
96
+ "cut_detection": {"detector": "scdet-isolated-v1", "hard_score": 10.0, "soft_score": 4.0, "isolation_ratio": 2.0,
97
+ "isolation_window_s": 0.3, "scaled": true, "agent_added": 3, "agent_removed": 0},
98
+ "profile": {
99
+ "shot_median_s": {"value": 2.58, "provenance": "reviewed"},
100
+ "cuts_per_min": {"value": 16.85, "provenance": "reviewed"},
101
+ "integrated_lufs": {"value": -14.8, "provenance": "measured"},
102
+ "narration_share": {"value": 0.65, "provenance": "labeled"},
103
+ "narration_chars_per_s": {"value": 4.0, "provenance": "labeled", "precision": "coarse_asr_windows"},
104
+ "switch_on_cut_share": {"value": 0.5, "provenance": "labeled"},
105
+ "first_original_at": {"value": {"fraction": 0.045}, "provenance": "labeled"}
106
+ },
107
+ "structure": [{"function": "hook", "at": [0.0, 0.1], "lead_owner": "narration"}],
108
+ "subtitles": {"burned": true, "max_lines": 1, "marks_original": "「」"},
109
+ "methods": [
110
+ {"id": "m1", "dimension": "audio_visual", "rule": "…", "applies_when": "…", "avoid_when": "…",
111
+ "applies_to": "visual_audio_board",
112
+ "targets": {"narration_cuts_per_min": {"value": 14.2, "provenance": "labeled"}}}
113
+ ],
114
+ "skipped_dimensions": {}
115
+ }
116
+ ```
117
+
118
+ - `provenance`:路径在 `derived.*` 下为 `labeled`(依赖 Agent 标注);`shots.*` 在写了 `cut_fixes`(含 `{}`)时为 `reviewed`,
119
+ 否则与 `loudness.*` 一样为 `measured`。值为 null 的 profile 项不导出。
120
+ - `cut_detection` 让读者知道这些切点数怎么来的:规则参数与 Agent 增删的切点数。
121
+ - 导出时丢弃 `source_facts`、`labels`、`evidence`、`entities`、`from`、文件身份和任何路径。
122
+ - 保留键 `written_by`、`template` 给以后的资源库绑定副本使用,本技能不写。
123
+
124
+ ## 好方法与坏方法
125
+
126
+ 方法写"在什么条件下、怎么做、为什么",数字放进 `targets`。
127
+
128
+ 好:
129
+
130
+ 1. "原声段保留原片自身的剪辑节奏,旁白段换成较长的镜头承载解说" + `applies_when: "对白冲突强、需旁白补前情的剧集解说"`。
131
+ 规则方向必须与 check 打印的派生值一致(这里是 `derived.by_owner.*.cuts_per_min`);R1–R8 不检查语义是否自相矛盾。
132
+ 2. "开场先用一句旁白交代身份与处境,第一次原声冲突尽早接管" + `targets.first_original.from: "derived.first_original_at"`。
133
+ 3. "声音归属的切换尽量落在画面硬切上,让观众用画面感知换手" + `evidence: ["measure:derived.switch_on_cut_share"]`。
134
+
135
+ 坏:
136
+
137
+ 1. "范闲进殿后旁白交代他的身世"——原片人名与事件,是事实不是方法(R6)。
138
+ 2. "在 1:23 切到原声,旁白语速保持每秒 4 字"——绝对时间码(R6)与手写数字(应写 target)。
139
+ 3. "照着这部片的结构做"——没有条件、不可迁移,也无法被后续制作采纳或拒绝。
@@ -0,0 +1,85 @@
1
+ """Self-contained utilities for the video-reference skill (no cross-skill imports)."""
2
+ import json
3
+ import os
4
+ import subprocess
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+
9
+ def log(msg):
10
+ print(f"[video-reference] {msg}", flush=True)
11
+
12
+
13
+ def run_cmd(cmd, **kwargs):
14
+ """Run a command list and return the CompletedProcess (stdout/stderr captured)."""
15
+ return subprocess.run(
16
+ [str(part) for part in cmd], capture_output=True, text=True,
17
+ encoding="utf-8", errors="replace", **kwargs,
18
+ )
19
+
20
+
21
+ def file_identity(path):
22
+ """{size, mtime_ns} — the cache identity of an input file (no content read)."""
23
+ st = os.stat(os.fspath(path))
24
+ return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
25
+
26
+
27
+ def _fraction(text):
28
+ try:
29
+ num, _, den = str(text).partition("/")
30
+ value = float(num) / float(den or 1)
31
+ except (ValueError, ZeroDivisionError):
32
+ return None
33
+ return round(value, 3) if value > 0 else None
34
+
35
+
36
+ def ffprobe(video_path):
37
+ """Duration, canvas, fps and soft-subtitle stream count of a media file."""
38
+ result = run_cmd([
39
+ "ffprobe", "-v", "error", "-show_entries",
40
+ "format=duration:stream=codec_type,width,height,avg_frame_rate,r_frame_rate",
41
+ "-of", "json", video_path,
42
+ ])
43
+ if result.returncode != 0:
44
+ raise RuntimeError(f"ffprobe 失败: {result.stderr.strip()[-400:]}")
45
+ payload = json.loads(result.stdout or "{}")
46
+ streams = payload.get("streams") or []
47
+ video = next((s for s in streams if s.get("codec_type") == "video"), None)
48
+ if video is None:
49
+ raise RuntimeError(f"没有视频流: {video_path}")
50
+ duration = float((payload.get("format") or {}).get("duration") or 0.0)
51
+ if duration <= 0:
52
+ raise RuntimeError(f"无法读取时长: {video_path}")
53
+ return {
54
+ "duration_s": round(duration, 3),
55
+ "canvas": {"width": int(video.get("width") or 0), "height": int(video.get("height") or 0)},
56
+ "fps": _fraction(video.get("avg_frame_rate")) or _fraction(video.get("r_frame_rate")),
57
+ "subtitle_streams": sum(1 for s in streams if s.get("codec_type") == "subtitle"),
58
+ "audio_streams": sum(1 for s in streams if s.get("codec_type") == "audio"),
59
+ }
60
+
61
+
62
+ def read_json(path, default=None):
63
+ """Parsed JSON at `path`, or `default` when the file does not exist."""
64
+ path = Path(path)
65
+ if not path.is_file():
66
+ return default
67
+ return json.loads(path.read_text(encoding="utf-8"))
68
+
69
+
70
+ def write_json(path, payload):
71
+ """Atomically write `payload` as UTF-8 JSON (temp file + os.replace)."""
72
+ path = Path(path)
73
+ path.parent.mkdir(parents=True, exist_ok=True)
74
+ fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
75
+ try:
76
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
77
+ json.dump(payload, handle, ensure_ascii=False, indent=2)
78
+ handle.write("\n")
79
+ os.replace(temporary, path)
80
+ finally:
81
+ try:
82
+ os.unlink(temporary)
83
+ except FileNotFoundError:
84
+ pass
85
+ return path
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ """On-demand production reference from a finished video: measure → (agent looks + labels) → check → export.
3
+
4
+ python3 scripts/reference.py measure <video> --work-dir U [--hard-score 10] [--soft-score 4] [--no-scale]
5
+ python3 scripts/reference.py frames <video> --work-dir U (--review | --longest N | --span A,B [--step S])
6
+ python3 scripts/reference.py check --work-dir U [--json]
7
+ python3 scripts/reference.py export --work-dir U --out <dir>/production_reference.json
8
+
9
+ Exit codes: 0 ok, 1 errors found (nothing exported), 2 bad input or ffmpeg failure.
10
+ """
11
+ import argparse
12
+ import json
13
+ import sys
14
+
15
+ from lib import read_json
16
+ from reference_check import run_check, run_export
17
+ from reference_frames import frames, legend
18
+ from reference_measure import DEFAULT_HARD_SCORE, DEFAULT_SOFT_SCORE, MEASUREMENTS_FILE, measure
19
+
20
+ __all__ = ["main"]
21
+
22
+
23
+ def _print_report(report, as_json):
24
+ if as_json:
25
+ print(json.dumps(report, ensure_ascii=False, indent=2))
26
+ return
27
+ if report.get("derived") is not None:
28
+ print("== derived(只在内存里;target 用 derived.* 路径引用)==")
29
+ print(json.dumps(report["derived"], ensure_ascii=False, indent=2))
30
+ for title, key, tag in (("errors", "errors", "ERROR"), ("warnings", "warnings", "WARN")):
31
+ print(f"== {title} ({len(report[key])}) ==")
32
+ for line in report[key]:
33
+ print(f"{tag} {line}")
34
+ if report.get("written"):
35
+ print(f"已导出 {report['written']}")
36
+ print("FAIL" if report["errors"] else "OK")
37
+
38
+
39
+ def main(argv=None):
40
+ parser = argparse.ArgumentParser(description="把成片拆成可复用的制作参考(按需,不在生产路径上)")
41
+ sub = parser.add_subparsers(dest="command", required=True)
42
+ p_measure = sub.add_parser("measure", help="一次 ffmpeg:镜头切点与响度,按文件身份缓存")
43
+ p_measure.add_argument("video")
44
+ p_measure.add_argument("--work-dir", required=True)
45
+ p_measure.add_argument("--hard-score", type=float, default=DEFAULT_HARD_SCORE,
46
+ help="scdet 分数达到它就算切点")
47
+ p_measure.add_argument("--soft-score", type=float, default=DEFAULT_SOFT_SCORE,
48
+ help="达到它且是 ±0.3s 内的孤立峰才算切点;被压下的进入待复核窗口")
49
+ p_measure.add_argument("--no-scale", action="store_true", help="scdet 前不缩放到 320 宽")
50
+ p_frames = sub.add_parser("frames", help="拼帧图看画面:待复核窗口、最长镜头或任意时间段")
51
+ p_frames.add_argument("video")
52
+ p_frames.add_argument("--work-dir", required=True)
53
+ mode = p_frames.add_mutually_exclusive_group(required=True)
54
+ mode.add_argument("--review", action="store_true", help="逐帧看 measure 压下的疑似切点")
55
+ mode.add_argument("--longest", type=int, metavar="N", help="看最长的 N 个镜头里有没有漏掉的切点")
56
+ mode.add_argument("--span", metavar="A,B", help="A 到 B 秒的接触表(没有理解产物时用它看片)")
57
+ p_frames.add_argument("--step", type=float, default=2.0, help="--span 的取帧间隔秒数")
58
+ p_check = sub.add_parser("check", help="R1–R7 校验 reference_breakdown.json 并打印派生值")
59
+ p_check.add_argument("--work-dir", required=True)
60
+ p_check.add_argument("--json", action="store_true")
61
+ p_export = sub.add_parser("export", help="零 error 时写 production_reference.json(R8 复扫)")
62
+ p_export.add_argument("--work-dir", required=True)
63
+ p_export.add_argument("--out", required=True)
64
+ p_export.add_argument("--json", action="store_true")
65
+ args = parser.parse_args(argv)
66
+
67
+ try:
68
+ if args.command == "measure":
69
+ payload = measure(args.video, args.work_dir, hard=args.hard_score, soft=args.soft_score,
70
+ scaled=not args.no_scale)
71
+ shots, loudness = payload["shots"], payload.get("loudness") or {}
72
+ print(f"镜头 {shots['count']} 个,中位 {shots['median_s']}s,{shots['cuts_per_min']} 切/分钟;"
73
+ f"整体响度 {loudness.get('integrated_lufs')} LUFS;待复核窗口 {len(shots['review_windows'])} 个")
74
+ return 0
75
+ if args.command == "frames":
76
+ span = None
77
+ if args.span:
78
+ start, _, end = args.span.partition(",")
79
+ span = (float(start), float(end))
80
+ if args.longest is not None and args.longest < 1:
81
+ raise ValueError("--longest 至少为 1")
82
+ mode = "review" if args.review else "span" if span else "longest"
83
+ pages = frames(args.video, args.work_dir, read_json(f"{args.work_dir}/{MEASUREMENTS_FILE}"),
84
+ mode=mode, longest=args.longest, span=span, step=args.step)
85
+ print(legend(pages) if pages else "没有要看的帧(没有待复核窗口)")
86
+ return 0
87
+ report = run_check(args.work_dir) if args.command == "check" else run_export(args.work_dir, args.out)
88
+ except (OSError, RuntimeError, ValueError) as exc:
89
+ print(f"[video-reference] {exc}", file=sys.stderr)
90
+ return 2
91
+ _print_report(report, args.json)
92
+ return 1 if report["errors"] else 0
93
+
94
+
95
+ if __name__ == "__main__":
96
+ raise SystemExit(main())