@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
@@ -10,8 +10,8 @@ set of tracks — exactly like a cut-tool project:
10
10
  - one **narration** audio track: the placed TTS beats;
11
11
  - an optional **bgm** audio track: a looped music bed with its own ducking;
12
12
  - one **subtitle** (text) track: the narration lines.
13
- - optional **image** tracks: local photo overlays with normalized center-origin,
14
- Y-up transforms for editable JianYing export.
13
+ - optional **image** tracks: local photo overlays with a normalized center-origin,
14
+ Y-up scale/position for editable JianYing export.
15
15
 
16
16
  The canonical ducking semantics live in `audio_automation.py`; ffmpeg
17
17
  (`assemble.py`) and this timeline model both derive their automation from that
@@ -44,8 +44,7 @@ def _floor_time(value, digits=4):
44
44
 
45
45
  def build_timeline(canvas, duration_s, video_clips, narration_segments,
46
46
  bgm=None, ducking=None, subtitle_segments=None,
47
- image_segments=(), resource_packages=None,
48
- style_presets=None, extra_tracks=()):
47
+ image_segments=()):
49
48
  """Assemble a Timeline dict from resolved placement data.
50
49
 
51
50
  canvas: {"width", "height", "fps"}
@@ -63,8 +62,8 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
63
62
  ducking: {"idle", "speech", "quiet", "fade", "bridge"} for the original-audio
64
63
  automation; None disables original ducking (flat original). `bridge` holds
65
64
  the duck across inter-beat gaps shorter than it.
66
- image_segments: optional v2 local image overlays [{"source_path", "timeline_start",
67
- "timeline_end", ...authoring extensions}], passed through as authored.
65
+ image_segments: optional local image overlays [{"source_path", "timeline_start",
66
+ "timeline_end", optional "scale"/"position"}], passed through as authored.
68
67
  """
69
68
  placed = [
70
69
  s for s in narration_segments
@@ -94,22 +93,14 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
94
93
  audio["base_gain"] = round(float(ducking["idle"]), 4)
95
94
  else:
96
95
  audio["base_gain"] = 1.0
97
- video_clip = {
96
+ video_clip_objs.append({
98
97
  "source_path": c["source_path"],
99
98
  "source_start": round(float(c["source_start"]), 4),
100
99
  "source_end": round(float(c["source_end"]), 4),
101
100
  "timeline_start": round(ts, 4),
102
101
  "timeline_end": round(te, 4),
103
102
  "audio": audio,
104
- }
105
- for key in (
106
- "chroma", "compound", "flip", "green_background", "lut", "mask",
107
- "opacity", "position", "reverse", "reverse_path", "rotation_degrees",
108
- "scale", "speed", "transition",
109
- ):
110
- if key in c:
111
- video_clip[key] = deepcopy(c[key])
112
- video_clip_objs.append(video_clip)
103
+ })
113
104
 
114
105
  tracks = [{"kind": "video", "name": "video", "clips": video_clip_objs}]
115
106
 
@@ -127,8 +118,6 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
127
118
  for key in ("source_duck_end", "source_restore_at", "source_handoff_status", "source_entry_status"):
128
119
  if key in s:
129
120
  narration[key] = deepcopy(s[key])
130
- if "speed" in s:
131
- narration["speed"] = float(s["speed"])
132
121
  narr_segs.append(narration)
133
122
  if narr_segs:
134
123
  tracks.append({"kind": "audio", "name": "narration", "role": "narration",
@@ -161,20 +150,16 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
161
150
  ts, te = float(s["timeline_start"]), float(s["timeline_end"])
162
151
  if te <= ts:
163
152
  continue
164
- text_segment = {
153
+ text_segs.append({
165
154
  "text": s["text"],
166
155
  "timeline_start": round(ts, 4),
167
156
  "timeline_end": round(te, 4),
168
- }
169
- for key in ("flip", "opacity", "position", "rotation_degrees", "scale", "style", "style_id", "words"):
170
- if key in s:
171
- text_segment[key] = deepcopy(s[key])
172
- text_segs.append(text_segment)
157
+ })
173
158
  if text_segs:
174
159
  tracks.append({"kind": "text", "name": "subtitle", "segments": text_segs})
175
160
 
176
- # --- local image overlays (optional, timeline schema v2). Transform fields are
177
- # optional authoring extensions; the JianYing exporter validates and defaults them.
161
+ # --- local image overlays (optional). scale/position are optional; the JianYing
162
+ # exporter validates and defaults them.
178
163
  images = []
179
164
  for segment in image_segments:
180
165
  image = {
@@ -182,16 +167,13 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
182
167
  "timeline_start": round(float(segment["timeline_start"]), 4),
183
168
  "timeline_end": round(float(segment["timeline_end"]), 4),
184
169
  }
185
- for key in ("flip", "lut", "mask", "opacity", "position", "rotation_degrees",
186
- "scale", "speed", "transition"):
170
+ for key in ("position", "scale"):
187
171
  if key in segment:
188
172
  image[key] = deepcopy(segment[key])
189
173
  images.append(image)
190
174
  if images:
191
175
  tracks.append({"kind": "image", "name": "image", "segments": images})
192
176
 
193
- tracks.extend(deepcopy(track) for track in extra_tracks)
194
-
195
177
  timeline = {
196
178
  "schema_version": SCHEMA_VERSION,
197
179
  "canvas": {"width": int(canvas["width"]), "height": int(canvas["height"]),
@@ -199,10 +181,6 @@ def build_timeline(canvas, duration_s, video_clips, narration_segments,
199
181
  "duration": round(float(duration_s), 4),
200
182
  "tracks": tracks,
201
183
  }
202
- if resource_packages:
203
- timeline["resource_packages"] = deepcopy(resource_packages)
204
- if style_presets:
205
- timeline["style_presets"] = deepcopy(style_presets)
206
184
  return timeline
207
185
 
208
186
 
@@ -81,11 +81,10 @@ def _emit_timeline(input_video, tts_segments, work_dir, duration_s, canvas, has_
81
81
  "volume": CONFIG["bgm_volume"],
82
82
  "ducking_volume": CONFIG["bgm_ducking_volume"],
83
83
  "fade": fade}
84
- # carry ducking automation whenever ducking is on at all; even under sidechain
85
- # mode the draft gets editable volume keyframes (ffmpeg stays the canonical mix)
84
+ # narration mode always ducks; the draft carries the same envelope as editable
85
+ # volume keyframes (ffmpeg stays the canonical mix)
86
86
  ducking = None
87
- if audio_mode == "narration" and explicit_audio_mix is None \
88
- and CONFIG["ducking_mode"] != "none":
87
+ if audio_mode == "narration" and explicit_audio_mix is None:
89
88
  ducking = {"idle": CONFIG["idle_orig_volume"],
90
89
  "speech": CONFIG["speech_ducking_volume"],
91
90
  "quiet": CONFIG["zone_ducking_volume"],
@@ -19,6 +19,7 @@ from source_subtitles import (
19
19
  _source_subtitle_mask_policy,
20
20
  )
21
21
  from subtitles.core import (
22
+ _frame_row,
22
23
  _measured_subtitle_band,
23
24
  _measured_subtitle_safe_area,
24
25
  _normalize_subtitle_text,
@@ -107,6 +108,7 @@ def _subtitle_layout_qc(entries, style, safe_area=None):
107
108
  return {
108
109
  "enabled": CONFIG["burn_subtitles"],
109
110
  "renderer": "ass" if CONFIG["burn_subtitles"] else "sidecar_srt",
111
+ "burn_degraded_reason": CONFIG["burn_subtitles_degraded"],
110
112
  "style": {
111
113
  "font_size": int(font_size),
112
114
  "max_chars": int(style["max_chars"]),
@@ -267,6 +269,28 @@ def _visual_overlay_filters(work_dir, canvas, video_duration):
267
269
  return filters, qc
268
270
 
269
271
 
272
+ def _subtitle_delivery_warnings(mask, *, has_subtitles):
273
+ """Non-blocking, machine-readable record of a default burn that degraded to the sidecar.
274
+
275
+ A run with no subtitle entries loses nothing to the missing libass (a burned empty ASS
276
+ shows nothing either), so it ships no sidecar and records no warning.
277
+ """
278
+ reason = CONFIG["burn_subtitles_degraded"]
279
+ if not reason or not has_subtitles:
280
+ return []
281
+ return [{
282
+ "code": "subtitle_burn_degraded",
283
+ "reason": reason,
284
+ "delivered": "sidecar_srt",
285
+ "mask_dropped": mask["trigger"] == "burn_subtitles_degraded",
286
+ "message": (
287
+ "ffmpeg lacks the libass subtitles filter: subtitles are not burned into the "
288
+ "video and ship as a .srt sidecar next to it"
289
+ ),
290
+ "next_action": "install an ffmpeg build with libass and rerun to burn, or deliver the .srt sidecar",
291
+ }]
292
+
293
+
270
294
  def _build_visual_qc(tts_segments, work_dir, video_duration, canvas, *, overlay_qc=None, mask_filter=None):
271
295
  entries = _combined_subtitle_entries(tts_segments, work_dir, video_duration)
272
296
  style = _style_for_measured_subtitle_band(_subtitle_style_config(canvas), canvas)
@@ -284,6 +308,7 @@ def _build_visual_qc(tts_segments, work_dir, video_duration, canvas, *, overlay_
284
308
  })
285
309
  if overlay_qc is None:
286
310
  overlay_qc = _visual_overlay_filters(work_dir, canvas, video_duration)[1]
311
+ warnings = _subtitle_delivery_warnings(mask, has_subtitles=bool(entries))
287
312
  blocking_codes = []
288
313
  if mask["blocking"]:
289
314
  blocking_codes.append("mask_policy_not_explicit")
@@ -299,6 +324,7 @@ def _build_visual_qc(tts_segments, work_dir, video_duration, canvas, *, overlay_
299
324
  "verdict": "FAIL" if blocking_codes else "PASS",
300
325
  "blocking": bool(blocking_codes),
301
326
  "blocking_codes": blocking_codes,
327
+ "warnings": warnings,
302
328
  "geometry": {
303
329
  "canvas": {
304
330
  "width": canvas["width"],
@@ -400,8 +426,8 @@ def _source_subtitle_mask_filter(canvas, work_dir, tts_segments, video_duration)
400
426
  if band is not None:
401
427
  y_top, y_bot = band
402
428
  padding = CONFIG["subtitle_mask_padding"]
403
- mask_top = max(0, y_top - padding)
404
- mask_bot = min(canvas["height"], y_bot + padding)
429
+ mask_top = _frame_row(canvas, max(0, y_top - padding))
430
+ mask_bot = _frame_row(canvas, min(canvas["height"], y_bot + padding))
405
431
  geometry = f"x=0:y={mask_top}:w=iw:h={mask_bot - mask_top}"
406
432
  else:
407
433
  # Our subtitle cues are one line. Keep the mask large enough for that line and its
@@ -13,9 +13,10 @@ description: >
13
13
  本技能只执行 Agent 已经做出的剪辑决定:
14
14
 
15
15
  1. 校验并补全 `clip_plan.json`,写出带 `clip_id`、原片/输出时间与时长的 `clip_plan_validated.json`。
16
- 2. 先避开原片硬切附近的闪帧风险,最后把边界吸附到可靠句末/自然停顿;声音完整性拥有最终优先级。
17
- 3. 拼接选定区间,输出 `edited_source.mp4`。
18
- 4. 到此停止,由 Agent 按真实输出时间线写 `narration.json`;本工具不读取旁白,也不做原片→输出映射。
16
+ 2. 先避开原片硬切附近的闪帧风险,再把边界吸附到可靠句末/自然停顿;声音完整性拥有最终优先级。
17
+ 3. 把每个入点对齐到源视频帧网格、每段时长对齐到整数个输出帧(不足一帧的移动,优先选句界门禁仍判为安全、仍在停顿内的一侧;两侧同样安全时选不跨过原片硬切的一侧,避免闪一帧),句界门禁检查的是对齐后的边界。
18
+ 4. 拼接选定区间,输出恒定帧率的 `edited_source.mp4`,帧数与 `clip_plan_validated.json` 记录的一致。
19
+ 5. 到此停止,由 Agent 按真实输出时间线写 `narration.json`;本工具不读取旁白,也不做原片→输出映射。
19
20
 
20
21
  相同输入会得到相同输出。`edited_source.mp4.meta.json` 记录标准化 clips、渲染设置和每个源文件的 `size`/`mtime_ns`;三者与当前一致且 `edited_source.mp4` 存在非空才复用,任一不同即重渲染。只有 sidecar 而没有媒体文件不复用。
21
22
 
@@ -29,8 +30,16 @@ description: >
29
30
 
30
31
  - `start` / `end` 是原片秒数;也接受 `source_start` / `source_end` 或 `in` / `out`。
31
32
  - 顶层可选 `target_duration`,例如 `"10m"`。
32
- - 多视频项目的每个片段还必须填写 `source_id`。
33
- - `speech_boundary_anchors.json` 与 ASR 时间段由理解阶段提供;Agent 先写大致区间,工具会尝试吸附并把仍在讲话区间内的入/出点作为 blocker 返回。
33
+ - 多视频项目的每个片段还必须填写 `source_id`(不接受 `id` 代替),并用 `--sources-manifest` 传入来源清单(形状见下)。
34
+ - `speech_boundary_anchors.json` 与 ASR 时间段由理解阶段提供;Agent 先写大致区间,工具会尝试吸附并把仍在讲话区间内的入/出点作为 blocker 返回。只含语气词或 ASR 杂音的窗口("啊!"、"Hi.")不算讲话区间,只在紧挨真实对白的一侧保留 1 秒保护。只有标点的窗口("……")仍算讲话。
35
+
36
+ 多视频来源清单只接受一种形状,其他形状直接报错并写明期望形状:
37
+
38
+ ```json
39
+ {"sources": [{"source_id": "ep1", "source_path": "/media/ep1.mp4", "duration": 1520.0, "source_work_dir": "sources/ep1"}]}
40
+ ```
41
+
42
+ `duration` 可省略(省略时用 ffprobe 读取);`source_work_dir` 可省略,填写时相对 `--work-dir`,用于读取该来源的静音、句末锚点与 ASR。其他键忽略。
34
43
 
35
44
  ## 3. 剪辑意图契约
36
45
 
@@ -68,24 +77,41 @@ beat_id | function | change | POV | preferred moment | 入点 reason | 出点 re
68
77
  ## 4. 运行命令
69
78
 
70
79
  ```bash
71
- python3 scripts/cut.py <video> --work-dir <work_dir> \
72
- [--target-duration 10m] [--clip-padding 0] [--allow-overlap]
80
+ python3 scripts/cut.py <video> --work-dir <work_dir> [--clip-plan <clip_plan.json>] \
81
+ [--sources-manifest <sources.json>] [--target-duration 10m] [--allow-overlap] \
82
+ [--allow-duration-drift] [--normalize-only] \
83
+ [--review-shots [--shot-scene-threshold 0.35] [--shot-roi X Y W H]]
73
84
  ```
74
85
 
86
+ - `--clip-plan`:剪辑计划路径,默认 `<work_dir>/clip_plan.json`。
87
+
88
+ - `--sources-manifest`:多源剪辑的来源清单 `{"sources": [{"source_id", "source_path"[, "duration", "source_work_dir"]}]}`;片段用 `source_id` 指明来源,并按自己的来源吸附句界与画面切点。
89
+ - `--target-duration`:目标时长。实际时长与目标之比在 0.85–1.15 之外记 warning,在 0.60–1.40 之外阻断。
90
+ - `--allow-duration-drift`:只放行时长偏差阻断(记为 `allowed: true` 的 warning),不放行句界或必保证据阻断。
91
+ - `--normalize-only`:只标准化、吸附并检查计划,写出 `clip_plan_validated.json` 后退出,不渲染。
92
+ - `--review-shots`:在渲染或复用的 `edited_source.mp4` 上召回短镜与密集切点候选,只报告、不修复;`--shot-scene-threshold` 是召回阈值(默认 0.35,不是验收标准),`--shot-roi` 只扫描该像素矩形(有黑边或包装时用),不裁画面。
93
+
94
+ cut 阻断时以非零状态退出,并把原因写入 `clip_plan_validated.json` 的 `qc.blocking`,每项带 `code`:
95
+
96
+ - `unsafe_clip_sentence_boundary`:片段边界仍在原声讲话内;逐边界判定见 `qc.boundary_status.sentence_checks`。被阻断的边界带 `nearest_safe: {"before", "after"}`:前后 5 秒内最近的安全边界 `{time, reason, delta}`(原片秒;`delta` 为相对当前边界的秒数,没有则为 `null`;已是原片帧网格上的落点并复核过;重跑时切镜头避让若把它拉回讲话内,这次避让会被撤回,`qc.boundary_status.shot_snaps` 记 `reverted_unsafe`,所以原样写回不会再因句界被阻断),按它改 `clip_plan.json` 的 `start`/`end` 后重跑。入点往前(`before`)是多保留、往后(`after`)是裁掉,出点相反;先确认改动不会切掉必保内容或与相邻片段重叠。两侧都是 `null` 说明附近没有停顿,要换区间而不是微调。
97
+ - `target_duration_drift`:时长偏差超出阻断阈值;明细见 `qc.target_duration`。
98
+ - `REQUIRED_EVIDENCE_INVALID` / `REQUIRED_EVIDENCE_MISSING` / `REQUIRED_EVIDENCE_ORDER` / `REQUIRED_EVIDENCE_AUDIO_UNAVAILABLE`:必保证据声明无效、缺段、错序或源无音轨;明细见 `qc.required_evidence`。
99
+
75
100
  ## 5. 输出契约
76
101
 
77
- - `clip_plan_validated.json`:标准化片段,包含 `clip_id`、`source_start/end`、`output_start/end` 与 `duration`。
78
- - `edited_source.mp4`:按计划拼接后的短视频。
102
+ - `clip_plan_validated.json`:标准化片段,包含 `clip_id`、`source_start/end`、`output_start/end`、`duration` 与 `frame_count`(该片段渲染的帧数)。`qc.frame_grid` 记录输出帧率 `output_frame_rate`(单源沿用源帧率,多源用画布帧率,NTSC 写成 `30000/1001`)、总帧数与各源帧率;每次帧对齐的前后时间写在 `qc.boundary_status.frame_snaps`。
103
+ - `edited_source.mp4`:按计划拼接后的恒定帧率短视频,帧数等于 `qc.frame_grid.frame_count`。
79
104
  - `shot_review.json`:仅 `--review-shots` 开启后生成的实际视频短镜/密集切镜候选;不会更改计划。
80
105
 
81
106
  下游把 `edited_source.mp4` 当作视频,把 Agent 按输出时间写的 `narration.json` 当作旁白。
82
107
 
83
108
  ## 6. 边界与时间线规则
84
109
 
85
- - `clip_plan.json` 使用原片时间;`narration.json` 直接使用剪后输出时间,不存在原片 → 输出的旁白映射。
110
+ - `clip_plan.json` 使用原片时间;`narration.json` 直接使用剪后输出时间,不存在原片 → 输出的旁白映射。输出时间以 `clip_plan_validated.json` 为准:帧对齐会把边界移动不到一帧(25fps 下不超过 40 ms),写旁白前读 validated 计划,不要用自己写的原始区间推算。
111
+ - 边界不在帧网格上时,concat 会在每个接点丢掉一个帧位(25fps 下画面停顿 80 ms,成片变成可变帧率),所以帧对齐无法关闭。源帧率未知(`r_frame_rate` 为 `0/0` 或大于 120)时入点不动,时长仍对齐到整数输出帧。
86
112
  - 默认禁止重叠或重复原片区间;`--allow-overlap` 开启后才允许。
87
113
  - 片段起点只能位于源头、可靠句末/静音窗,或与上一片段构成无损同源连续连接;片段终点同理。ASR 判定仍在讲话且无法吸附时写入 `unsafe_clip_sentence_boundary` 并阻断。
88
- - `SCENE_CUT_SNAP` 默认开启:先按画面把 source start 向后、source end 向前吸附到附近硬切,随后句末吸附再做最终修正,避免视觉修正重新制造半句原声。默认范围为 `SCENE_CUT_SNAP_MARGIN=0.5` 秒,检测阈值为 `SCENE_CUT_DETECT_THRESHOLD=0.4`。
114
+ - `SCENE_CUT_SNAP` 默认开启:先按画面把 source start 向后、source end 向前吸附到附近硬切,随后句末吸附再做最终修正,避免视觉修正重新制造半句原声;附近没有停顿、句末吸附修不回来时,把边界移进讲话的那次避让会被撤回(原位置能过门禁时)。默认范围为 `SCENE_CUT_SNAP_MARGIN=0.5` 秒,检测阈值为 `SCENE_CUT_DETECT_THRESHOLD=0.4`。
89
115
  - scene-change score 只提供接点候选,不证明接点自然。先检查短时间窗内是否出现密集候选,再区分来源:原片自带的无关短镜头整段删除;相关但短到像闪帧的镜头通过扩展 IN/OUT 保留完整动作、反应或台词,不用定格/慢放伪造时长;由本次拼接制造的切点则优先移动边界、恢复同源连续运动、合并相邻片段或改用更自然的连接,尽量消除。成片后仍要逐个播放接点前后约 0.5–1 秒;白闪或曝光叠化再结合逐帧亮度定位,不能为了通过视觉检测切断完整台词,也不能用转场遮掩坏接点。
90
116
  - 修短残镜时不得仅为压低 scene 分数而对接点附近施加与所属镜头不连续的极端放大或位移;取景复核与修复验证流程见 `references/shot-review.md`。
91
117
  - 连续同源片段的无损连接不做句中双侧音频淡出;非连续片段仍在安全停顿内做防爆音淡入淡出。
@@ -30,7 +30,7 @@ python3 scripts/cut.py source.mp4 --work-dir work --review-shots
30
30
 
31
31
  - `ffprobe` 完整解码收集真实帧 PTS;scene filter 使用整数 PTS 与过滤器 timebase,不用 seek 后的浮点 `pts_time` 反推帧号。
32
32
  - 帧号从 0 开始,区间半开 `[start_frame,end_frame)`;首镜、末镜也检查。VFR 同时记录真实 rational PTS 时长,不能用平均 fps 乘秒数。
33
- - 默认候选规则:镜头不超过 **1 秒**,或 **2 秒内至少 4 个切点**。帧数上限缺省由实测帧钟换算(`round(fps × max_short_seconds)`),不写死某个帧率;需要固定帧数时用 `--max-short-frames` 显式覆盖。参数只控制召回,不是统一剪辑标准。
33
+ - 默认候选规则:镜头不超过 **1 秒**,或 **2 秒内至少 4 个切点**。帧数上限缺省由实测帧钟换算(`round(fps × 1 秒)`),不写死某个帧率;需要固定帧数时用 `--max-short-frames` 显式覆盖。参数只控制召回,不是统一剪辑标准。
34
34
  - `0.35` 是默认 scene 起始阈值。加黑边、包装占比大、低反差的画面会系统性少报。**某个阈值下零候选不是没有闪帧的证据。** 用一段已知有坏短镜的窗口校准阈值,保留每一轮的报告,不静默换阈值只报告“通过”。降低阈值也可能增加曝光、运动和动效误报。
35
35
  - 未提供计划时来源为 `UNKNOWN`。提供计划时先核对计划 clips、渲染设置与 `edited_source.mp4.meta.json` 一致、源文件 `size`/`mtime_ns` 未变、输出存在非空;失配直接失败,不猜“最新版本”。报告 `plan_binding.path` 只记录所用计划路径。
36
36
  - 靠近量化后 EDL 拼接点一帧以内仅标 `EDIT_JOIN_CANDIDATE`。内部候选可以标所在已绑定 clip、估计原片秒数,但仍为 `UNKNOWN`;没有独立源片证据,不能认定是原片自带切镜。
@@ -1,41 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Public API and CLI entrypoint for the self-contained video-cut skill."""
2
+ """CLI entrypoint for the self-contained video-cut skill."""
3
3
 
4
4
  from cut_cli import main
5
- from cut_contract import (
6
- edited_source_render_cache_payload,
7
- normalize_clip_plan,
8
- normalize_multi_source_clip_plan,
9
- parse_duration_seconds,
10
- should_reuse_edited_source,
11
- )
12
- from cut_render import build_edited_source_video
13
- from media_geometry import VideoGeometry
14
- from narration_mapping import update_cut_qc
15
- from sentence_boundaries import (
16
- enforce_clip_sentence_boundaries,
17
- snap_clip_ends_to_lines,
18
- snap_clip_starts_to_lines,
19
- snap_clips_off_shot_changes,
20
- snap_multi_source_clips,
21
- )
22
5
 
23
- __all__ = [
24
- "VideoGeometry",
25
- "build_edited_source_video",
26
- "edited_source_render_cache_payload",
27
- "enforce_clip_sentence_boundaries",
28
- "main",
29
- "normalize_clip_plan",
30
- "normalize_multi_source_clip_plan",
31
- "parse_duration_seconds",
32
- "should_reuse_edited_source",
33
- "snap_clip_ends_to_lines",
34
- "snap_clip_starts_to_lines",
35
- "snap_clips_off_shot_changes",
36
- "snap_multi_source_clips",
37
- "update_cut_qc",
38
- ]
6
+ __all__ = ["main"]
39
7
 
40
8
  if __name__ == "__main__":
41
9
  main()