@oh-story/dsh 0.1.12 → 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
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  import math
5
+ import os
5
6
 
6
7
 
7
8
  from pathlib import Path
@@ -10,6 +11,7 @@ from lib import CONFIG, get_video_duration, log
10
11
  import shot_review
11
12
 
12
13
  from cut_contract import (
14
+ SOURCES_MANIFEST_SHAPE,
13
15
  _write_edited_source_meta,
14
16
  load_clip_plan,
15
17
  normalize_clip_plan,
@@ -17,25 +19,46 @@ from cut_contract import (
17
19
  parse_duration_seconds,
18
20
  should_reuse_edited_source,
19
21
  )
20
- from cut_render import (
21
- build_edited_source_video,
22
- update_delivery_qc,
23
- write_cut_delivery_qc,
24
- )
22
+ from cut_render import build_edited_source_video
23
+ from frame_grid import record_frame_grid, source_frame_grids
25
24
  from media_geometry import _has_audio_stream, _select_output_geometry
26
25
  from narrative_selection import check_required_evidence
27
- from narration_mapping import update_cut_qc
28
- from sentence_boundaries import (
29
- _combine_boundary_windows,
30
- _load_sentence_boundary_windows,
31
- _load_silence_for_source,
32
- _load_source_speech_spans,
33
- enforce_clip_sentence_boundaries,
34
- snap_clip_ends_to_lines,
35
- snap_clip_starts_to_lines,
36
- snap_clips_off_shot_changes,
37
- snap_multi_source_clips,
38
- )
26
+ from cut_qc import update_cut_qc
27
+ from sentence_boundaries import snap_multi_source_clips, snap_source_clips
28
+
29
+
30
+ def _write_validated_plan(path, plan, raw_plan_paths):
31
+ """Write clip_plan_validated.json unless the file already holds exactly this plan.
32
+
33
+ Downstream output-clock evidence binds to this file's {size, mtime_ns}, so a resumed
34
+ run that re-validates an unchanged plan must leave it alone. The file is still
35
+ rewritten when any raw plan is newer, so "validated older than raw = stale" holds."""
36
+ text = json.dumps(plan, ensure_ascii=False, indent=2)
37
+ if path.exists() and path.read_text(encoding="utf-8") == text:
38
+ written = path.stat().st_mtime_ns
39
+ if all(not raw.exists() or raw.stat().st_mtime_ns <= written for raw in raw_plan_paths):
40
+ return
41
+ path.write_text(text, encoding="utf-8")
42
+
43
+
44
+ def _required_ranges_by_source(raw_plan, source_paths):
45
+ """{plan source path: [(start, end)]} of declared required-evidence nodes.
46
+
47
+ Only a hint for frame snapping; check_required_evidence validates the contract itself.
48
+ """
49
+ nodes = raw_plan.get("required_evidence") if isinstance(raw_plan, dict) else None
50
+ nodes = nodes.get("nodes") if isinstance(nodes, dict) else None
51
+ by_realpath = {os.path.realpath(path): path for path in source_paths}
52
+ ranges = {}
53
+ for node in nodes if isinstance(nodes, list) else []:
54
+ try:
55
+ path = by_realpath.get(os.path.realpath(node["source"]))
56
+ start, end = float(node["start"]), float(node["end"])
57
+ except (KeyError, TypeError, ValueError):
58
+ continue
59
+ if path is not None:
60
+ ranges.setdefault(path, []).append((start, end))
61
+ return ranges
39
62
 
40
63
 
41
64
  def main():
@@ -58,19 +81,13 @@ def main():
58
81
  parser.add_argument(
59
82
  "--sources-manifest",
60
83
  default=None,
61
- help="multi-source manifest json mapping source_id values to source media",
84
+ help=f"multi-source manifest json: {SOURCES_MANIFEST_SHAPE}",
62
85
  )
63
86
  parser.add_argument(
64
87
  "--target-duration",
65
88
  default=None,
66
89
  help="target output duration, e.g. 10m / 600 / 00:10:00",
67
90
  )
68
- parser.add_argument(
69
- "--clip-padding",
70
- type=float,
71
- default=None,
72
- help="seconds to pad each clip on both ends (default: CLIP_PADDING env, else 0)",
73
- )
74
91
  parser.add_argument(
75
92
  "--allow-overlap",
76
93
  action="store_true",
@@ -79,8 +96,8 @@ def main():
79
96
  parser.add_argument(
80
97
  "--normalize-only",
81
98
  action="store_true",
82
- help="only normalize the clip plan -> clip_plan_validated.json (no render); "
83
- "lets validate lint the SAME padded/pruned plan the render uses",
99
+ help="normalize, snap and QC the clip plan, write clip_plan_validated.json, then exit "
100
+ "without rendering",
84
101
  )
85
102
  parser.add_argument(
86
103
  "--review-shots", action="store_true",
@@ -110,13 +127,6 @@ def main():
110
127
  ):
111
128
  parser.error("--shot-roi requires --review-shots, nonnegative X/Y and positive WIDTH/HEIGHT")
112
129
 
113
- # CLIP_PADDING is declared in every skill's CONFIG, but video-cut is the only place that
114
- # implements padding — and it used to read the CLI flag alone, so setting the env var did
115
- # nothing at all while `clip_padding_source: "env"` reported otherwise. CLI still wins.
116
- clip_padding = (
117
- args.clip_padding if args.clip_padding is not None else CONFIG["clip_padding"]
118
- )
119
-
120
130
  work_dir = Path(args.work_dir)
121
131
  work_dir.mkdir(parents=True, exist_ok=True)
122
132
  clip_plan_path = (
@@ -132,90 +142,68 @@ def main():
132
142
  if args.sources_manifest
133
143
  else None
134
144
  )
135
- if sources_manifest is not None:
136
- validated_plan = normalize_multi_source_clip_plan(
145
+ # Visual shot-change cleanup runs first; the sentence/quiet pass is the final authority,
146
+ # so a prettier edit point never moves a boundary back inside a spoken sentence.
147
+ snap_options = {
148
+ "line_max_extend": CONFIG["clip_snap_max_extend"],
149
+ "scene_margin": CONFIG["scene_cut_snap_margin"],
150
+ "scene_threshold": CONFIG["scene_cut_detect_threshold"],
151
+ "start_max_prepend": CONFIG["clip_start_snap_max_prepend"],
152
+ "start_max_trim": CONFIG["clip_start_snap_max_trim"],
153
+ "do_line_snap": CONFIG["snap_clip_line_end"],
154
+ "do_scene_snap": CONFIG["scene_cut_snap"],
155
+ }
156
+ if sources_manifest is None:
157
+ video_duration = get_video_duration(args.video)
158
+ validated_plan = normalize_clip_plan(
137
159
  raw_plan,
138
- sources_manifest,
160
+ video_duration,
139
161
  target_duration=target_seconds,
140
- clip_padding=clip_padding,
141
162
  allow_overlap=args.allow_overlap,
142
163
  )
143
- video_duration = None
144
164
  else:
145
- video_duration = get_video_duration(args.video)
146
- validated_plan = normalize_clip_plan(
165
+ validated_plan = normalize_multi_source_clip_plan(
147
166
  raw_plan,
148
- video_duration,
167
+ sources_manifest,
149
168
  target_duration=target_seconds,
150
- clip_padding=clip_padding,
151
169
  allow_overlap=args.allow_overlap,
152
170
  )
153
-
154
- # Keep boundaries off the original footage's hard cuts (avoids 闪烁 at the edit point).
155
- # This visual-only pass runs FIRST. The sentence/quiet pass below is the final authority:
156
- # a prettier edit point must never move the final boundary back inside a spoken sentence.
157
- if sources_manifest is None and CONFIG["scene_cut_snap"]:
158
- validated_plan = snap_clips_off_shot_changes(
159
- validated_plan,
160
- args.video,
161
- margin=CONFIG["scene_cut_snap_margin"],
162
- threshold=CONFIG["scene_cut_detect_threshold"],
171
+ # The canvas (and so the output frame rate) is chosen before snapping because the
172
+ # frame-grid pass snaps clip lengths to whole output frames; the same geometry is
173
+ # recorded in clip_plan_validated.json and used for the render.
174
+ # Single-source clips carry no source_path; the CLI video is the only input.
175
+ source_paths = list(
176
+ dict.fromkeys(
177
+ clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip
163
178
  )
164
-
179
+ ) or [str(args.video)]
180
+ _, _, _, geometry_qc = _select_output_geometry(source_paths, validated_plan["clips"])
181
+ frame_grids = source_frame_grids(geometry_qc)
182
+ for path, ranges in _required_ranges_by_source(raw_plan, source_paths).items():
183
+ frame_grids[path]["keep_ranges"] = ranges
165
184
  if sources_manifest is None:
166
- safe_boundaries = _combine_boundary_windows(
167
- _load_silence_for_source(work_dir, None),
168
- _load_sentence_boundary_windows(work_dir),
185
+ validated_plan = snap_source_clips(
186
+ validated_plan, args.video, video_duration, work_dir,
187
+ frame_grid=frame_grids[str(args.video)], **snap_options,
169
188
  )
170
- if CONFIG["snap_clip_line_end"]:
171
- validated_plan = snap_clip_starts_to_lines(
172
- validated_plan,
173
- safe_boundaries,
174
- video_duration,
175
- CONFIG["clip_start_snap_max_prepend"],
176
- max_trim=CONFIG["clip_start_snap_max_trim"],
177
- )
178
- validated_plan = snap_clip_ends_to_lines(
179
- validated_plan,
180
- safe_boundaries,
181
- video_duration,
182
- CONFIG["clip_snap_max_extend"],
183
- )
184
- validated_plan = enforce_clip_sentence_boundaries(
185
- validated_plan,
186
- safe_boundaries,
187
- _load_source_speech_spans(work_dir),
188
- video_duration,
189
- )
190
-
191
- # Multi-source: snap each clip against ITS OWN source's pauses/shot-changes (single-source
192
- # snaps above can't, since silence_periods.json and args.video are per-project, not per-source).
193
- if sources_manifest is not None:
189
+ else:
190
+ # Each clip snaps against ITS OWN source's pauses, shot changes and frame grid.
194
191
  validated_plan = snap_multi_source_clips(
195
- validated_plan,
196
- validated_plan["sources"],
197
- work_dir,
198
- line_max_extend=CONFIG["clip_snap_max_extend"],
199
- scene_margin=CONFIG["scene_cut_snap_margin"],
200
- scene_threshold=CONFIG["scene_cut_detect_threshold"],
201
- do_line_snap=CONFIG["snap_clip_line_end"],
202
- do_scene_snap=CONFIG["scene_cut_snap"],
203
- start_max_prepend=CONFIG["clip_start_snap_max_prepend"],
204
- start_max_trim=CONFIG["clip_start_snap_max_trim"],
192
+ validated_plan, validated_plan["sources"], work_dir,
193
+ frame_grids={
194
+ sid: frame_grids[source["source_path"]]
195
+ for sid, source in validated_plan["sources"].items()
196
+ if source["source_path"] in frame_grids
197
+ },
198
+ **snap_options,
205
199
  )
206
200
 
207
201
  validated_plan.setdefault("qc", {})["join_fade_ms"] = round(
208
202
  CONFIG["clip_join_audio_fade_ms"], 3
209
203
  )
210
- # Single-source clips carry no source_path; the CLI video is the only input.
211
- source_paths = list(
212
- dict.fromkeys(
213
- clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip
214
- )
215
- ) or [str(args.video)]
216
- _, _, _, geometry_qc = _select_output_geometry(source_paths, validated_plan["clips"])
217
204
  validated_plan["qc"]["output_geometry"] = geometry_qc
218
205
  validated_plan["qc"]["output_geometry_reason"] = geometry_qc["reason"]
206
+ record_frame_grid(validated_plan, geometry_qc)
219
207
  update_cut_qc(
220
208
  validated_plan,
221
209
  allow_duration_drift=bool(args.allow_duration_drift),
@@ -223,8 +211,6 @@ def main():
223
211
  )
224
212
  if isinstance(raw_plan, dict) and 'required_evidence' in raw_plan:
225
213
  # Re-evaluate the final snapped ranges even when the media cache can be reused.
226
- # A prior rendered receipt must not survive a failed revision preflight.
227
- (work_dir / 'cut_delivery_qc.json').unlink(missing_ok=True)
228
214
  contract = raw_plan['required_evidence']
229
215
  plan_sources = {str(Path(path).resolve()): path for path in source_paths}
230
216
  source_audio = {}
@@ -240,14 +226,16 @@ def main():
240
226
  validated_plan['qc']['required_evidence'] = {**report, 'contract': contract}
241
227
  if report['selection_status'] == 'BLOCK':
242
228
  validated_plan['qc'].setdefault('blocking', []).extend(report['findings'])
243
- update_delivery_qc(
244
- validated_plan,
245
- source_paths=source_paths,
246
- output_path=work_dir / "edited_source.mp4",
247
- )
248
- (work_dir / "clip_plan_validated.json").write_text(
249
- json.dumps(validated_plan, ensure_ascii=False, indent=2), encoding="utf-8"
229
+ plan_path = work_dir / "clip_plan_validated.json"
230
+ raw_plan_paths = {clip_plan_path, work_dir / "clip_plan.json"}
231
+ edited_source_path = work_dir / "edited_source.mp4"
232
+ reuse = (
233
+ not validated_plan["qc"].get("blocking")
234
+ and not args.normalize_only
235
+ and should_reuse_edited_source(edited_source_path, validated_plan, args.video)
250
236
  )
237
+ if not reuse:
238
+ _write_validated_plan(plan_path, validated_plan, raw_plan_paths)
251
239
  if validated_plan["qc"].get("blocking"):
252
240
  raise SystemExit(
253
241
  "clip_plan QC blocking: fix required source evidence, unsafe sentence boundaries or target-duration drift. "
@@ -255,9 +243,6 @@ def main():
255
243
  "sentence truncation is never allowed. See clip_plan_validated.json['qc']."
256
244
  )
257
245
  if args.normalize_only:
258
- # normalize-only produces planned delivery facts in clip_plan_validated.json, but no
259
- # rendered/reused media exists in this run, so remove any stale final delivery artifact.
260
- (work_dir / "cut_delivery_qc.json").unlink(missing_ok=True)
261
246
  print(
262
247
  json.dumps(
263
248
  {
@@ -270,27 +255,14 @@ def main():
270
255
  )
271
256
  return
272
257
 
273
- edited_source_path = work_dir / "edited_source.mp4"
274
- if should_reuse_edited_source(edited_source_path, validated_plan, args.video):
258
+ if reuse:
275
259
  log(f"复用剪辑源视频: {edited_source_path}")
276
- update_delivery_qc(
277
- validated_plan,
278
- source_paths=source_paths,
279
- output_path=edited_source_path,
280
- rendered=True,
281
- )
282
- write_cut_delivery_qc(work_dir, validated_plan)
283
260
  _write_edited_source_meta(edited_source_path, validated_plan, args.video)
284
- (work_dir / "clip_plan_validated.json").write_text(
285
- json.dumps(validated_plan, ensure_ascii=False, indent=2), encoding="utf-8"
286
- )
287
261
  else:
288
262
  build_edited_source_video(
289
263
  args.video, validated_plan, work_dir, edited_source_path
290
264
  )
291
- (work_dir / "clip_plan_validated.json").write_text(
292
- json.dumps(validated_plan, ensure_ascii=False, indent=2), encoding="utf-8"
293
- )
265
+ _write_validated_plan(plan_path, validated_plan, raw_plan_paths)
294
266
 
295
267
  if args.review_shots:
296
268
  review_options = {"plan_path": work_dir / "clip_plan_validated.json"}