@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
@@ -23,7 +23,7 @@ import stat
23
23
  import sys
24
24
  from pathlib import Path
25
25
 
26
- from materials import file_identity
26
+ from lib import file_identity
27
27
 
28
28
  LIBRARY_ENV = "VIDEO_RECAP_MATERIAL_LIBRARY_DIR"
29
29
  LIBRARY_SCHEMA = "video-recap.library.v1"
@@ -39,7 +39,14 @@ RESOURCE_EXTS = {
39
39
  "font": {".ttf", ".otf", ".ttc"},
40
40
  "image": {".png", ".jpg", ".jpeg", ".webp"},
41
41
  }
42
- TEMPLATE_KINDS = ("subtitle_style", "packaging")
42
+ TEMPLATE_KINDS = ("subtitle_style", "packaging", "production_reference")
43
+ # A production_reference template points at an export of the on-demand reference skill. This is
44
+ # a separate shape check (no import of that skill): methods only, never the source's facts.
45
+ REFERENCE_FILE = "production_reference.json"
46
+ REFERENCE_SCHEMA = "video-reference.production.v1"
47
+ REFERENCE_DIMENSIONS = ("narrative_structure", "pacing", "shots_editing", "narration_subtitles", "audio_visual")
48
+ # Same banned keys as the reference export's own re-scan: source facts plus the measurement pointers.
49
+ REFERENCE_FACT_KEYS = {"source_facts", "labels", "evidence", "entities", "statement", "from", "path"}
43
50
  LICENSE_STATUSES = ("unknown", "owned", "licensed", "restricted")
44
51
  CONSENT_STATUSES = ("unknown", "granted", "denied")
45
52
  TEMPLATE_STATUSES = ("draft", "adopted", "retired")
@@ -90,7 +97,7 @@ class Report:
90
97
  MAX_RECORD_BYTES = 2 * 1024 * 1024
91
98
 
92
99
 
93
- def _load(path: Path, schema: str, keys: set, report: Report):
100
+ def _load(path: Path, schema: str, keys: set | None, report: Report):
94
101
  try:
95
102
  st = os.stat(path)
96
103
  if not stat.S_ISREG(st.st_mode) or st.st_size > MAX_RECORD_BYTES:
@@ -106,7 +113,7 @@ def _load(path: Path, schema: str, keys: set, report: Report):
106
113
  if data.get("schema") != schema:
107
114
  report.error(path, "schema", f"schema 必须是 {schema}")
108
115
  return None
109
- unknown = sorted(set(data) - keys)
116
+ unknown = sorted(set(data) - keys) if keys is not None else []
110
117
  if unknown:
111
118
  report.error(path, "unknown_keys", f"未知字段: {', '.join(unknown)}")
112
119
  return data
@@ -217,6 +224,33 @@ def _param_number(params, key):
217
224
  return value if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0 else None
218
225
 
219
226
 
227
+ def _check_production_reference(path: Path, params: dict, report: Report):
228
+ """Issues are filed on template.json, so a binding refuses the template when its file is bad."""
229
+ if params != {"reference": {"path": REFERENCE_FILE}}:
230
+ report.error(path, "reference_params",
231
+ f'production_reference 的 params 必须恰好是 {{"reference": {{"path": "{REFERENCE_FILE}"}}}}')
232
+ return
233
+ target = resolve_inside(path.parent, path.parent, REFERENCE_FILE)
234
+ if target is None or not target.is_file():
235
+ report.error(path, "file_missing", f"版本目录内没有 {REFERENCE_FILE}")
236
+ return
237
+ loaded = Report(report.root)
238
+ data = _load(target, REFERENCE_SCHEMA, None, loaded)
239
+ for item in loaded.errors:
240
+ report.error(path, item["code"], f"{REFERENCE_FILE}: {item['message']}")
241
+ if data is None:
242
+ return
243
+ methods = data.get("methods")
244
+ if not (isinstance(methods, list) and methods and all(
245
+ isinstance(m, dict) and m.get("dimension") in REFERENCE_DIMENSIONS and _nonempty_str(m.get("rule"))
246
+ for m in methods
247
+ )):
248
+ report.error(path, "methods", f"{REFERENCE_FILE} 的 methods 需要非空,每条有 {REFERENCE_DIMENSIONS} 之一的 dimension 与非空 rule")
249
+ leaked = sorted({key for _, node in _param_values(data) for key in node if key in REFERENCE_FACT_KEYS})
250
+ if leaked:
251
+ report.error(path, "source_facts", f"{REFERENCE_FILE} 只放方法,不能带原片事实字段: {', '.join(leaked)}")
252
+
253
+
220
254
  def _check_template(path: Path, data: dict, report: Report) -> dict | None:
221
255
  vdir = path.parent
222
256
  tid, kind = vdir.parent.name, vdir.parent.parent.name
@@ -297,6 +331,8 @@ def _check_template(path: Path, data: dict, report: Report) -> dict | None:
297
331
  _check_rect(path, f"params.layers[{index}].rect", layer.get("rect"), canvas, report)
298
332
  if canvas and "safe_rect" in params:
299
333
  _check_rect(path, "params.safe_rect", params["safe_rect"], canvas, report)
334
+ if kind == "production_reference":
335
+ _check_production_reference(path, params, report)
300
336
  status = data.get("status")
301
337
  if status not in TEMPLATE_STATUSES:
302
338
  report.error(path, "status", f"status 必须是 {TEMPLATE_STATUSES} 之一")
@@ -16,14 +16,16 @@ written into scenes/ASR/VLM/summary JSON.
16
16
  from __future__ import annotations
17
17
 
18
18
  import json
19
- import os
20
19
  import re
21
20
  import tempfile
22
21
  from datetime import datetime, timezone
23
22
  from pathlib import Path
24
23
 
25
- from lib import load_json
24
+ from lib import load_json, material_id_for, read_json_object, source_id_for
26
25
 
26
+ # asr_timing_evidence.json stays out on purpose: it binds asr_result.json and audio.wav by
27
+ # {size, mtime_ns}, and a restore rewrites the former (redacted copy) and never copies the
28
+ # latter, so a restored sidecar could only ever read as MISSING_OR_STALE.
27
29
  ALLOWED_ARTIFACTS = {
28
30
  "scenes.json",
29
31
  "asr_result.json",
@@ -35,10 +37,9 @@ ALLOWED_ARTIFACTS = {
35
37
  "timeline_fusion.json",
36
38
  "understanding_index.json",
37
39
  "understanding_index.md",
40
+ "consolidation.status.json",
38
41
  "agent_narration_brief.md",
39
42
  "background_research.json",
40
- "reference_profile.json",
41
- "reference_match_report.json",
42
43
  "recap_run_manifest.json",
43
44
  }
44
45
  # Redaction targets credential VALUE shapes, not English/Chinese dictionary words — the
@@ -67,30 +68,6 @@ def utc_now_iso() -> str:
67
68
  return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
68
69
 
69
70
 
70
- def file_identity(path: str | Path) -> dict:
71
- """``{size, mtime_ns}`` of a file: the identity recap records and compares for a source
72
- video or adopted artifact. A file rewritten in place gets a new mtime_ns."""
73
- st = os.stat(os.fspath(path))
74
- return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
75
-
76
-
77
- def _slug(text: str, max_len: int = 48) -> str:
78
- raw = Path(text).stem.lower()
79
- raw = re.sub(r"[^a-z0-9\u4e00-\u9fff._-]+", "-", raw).strip("-._")
80
- return (raw or "material")[:max_len].strip("-._") or "material"
81
-
82
-
83
- def _id_stem(source_path: str | Path, max_len: int = 32) -> str:
84
- raw = re.sub(r"[^a-z0-9]+", "-", Path(source_path).stem.lower()).strip("-")
85
- return (raw or "source")[:max_len].strip("-") or "source"
86
-
87
-
88
- def source_id_for(source_path: str | Path) -> str:
89
- """``src_<stem>_<size>``: readable, stable across runs, and distinct for a different cut
90
- of the same title (the size changes)."""
91
- return f"src_{_id_stem(source_path)}_{os.stat(os.fspath(source_path)).st_size}"
92
-
93
-
94
71
  def assign_source_ids(sources: list[dict]) -> list[dict]:
95
72
  """Assign deterministic source_id values to manifest source records.
96
73
 
@@ -115,10 +92,6 @@ def assign_source_ids(sources: list[dict]) -> list[dict]:
115
92
  return assigned
116
93
 
117
94
 
118
- def material_id_for(source_path: str | Path, source_identity: dict) -> str:
119
- return f"{_slug(str(source_path))}-{source_identity['size']}"
120
-
121
-
122
95
  def material_dir(library_dir: str | Path, material_id: str) -> Path:
123
96
  return Path(library_dir) / "materials" / material_id
124
97
 
@@ -207,16 +180,8 @@ def write_material_md(path: Path, metadata: dict, summary: str, tags: list[str])
207
180
  path.write_text(text, encoding="utf-8")
208
181
 
209
182
 
210
- def _read_material_metadata(path: Path) -> dict | None:
211
- try:
212
- data = load_json(path)
213
- except (OSError, ValueError):
214
- return None
215
- return data if isinstance(data, dict) else None
216
-
217
-
218
183
  def _material_cache_entry(path: Path) -> dict | None:
219
- data = _read_material_metadata(path)
184
+ data = read_json_object(path)
220
185
  if not data or not all(data.get(key) for key in (
221
186
  "source_path", "updated_at", "material_id",
222
187
  )) or not isinstance(data.get("artifacts"), list):
@@ -261,7 +226,7 @@ def save_material(
261
226
  copied.append({"name": src.name, "path": f"artifacts/{src.name}", "bytes": dst.stat().st_size})
262
227
 
263
228
  meta_path = dest / "material.json"
264
- previous = _read_material_metadata(meta_path)
229
+ previous = read_json_object(meta_path)
265
230
  created_at = previous.get("created_at") if previous else None
266
231
  if not isinstance(created_at, str) or not created_at:
267
232
  created_at = now
@@ -301,32 +266,6 @@ def save_material(
301
266
  return metadata
302
267
 
303
268
 
304
- def find_material_by_source(
305
- library_dir: str | Path, source_path: str | Path, source_identity: dict
306
- ) -> dict | None:
307
- """The newest material saved for this exact source file (resolved path + identity)."""
308
- root = Path(library_dir) / "materials"
309
- if not root.exists():
310
- return None
311
- resolved = str(Path(source_path).resolve())
312
- candidates = []
313
- for meta_path in root.glob("*/material.json"):
314
- data = _material_cache_entry(meta_path)
315
- if (
316
- data is not None
317
- and data["source_path"] == resolved
318
- and data["source_video_identity"] == source_identity
319
- ):
320
- data["material_dir"] = str(meta_path.parent)
321
- candidates.append(data)
322
- if not candidates:
323
- return None
324
- # Deterministic fallback policy for legacy/manual callers that do not know
325
- # the expected material_id: newest wins, then material_id for stable ties.
326
- candidates.sort(key=lambda d: (d["updated_at"], d["material_id"]), reverse=True)
327
- return candidates[0]
328
-
329
-
330
269
  def restore_material(
331
270
  library_dir: str | Path,
332
271
  work_dir: str | Path,
@@ -334,7 +273,7 @@ def restore_material(
334
273
  source_path: str | Path,
335
274
  source_identity: dict,
336
275
  settings: dict,
337
- material_id: str | None = None,
276
+ material_id: str,
338
277
  overwrite: bool = True,
339
278
  prune_stale_allowed: bool = True,
340
279
  ) -> dict:
@@ -349,17 +288,11 @@ def restore_material(
349
288
  newly restored material. Non-allowed files (for example narration.json or
350
289
  clip_plan.json) are never removed here.
351
290
  """
352
- lib = Path(library_dir)
353
- if material_id:
354
- meta_path = material_dir(lib, material_id) / "material.json"
355
- meta = _material_cache_entry(meta_path)
356
- if meta is None:
357
- return {"restored": False, "reason": "material missing or invalid"}
358
- meta["material_dir"] = str(meta_path.parent)
359
- else:
360
- meta = find_material_by_source(lib, source_path, source_identity)
361
- if meta is None:
362
- return {"restored": False, "reason": "material not found"}
291
+ meta_path = material_dir(Path(library_dir), material_id) / "material.json"
292
+ meta = _material_cache_entry(meta_path)
293
+ if meta is None:
294
+ return {"restored": False, "reason": "material missing or invalid"}
295
+ meta["material_dir"] = str(meta_path.parent)
363
296
  if meta["source_path"] != str(Path(source_path).resolve()) \
364
297
  or meta["source_video_identity"] != source_identity:
365
298
  return {"restored": False, "reason": "source identity mismatch", "material_id": meta["material_id"]}
@@ -2,12 +2,12 @@
2
2
 
3
3
  import argparse
4
4
  import os
5
+ import sys
6
+ from pathlib import Path
5
7
 
6
- from lib import env_bool
8
+ from lib import TTS_PROVIDERS
7
9
  from recap_source import AUDIO_MODES
8
10
 
9
- TTS_PROVIDERS = ("auto", "mimo-tts", "fish-audio", "index-tts")
10
-
11
11
 
12
12
  class _RecordExplicit:
13
13
  """Record the option argparse actually consumed, not the raw argv spelling."""
@@ -35,6 +35,46 @@ def _record_explicit_options(parser):
35
35
  )
36
36
 
37
37
 
38
+ # Options whose value names a file or directory; the resume command makes them absolute.
39
+ _PATH_DESTS = frozenset({
40
+ "work_dir", "output_dir", "voice_ref", "material_library_dir", "project",
41
+ "tts_meta", "narration_adoption", "audio_mix_adoption",
42
+ })
43
+
44
+
45
+ def _absolute(value):
46
+ return str(Path(value).expanduser().resolve()) if value else value
47
+
48
+
49
+ def _replayable_argv(parser, argv):
50
+ """The argv as typed, with videos and path values made absolute so it replays from any cwd.
51
+
52
+ Runs only after a successful parse, so every option token is an exact option string
53
+ (allow_abbrev=False) and every value-taking option is followed by its value.
54
+ """
55
+ options = {name: action for action in parser._actions for name in action.option_strings}
56
+ out, tokens, positional_only = [], iter(argv), False
57
+ for token in tokens:
58
+ if positional_only or not token.startswith("-"):
59
+ out.append(_absolute(token))
60
+ continue
61
+ if token == "--":
62
+ positional_only = True
63
+ out.append(token)
64
+ continue
65
+ name, inline, value = token.partition("=")
66
+ action = options[name]
67
+ if action.nargs == 0:
68
+ out.append(token)
69
+ continue
70
+ fix = _absolute if action.dest in _PATH_DESTS else str
71
+ if inline:
72
+ out.append(f"{name}={fix(value)}")
73
+ else:
74
+ out += [token, fix(next(tokens))]
75
+ return out
76
+
77
+
38
78
  def parse_args(argv=None):
39
79
  parser = argparse.ArgumentParser(
40
80
  description="Full video recap orchestrator (video-* skill bundle).",
@@ -102,7 +142,9 @@ def parse_args(argv=None):
102
142
  "--burn-subtitles",
103
143
  action=argparse.BooleanOptionalAction,
104
144
  default=None,
105
- help="burn narration subtitles into the video (default on; --no-burn-subtitles to disable)",
145
+ help="burn narration subtitles into the video (default on; without libass the default "
146
+ "delivers a .srt sidecar instead, while an explicit --burn-subtitles fails; "
147
+ "--no-burn-subtitles to disable)",
106
148
  )
107
149
  voice.add_argument(
108
150
  "--subtitle-y-top",
@@ -143,22 +185,10 @@ def parse_args(argv=None):
143
185
  action="store_true",
144
186
  help="make narration review a strict pre-TTS gate (also REQUIRE_NARRATION_REVIEW=1)",
145
187
  )
146
- review.add_argument(
147
- "--mimo-qc",
148
- default=os.environ.get("MIMO_QC", "off"),
149
- choices=["off", "pre-assemble", "post-render", "both"],
150
- help="optional advisory MiMo QC stage(s); never blocks the pipeline",
151
- )
152
- review.add_argument(
153
- "--mimo-qc-refresh",
154
- action="store_true",
155
- default=env_bool("MIMO_QC_REFRESH", False),
156
- help="ignore a matching MiMo QC stage cache",
157
- )
158
188
  review.add_argument(
159
189
  "--require-final-qc",
160
190
  action="store_true",
161
- help="full/cut: require literal passing final_qc and golden_eval summaries",
191
+ help="full/cut: require a literal passing final_qc summary",
162
192
  )
163
193
  review.add_argument(
164
194
  "--export-jianying",
@@ -204,6 +234,8 @@ def parse_args(argv=None):
204
234
  selfcheck.add_argument("--doctor", action="store_true")
205
235
 
206
236
  _record_explicit_options(parser)
237
+ argv = sys.argv[1:] if argv is None else [str(token) for token in argv]
207
238
  args = parser.parse_args(argv)
208
239
  args._explicit_options = frozenset(getattr(args, "_explicit_options", ()))
240
+ args._argv = _replayable_argv(parser, argv)
209
241
  return parser, args
@@ -21,9 +21,7 @@ import argparse
21
21
  import json
22
22
  from pathlib import Path
23
23
 
24
-
25
- def load_json(path):
26
- return json.loads(Path(path).read_text(encoding="utf-8"))
24
+ from lib import load_json
27
25
 
28
26
 
29
27
  # --- artifact catalog --------------------------------------------------------