@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
@@ -6,12 +6,16 @@ from pathlib import Path
6
6
 
7
7
  from asr_timing_evidence import EVIDENCE_FILENAME, validate_asr_timing_evidence
8
8
  from extract import FRAME_TIME_CONVENTION_VERSION
9
- from lib import CONFIG, log, file_identity, load_prompt
9
+ from lib import (
10
+ CONFIG,
11
+ file_identity,
12
+ is_usable_overview_chunk,
13
+ log,
14
+ offline_ignored_settings,
15
+ )
10
16
 
11
17
 
12
- from vlm import (
13
- _is_mimo_chunk_usable,
14
- )
18
+ from vlm import vlm_prompt_payload
15
19
 
16
20
 
17
21
  def _fresh(out, *inputs):
@@ -45,9 +49,19 @@ def _artifact_identity(path):
45
49
  return file_identity(path) if path.exists() else None
46
50
 
47
51
 
48
- def _stage_cache_valid(artifact_path, expected_meta):
52
+ def _without_settings(meta, keys):
53
+ settings = meta.get("settings")
54
+ if not keys or not isinstance(settings, dict):
55
+ return meta
56
+ return {**meta, "settings": {k: v for k, v in settings.items() if k not in keys}}
57
+
58
+
59
+ def _stage_cache_valid(artifact_path, expected_meta, *, ignore_settings=()):
49
60
  """A stage output is reusable when it exists, is non-empty, its sidecar equals the
50
- expected inputs/settings dict, and the output itself is the one the sidecar recorded."""
61
+ expected inputs/settings dict, and the output itself is the one the sidecar recorded.
62
+
63
+ `ignore_settings` names `settings` keys left out of the comparison on both sides (an
64
+ endpoint URL that only reflects which credential is set, or a retired legacy key)."""
51
65
  artifact_path = Path(artifact_path)
52
66
  if not artifact_path.exists() or artifact_path.stat().st_size == 0:
53
67
  return False
@@ -62,7 +76,9 @@ def _stage_cache_valid(artifact_path, expected_meta):
62
76
  return False
63
77
  expected = dict(expected_meta)
64
78
  expected["artifact"] = meta["artifact"]
65
- return meta == expected
79
+ return _without_settings(meta, ignore_settings) == _without_settings(
80
+ expected, ignore_settings
81
+ )
66
82
 
67
83
 
68
84
  def _write_stage_meta(artifact_path, meta):
@@ -129,7 +145,7 @@ def _merge_overview_into_scenes(scenes, overview_path):
129
145
  by_scene = {}
130
146
  for chunk in overview["chunks"]:
131
147
  content = chunk["content"].strip()
132
- if _is_mimo_chunk_usable(content):
148
+ if is_usable_overview_chunk(content):
133
149
  by_scene.setdefault(chunk["scene_id"], []).append(content)
134
150
  if not by_scene:
135
151
  return scenes
@@ -206,7 +222,6 @@ def _asr_cache_payload(video_path, *, skip_asr=False):
206
222
  "inputs": {"video": _video_input(video_path)},
207
223
  "settings": {
208
224
  "skip_asr": bool(skip_asr),
209
- "mimo_asr_api_key_present": bool(CONFIG.get("mimo_asr_api_key")),
210
225
  "mimo_asr_api_url": CONFIG.get("mimo_asr_api_url"),
211
226
  "mimo_asr_model": CONFIG.get("mimo_asr_model"),
212
227
  "mimo_asr_language": CONFIG.get("mimo_asr_language"),
@@ -216,21 +231,29 @@ def _asr_cache_payload(video_path, *, skip_asr=False):
216
231
  }
217
232
 
218
233
 
234
+ # Sidecars written before 0.6.1 recorded whether an ASR key was set. Key presence is not an
235
+ # output setting: a missing key only meant the run wrote an UNAVAILABLE_NO_KEY placeholder,
236
+ # which the evidence status below already refuses to reuse.
237
+ _LEGACY_ASR_SETTINGS = ("mimo_asr_api_key_present",)
238
+
239
+
219
240
  def _asr_cache_state(artifact_path, expected_meta, video_path):
220
241
  """Classify an ASR cache without upgrading stale evidence into new authority."""
221
242
  artifact_path = Path(artifact_path)
222
- if not _stage_cache_valid(artifact_path, expected_meta):
243
+ ignored = _LEGACY_ASR_SETTINGS + offline_ignored_settings(
244
+ CONFIG.get("mimo_asr_api_key"), "mimo_asr_api_url"
245
+ )
246
+ if not _stage_cache_valid(artifact_path, expected_meta, ignore_settings=ignored):
223
247
  return "MISS"
224
248
  evidence_path = artifact_path.parent / EVIDENCE_FILENAME
225
- if not evidence_path.exists():
226
- return "LEGACY_UNVERIFIED"
227
249
  if validate_asr_timing_evidence(evidence_path, video_path, artifact_path):
228
250
  evidence = _load_json(evidence_path)
229
- if evidence.get("status") == "LEGACY_UNVERIFIED":
230
- return "LEGACY_UNVERIFIED"
231
251
  # An all-empty transcription is an unexplained outcome, not proven silence; treating it
232
- # as fresh would make one bad run a permanent cache hit.
233
- if evidence.get("status") in {"UNAVAILABLE_NO_DURATION", "EMPTY_UNKNOWN"}:
252
+ # as fresh would make one bad run a permanent cache hit. A key-less placeholder is
253
+ # retried too, so setting the key later transcribes for real.
254
+ if evidence.get("status") in {
255
+ "UNAVAILABLE_NO_DURATION", "EMPTY_UNKNOWN", "UNAVAILABLE_NO_KEY"
256
+ }:
234
257
  return "MISS"
235
258
  return "FRESH"
236
259
  return "MISS"
@@ -263,21 +286,6 @@ def _silence_cache_payload(video_path, asr_json):
263
286
  }
264
287
 
265
288
 
266
- def _vlm_prompt_payload():
267
- """The exact prompt/context text the VLM stage sends; compared by equality."""
268
- prompt = load_prompt("VLM_DEPTH_PROMPT")
269
- if not prompt:
270
- prompt = (
271
- "仔细观察这些视频帧。分两部分输出:\n"
272
- "【描述】不超过80字,描述画面中正在发生什么。\n"
273
- "【深层分析】不超过120字,分析角色情绪、关系动态、潜台词。"
274
- )
275
- context = CONFIG.get("context_info", "")
276
- if context:
277
- prompt = f"已知信息:{context}\n\n{prompt}"
278
- return {"prompt_text": prompt, "context_info": context}
279
-
280
-
281
289
  def _vlm_cache_payload(video_path, work_dir, scenes_json, frames):
282
290
  return {
283
291
  "schema_version": 1,
@@ -290,7 +298,7 @@ def _vlm_cache_payload(video_path, work_dir, scenes_json, frames):
290
298
  ),
291
299
  },
292
300
  "frames": _frame_cache_payload(video_path, CONFIG.get("fps"), frames),
293
- "prompt": _vlm_prompt_payload(),
301
+ "prompt": vlm_prompt_payload(),
294
302
  "settings": {
295
303
  "fps": CONFIG.get("fps"),
296
304
  "vlm_model": CONFIG.get("vlm_model"),
@@ -7,14 +7,14 @@ import json
7
7
 
8
8
  from pathlib import Path
9
9
 
10
- from lib import CONFIG, log, get_video_duration, api_call
10
+ from lib import CONFIG, log, get_video_duration, api_call, offline_ignored_settings
11
11
 
12
12
  from extract import extract_frames
13
13
 
14
- from detect import detect_scenes, detect_silence_periods, detect_speech_boundary_anchors
14
+ from detect import detect_scenes, detect_silence_periods, ensure_speech_boundary_anchors
15
15
 
16
16
  from asr import transcribe_audio
17
- from asr_timing_evidence import write_asr_timing_evidence, asr_evidence_summary_for_brief
17
+ from asr_timing_evidence import write_asr_timing_evidence
18
18
 
19
19
  from vlm import (
20
20
  analyze_scenes,
@@ -22,10 +22,12 @@ from vlm import (
22
22
  mimo_video_overview_cache_fresh,
23
23
  )
24
24
 
25
- from brief import build_agent_brief, assess_understanding_substrate
26
-
27
-
28
- from understanding_brief import _research_context, _write_brief_from_existing_artifacts
25
+ from understanding_brief import (
26
+ _finish_brief,
27
+ _research_context,
28
+ _write_brief_from_existing_artifacts,
29
+ )
30
+ from understanding_storyboard import _write_edited_storyboard_only
29
31
  from understanding_cache import (
30
32
  _asr_cache_payload,
31
33
  _asr_cache_state,
@@ -43,11 +45,28 @@ from understanding_cache import (
43
45
  _write_mimo_overview_status,
44
46
  _write_stage_meta,
45
47
  )
46
- from understanding_storyboard import (
47
- _generate_edited_storyboard,
48
- _generate_source_storyboard,
49
- _prepend_storyboard_brief_header,
50
- )
48
+
49
+
50
+ def _refuse_keyless_overwrite(asr_json):
51
+ """Stop before a key-less ASR miss replaces a real transcript with the [] placeholder.
52
+
53
+ The cache can miss for reasons that say nothing about the transcript, e.g. a work_dir
54
+ copied without keeping mtimes changes the recorded artifact identity."""
55
+ if CONFIG["mimo_asr_api_key"] or not asr_json.exists():
56
+ return
57
+ try:
58
+ existing = json.loads(asr_json.read_text(encoding="utf-8"))
59
+ except (OSError, ValueError):
60
+ return
61
+ if isinstance(existing, list) and existing:
62
+ raise SystemExit(
63
+ "ASR 缓存与当前视频、设置或文件时间不匹配(常见原因:复制 work_dir 或视频时没保留"
64
+ "修改时间),且未设置 MIMO_ASR_API_KEY / MIMO_API_KEY,无法重新转写;"
65
+ f"已保留现有 asr_result.json({len(existing)} 段),未覆盖。"
66
+ "请用保留时间的方式重新复制(cp -p / cp -Rp / rsync -t),"
67
+ "或设置 MIMO_ASR_API_KEY 或 MIMO_API_KEY 后重跑(会重新转写)。"
68
+ "不要用 --skip-asr 绕过:它会把现有转写替换成 []。"
69
+ )
51
70
 
52
71
 
53
72
  def main():
@@ -82,6 +101,14 @@ def main():
82
101
  action="store_true",
83
102
  help="rebuild agent_narration_brief.md from existing artifacts only; no extraction/API",
84
103
  )
104
+ ap.add_argument(
105
+ "--edited-storyboard-only",
106
+ action="store_true",
107
+ help=(
108
+ "write storyboard/edited_storyboard.* from clip_plan_validated.json (single- or "
109
+ "multi-source) and add its pointer to the existing brief; no extraction/API"
110
+ ),
111
+ )
85
112
  ap.add_argument(
86
113
  "--consolidate",
87
114
  action=argparse.BooleanOptionalAction,
@@ -94,6 +121,8 @@ def main():
94
121
  help="also clean the ASR transcript (Pass A)",
95
122
  )
96
123
  args = ap.parse_args()
124
+ if args.brief_only and args.edited_storyboard_only:
125
+ ap.error("--brief-only and --edited-storyboard-only are mutually exclusive")
97
126
 
98
127
  video = args.video
99
128
  work_dir = Path(args.work_dir)
@@ -125,6 +154,9 @@ def main():
125
154
  if args.brief_only:
126
155
  _write_brief_from_existing_artifacts(video, work_dir, args, video_duration)
127
156
  return
157
+ if args.edited_storyboard_only:
158
+ _write_edited_storyboard_only(video, work_dir)
159
+ return
128
160
 
129
161
  scenes_json = work_dir / "scenes.json"
130
162
  asr_json = work_dir / "asr_result.json"
@@ -149,6 +181,17 @@ def main():
149
181
  scenes = detect_scenes(video, work_dir, CONFIG["scene_threshold"])
150
182
  _write_stage_meta(scenes_json, scenes_meta)
151
183
 
184
+ # The VLM cache does not depend on ASR, so an offline run that would stop at Step 4 stops
185
+ # here instead, before Step 3 can rewrite the transcript or the silence windows.
186
+ vlm_meta = _vlm_cache_payload(video, work_dir, scenes_json, frames)
187
+ vlm_offline = offline_ignored_settings(CONFIG["api_key"], "api_url")
188
+ vlm_cached = not args.force and _stage_cache_valid(
189
+ vlm_json, vlm_meta, ignore_settings=vlm_offline
190
+ )
191
+ if not vlm_cached and not CONFIG["api_key"]:
192
+ key_name = CONFIG["api_env_var"]
193
+ raise SystemExit(f"请设置 {key_name} 环境变量(VLM 画面分析需要)")
194
+
152
195
  # Step 3: ASR
153
196
  asr_meta = _asr_cache_payload(video, skip_asr=args.skip_asr)
154
197
  cache_state = None
@@ -164,22 +207,11 @@ def main():
164
207
  work_dir, video, "EXPLICITLY_SKIPPED", final_segments=asr_result
165
208
  )
166
209
  log("跳过 ASR(--skip-asr)")
167
- elif cache_state in {
168
- "FRESH",
169
- "LEGACY_UNVERIFIED",
170
- }:
210
+ elif cache_state == "FRESH":
171
211
  asr_result = _load_json(asr_json)
172
- if cache_state == "LEGACY_UNVERIFIED":
173
- write_asr_timing_evidence(
174
- work_dir,
175
- video,
176
- "LEGACY_UNVERIFIED",
177
- final_segments=asr_result,
178
- )
179
- log(f"复用旧 ASR({len(asr_result)} 段;时间/声学证据未经验证)")
180
- else:
181
- log(f"跳过 ASR(证据匹配,已存在 {len(asr_result)} 段)")
212
+ log(f"跳过 ASR(证据匹配,已存在 {len(asr_result)} 段)")
182
213
  else:
214
+ _refuse_keyless_overwrite(asr_json)
183
215
  try:
184
216
  asr_result = transcribe_audio(video, work_dir)
185
217
  except Exception as e:
@@ -195,21 +227,16 @@ def main():
195
227
  if not args.force and _stage_cache_valid(silence_json, silence_meta):
196
228
  silence_periods = _load_json(silence_json)
197
229
  log(f"跳过静音检测(已存在 {len(silence_periods)} 个窗口)")
198
- if not (work_dir / "speech_boundary_anchors.json").exists():
199
- detect_speech_boundary_anchors(work_dir, asr_result)
230
+ ensure_speech_boundary_anchors(work_dir, asr_result)
200
231
  else:
201
232
  silence_periods = detect_silence_periods(video, work_dir, asr_result)
202
233
  _write_stage_meta(silence_json, silence_meta)
203
234
 
204
235
  # Step 4: VLM analysis (the only stage that requires the chat API key)
205
- vlm_meta = _vlm_cache_payload(video, work_dir, scenes_json, frames)
206
- if not args.force and _stage_cache_valid(vlm_json, vlm_meta):
236
+ if vlm_cached:
207
237
  vlm_analysis = _load_json(vlm_json)
208
238
  log(f"跳过 VLM 分析(已存在 {len(vlm_analysis)} 个场景)")
209
239
  else:
210
- if not CONFIG["api_key"]:
211
- key_name = CONFIG["api_env_var"]
212
- raise SystemExit(f"请设置 {key_name} 环境变量(VLM 画面分析需要)")
213
240
  log("VLM API 连通性预检...")
214
241
  api_call(
215
242
  {
@@ -224,7 +251,14 @@ def main():
224
251
  # Step 4.1: optional MiMo scene-chunk video understanding
225
252
  overview_path = work_dir / "mimo_video_overview.json"
226
253
  if CONFIG["mimo_video_overview"]:
227
- if not CONFIG["mimo_video_api_key"]:
254
+ if not CONFIG["mimo_video_api_key"] and mimo_video_overview_cache_fresh(
255
+ overview_path, video, scenes
256
+ ):
257
+ log("未设置 MIMO_API_KEY,复用已缓存的 MiMo 分片视频概览")
258
+ _write_mimo_overview_status(
259
+ work_dir, "cached", "未设置 MIMO_API_KEY,复用缓存", overview_path.name
260
+ )
261
+ elif not CONFIG["mimo_video_api_key"]:
228
262
  log("跳过 MiMo 分片视频概览:未设置 MIMO_API_KEY")
229
263
  overview_path.unlink(missing_ok=True)
230
264
  _write_mimo_overview_status(
@@ -265,123 +299,62 @@ def main():
265
299
  vlm_analysis = _merge_overview_into_scenes(vlm_analysis, overview_path)
266
300
 
267
301
  # optional consolidation (整理): build the understanding index before the brief folds it in
268
- if args.consolidate or args.consolidate_asr:
302
+ enabled = bool(args.consolidate or args.consolidate_asr)
303
+ status, message, artifacts = "disabled", "consolidation 未启用", []
304
+ if enabled:
269
305
  from consolidate import consolidate
270
306
 
307
+ failure = None
308
+ result = {}
271
309
  try:
272
- consolidate(
310
+ result = consolidate(
273
311
  work_dir, do_asr=args.consolidate_asr, do_index=args.consolidate
274
312
  )
275
313
  except Exception as e:
276
314
  log(f"consolidate 跳过(忽略): {e}")
277
- _write_consolidation_status(
278
- work_dir,
279
- "failed",
280
- e,
281
- _present_consolidation_artifacts(work_dir),
282
- do_asr=args.consolidate_asr,
283
- do_index=args.consolidate,
284
- )
315
+ failure = e
316
+ artifacts = _present_consolidation_artifacts(work_dir)
317
+ no_key = result.get("skipped_no_key") or []
318
+ if failure is not None:
319
+ status, message = "failed", failure
285
320
  else:
286
321
  expected = []
287
322
  skipped = []
288
- if args.consolidate:
323
+ if args.consolidate and "index" not in no_key:
289
324
  if vlm_analysis:
290
325
  expected.append("understanding_index.json")
291
326
  else:
292
327
  skipped.append("无 vlm_analysis,跳过 index")
293
- if args.consolidate_asr:
328
+ if args.consolidate_asr and "asr" not in no_key:
294
329
  if asr_result:
295
330
  expected.append("asr_clean.json")
296
331
  else:
297
332
  skipped.append("无 ASR 文本,跳过 ASR 清洗")
298
- artifacts = _present_consolidation_artifacts(work_dir)
299
333
  missing = [name for name in expected if name not in artifacts]
300
334
  if missing:
301
- _write_consolidation_status(
302
- work_dir,
303
- "failed",
304
- f"未产出预期 artifact: {', '.join(missing)}",
305
- artifacts,
306
- do_asr=args.consolidate_asr,
307
- do_index=args.consolidate,
335
+ status, message = "failed", f"未产出预期 artifact: {', '.join(missing)}"
336
+ elif no_key:
337
+ status, message = (
338
+ "skipped_no_key",
339
+ f"未设置 {CONFIG['api_env_var']},consolidation({', '.join(no_key)})未发送请求",
308
340
  )
309
341
  elif expected:
310
- _write_consolidation_status(
311
- work_dir,
312
- "ok",
313
- "consolidation 完成",
314
- artifacts,
315
- do_asr=args.consolidate_asr,
316
- do_index=args.consolidate,
317
- )
342
+ status, message = "ok", "consolidation 完成"
318
343
  else:
319
- _write_consolidation_status(
320
- work_dir,
321
- "skipped",
322
- ";".join(skipped) or "无可整理输入",
323
- artifacts,
324
- do_asr=args.consolidate_asr,
325
- do_index=args.consolidate,
326
- )
327
- else:
328
- _write_consolidation_status(
329
- work_dir,
330
- "disabled",
331
- "consolidation 未启用",
332
- [],
333
- enabled=False,
334
- do_asr=False,
335
- do_index=False,
336
- )
337
-
338
- # Storyboard contact sheets (advisory, never blocking). Source uses scene anchors over the
339
- # source timeline; edited is gated on clip_plan_validated.json file-presence (NOT edit_mode —
340
- # recap.py forwards --edit-mode cut in BOTH passes, so the validated plan is the only reliable
341
- # pass2 signal). Both cache via _write_stage_meta/_stage_cache_valid with fps + frame-set in the key.
342
- source_storyboard = _generate_source_storyboard(
343
- work_dir, Path(video), scenes, scenes_json, force=args.force
344
- )
345
- edited_storyboard = _generate_edited_storyboard(work_dir, video, force=args.force)
346
- cut_mode = (work_dir / "clip_plan_validated.json").exists()
347
-
348
- # understanding substrate warning + writing brief
349
- substrate = assess_understanding_substrate(vlm_analysis, asr_result)
350
- if substrate["level"] != "rich":
351
- banner = "理解素材为空" if substrate["level"] == "empty" else "理解素材偏薄"
352
- log(
353
- f"⚠️ {banner}:ASR {substrate['asr_chars']} 字 | 场景 {substrate['scene_count']} | "
354
- f"带 frame_facts 的场景 {substrate['scenes_with_frame_facts']} | 平均画面描述 {substrate['avg_description_len']} 字"
355
- )
356
- brief_path = build_agent_brief(
357
- vlm_analysis,
358
- asr_result,
359
- silence_periods,
360
- video_duration,
344
+ status, message = "skipped", ";".join(skipped) or "无可整理输入"
345
+ _write_consolidation_status(
361
346
  work_dir,
362
- args.style,
363
- mimo_overview_enabled=CONFIG["mimo_video_overview"],
364
- mimo_overview_video_path=video,
365
- asr_evidence=asr_evidence_summary_for_brief(work_dir, video),
366
- )
367
- # C1: post-process the RETURNED brief FILE (not brief.py) so the brief⇄narration twin stays
368
- # byte-identical. Prepends a storyboard header pointing the agent at the sheet(s).
369
- _prepend_storyboard_brief_header(
370
- brief_path, source_storyboard, edited_storyboard, cut_mode=cut_mode
347
+ status,
348
+ message,
349
+ artifacts,
350
+ enabled=enabled,
351
+ do_asr=args.consolidate_asr,
352
+ do_index=args.consolidate,
371
353
  )
372
354
 
373
- log("=" * 50)
374
- log(f"理解完成。写作 brief: {brief_path}")
375
- print(
376
- json.dumps(
377
- {
378
- "status": "analyzed",
379
- "work_dir": str(work_dir),
380
- "brief": str(brief_path),
381
- "substrate": substrate["level"],
382
- "scenes": len(scenes),
383
- "asr_segments": len(asr_result),
384
- },
385
- ensure_ascii=False,
386
- )
355
+ _finish_brief(
356
+ video, work_dir, args, video_duration,
357
+ storyboard_scenes=scenes, brief_scenes=vlm_analysis,
358
+ asr_result=asr_result, silence_periods=silence_periods,
359
+ status="analyzed", done_label="理解完成", force=args.force,
387
360
  )