@oh-story/dsh 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +17 -11
  2. package/lib/client.js +85 -35
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +41 -36
  5. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
  6. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
  7. package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
  8. package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
  9. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
  10. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
  11. package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
  12. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
  13. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
  14. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
  15. package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
  16. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
  17. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
  18. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
  19. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
  20. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  21. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
  22. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
  23. package/lib/index.js +16 -7
  24. package/lib/index.js.map +2 -2
  25. package/lib/novel-to-game/README.md +98 -44
  26. package/lib/novel-to-game/README_ZH.md +100 -46
  27. package/lib/novel-to-game/VERSION +1 -1
  28. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
  29. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
  30. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
  31. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
  32. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
  33. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
  34. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
  35. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
  36. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
  37. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
  38. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
  39. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
  40. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
  41. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
  42. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
  43. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
  44. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
  45. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
  46. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
  47. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
  57. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
  58. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
  59. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
  60. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
  61. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
  62. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
  63. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
  64. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
  65. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
  66. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
  91. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
  92. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
  93. package/lib/novel-to-game/manifest.json +303 -78
  94. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
  95. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
  96. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
  98. package/lib/types/client/drama-production-view.d.ts +11 -0
  99. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  100. package/lib/types/client/file-activity.d.ts +1 -1
  101. package/lib/types/client/file-activity.d.ts.map +1 -1
  102. package/lib/types/client/production-prompts.d.ts +7 -1
  103. package/lib/types/client/production-prompts.d.ts.map +1 -1
  104. package/lib/types/client/production-runtime.d.ts +7 -2
  105. package/lib/types/client/production-runtime.d.ts.map +1 -1
  106. package/lib/types/drama-adapters.d.ts.map +1 -1
  107. package/lib/types/skill-provider.d.ts.map +1 -1
  108. package/lib/types/video-project.d.ts.map +1 -1
  109. package/lib/video-recap/CHANGELOG.md +108 -1
  110. package/lib/video-recap/README.en.md +18 -14
  111. package/lib/video-recap/README.md +18 -14
  112. package/lib/video-recap/manifest.json +297 -321
  113. package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
  114. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
  115. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
  116. package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
  117. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
  118. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
  119. package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
  120. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
  121. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
  122. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
  123. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
  124. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
  125. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
  126. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
  127. package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
  128. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
  129. package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
  130. package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
  131. package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
  132. package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
  133. package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
  134. package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
  135. package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
  136. package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
  137. package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
  138. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
  139. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
  140. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
  141. package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
  142. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
  143. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
  144. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
  145. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
  146. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
  147. package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
  148. package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
  149. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
  150. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
  151. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
  152. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
  153. package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
  154. package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
  155. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
  156. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
  157. package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
  158. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
  159. package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
  160. package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
  161. package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
  162. package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
  163. package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
  164. package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
  165. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
  166. package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
  167. package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
  168. package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
  169. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
  170. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
  171. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
  172. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
  173. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
  174. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
  175. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
  176. package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
  177. package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
  178. package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
  179. package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
  180. package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
  181. package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
  182. package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
  183. package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
  184. package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
  185. package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
  186. package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
  187. package/lib/video-recap/skills/video-script/SKILL.md +9 -7
  188. package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
  189. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
  190. package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
  191. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
  192. package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
  193. package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
  194. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
  195. package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
  196. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
  197. package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
  198. package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
  199. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
  200. package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
  201. package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
  202. package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
  203. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
  204. package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
  205. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
  206. package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
  207. package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
  208. package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
  209. package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
  210. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
  211. package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
  212. package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
  213. package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
  214. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
  215. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
  216. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
  217. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
  218. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
  219. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
  220. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
  221. package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
  222. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
  223. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
  224. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
  225. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
  226. package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
  227. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
  228. package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
  229. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
  230. package/package.json +45 -45
  231. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  232. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  233. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  234. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
  235. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
  236. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
  237. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
  238. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
  239. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
  240. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
  241. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
  242. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
  243. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
  244. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
  245. package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
  246. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
  247. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
  248. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
  249. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
  250. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
  251. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
  252. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
  253. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
  254. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
  255. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
  256. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
  257. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
  258. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
  259. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
  260. package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
  261. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
  262. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
  263. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
  264. /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
@@ -68,18 +68,6 @@ def parse_duration_seconds(value):
68
68
  return seconds
69
69
 
70
70
 
71
- def _overlaps_authored_range(ranges, start, end):
72
- """Whether [start,end) collides with an already-accepted clip, as AUTHORED.
73
-
74
- Overlap is judged on the agent's own in/out points, never on the padded ones.
75
- `clip_padding` deliberately widens every clip by the same amount on both ends, so
76
- judging padded ranges makes any two back-to-back clips (…, 10) and (10, …) look like
77
- duplicate footage and hard-fails a perfectly ordinary plan. Padding is an output
78
- nicety; only what the agent actually asked for defines duplication.
79
- """
80
- return any(start < other_end and end > other_start for other_start, other_end in ranges)
81
-
82
-
83
71
  def _clip_value(raw, *names):
84
72
  for name in names:
85
73
  if name in raw:
@@ -113,13 +101,23 @@ def _source_identities_for_plan(validated_plan, input_video=None):
113
101
  return {str(Path(path)): file_identity(path) for path in sorted(paths)}
114
102
 
115
103
 
104
+ # Version of the picture-format rules in the edited_source encode (yuv420p, colour tags,
105
+ # RGB conversion; see media_geometry._output_color_tags). Bump it whenever those rules
106
+ # change, so a cached edited_source.mp4 rendered under older rules is rebuilt instead of
107
+ # reaching the final render with labels it no longer matches.
108
+ EDITED_SOURCE_PICTURE_RULES = "yuv420p-color-tags-v2"
109
+
110
+
116
111
  def edited_source_render_cache_payload():
117
112
  """Render-affecting settings that invalidate edited_source.mp4 cache reuse.
118
113
 
119
114
  Keep this payload limited to inputs that can change rendered media bytes.
120
115
  Observational QC produced after validation/render is intentionally excluded.
121
116
  """
122
- return {"clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3)}
117
+ return {
118
+ "clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3),
119
+ "picture_rules": EDITED_SOURCE_PICTURE_RULES,
120
+ }
123
121
 
124
122
 
125
123
  def _write_edited_source_meta(output_path, validated_plan, input_video=None):
@@ -151,47 +149,33 @@ def should_reuse_edited_source(output_path, validated_plan, input_video=None):
151
149
  )
152
150
 
153
151
 
154
- def _manifest_source_entries(sources_manifest):
155
- """Return source rows from common multi-source manifest shapes."""
156
- if isinstance(sources_manifest, dict):
157
- if isinstance(sources_manifest.get("sources"), list):
158
- return sources_manifest["sources"]
159
- rows = []
160
- for source_id, value in sources_manifest.items():
161
- if source_id in {"schema_version", "version"}:
162
- continue
163
- if isinstance(value, dict):
164
- row = dict(value)
165
- row.setdefault("source_id", source_id)
166
- rows.append(row)
167
- if rows:
168
- return rows
169
- elif isinstance(sources_manifest, list):
170
- return sources_manifest
171
- raise ValueError(
172
- "sources manifest must be a list, a {sources:[...]} object, or a source_id map"
173
- )
152
+ SOURCES_MANIFEST_SHAPE = (
153
+ '{"sources": [{"source_id": ..., "source_path": ...'
154
+ '[, "duration": seconds, "source_work_dir": ...]}]}'
155
+ )
174
156
 
175
157
 
176
158
  def normalize_sources_manifest(sources_manifest):
177
- """Normalize source manifest rows to {source_id: {source_path, duration}}."""
159
+ """Read the one accepted manifest shape into {source_id: {source_path, duration[, source_work_dir]}}.
160
+
161
+ The shape is SOURCES_MANIFEST_SHAPE, which is what video-recap writes. A missing
162
+ duration is probed from the media file.
163
+ """
164
+ rows = sources_manifest.get("sources") if isinstance(sources_manifest, dict) else None
165
+ if not isinstance(rows, list):
166
+ raise ValueError(f"sources manifest must be {SOURCES_MANIFEST_SHAPE}")
178
167
  sources = {}
179
- for idx, raw in enumerate(_manifest_source_entries(sources_manifest)):
168
+ for idx, raw in enumerate(rows):
180
169
  if not isinstance(raw, dict):
181
- raise ValueError(f"source #{idx + 1} must be an object")
182
- source_id = raw.get("source_id", raw.get("id", raw.get("name")))
170
+ raise ValueError(f"source #{idx + 1} must be an object; expected {SOURCES_MANIFEST_SHAPE}")
171
+ source_id = raw.get("source_id")
183
172
  if source_id in (None, ""):
184
173
  raise ValueError(f"source #{idx + 1} is missing source_id")
185
174
  source_id = str(source_id)
186
- source_path = raw.get(
187
- "source_path",
188
- raw.get("path", raw.get("video_path", raw.get("video", raw.get("file")))),
189
- )
175
+ source_path = raw.get("source_path")
190
176
  if not source_path:
191
- raise ValueError(f"source {source_id} is missing source_path/path")
192
- duration = raw.get(
193
- "duration", raw.get("duration_seconds", raw.get("source_duration"))
194
- )
177
+ raise ValueError(f"source {source_id} is missing source_path")
178
+ duration = raw.get("duration")
195
179
  if duration in (None, ""):
196
180
  duration = get_video_duration(source_path)
197
181
  try:
@@ -212,24 +196,17 @@ def normalize_sources_manifest(sources_manifest):
212
196
  return sources
213
197
 
214
198
 
215
- def normalize_multi_source_clip_plan(
216
- raw_plan,
217
- sources_manifest,
218
- target_duration=None,
219
- clip_padding=0.0,
220
- min_clip_duration=0.3,
221
- allow_overlap=False,
222
- ):
223
- """Validate a multi-source clip plan and map source_id clips to source paths/durations.
199
+ def _normalize_clips(raw_plan, target_duration, min_clip_duration, allow_overlap, resolve_source):
200
+ """The normalize loop both plan shapes share.
224
201
 
225
- Clip order follows the raw plan; overlap validation is isolated per source_id.
202
+ `resolve_source(raw, idx)` returns (overlap_key, source_duration, clip_fields):
203
+ overlap is judged per key (None for a single source), the clip is clamped to
204
+ source_duration, and clip_fields are added right after `clip_id`. Clip order
205
+ follows the raw plan, so montage ordering is possible.
226
206
  """
227
- sources = normalize_sources_manifest(sources_manifest)
228
207
  if isinstance(raw_plan, dict):
229
208
  raw_clips = raw_plan.get("clips", [])
230
- plan_target = raw_plan.get("target_duration") or raw_plan.get(
231
- "target_duration_seconds"
232
- )
209
+ plan_target = raw_plan.get("target_duration")
233
210
  if target_duration is None and plan_target not in (None, ""):
234
211
  target_duration = parse_duration_seconds(plan_target)
235
212
  elif isinstance(raw_plan, list):
@@ -242,25 +219,16 @@ def normalize_multi_source_clip_plan(
242
219
  if not isinstance(raw_clips, list):
243
220
  raise ValueError("clip_plan.json field `clips` must be an array")
244
221
 
245
- padding = max(0.0, clip_padding)
246
222
  min_duration = max(0.05, min_clip_duration)
247
223
  clips = []
248
- source_ranges = {}
224
+ accepted_ranges = {}
249
225
  cursor = 0.0
250
226
 
251
227
  for idx, raw in enumerate(raw_clips):
252
228
  if not isinstance(raw, dict):
253
229
  log(f" 跳过无效 clip #{idx + 1}: not an object")
254
230
  continue
255
- source_id = raw.get("source_id", raw.get("id"))
256
- if source_id in (None, ""):
257
- raise ValueError(f"clip #{idx + 1} is missing source_id")
258
- source_id = str(source_id)
259
- source = sources.get(source_id)
260
- if not source:
261
- raise ValueError(
262
- f"clip #{idx + 1} references unknown source_id: {source_id}"
263
- )
231
+ overlap_key, source_duration, clip_fields = resolve_source(raw, idx)
264
232
  try:
265
233
  raw_start = float(_clip_value(raw, "start", "source_start", "in"))
266
234
  raw_end = float(_clip_value(raw, "end", "source_end", "out"))
@@ -270,155 +238,102 @@ def normalize_multi_source_clip_plan(
270
238
  if raw_end - raw_start < min_duration:
271
239
  log(f" 跳过过短 clip #{idx + 1}: {raw_start:.1f}-{raw_end:.1f}s")
272
240
  continue
273
- source_duration = source["duration"]
274
- start = round(max(0.0, min(raw_start - padding, source_duration)), 3)
275
- end = round(max(0.0, min(raw_end + padding, source_duration)), 3)
241
+ start = round(max(0.0, min(raw_start, source_duration)), 3)
242
+ end = round(max(0.0, min(raw_end, source_duration)), 3)
276
243
  if end - start < min_duration:
277
244
  log(f" 跳过过短 clip #{idx + 1}: {start:.1f}-{end:.1f}s")
278
245
  continue
279
- ranges = source_ranges.setdefault(source_id, [])
280
- if not allow_overlap and _overlaps_authored_range(ranges, raw_start, raw_end):
246
+ ranges = accepted_ranges.setdefault(overlap_key, [])
247
+ if not allow_overlap and any(raw_start < e and raw_end > s for s, e in ranges):
248
+ where = "" if overlap_key is None else f" for source_id {overlap_key}"
281
249
  raise ValueError(
282
- f"clip #{idx + 1} overlaps an earlier source range for source_id {source_id}; "
283
- "split or remove duplicate source footage before mapping narration"
250
+ f"clip #{idx + 1} overlaps an earlier source range{where}; "
251
+ "split or remove duplicate source footage in the clip plan"
284
252
  )
285
253
  ranges.append((raw_start, raw_end))
286
254
 
287
255
  duration = round(end - start, 3)
288
- clip = {
256
+ clips.append({
289
257
  "clip_id": len(clips),
290
- "source_id": source_id,
291
- "source_path": source["source_path"],
258
+ **clip_fields,
292
259
  "source_start": start,
293
260
  "source_end": end,
294
261
  "output_start": round(cursor, 3),
295
262
  "output_end": round(cursor + duration, 3),
296
263
  "duration": duration,
297
264
  "reason": str(raw.get("reason", raw.get("note", ""))).strip(),
298
- }
299
- clips.append(clip)
265
+ })
300
266
  cursor += duration
301
267
 
302
268
  if not clips:
303
269
  raise ValueError("clip_plan.json has no valid clips")
304
270
 
305
- total_duration = round(sum(c["duration"] for c in clips), 3)
306
- plan = {
271
+ return {
307
272
  "clips": clips,
308
- "total_duration": total_duration,
273
+ "total_duration": round(sum(c["duration"] for c in clips), 3),
309
274
  "target_duration": round(target_duration, 3) if target_duration else None,
310
- "sources": {
311
- sid: {
312
- "source_path": s["source_path"],
313
- "duration": round(s["duration"], 3),
314
- **(
315
- {"source_work_dir": s["source_work_dir"]}
316
- if s.get("source_work_dir")
317
- else {}
318
- ),
319
- }
320
- for sid, s in sources.items()
321
- },
322
- "allow_overlap": bool(allow_overlap),
323
275
  }
324
- if target_duration and total_duration > target_duration * 1.15:
325
- plan["warning"] = (
326
- f"validated clips total {total_duration:.1f}s exceeds target "
327
- f"{target_duration:.1f}s by more than 15%"
328
- )
329
- log(f"警告: {plan['warning']}")
330
- return plan
331
276
 
332
277
 
333
- def normalize_clip_plan(
278
+ def normalize_multi_source_clip_plan(
334
279
  raw_plan,
335
- video_duration,
280
+ sources_manifest,
336
281
  target_duration=None,
337
- clip_padding=0.0,
338
282
  min_clip_duration=0.3,
339
283
  allow_overlap=False,
340
284
  ):
341
- """Validate and enrich an agent-authored clip plan.
285
+ """Validate a multi-source clip plan and map source_id clips to source paths/durations.
342
286
 
343
- Returns a dict with validated `clips`, `total_duration`, and target metadata.
344
- Clip order follows the agent-provided order, so montage ordering is possible.
287
+ Clip order follows the raw plan; overlap validation is isolated per source_id.
345
288
  """
346
- if isinstance(raw_plan, dict):
347
- raw_clips = raw_plan.get("clips", [])
348
- plan_target = raw_plan.get("target_duration") or raw_plan.get(
349
- "target_duration_seconds"
350
- )
351
- if target_duration is None and plan_target not in (None, ""):
352
- target_duration = parse_duration_seconds(plan_target)
353
- elif isinstance(raw_plan, list):
354
- raw_clips = raw_plan
355
- else:
356
- raise ValueError(
357
- "clip_plan.json must be a JSON array or an object with a clips array"
358
- )
359
-
360
- if not isinstance(raw_clips, list):
361
- raise ValueError("clip_plan.json field `clips` must be an array")
362
-
363
- padding = max(0.0, clip_padding)
364
- min_duration = max(0.05, min_clip_duration)
365
- clips = []
366
- source_ranges = []
367
- cursor = 0.0
289
+ sources = normalize_sources_manifest(sources_manifest)
368
290
 
369
- for idx, raw in enumerate(raw_clips):
370
- if not isinstance(raw, dict):
371
- log(f" 跳过无效 clip #{idx + 1}: not an object")
372
- continue
373
- try:
374
- raw_start = float(_clip_value(raw, "start", "source_start", "in"))
375
- raw_end = float(_clip_value(raw, "end", "source_end", "out"))
376
- except (TypeError, ValueError):
377
- log(f" 跳过无效 clip #{idx + 1}: missing numeric start/end")
378
- continue
379
- if raw_end - raw_start < min_duration:
380
- log(f" 跳过过短 clip #{idx + 1}: {raw_start:.1f}-{raw_end:.1f}s")
381
- continue
382
- start = round(max(0.0, min(raw_start - padding, video_duration)), 3)
383
- end = round(max(0.0, min(raw_end + padding, video_duration)), 3)
384
- if end - start < min_duration:
385
- log(f" 跳过过短 clip #{idx + 1}: {start:.1f}-{end:.1f}s")
386
- continue
387
- if not allow_overlap and _overlaps_authored_range(source_ranges, raw_start, raw_end):
291
+ def resolve_source(raw, idx):
292
+ source_id = raw.get("source_id")
293
+ if source_id in (None, ""):
294
+ raise ValueError(f"clip #{idx + 1} is missing source_id")
295
+ source_id = str(source_id)
296
+ source = sources.get(source_id)
297
+ if not source:
388
298
  raise ValueError(
389
- f"clip #{idx + 1} overlaps an earlier source range; "
390
- "split or remove duplicate source footage before mapping narration"
299
+ f"clip #{idx + 1} references unknown source_id: {source_id}"
391
300
  )
392
- source_ranges.append((raw_start, raw_end))
301
+ fields = {"source_id": source_id, "source_path": source["source_path"]}
302
+ return source_id, source["duration"], fields
393
303
 
394
- duration = round(end - start, 3)
395
- clip = {
396
- "clip_id": len(clips),
397
- "source_start": start,
398
- "source_end": end,
399
- "output_start": round(cursor, 3),
400
- "output_end": round(cursor + duration, 3),
401
- "duration": duration,
402
- "reason": str(raw.get("reason", raw.get("note", ""))).strip(),
304
+ plan = _normalize_clips(
305
+ raw_plan, target_duration, min_clip_duration, allow_overlap, resolve_source
306
+ )
307
+ plan["sources"] = {
308
+ sid: {
309
+ "source_path": s["source_path"],
310
+ "duration": round(s["duration"], 3),
311
+ **({"source_work_dir": s["source_work_dir"]} if s.get("source_work_dir") else {}),
403
312
  }
404
- clips.append(clip)
405
- cursor += duration
313
+ for sid, s in sources.items()
314
+ }
315
+ plan["allow_overlap"] = bool(allow_overlap)
316
+ return plan
406
317
 
407
- if not clips:
408
- raise ValueError("clip_plan.json has no valid clips")
409
318
 
410
- total_duration = round(sum(c["duration"] for c in clips), 3)
411
- plan = {
412
- "clips": clips,
413
- "total_duration": total_duration,
414
- "target_duration": round(target_duration, 3) if target_duration else None,
415
- "source_duration": round(video_duration, 3),
416
- "allow_overlap": bool(allow_overlap),
417
- }
418
- if target_duration and total_duration > target_duration * 1.15:
419
- plan["warning"] = (
420
- f"validated clips total {total_duration:.1f}s exceeds target "
421
- f"{target_duration:.1f}s by more than 15%"
422
- )
423
- log(f"警告: {plan['warning']}")
319
+ def normalize_clip_plan(
320
+ raw_plan,
321
+ video_duration,
322
+ target_duration=None,
323
+ min_clip_duration=0.3,
324
+ allow_overlap=False,
325
+ ):
326
+ """Validate and enrich an agent-authored single-source clip plan.
327
+
328
+ Returns a dict with validated `clips`, `total_duration`, and target metadata.
329
+ """
330
+ plan = _normalize_clips(
331
+ raw_plan,
332
+ target_duration,
333
+ min_clip_duration,
334
+ allow_overlap,
335
+ lambda raw, idx: (None, video_duration, {}),
336
+ )
337
+ plan["source_duration"] = round(video_duration, 3)
338
+ plan["allow_overlap"] = bool(allow_overlap)
424
339
  return plan