@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
@@ -2,15 +2,18 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import shutil
5
6
  from pathlib import Path
6
7
 
7
8
  import materials as material_lib
8
- import project_binding
9
- import resource_lock
9
+ import resources.project_binding as project_binding
10
+ import resources.lock as resource_lock
10
11
 
11
- from recap_cli import TTS_PROVIDERS, parse_args
12
+ from lib import TTS_PROVIDERS, file_identity, material_id_for, source_id_for
13
+ from recap_cli import parse_args
12
14
  from recap_review import run_narration_review
13
15
  from recap_runtime import (
16
+ RUN_MANIFEST,
14
17
  _analysis_settings,
15
18
  _build_multi_source_records,
16
19
  _coerce_videos,
@@ -26,18 +29,12 @@ from recap_runtime import (
26
29
  _write_run_manifest,
27
30
  )
28
31
  from recap_stage_qc import (
29
- _post_render_qc_metadata,
30
- _prepare_mimo_qc,
31
32
  _print_final_qc_pointer,
33
+ _print_render_warnings,
32
34
  _require_final_qc,
33
- _run_mimo_qc_stage,
34
- _tts_qc_metadata,
35
35
  _write_final_qc_reports,
36
- _write_shift_left_stage_qc,
37
36
  )
38
37
  from recap_source import (
39
- begin_local_adoption_qc,
40
- begin_non_narration_qc,
41
38
  extend_assemble_args,
42
39
  load_local_assembly_evidence,
43
40
  needs_voiceover,
@@ -58,6 +55,7 @@ from recap_timeline import (
58
55
  _materials_enabled,
59
56
  _multi_manifest_mismatches,
60
57
  _pause_for_agent,
58
+ _preflight_visual_overlays,
61
59
  _print_narration_review_pointer,
62
60
  _read_assembly_output,
63
61
  _read_phase_ledger,
@@ -71,8 +69,6 @@ from recap_timeline import (
71
69
  _write_phase_ledger,
72
70
  )
73
71
 
74
- RUN_MANIFEST = "recap_run_manifest.json"
75
-
76
72
 
77
73
  def _voiceover_args(work_dir, narration_path, args):
78
74
  """Build the shared full/cut invocation for video-voiceover."""
@@ -90,18 +86,11 @@ def _voiceover_args(work_dir, narration_path, args):
90
86
  return result
91
87
 
92
88
 
93
- def _approved_validation_args(args):
94
- return ["--preserve-approved-text"] if args.preserve_approved_text else []
95
-
96
-
97
89
  def _record_resources(work_dir, args):
98
90
  """Write resource_lock.json for the finished render and surface anything needing a person."""
99
- library_dir = getattr(args, "material_library_dir", None) or os.environ.get(
100
- "VIDEO_RECAP_MATERIAL_LIBRARY_DIR"
101
- )
102
91
  try:
103
92
  lock = resource_lock.write_resource_lock(
104
- work_dir, library_dir=library_dir, project=getattr(args, "resolved_project", None)
93
+ work_dir, library_dir=_material_library_dir(args), project=getattr(args, "resolved_project", None)
105
94
  )
106
95
  except (OSError, ValueError, TypeError, KeyError) as exc: # a record must never fail a finished render
107
96
  print(f"[video-recap] ⚠ 未能写出 resource_lock.json: {type(exc).__name__}: {exc}", flush=True)
@@ -114,6 +103,7 @@ def _finish_recap(work_dir, final_output, args):
114
103
  if args.require_final_qc:
115
104
  _require_final_qc(final_qc_result, work_dir)
116
105
  print(f"[video-recap] ✅ 完成: {final_output}")
106
+ _print_render_warnings(final_qc_result, work_dir, final_output)
117
107
  _print_final_qc_pointer(final_qc_result)
118
108
 
119
109
 
@@ -128,7 +118,6 @@ def _run_local_adoption(video, work_dir, args):
128
118
  (work_dir / RUN_MANIFEST).write_text(
129
119
  json.dumps(failed, ensure_ascii=False, indent=2), encoding="utf-8"
130
120
  )
131
- begin_local_adoption_qc(work_dir, _write_shift_left_stage_qc)
132
121
  assemble_args = [
133
122
  str(video),
134
123
  "--work-dir", str(work_dir),
@@ -159,16 +148,30 @@ def _run_local_adoption(video, work_dir, args):
159
148
  raise
160
149
  final_output = _read_assembly_output(work_dir)
161
150
  _record_resources(work_dir, args)
162
- _write_shift_left_stage_qc(
163
- work_dir,
164
- "post_render",
165
- metadata=_post_render_qc_metadata(work_dir, final_output),
166
- )
167
151
  _finish_recap(work_dir, final_output, args)
168
152
 
169
153
 
170
- def _run_or_restore_understanding(source_record, source_work_dir, args):
171
- """Run video-understanding for one source, or restore it from the material library."""
154
+ def _share_project_background_research(project_work_dir, source_work_dir):
155
+ """Give one source of a multi-video run the project-level background_research.json.
156
+
157
+ video-understanding only reads background_research.json from its own work_dir
158
+ (sources/<source_id>/), while the Agent writes the research once at the project level.
159
+ The project file is copied in when the source has none or an older one; a per-source
160
+ file newer than the project file is kept. copy2 keeps the mtime, so an unchanged
161
+ project file leaves the understanding cache identity unchanged."""
162
+ shared = Path(project_work_dir) / "background_research.json"
163
+ if not shared.is_file():
164
+ return
165
+ target = Path(source_work_dir) / "background_research.json"
166
+ if target.is_file() and target.stat().st_mtime_ns >= shared.stat().st_mtime_ns:
167
+ return
168
+ shutil.copy2(shared, target)
169
+
170
+
171
+ def _run_or_restore_understanding(source_record, source_work_dir, args, project_work_dir=None):
172
+ """Run video-understanding for one source, or restore it from the material library.
173
+
174
+ `project_work_dir` is set for multi-video runs, whose sources share its research file."""
172
175
  source_work_dir = Path(source_work_dir)
173
176
  source_work_dir.mkdir(parents=True, exist_ok=True)
174
177
  source_path = source_record["source_path"]
@@ -183,6 +186,7 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
183
186
  source_path=source_path,
184
187
  source_identity=source_identity,
185
188
  settings=settings,
189
+ material_id=source_record["material_id"],
186
190
  )
187
191
  restored = result["restored"]
188
192
  if restored:
@@ -196,6 +200,8 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
196
200
  flush=True,
197
201
  )
198
202
  if not restored:
203
+ if project_work_dir is not None:
204
+ _share_project_background_research(project_work_dir, source_work_dir)
199
205
  _run(
200
206
  "video-understanding",
201
207
  "understand.py",
@@ -222,14 +228,14 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
222
228
 
223
229
  def _single_source_record(video, args):
224
230
  """Identity + settings for the one source of a single-video run."""
225
- identity = material_lib.file_identity(video)
231
+ identity = file_identity(video)
226
232
  return {
227
- "source_id": material_lib.source_id_for(video),
233
+ "source_id": source_id_for(video),
228
234
  "source_path": str(video),
229
235
  "source_name": video.name,
230
236
  "source_video_identity": identity,
231
237
  "settings": _analysis_settings(args),
232
- "material_id": material_lib.material_id_for(video, identity),
238
+ "material_id": material_id_for(video, identity),
233
239
  }
234
240
 
235
241
 
@@ -247,6 +253,20 @@ def _rebuild_understanding_brief(source_record, source_work_dir, args):
247
253
  )
248
254
 
249
255
 
256
+ def _write_multi_source_edited_storyboard(work_dir, video):
257
+ """Cut pass 2 for several sources: the same OUTPUT-timeline storyboard a single-source
258
+ --brief-only rebuild produces, drawn from each source's frames, with its pointer added to
259
+ the multi-source brief. Advisory like every storyboard: a failure warns and the pause
260
+ still happens."""
261
+ try:
262
+ _run(
263
+ "video-understanding", "understand.py", str(video),
264
+ "--work-dir", str(work_dir), "--edited-storyboard-only",
265
+ )
266
+ except SystemExit as exc:
267
+ print(f"[video-recap] ⚠ 剪后故事板未生成(建议性,继续): {exc}", flush=True)
268
+
269
+
250
270
  def _reject_stale(mismatches, label):
251
271
  if mismatches:
252
272
  details = "\n - ".join(mismatches)
@@ -262,15 +282,14 @@ def _reject_stale_multi_manifest(work_dir, videos, args, source_records):
262
282
 
263
283
 
264
284
  def _render_cut(video_arg, work_dir, args, *extra):
265
- """Render edited_source.mp4 from clip_plan.json and record the post-cut QC stage."""
285
+ """Render edited_source.mp4 from clip_plan.json and print its cut QC line."""
266
286
  crender = [str(video_arg), "--work-dir", str(work_dir), *extra]
267
287
  if args.target_duration:
268
288
  crender += ["--target-duration", args.target_duration]
269
289
  if args.allow_duration_drift:
270
290
  crender.append("--allow-duration-drift")
271
291
  _run("video-cut", "cut.py", *crender)
272
- cut_qc = _surface_cut_qc(work_dir)
273
- _write_shift_left_stage_qc(work_dir, "post_cut", metadata={"cut_qc": cut_qc})
292
+ _surface_cut_qc(work_dir)
274
293
 
275
294
 
276
295
  def _reject_stale_cut_narration(work_dir, clip_plan_identity):
@@ -281,12 +300,11 @@ def _reject_stale_cut_narration(work_dir, clip_plan_identity):
281
300
  )
282
301
 
283
302
 
284
- def _validate_cut_output_narration(work_dir, args):
303
+ def _validate_cut_output_narration(work_dir):
285
304
  output_duration = _read_video_duration_or_raise(work_dir / "edited_source.mp4")
286
305
  _run(
287
306
  "video-script", "validate.py", "--work-dir", work_dir,
288
307
  "--mode", "cut_output", "--output-duration", f"{output_duration:.3f}",
289
- *_approved_validation_args(args),
290
308
  )
291
309
 
292
310
 
@@ -294,28 +312,21 @@ def _narrate(work_dir, args, timeline):
294
312
  """Review -> TTS -> visual overlays for a validated narration.json."""
295
313
  narration_json = work_dir / "narration.json"
296
314
  review_ran = run_narration_review(work_dir, args, run=_run, timeline=timeline)
297
- _write_shift_left_stage_qc(
298
- work_dir, "pre_tts", metadata={"review_ran": review_ran, "timeline": timeline}
299
- )
300
315
  _run(
301
316
  "video-voiceover", "voiceover.py",
302
317
  *_voiceover_args(work_dir, narration_json, args),
303
318
  )
304
- _write_shift_left_stage_qc(work_dir, "post_tts", metadata=_tts_qc_metadata(work_dir))
305
- overlays_path = _write_canonical_visual_overlays(work_dir, narration_json)
306
- _write_shift_left_stage_qc(
307
- work_dir, "pre_assemble", metadata={"visual_overlays": str(overlays_path)}
308
- )
309
- _run_mimo_qc_stage(work_dir, args, "pre_assemble")
319
+ _write_canonical_visual_overlays(work_dir, narration_json)
310
320
  return review_ran
311
321
 
312
322
 
313
323
  def _deliver(work_dir, args, assemble_video, recap_stem, timeline, extra_assemble_args=()):
314
324
  """Shared tail of every full/cut run: narration (if owned) -> assemble -> final QC."""
315
325
  project = getattr(args, "resolved_project", None)
316
- if project and project["templates"]:
326
+ if project and any(t["role"] in project_binding.GEOMETRY_ROLES for t in project["templates"]):
317
327
  project_binding.check_canvas(project, *_probe_display_size_or_raise(assemble_video))
318
328
  project_binding.sync_packaging_layers(work_dir, project)
329
+ _preflight_visual_overlays(work_dir, narration=uses_narration(args))
319
330
  review_ran = _narrate(work_dir, args, timeline) if uses_narration(args) else None
320
331
  aargs = [str(assemble_video), "--work-dir", str(work_dir), "--recap-stem", recap_stem]
321
332
  extend_assemble_args(aargs, args)
@@ -343,13 +354,6 @@ def _deliver(work_dir, args, assemble_video, recap_stem, timeline, extra_assembl
343
354
 
344
355
  final_output = _read_assembly_output(work_dir)
345
356
  _record_resources(work_dir, args)
346
- _write_shift_left_stage_qc(
347
- work_dir,
348
- "post_render",
349
- metadata=_post_render_qc_metadata(work_dir, final_output),
350
- )
351
- if uses_narration(args):
352
- _run_mimo_qc_stage(work_dir, args, "post_render", final_output=final_output)
353
357
  _finish_recap(work_dir, final_output, args)
354
358
  if uses_narration(args):
355
359
  _print_narration_review_pointer(work_dir, review_ran=review_ran)
@@ -362,6 +366,7 @@ def _run_multi_cut(videos, work_dir, args):
362
366
  work_dir.mkdir(parents=True, exist_ok=True)
363
367
  reject_unbound_narration_workdir(work_dir, args)
364
368
  reject_unsupported_subtitle_track(work_dir, args)
369
+ project_binding.sync_production_reference(work_dir, getattr(args, "resolved_project", None))
365
370
  source_records = _build_multi_source_records(videos, args)
366
371
  narration_json = work_dir / "narration.json"
367
372
  clip_plan_json = work_dir / "clip_plan.json"
@@ -371,12 +376,10 @@ def _run_multi_cut(videos, work_dir, args):
371
376
  if (work_dir / RUN_MANIFEST).exists():
372
377
  _reject_stale_multi_manifest(work_dir, videos, args, source_records)
373
378
  manifest_path = _write_multi_source_manifest(work_dir, source_records)
374
- if not uses_narration(args):
375
- begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
376
379
  if not clip_plan_json.exists():
377
380
  for record in source_records:
378
381
  _run_or_restore_understanding(
379
- record, _source_work_dir(work_dir, record), args
382
+ record, _source_work_dir(work_dir, record), args, project_work_dir=work_dir
380
383
  )
381
384
  # Rewrite: _run_or_restore_understanding fills in material_id per record.
382
385
  manifest_path = _write_multi_source_manifest(work_dir, source_records)
@@ -385,43 +388,38 @@ def _run_multi_cut(videos, work_dir, args):
385
388
  _pause_for_agent(
386
389
  work_dir,
387
390
  f"{clip_plan_json}(多视频剪辑计划;每个 clip 必须带 source_id)",
388
- _continuation_command(videos, work_dir, args),
391
+ _continuation_command(work_dir, args),
389
392
  inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
393
+ project=getattr(args, "resolved_project", None),
390
394
  )
391
395
  return
392
396
 
393
397
  _reject_stale_multi_manifest(work_dir, videos, args, source_records)
394
- cp_identity = material_lib.file_identity(clip_plan_json)
398
+ cp_identity = file_identity(clip_plan_json)
399
+ # A narration written for an older clip_plan aborts before cut.py re-renders anything.
400
+ if uses_narration(args) and narration_json.exists():
401
+ _reject_stale_cut_narration(work_dir, cp_identity)
395
402
  _render_cut(videos[0], work_dir, args, "--sources-manifest", str(manifest_path))
396
403
  if uses_narration(args):
397
404
  if not narration_json.exists():
398
405
  _write_multi_source_output_brief(
399
406
  work_dir, source_records, work_dir / "clip_plan_validated.json"
400
407
  )
401
- _write_phase_ledger(
402
- work_dir,
403
- clip_plan_identity=cp_identity,
404
- edited_source_rendered=True,
405
- multi_source=True,
406
- )
408
+ _write_multi_source_edited_storyboard(work_dir, videos[0])
409
+ _write_phase_ledger(work_dir, cp_identity)
407
410
  _pause_for_agent(
408
411
  work_dir,
409
412
  f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
410
- _continuation_command(videos, work_dir, args),
413
+ _continuation_command(work_dir, args),
411
414
  inspect_hint=(
412
415
  f"python3 {inspect_py} --work-dir {work_dir} "
413
416
  "clip-map --output-start <s> --output-end <e>"
414
417
  ),
418
+ project=getattr(args, "resolved_project", None),
415
419
  )
416
420
  return
417
- _reject_stale_cut_narration(work_dir, cp_identity)
418
- _write_phase_ledger(
419
- work_dir,
420
- clip_plan_identity=cp_identity,
421
- narration_written=True,
422
- multi_source=True,
423
- )
424
- _validate_cut_output_narration(work_dir, args)
421
+ _write_phase_ledger(work_dir, cp_identity)
422
+ _validate_cut_output_narration(work_dir)
425
423
 
426
424
  _deliver(work_dir, args, edited_source, f"multi_{videos[0].stem}", "cut_output")
427
425
 
@@ -447,12 +445,6 @@ def main():
447
445
  doctor_args += ["--tts-provider", args.tts_provider]
448
446
  _run("video-recap", "doctor.py", *doctor_args)
449
447
  return
450
- if not uses_local_adoption(args) and args.mimo_qc not in {
451
- "off", "pre-assemble", "post-render", "both"
452
- }:
453
- ap.error(
454
- "MIMO_QC/--mimo-qc must be one of: off, pre-assemble, post-render, both"
455
- )
456
448
  if not args.video:
457
449
  ap.error("video is required (unless --doctor)")
458
450
  validate_audio_routing(ap, args)
@@ -524,7 +516,7 @@ def main():
524
516
  ap.error("多视频 cut 暂不支持全局 subtitle Y 坐标;各源字幕带可能不同")
525
517
  if args.subtitle_y_top is not None:
526
518
  canvas_height = _probe_display_height_or_raise(
527
- videos[0], require_square_pixels=True
519
+ videos[0], require_near_square_pixels=True
528
520
  )
529
521
  if args.subtitle_y_bot > canvas_height:
530
522
  ap.error(
@@ -555,9 +547,8 @@ def _execute_pipeline(args, videos):
555
547
  if args.work_dir
556
548
  else videos[0].parent / f"work_dir_multi_{videos[0].stem}"
557
549
  )
558
- # Validate the work-directory audio policy before any run-local QC state is reset.
550
+ # Validate the work-directory audio policy before any stage writes into it.
559
551
  reject_unbound_narration_workdir(work_dir, args)
560
- _prepare_mimo_qc(work_dir, args)
561
552
  _run_multi_cut(videos, work_dir, args)
562
553
  return
563
554
 
@@ -570,7 +561,6 @@ def _execute_pipeline(args, videos):
570
561
  work_dir.mkdir(parents=True, exist_ok=True)
571
562
  reject_unbound_narration_workdir(work_dir, args)
572
563
  reject_unsupported_subtitle_track(work_dir, args)
573
- _prepare_mimo_qc(work_dir, args)
574
564
  if args.edit_mode == "dub":
575
565
  _run_dub(video, work_dir, args)
576
566
  else:
@@ -586,13 +576,10 @@ def _run_dub(video, work_dir, args):
586
576
  if not dub_script.exists():
587
577
  _run("video-voiceover", "dub.py", "--stage", "prepare", *dub_args)
588
578
  _write_run_manifest(work_dir, video, args)
589
- cont = _continuation_command(video, work_dir, args)
579
+ cont = _continuation_command(work_dir, args)
590
580
  print("=" * 50)
591
581
  print(
592
- f"[video-recap] ⏸ 阅读 {work_dir / 'dub_brief.md'},把英文原声转写切分并翻译成中文,写入 {dub_script}"
593
- )
594
- print(
595
- '[video-recap] 格式 [{"start": 起秒, "end": 止秒, "zh": "译文"}](按 start 升序);逐句忠实、跟原声节奏一致、保留原音色'
582
+ f"[video-recap] ⏸ 按 {work_dir / 'dub_brief.md'} 的要求翻译英文原声,写入 {dub_script}"
596
583
  )
597
584
  print(f"[video-recap] 写完后重跑继续: {cont}")
598
585
  print("=" * 50)
@@ -604,6 +591,7 @@ def _run_dub(video, work_dir, args):
604
591
 
605
592
  def _run_single(video, work_dir, args):
606
593
  """Single-video full or cut run; returns early at each agent pause."""
594
+ project_binding.sync_production_reference(work_dir, getattr(args, "resolved_project", None))
607
595
  cut = args.edit_mode == "cut"
608
596
  narration_json = work_dir / "narration.json"
609
597
  clip_plan_json = work_dir / "clip_plan.json"
@@ -619,8 +607,9 @@ def _run_single(video, work_dir, args):
619
607
  _pause_for_agent(
620
608
  work_dir,
621
609
  need_text,
622
- _continuation_command(video, work_dir, args),
610
+ _continuation_command(work_dir, args),
623
611
  inspect_hint=inspect_hint,
612
+ project=getattr(args, "resolved_project", None),
624
613
  )
625
614
 
626
615
  def _reject_stale_manifest():
@@ -631,20 +620,14 @@ def _run_single(video, work_dir, args):
631
620
  if uses_narration(args):
632
621
  if not narration_json.exists():
633
622
  _understand()
634
- _write_run_manifest(work_dir, video, args)
635
623
  _pause(f"{narration_json}", state_hint)
636
624
  return
637
625
  _reject_stale_manifest()
638
- _run(
639
- "video-script", "validate.py", "--work-dir", work_dir,
640
- "--mode", "full", *_approved_validation_args(args),
641
- )
626
+ _run("video-script", "validate.py", "--work-dir", work_dir, "--mode", "full")
642
627
  elif (work_dir / RUN_MANIFEST).exists():
643
628
  _reject_stale_manifest()
644
629
  else:
645
630
  _write_run_manifest(work_dir, video, args)
646
- if not uses_narration(args):
647
- begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
648
631
  _deliver(work_dir, args, video, video.stem, "source")
649
632
  return
650
633
 
@@ -654,41 +637,28 @@ def _run_single(video, work_dir, args):
654
637
  # PASS 1: understand -> agent writes clip_plan.json ONLY.
655
638
  if not uses_narration(args) and (work_dir / RUN_MANIFEST).exists():
656
639
  _reject_stale_manifest()
657
- if not uses_narration(args):
658
- begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
659
640
  _understand()
660
- _write_run_manifest(work_dir, video, args)
661
641
  _pause(f"{clip_plan_json}(只写剪辑计划;解说下一步对着剪好的成片写)", state_hint)
662
642
  return
663
643
  _reject_stale_manifest()
664
- if not uses_narration(args):
665
- begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
666
- cp_identity = material_lib.file_identity(clip_plan_json)
644
+ cp_identity = file_identity(clip_plan_json)
645
+ # A narration written for an older clip_plan aborts before cut.py re-renders anything.
646
+ if uses_narration(args) and narration_json.exists():
647
+ _reject_stale_cut_narration(work_dir, cp_identity)
667
648
  _render_cut(video, work_dir, args)
668
649
  if uses_narration(args):
669
650
  if not narration_json.exists():
670
651
  # PASS 2: rebuild the brief (now an OUTPUT-timeline variant) and pause for narration.
671
652
  _rebuild_understanding_brief(_single_source_record(video, args), work_dir, args)
672
- _write_phase_ledger(
673
- work_dir, clip_plan_identity=cp_identity, edited_source_rendered=True
674
- )
653
+ _write_phase_ledger(work_dir, cp_identity)
675
654
  _pause(
676
655
  f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
677
656
  f"python3 {inspect_py} --work-dir {work_dir} "
678
657
  "clip-map --output-start <s> --output-end <e> # 核对输出↔原片时间轴",
679
658
  )
680
659
  return
681
- _reject_stale_cut_narration(work_dir, cp_identity)
682
- _write_phase_ledger(work_dir, clip_plan_identity=cp_identity, narration_written=True)
683
- _validate_cut_output_narration(work_dir, args)
684
- else:
685
- _write_phase_ledger(
686
- work_dir,
687
- clip_plan_identity=cp_identity,
688
- edited_source_rendered=True,
689
- audio_mode=args.audio_mode,
690
- audio_stream_index=args.audio_stream_index,
691
- )
660
+ _write_phase_ledger(work_dir, cp_identity)
661
+ _validate_cut_output_narration(work_dir)
692
662
  # let the timeline / 剪映 export reference the original clips, not edited_source.mp4
693
663
  _deliver(
694
664
  work_dir, args, edited_source, video.stem, "cut_output",