@oh-story/dsh 0.1.9 → 0.1.11

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 (529) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +209 -73
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +248 -168
  5. package/lib/drama/skills/short-drama/SKILL.md +16 -4
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  10. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  11. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  12. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  13. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  14. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  15. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  16. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  17. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  18. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  19. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  20. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  21. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  22. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  23. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  25. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  26. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  27. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  28. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  29. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  30. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  31. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  32. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  33. package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  46. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  47. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  48. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  49. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  50. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
  51. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
  52. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
  53. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
  54. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
  55. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
  56. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
  57. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
  58. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  59. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  60. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  61. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  62. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  63. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  64. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  65. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  66. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  67. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  68. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  69. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  70. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  71. package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
  72. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  73. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  74. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  75. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  76. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
  77. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  78. package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
  79. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  80. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  81. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  82. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
  83. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
  84. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
  85. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  87. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  88. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
  89. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
  90. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
  91. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
  92. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  93. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  94. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  95. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
  96. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  97. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  98. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  99. package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
  100. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  101. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  102. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  103. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  104. package/lib/index.js +346 -98
  105. package/lib/index.js.map +4 -4
  106. package/lib/novel-to-game/README.md +299 -59
  107. package/lib/novel-to-game/README_ZH.md +299 -56
  108. package/lib/novel-to-game/VERSION +1 -1
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  118. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  119. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  120. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  121. package/lib/novel-to-game/manifest.json +77 -87
  122. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  123. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  124. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  125. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  126. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  127. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  128. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  129. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  130. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  131. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  132. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  133. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  134. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  135. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  136. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  137. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  138. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  139. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  140. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  141. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  142. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  143. package/lib/oh-story/manifest.json +607 -327
  144. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  145. package/lib/oh-story/roles/character-designer.md +1 -2
  146. package/lib/oh-story/roles/consistency-checker.md +15 -13
  147. package/lib/oh-story/roles/narrative-writer.md +36 -38
  148. package/lib/oh-story/roles/story-architect.md +9 -61
  149. package/lib/oh-story/roles/story-explorer.md +20 -125
  150. package/lib/oh-story/roles/story-researcher.md +0 -1
  151. package/lib/oh-story/skills/story/SKILL.md +15 -9
  152. package/lib/oh-story/skills/story/VERSION +1 -1
  153. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  154. package/lib/oh-story/skills/story/references/author-memory.md +46 -94
  155. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  157. package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
  158. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  159. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
  160. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  161. package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
  162. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  163. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
  164. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  165. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  166. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  167. package/lib/oh-story/skills/story-import/SKILL.md +64 -584
  168. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  169. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  170. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  171. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  172. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  173. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  174. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  175. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  176. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  177. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  180. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  181. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
  182. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  183. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
  184. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
  185. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
  186. package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
  187. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
  188. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
  189. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  190. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  191. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  192. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
  193. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  194. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  195. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  196. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  197. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  198. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  199. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
  200. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
  201. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  202. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  203. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  204. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  205. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  206. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  207. package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
  208. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  209. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  210. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  211. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  212. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  213. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  214. package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
  215. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  216. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
  217. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
  218. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  219. package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
  220. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  221. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
  222. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  223. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  224. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  225. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  226. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  227. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  228. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  229. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  230. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
  231. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  232. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  234. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  235. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  236. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  237. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  238. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  239. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  240. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  241. package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
  242. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
  243. package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
  244. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  245. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  246. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  247. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  248. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  249. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
  250. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
  251. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
  252. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  253. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
  254. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
  255. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  256. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
  257. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
  258. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  259. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  260. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
  261. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  262. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
  263. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  264. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
  265. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
  266. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
  267. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
  268. package/lib/oh-story/skills/story-review/SKILL.md +31 -302
  269. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  270. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
  271. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  272. package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
  273. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  274. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  275. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  276. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  277. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
  278. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  279. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  280. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  281. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  282. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
  283. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  284. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  285. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
  286. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
  287. package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
  288. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  289. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
  290. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  291. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  292. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  293. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
  294. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  295. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  296. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  297. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  298. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  299. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  300. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  301. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  302. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  303. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  304. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  305. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  306. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  307. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  308. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  309. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  310. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  311. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  312. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  313. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
  314. package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
  315. package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
  316. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  317. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  318. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  319. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  320. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  321. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  322. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  323. package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  324. package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
  325. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  326. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  327. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  328. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  329. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
  330. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  331. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  332. package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
  333. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  335. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  336. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  337. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  338. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  339. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
  340. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  341. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  342. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  343. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  344. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  345. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  346. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  347. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  348. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
  349. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
  350. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
  351. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  352. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  353. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  354. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  355. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  356. package/lib/types/client/file-activity.d.ts +19 -1
  357. package/lib/types/client/file-activity.d.ts.map +1 -1
  358. package/lib/types/client/index.d.ts.map +1 -1
  359. package/lib/types/client/production-prompts.d.ts +7 -0
  360. package/lib/types/client/production-prompts.d.ts.map +1 -1
  361. package/lib/types/client/production-runtime.d.ts +16 -0
  362. package/lib/types/client/production-runtime.d.ts.map +1 -1
  363. package/lib/types/client/video-studio.d.ts.map +1 -1
  364. package/lib/types/drama-adapters.d.ts +5 -3
  365. package/lib/types/drama-adapters.d.ts.map +1 -1
  366. package/lib/types/native-hooks.d.ts +53 -1
  367. package/lib/types/native-hooks.d.ts.map +1 -1
  368. package/lib/types/role-identity.d.ts +17 -0
  369. package/lib/types/role-identity.d.ts.map +1 -0
  370. package/lib/types/role-tool.d.ts.map +1 -1
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts.map +1 -1
  373. package/lib/types/workspace-route.d.ts.map +1 -1
  374. package/lib/video-recap/CHANGELOG.md +60 -1
  375. package/lib/video-recap/README.en.md +348 -137
  376. package/lib/video-recap/README.md +340 -136
  377. package/lib/video-recap/manifest.json +406 -251
  378. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  379. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  380. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  381. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  382. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  383. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  384. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  385. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  386. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  387. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  388. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  389. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  390. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  391. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  392. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  393. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  394. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  395. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  396. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  397. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  398. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  399. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  401. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  403. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  404. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  407. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  408. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  409. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  410. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  411. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  412. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  415. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  417. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  419. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  420. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  423. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  424. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  425. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  426. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  428. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  429. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  430. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  431. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  432. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  433. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  434. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  435. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  436. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  437. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  438. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  439. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  440. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  441. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  442. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  443. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  444. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  445. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  447. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  448. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  449. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  450. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  451. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  452. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  453. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  454. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  455. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  456. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  457. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  458. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  459. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  460. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  461. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  462. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  463. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  464. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  465. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  466. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  467. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  468. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  471. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  472. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  473. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  474. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  475. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  476. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  477. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  478. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  479. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  480. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  481. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  482. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  483. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  484. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  485. package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
  486. package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
  487. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  488. package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
  489. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  490. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  491. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  492. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  493. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  494. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  495. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  496. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  497. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  498. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  499. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  500. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  501. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  502. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  503. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  504. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  505. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  506. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  508. package/package.json +45 -45
  509. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  510. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  511. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  512. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
  513. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
  514. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  515. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
  516. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
  517. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  518. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  519. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  520. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  521. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  522. package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
  523. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  524. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
  525. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  526. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
  527. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  528. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  529. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -1,20 +1,24 @@
1
1
  """Orchestrate single- and multi-source recap pipelines."""
2
2
 
3
+ import json
3
4
  import os
4
5
  from pathlib import Path
5
6
 
6
7
  import materials as material_lib
8
+ import project_binding
9
+ import resource_lock
7
10
 
8
11
  from recap_cli import TTS_PROVIDERS, parse_args
9
12
  from recap_review import run_narration_review
10
13
  from recap_runtime import (
14
+ _analysis_settings,
11
15
  _build_multi_source_records,
12
16
  _coerce_videos,
13
17
  _entry,
14
- _material_settings_fingerprint,
15
18
  _optional_env_int,
16
19
  _preflight_burn_subtitles,
17
20
  _probe_display_height_or_raise,
21
+ _probe_display_size_or_raise,
18
22
  _read_video_duration_or_raise,
19
23
  _run,
20
24
  _write_multi_source_manifest,
@@ -25,15 +29,30 @@ from recap_stage_qc import (
25
29
  _post_render_qc_metadata,
26
30
  _prepare_mimo_qc,
27
31
  _print_final_qc_pointer,
32
+ _require_final_qc,
28
33
  _run_mimo_qc_stage,
29
34
  _tts_qc_metadata,
30
35
  _write_final_qc_reports,
31
36
  _write_shift_left_stage_qc,
32
37
  )
38
+ from recap_source import (
39
+ begin_local_adoption_qc,
40
+ begin_non_narration_qc,
41
+ extend_assemble_args,
42
+ load_local_assembly_evidence,
43
+ needs_voiceover,
44
+ owned_local_delivery,
45
+ reject_unbound_narration_workdir,
46
+ reject_unsupported_subtitle_track,
47
+ uses_local_adoption,
48
+ uses_narration,
49
+ validate_audio_routing,
50
+ verify_local_assembly_evidence,
51
+ remove_owned_local_delivery,
52
+ )
33
53
  from recap_timeline import (
34
54
  _continuation_command,
35
55
  _cut_narration_is_stale,
36
- _file_md5,
37
56
  _manifest_mismatches,
38
57
  _material_library_dir,
39
58
  _materials_enabled,
@@ -66,23 +85,104 @@ def _voiceover_args(work_dir, narration_path, args):
66
85
  result += ["--voice-ref", args.voice_ref]
67
86
  if args.allow_partial_tts:
68
87
  result.append("--allow-partial-tts")
88
+ if args.preserve_approved_text:
89
+ result.append("--preserve-approved-text")
69
90
  return result
70
91
 
71
92
 
93
+ def _approved_validation_args(args):
94
+ return ["--preserve-approved-text"] if args.preserve_approved_text else []
95
+
96
+
97
+ def _record_resources(work_dir, args):
98
+ """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
+ try:
103
+ lock = resource_lock.write_resource_lock(
104
+ work_dir, library_dir=library_dir, project=getattr(args, "resolved_project", None)
105
+ )
106
+ except (OSError, ValueError, TypeError, KeyError) as exc: # a record must never fail a finished render
107
+ print(f"[video-recap] ⚠ 未能写出 resource_lock.json: {type(exc).__name__}: {exc}", flush=True)
108
+ return
109
+ print(resource_lock.summary_line(lock), flush=True)
110
+
111
+
112
+ def _finish_recap(work_dir, final_output, args):
113
+ final_qc_result = _write_final_qc_reports(work_dir, final_output)
114
+ if args.require_final_qc:
115
+ _require_final_qc(final_qc_result, work_dir)
116
+ print(f"[video-recap] ✅ 完成: {final_output}")
117
+ _print_final_qc_pointer(final_qc_result)
118
+
119
+
120
+ def _run_local_adoption(video, work_dir, args):
121
+ """Run the strict local bundle directly through the authoritative assembler."""
122
+ work_dir.mkdir(parents=True, exist_ok=False)
123
+ manifest = _write_run_manifest(work_dir, video, args)
124
+ def record_failure(exc):
125
+ failed = {**manifest, "local_adoption_run": {
126
+ "status": "FAILED", "error_type": type(exc).__name__,
127
+ }}
128
+ (work_dir / RUN_MANIFEST).write_text(
129
+ json.dumps(failed, ensure_ascii=False, indent=2), encoding="utf-8"
130
+ )
131
+ begin_local_adoption_qc(work_dir, _write_shift_left_stage_qc)
132
+ assemble_args = [
133
+ str(video),
134
+ "--work-dir", str(work_dir),
135
+ "--recap-stem", video.stem,
136
+ "--output-dir", args.output_dir,
137
+ "--tts-meta", args.tts_meta,
138
+ "--narration-adoption", args.narration_adoption,
139
+ "--audio-mix-adoption", args.audio_mix_adoption,
140
+ ]
141
+ if args.burn_subtitles is not None:
142
+ assemble_args.append(
143
+ "--burn-subtitles" if args.burn_subtitles else "--no-burn-subtitles"
144
+ )
145
+ if args.subtitle_y_top is not None:
146
+ assemble_args += [
147
+ "--subtitle-y-top", str(args.subtitle_y_top),
148
+ "--subtitle-y-bot", str(args.subtitle_y_bot),
149
+ ]
150
+ owned_delivery = None
151
+ try:
152
+ _run("video-assemble", "assemble.py", *assemble_args)
153
+ evidence = load_local_assembly_evidence(work_dir)
154
+ owned_delivery = owned_local_delivery(evidence)
155
+ verify_local_assembly_evidence(evidence, manifest)
156
+ except BaseException as exc:
157
+ remove_owned_local_delivery(owned_delivery)
158
+ record_failure(exc)
159
+ raise
160
+ final_output = _read_assembly_output(work_dir)
161
+ _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
+ _finish_recap(work_dir, final_output, args)
168
+
169
+
72
170
  def _run_or_restore_understanding(source_record, source_work_dir, args):
73
171
  """Run video-understanding for one source, or restore it from the material library."""
74
172
  source_work_dir = Path(source_work_dir)
75
173
  source_work_dir.mkdir(parents=True, exist_ok=True)
76
- source_fp = source_record["source_video_fingerprint"]
77
- settings_fp = source_record["settings_fingerprint"]
174
+ source_path = source_record["source_path"]
175
+ source_identity = source_record["source_video_identity"]
176
+ settings = source_record["settings"]
78
177
  lib_dir = _material_library_dir(args)
79
178
  restored = False
80
179
  if _materials_enabled(args):
81
180
  result = material_lib.restore_material(
82
181
  lib_dir,
83
182
  source_work_dir,
84
- source_fingerprint=source_fp,
85
- settings_fp=settings_fp,
183
+ source_path=source_path,
184
+ source_identity=source_identity,
185
+ settings=settings,
86
186
  )
87
187
  restored = result["restored"]
88
188
  if restored:
@@ -95,22 +195,20 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
95
195
  f"[video-recap] 素材库未复用 {source_record['source_name']}: {result['reason']}",
96
196
  flush=True,
97
197
  )
98
-
99
198
  if not restored:
100
199
  _run(
101
200
  "video-understanding",
102
201
  "understand.py",
103
202
  *_understand_args_for_source(source_record, source_work_dir, args),
104
203
  )
105
-
106
204
  _write_run_manifest(source_work_dir, source_record["source_path"], args)
107
205
  if _save_materials_enabled(args):
108
206
  meta = material_lib.save_material(
109
207
  lib_dir,
110
208
  source_work_dir,
111
- source_record["source_path"],
112
- source_fp,
113
- settings_fp,
209
+ source_path,
210
+ source_identity,
211
+ settings,
114
212
  source_id=source_record["source_id"],
115
213
  material_id=source_record["material_id"],
116
214
  )
@@ -124,20 +222,19 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
124
222
 
125
223
  def _single_source_record(video, args):
126
224
  """Identity + settings for the one source of a single-video run."""
127
- fp = material_lib.file_fingerprint(video)
225
+ identity = material_lib.file_identity(video)
128
226
  return {
129
- "source_id": material_lib.source_id_from_fingerprint(fp),
227
+ "source_id": material_lib.source_id_for(video),
130
228
  "source_path": str(video),
131
229
  "source_name": video.name,
132
- "source_video_fingerprint": fp,
133
- "settings_fingerprint": _material_settings_fingerprint(args),
134
- "material_id": material_lib.material_id_for(video, fp),
230
+ "source_video_identity": identity,
231
+ "settings": _analysis_settings(args),
232
+ "material_id": material_lib.material_id_for(video, identity),
135
233
  }
136
234
 
137
235
 
138
236
  def _rebuild_understanding_brief(source_record, source_work_dir, args):
139
237
  """Rebuild agent_narration_brief.md from cached/restored analysis only.
140
-
141
238
  Cut pass 2 needs an OUTPUT-time brief after edited_source.mp4 exists. A
142
239
  material restore may have supplied pass-1 analysis artifacts (and even a
143
240
  source-time brief), but it must not skip this phase-specific brief rebuild.
@@ -150,147 +247,93 @@ def _rebuild_understanding_brief(source_record, source_work_dir, args):
150
247
  )
151
248
 
152
249
 
153
- def _reject_stale_multi_manifest(work_dir, videos, args, source_records):
154
- mismatches = _multi_manifest_mismatches(work_dir, videos, args, source_records)
250
+ def _reject_stale(mismatches, label):
155
251
  if mismatches:
156
252
  details = "\n - ".join(mismatches)
157
253
  raise SystemExit(
158
- "work_dir 与当前多视频 recap 输入不匹配,拒绝复用既有 narration/clip_plan;"
254
+ f"work_dir 与当前{label}recap 输入不匹配,拒绝复用既有 narration/clip_plan;"
159
255
  "请使用新的 --work-dir,或删除旧产物后重新运行 Phase A。\n"
160
256
  f" - {details}"
161
257
  )
162
258
 
163
259
 
164
- def _run_multi_cut(videos, work_dir, args):
165
- """Multi-video MVP: cut-first/narrate-second over a project work_dir."""
166
- videos = _coerce_videos(videos)
167
- work_dir = Path(work_dir)
168
- work_dir.mkdir(parents=True, exist_ok=True)
169
- source_records = _build_multi_source_records(videos, args)
170
- narration_json = work_dir / "narration.json"
171
- clip_plan_json = work_dir / "clip_plan.json"
172
- edited_source = work_dir / "edited_source.mp4"
173
- inspect_py = _entry("video-recap", "recap_inspect.py")
260
+ def _reject_stale_multi_manifest(work_dir, videos, args, source_records):
261
+ _reject_stale(_multi_manifest_mismatches(work_dir, videos, args, source_records), "多视频 ")
174
262
 
175
- # If a project manifest already exists, it must match before any Phase-B reuse.
176
- if (work_dir / RUN_MANIFEST).exists():
177
- _reject_stale_multi_manifest(work_dir, videos, args, source_records)
178
- manifest_path = _write_multi_source_manifest(work_dir, source_records)
179
263
 
180
- if not clip_plan_json.exists():
181
- for record in source_records:
182
- _run_or_restore_understanding(
183
- record, _source_work_dir(work_dir, record), args
184
- )
185
- # Rewrite: _run_or_restore_understanding fills in material_id per record.
186
- manifest_path = _write_multi_source_manifest(work_dir, source_records)
187
- _write_project_run_manifest(work_dir, videos, args, source_records)
188
- _write_multi_source_clip_brief(work_dir, source_records, args)
189
- _pause_for_agent(
190
- work_dir,
191
- f"{clip_plan_json}(多视频剪辑计划;每个 clip 必须带 source_id)",
192
- _continuation_command(videos, work_dir, args),
193
- inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
194
- )
195
- return
196
-
197
- _reject_stale_multi_manifest(work_dir, videos, args, source_records)
198
- cp_fp = _file_md5(clip_plan_json)
199
- crender = [
200
- str(videos[0]),
201
- "--work-dir",
202
- str(work_dir),
203
- "--sources-manifest",
204
- str(manifest_path),
205
- "--no-narration-map",
206
- ]
264
+ 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."""
266
+ crender = [str(video_arg), "--work-dir", str(work_dir), *extra]
207
267
  if args.target_duration:
208
268
  crender += ["--target-duration", args.target_duration]
209
269
  if args.allow_duration_drift:
210
270
  crender.append("--allow-duration-drift")
211
- if args.allow_sparse_cut:
212
- crender.append("--allow-sparse-cut")
213
271
  _run("video-cut", "cut.py", *crender)
214
272
  cut_qc = _surface_cut_qc(work_dir)
215
273
  _write_shift_left_stage_qc(work_dir, "post_cut", metadata={"cut_qc": cut_qc})
216
- if not narration_json.exists():
217
- _write_multi_source_output_brief(
218
- work_dir, source_records, work_dir / "clip_plan_validated.json"
219
- )
220
- _write_phase_ledger(
221
- work_dir,
222
- clip_plan_fingerprint=cp_fp,
223
- edited_source_rendered=True,
224
- multi_source=True,
225
- )
226
- _pause_for_agent(
227
- work_dir,
228
- f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
229
- _continuation_command(videos, work_dir, args),
230
- inspect_hint=(
231
- f"python3 {inspect_py} --work-dir {work_dir} "
232
- "clip-map --output-start <s> --output-end <e>"
233
- ),
234
- )
235
- return
236
- if _cut_narration_is_stale(_read_phase_ledger(work_dir), cp_fp):
274
+
275
+
276
+ def _reject_stale_cut_narration(work_dir, clip_plan_identity):
277
+ if _cut_narration_is_stale(_read_phase_ledger(work_dir), clip_plan_identity):
237
278
  raise SystemExit(
238
279
  "clip_plan.json 已改变,但 narration.json 仍是对旧剪辑写的,会与剪后画面对不上。"
239
280
  "请删除 narration.json,重跑后按新成片重新写解说。"
240
281
  )
241
- _write_phase_ledger(
242
- work_dir,
243
- clip_plan_fingerprint=cp_fp,
244
- narration_fingerprint=_file_md5(narration_json),
245
- narration_written=True,
246
- multi_source=True,
247
- )
248
- output_duration = _read_video_duration_or_raise(edited_source)
282
+
283
+
284
+ def _validate_cut_output_narration(work_dir, args):
285
+ output_duration = _read_video_duration_or_raise(work_dir / "edited_source.mp4")
249
286
  _run(
250
- "video-script",
251
- "validate.py",
252
- "--work-dir",
253
- work_dir,
254
- "--mode",
255
- "cut_output",
256
- "--output-duration",
257
- f"{output_duration:.3f}",
287
+ "video-script", "validate.py", "--work-dir", work_dir,
288
+ "--mode", "cut_output", "--output-duration", f"{output_duration:.3f}",
289
+ *_approved_validation_args(args),
258
290
  )
259
- review_ran = run_narration_review(work_dir, args, run=_run, timeline="cut_output")
291
+
292
+
293
+ def _narrate(work_dir, args, timeline):
294
+ """Review -> TTS -> visual overlays for a validated narration.json."""
295
+ narration_json = work_dir / "narration.json"
296
+ review_ran = run_narration_review(work_dir, args, run=_run, timeline=timeline)
260
297
  _write_shift_left_stage_qc(
261
- work_dir,
262
- "pre_tts",
263
- metadata={"review_ran": review_ran, "timeline": "cut_output"},
298
+ work_dir, "pre_tts", metadata={"review_ran": review_ran, "timeline": timeline}
264
299
  )
265
300
  _run(
266
- "video-voiceover",
267
- "voiceover.py",
301
+ "video-voiceover", "voiceover.py",
268
302
  *_voiceover_args(work_dir, narration_json, args),
269
303
  )
270
- _write_shift_left_stage_qc(
271
- work_dir, "post_tts", metadata=_tts_qc_metadata(work_dir)
272
- )
304
+ _write_shift_left_stage_qc(work_dir, "post_tts", metadata=_tts_qc_metadata(work_dir))
273
305
  overlays_path = _write_canonical_visual_overlays(work_dir, narration_json)
274
306
  _write_shift_left_stage_qc(
275
307
  work_dir, "pre_assemble", metadata={"visual_overlays": str(overlays_path)}
276
308
  )
277
309
  _run_mimo_qc_stage(work_dir, args, "pre_assemble")
278
-
279
- recap_stem = f"multi_{videos[0].stem}"
280
- aargs = [
281
- str(edited_source),
282
- "--work-dir",
283
- str(work_dir),
284
- "--recap-stem",
285
- recap_stem,
286
- ]
310
+ return review_ran
311
+
312
+
313
+ def _deliver(work_dir, args, assemble_video, recap_stem, timeline, extra_assemble_args=()):
314
+ """Shared tail of every full/cut run: narration (if owned) -> assemble -> final QC."""
315
+ project = getattr(args, "resolved_project", None)
316
+ if project and project["templates"]:
317
+ project_binding.check_canvas(project, *_probe_display_size_or_raise(assemble_video))
318
+ project_binding.sync_packaging_layers(work_dir, project)
319
+ review_ran = _narrate(work_dir, args, timeline) if uses_narration(args) else None
320
+ aargs = [str(assemble_video), "--work-dir", str(work_dir), "--recap-stem", recap_stem]
321
+ extend_assemble_args(aargs, args)
287
322
  if args.output_dir:
288
323
  aargs += ["--output-dir", args.output_dir]
289
324
  if args.burn_subtitles is not None:
290
325
  aargs.append(
291
326
  "--burn-subtitles" if args.burn_subtitles else "--no-burn-subtitles"
292
327
  )
293
- if args.export_jianying:
328
+ if args.subtitle_y_top is not None:
329
+ aargs += [
330
+ "--subtitle-y-top", str(args.subtitle_y_top),
331
+ "--subtitle-y-bot", str(args.subtitle_y_bot),
332
+ ]
333
+ # env-only burn intent (BURN_SUBTITLES) is propagated implicitly: assemble re-derives it
334
+ # via the same env_bool default the preflight used, so the two agree by shared env.
335
+ aargs += extra_assemble_args
336
+ if args.export_jianying: # env EXPORT_JIANYING is honored by assemble.py itself
294
337
  aargs.append("--export-jianying")
295
338
  if args.jianying_bundle_media:
296
339
  aargs.append("--jianying-bundle-media")
@@ -299,42 +342,138 @@ def _run_multi_cut(videos, work_dir, args):
299
342
  _run("video-assemble", "assemble.py", *aargs)
300
343
 
301
344
  final_output = _read_assembly_output(work_dir)
345
+ _record_resources(work_dir, args)
302
346
  _write_shift_left_stage_qc(
303
347
  work_dir,
304
348
  "post_render",
305
349
  metadata=_post_render_qc_metadata(work_dir, final_output),
306
350
  )
307
- _run_mimo_qc_stage(work_dir, args, "post_render", final_output=final_output)
308
- final_qc_result = _write_final_qc_reports(work_dir, final_output)
309
- print(f"[video-recap] ✅ 完成: {final_output}")
310
- _print_final_qc_pointer(final_qc_result)
311
- _print_narration_review_pointer(work_dir, review_ran=review_ran)
351
+ if uses_narration(args):
352
+ _run_mimo_qc_stage(work_dir, args, "post_render", final_output=final_output)
353
+ _finish_recap(work_dir, final_output, args)
354
+ if uses_narration(args):
355
+ _print_narration_review_pointer(work_dir, review_ran=review_ran)
356
+
357
+
358
+ def _run_multi_cut(videos, work_dir, args):
359
+ """Multi-video MVP: cut-first/narrate-second over a project work_dir."""
360
+ videos = _coerce_videos(videos)
361
+ work_dir = Path(work_dir)
362
+ work_dir.mkdir(parents=True, exist_ok=True)
363
+ reject_unbound_narration_workdir(work_dir, args)
364
+ reject_unsupported_subtitle_track(work_dir, args)
365
+ source_records = _build_multi_source_records(videos, args)
366
+ narration_json = work_dir / "narration.json"
367
+ clip_plan_json = work_dir / "clip_plan.json"
368
+ edited_source = work_dir / "edited_source.mp4"
369
+ inspect_py = _entry("video-recap", "recap_inspect.py")
370
+ # If a project manifest already exists, it must match before any Phase-B reuse.
371
+ if (work_dir / RUN_MANIFEST).exists():
372
+ _reject_stale_multi_manifest(work_dir, videos, args, source_records)
373
+ 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
+ if not clip_plan_json.exists():
377
+ for record in source_records:
378
+ _run_or_restore_understanding(
379
+ record, _source_work_dir(work_dir, record), args
380
+ )
381
+ # Rewrite: _run_or_restore_understanding fills in material_id per record.
382
+ manifest_path = _write_multi_source_manifest(work_dir, source_records)
383
+ _write_project_run_manifest(work_dir, videos, args, source_records)
384
+ _write_multi_source_clip_brief(work_dir, source_records, args)
385
+ _pause_for_agent(
386
+ work_dir,
387
+ f"{clip_plan_json}(多视频剪辑计划;每个 clip 必须带 source_id)",
388
+ _continuation_command(videos, work_dir, args),
389
+ inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
390
+ )
391
+ return
392
+
393
+ _reject_stale_multi_manifest(work_dir, videos, args, source_records)
394
+ cp_identity = material_lib.file_identity(clip_plan_json)
395
+ _render_cut(videos[0], work_dir, args, "--sources-manifest", str(manifest_path))
396
+ if uses_narration(args):
397
+ if not narration_json.exists():
398
+ _write_multi_source_output_brief(
399
+ work_dir, source_records, work_dir / "clip_plan_validated.json"
400
+ )
401
+ _write_phase_ledger(
402
+ work_dir,
403
+ clip_plan_identity=cp_identity,
404
+ edited_source_rendered=True,
405
+ multi_source=True,
406
+ )
407
+ _pause_for_agent(
408
+ work_dir,
409
+ f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
410
+ _continuation_command(videos, work_dir, args),
411
+ inspect_hint=(
412
+ f"python3 {inspect_py} --work-dir {work_dir} "
413
+ "clip-map --output-start <s> --output-end <e>"
414
+ ),
415
+ )
416
+ 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)
425
+
426
+ _deliver(work_dir, args, edited_source, f"multi_{videos[0].stem}", "cut_output")
312
427
 
313
428
 
314
429
  def main():
315
430
  ap, args = parse_args()
316
431
 
317
- # argparse does not validate environment-derived defaults against choices.
318
- if args.mimo_qc not in {"off", "pre-assemble", "post-render", "both"}:
319
- ap.error(
320
- "MIMO_QC/--mimo-qc must be one of: off, pre-assemble, post-render, both"
321
- )
322
- if args.tts_provider not in TTS_PROVIDERS:
323
- ap.error(
324
- "TTS_PROVIDER/--tts-provider must be one of: auto, mimo-tts, fish-audio"
325
- )
432
+ if args.require_final_qc and args.edit_mode == "dub":
433
+ ap.error("--require-final-qc is only supported in full/cut modes, not dub")
434
+ # argparse `choices` does not cover the TTS_PROVIDER env default; source-owned audio and
435
+ # local adoption never synthesise, so an ambient provider is irrelevant there.
436
+ if (args.doctor or needs_voiceover(args)) and args.tts_provider not in TTS_PROVIDERS:
437
+ ap.error("TTS_PROVIDER/--tts-provider must be one of: " + ", ".join(TTS_PROVIDERS))
326
438
 
327
439
  if args.doctor:
440
+ if any(
441
+ getattr(args, field) is not None
442
+ for field in ("tts_meta", "narration_adoption", "audio_mix_adoption")
443
+ ):
444
+ ap.error("--doctor cannot be combined with local adoption inputs")
328
445
  doctor_args = []
329
446
  if args.tts_provider != "auto":
330
447
  doctor_args += ["--tts-provider", args.tts_provider]
331
448
  _run("video-recap", "doctor.py", *doctor_args)
332
449
  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
+ )
333
456
  if not args.video:
334
457
  ap.error("video is required (unless --doctor)")
458
+ validate_audio_routing(ap, args)
459
+ args.resolved_project = None
460
+ if args.project:
461
+ args.project = str(project_binding.project_path(args.project))
462
+ if uses_local_adoption(args) or args.edit_mode == "dub":
463
+ ap.error("--project applies to full/cut runs, not dub or local adoption bundles")
464
+ project_binding.apply_project(
465
+ project_binding.resolve_project(
466
+ args.project, args, include_voice=needs_voiceover(args)
467
+ ),
468
+ args,
469
+ )
335
470
 
336
- if args.voice_ref is None:
471
+ if needs_voiceover(args) and args.voice_ref is None:
337
472
  args.voice_ref = os.environ.get("VOICE_REF", "").strip() or None
473
+ elif not needs_voiceover(args):
474
+ # Source-owned audio must not inherit ambient narration configuration. Explicit
475
+ # narration flags were already rejected by validate_audio_routing().
476
+ args.voice_ref = None
338
477
  try:
339
478
  if args.subtitle_y_top is None:
340
479
  args.subtitle_y_top = _optional_env_int("SUBTITLE_Y_TOP")
@@ -343,15 +482,18 @@ def main():
343
482
  except ValueError as exc:
344
483
  ap.error(str(exc))
345
484
 
346
- explicit_mimo_voice = (
347
- args.mimo_tts_voice or os.environ.get("MIMO_TTS_VOICE", "").strip()
348
- )
349
- if explicit_mimo_voice and args.voice_ref:
350
- ap.error("--mimo-tts-voice and --voice-ref are mutually exclusive")
351
- if args.tts_provider == "fish-audio" and (explicit_mimo_voice or args.voice_ref):
352
- ap.error(
353
- "--mimo-tts-voice/--voice-ref are only supported by --tts-provider mimo-tts"
485
+ if needs_voiceover(args):
486
+ explicit_mimo_voice = (
487
+ args.mimo_tts_voice or os.environ.get("MIMO_TTS_VOICE", "").strip()
354
488
  )
489
+ if explicit_mimo_voice and args.voice_ref:
490
+ ap.error("--mimo-tts-voice and --voice-ref are mutually exclusive")
491
+ if args.tts_provider in {"fish-audio", "index-tts"} and (
492
+ explicit_mimo_voice or args.voice_ref
493
+ ):
494
+ ap.error(
495
+ "--mimo-tts-voice/--voice-ref are only supported by --tts-provider mimo-tts"
496
+ )
355
497
  if args.edit_mode == "dub" and args.voice_ref:
356
498
  ap.error(
357
499
  "--voice-ref is only supported in full/cut modes; dub clones the source voice automatically"
@@ -365,6 +507,8 @@ def main():
365
507
  ap.error(
366
508
  "--subtitle-y-top/--subtitle-y-bot are only supported in full/cut modes"
367
509
  )
510
+ if args.edit_mode == "dub" and args.burn_subtitles:
511
+ ap.error("--burn-subtitles is only supported in full/cut modes; dub never burns subtitles")
368
512
  if (args.subtitle_y_top is None) != (args.subtitle_y_bot is None):
369
513
  ap.error("--subtitle-y-top and --subtitle-y-bot must be provided together")
370
514
  if args.subtitle_y_top is not None:
@@ -393,16 +537,26 @@ def main():
393
537
  ap.error(f"reference audio does not exist or is not a file: {voice_ref}")
394
538
  args.voice_ref = str(voice_ref)
395
539
 
540
+ _execute_pipeline(args, videos)
541
+
542
+
543
+ def _execute_pipeline(args, videos):
396
544
  # Fail fast before any expensive understanding/VLM/ASR/TTS work if the run will burn
397
545
  # subtitles but this ffmpeg can't (otherwise it only blows up at the final render).
398
546
  _preflight_burn_subtitles(args)
399
547
 
548
+ if uses_local_adoption(args):
549
+ _run_local_adoption(videos[0], Path(args.work_dir), args)
550
+ return
551
+
400
552
  if len(videos) > 1:
401
553
  work_dir = (
402
554
  Path(args.work_dir).resolve()
403
555
  if args.work_dir
404
556
  else videos[0].parent / f"work_dir_multi_{videos[0].stem}"
405
557
  )
558
+ # Validate the work-directory audio policy before any run-local QC state is reset.
559
+ reject_unbound_narration_workdir(work_dir, args)
406
560
  _prepare_mimo_qc(work_dir, args)
407
561
  _run_multi_cut(videos, work_dir, args)
408
562
  return
@@ -414,24 +568,53 @@ def main():
414
568
  else video.parent / f"work_dir_{video.stem}"
415
569
  )
416
570
  work_dir.mkdir(parents=True, exist_ok=True)
571
+ reject_unbound_narration_workdir(work_dir, args)
572
+ reject_unsupported_subtitle_track(work_dir, args)
417
573
  _prepare_mimo_qc(work_dir, args)
574
+ if args.edit_mode == "dub":
575
+ _run_dub(video, work_dir, args)
576
+ else:
577
+ _run_single(video, work_dir, args)
578
+
579
+
580
+ def _run_dub(video, work_dir, args):
581
+ """EN->ZH translation-dub in the original cloned voice (replaces speech, not overlay).
582
+ One pause: prepare (ASR + sentence-seg + reference) -> agent writes the Chinese
583
+ translation (dub_script.json) -> render (clone TTS + full-replace mux)."""
584
+ dub_script = work_dir / "dub_script.json"
585
+ dub_args = ["--video", str(video), "--work-dir", str(work_dir)]
586
+ if not dub_script.exists():
587
+ _run("video-voiceover", "dub.py", "--stage", "prepare", *dub_args)
588
+ _write_run_manifest(work_dir, video, args)
589
+ cont = _continuation_command(video, work_dir, args)
590
+ print("=" * 50)
591
+ print(
592
+ f"[video-recap] ⏸ 阅读 {work_dir / 'dub_brief.md'},把英文原声转写切分并翻译成中文,写入 {dub_script}"
593
+ )
594
+ print(
595
+ '[video-recap] 格式 [{"start": 起秒, "end": 止秒, "zh": "译文"}](按 start 升序);逐句忠实、跟原声节奏一致、保留原音色'
596
+ )
597
+ print(f"[video-recap] 写完后重跑继续: {cont}")
598
+ print("=" * 50)
599
+ return
600
+ _reject_stale(_manifest_mismatches(work_dir, video, args), " ")
601
+ _run("video-voiceover", "dub.py", "--stage", "render", *dub_args)
602
+ print(f"[video-recap] ✅ 配音完成: {work_dir / ('dub_' + video.stem + '.mp4')}")
603
+
604
+
605
+ def _run_single(video, work_dir, args):
606
+ """Single-video full or cut run; returns early at each agent pause."""
418
607
  cut = args.edit_mode == "cut"
419
608
  narration_json = work_dir / "narration.json"
420
609
  clip_plan_json = work_dir / "clip_plan.json"
421
-
422
610
  edited_source = work_dir / "edited_source.mp4"
611
+ inspect_py = _entry("video-recap", "recap_inspect.py")
612
+ state_hint = f"python3 {inspect_py} --work-dir {work_dir} state"
423
613
 
424
614
  def _understand():
425
- source_record = _single_source_record(video, args)
426
- _run_or_restore_understanding(source_record, work_dir, args)
427
- return source_record
615
+ _run_or_restore_understanding(_single_source_record(video, args), work_dir, args)
428
616
 
429
- def _rebuild_output_brief():
430
- _rebuild_understanding_brief(_single_source_record(video, args), work_dir, args)
431
-
432
- inspect_py = _entry("video-recap", "recap_inspect.py")
433
-
434
- def _pause(need_text, inspect_hint=None):
617
+ def _pause(need_text, inspect_hint):
435
618
  # Same banner as the multi-source flow; only the continuation command differs.
436
619
  _pause_for_agent(
437
620
  work_dir,
@@ -441,203 +624,73 @@ def main():
441
624
  )
442
625
 
443
626
  def _reject_stale_manifest():
444
- mismatches = _manifest_mismatches(work_dir, video, args)
445
- if mismatches:
446
- details = "\n - ".join(mismatches)
447
- raise SystemExit(
448
- "work_dir 与当前 recap 输入不匹配,拒绝复用既有 narration/clip_plan;"
449
- "请使用新的 --work-dir,或删除旧产物后重新运行 Phase A。\n"
450
- f" - {details}"
451
- )
627
+ _reject_stale(_manifest_mismatches(work_dir, video, args), " ")
452
628
 
453
- if args.edit_mode == "dub":
454
- # Dub mode: EN→ZH translation-dub in the original cloned voice (replaces speech, not
455
- # overlay). One pause: prepare (ASR + sentence-seg + reference) -> agent writes the
456
- # Chinese translation (dub_script.json) -> render (clone TTS + full-replace mux).
457
- dub_script = work_dir / "dub_script.json"
458
- if not dub_script.exists():
629
+ if not cut:
630
+ # Full mode: a single pause (understand -> agent writes narration.json -> produce).
631
+ if uses_narration(args):
632
+ if not narration_json.exists():
633
+ _understand()
634
+ _write_run_manifest(work_dir, video, args)
635
+ _pause(f"{narration_json}", state_hint)
636
+ return
637
+ _reject_stale_manifest()
459
638
  _run(
460
- "video-voiceover",
461
- "dub.py",
462
- "--stage",
463
- "prepare",
464
- "--video",
465
- str(video),
466
- "--work-dir",
467
- str(work_dir),
639
+ "video-script", "validate.py", "--work-dir", work_dir,
640
+ "--mode", "full", *_approved_validation_args(args),
468
641
  )
642
+ elif (work_dir / RUN_MANIFEST).exists():
643
+ _reject_stale_manifest()
644
+ else:
469
645
  _write_run_manifest(work_dir, video, args)
470
- cont = _continuation_command(video, work_dir, args)
471
- print("=" * 50)
472
- print(
473
- f"[video-recap] ⏸ 阅读 {work_dir / 'dub_brief.md'},把英文原声转写切分并翻译成中文,写入 {dub_script}"
474
- )
475
- print(
476
- '[video-recap] 格式 [{"start": 起秒, "end": 止秒, "zh": "译文"}](按 start 升序);逐句忠实、跟原声节奏一致、保留原音色'
477
- )
478
- print(f"[video-recap] 写完后重跑继续: {cont}")
479
- print("=" * 50)
480
- return
481
- _reject_stale_manifest()
482
- _run(
483
- "video-voiceover",
484
- "dub.py",
485
- "--stage",
486
- "render",
487
- "--video",
488
- str(video),
489
- "--work-dir",
490
- str(work_dir),
491
- )
492
- print(f"[video-recap] ✅ 配音完成: {work_dir / ('dub_' + video.stem + '.mp4')}")
646
+ if not uses_narration(args):
647
+ begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
648
+ _deliver(work_dir, args, video, video.stem, "source")
493
649
  return
494
650
 
495
- if not cut:
496
- # Full mode: a single pause (understand -> agent writes narration.json -> produce).
497
- if not narration_json.exists():
498
- _understand()
499
- _write_run_manifest(work_dir, video, args)
500
- _pause(
501
- f"{narration_json}",
502
- inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
503
- )
504
- return
505
- _reject_stale_manifest()
506
- _run("video-script", "validate.py", "--work-dir", work_dir, "--mode", "full")
507
- narration_for_tts = narration_json
508
- assemble_video_path = video
509
- else:
510
- # Cut mode: cut-first / narrate-second (two pauses), so narration is authored against the
511
- # REAL output timeline — map_narration_to_clips is never used and cannot drop/clamp/desync.
512
- if not clip_plan_json.exists():
513
- # PASS 1: understand -> agent writes clip_plan.json ONLY.
514
- _understand()
515
- _write_run_manifest(work_dir, video, args)
516
- _pause(
517
- f"{clip_plan_json}(只写剪辑计划;解说下一步对着剪好的成片写)",
518
- inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
519
- )
520
- return
521
- _reject_stale_manifest()
522
- cp_fp = _file_md5(clip_plan_json)
523
- # Render the cut from clip_plan (no narration mapping — narration is OUTPUT-time).
524
- crender = [str(video), "--work-dir", str(work_dir), "--no-narration-map"]
525
- if args.target_duration:
526
- crender += ["--target-duration", args.target_duration]
527
- if args.allow_duration_drift:
528
- crender.append("--allow-duration-drift")
529
- if args.allow_sparse_cut:
530
- crender.append("--allow-sparse-cut")
531
- _run("video-cut", "cut.py", *crender)
532
- cut_qc = _surface_cut_qc(work_dir)
533
- _write_shift_left_stage_qc(work_dir, "post_cut", metadata={"cut_qc": cut_qc})
651
+ # Cut mode: cut-first / narrate-second (two pauses), so narration is authored against the
652
+ # REAL output timeline — no source-time mapping exists that could drop/clamp/desync.
653
+ if not clip_plan_json.exists():
654
+ # PASS 1: understand -> agent writes clip_plan.json ONLY.
655
+ if not uses_narration(args) and (work_dir / RUN_MANIFEST).exists():
656
+ _reject_stale_manifest()
657
+ if not uses_narration(args):
658
+ begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
659
+ _understand()
660
+ _write_run_manifest(work_dir, video, args)
661
+ _pause(f"{clip_plan_json}(只写剪辑计划;解说下一步对着剪好的成片写)", state_hint)
662
+ return
663
+ _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)
667
+ _render_cut(video, work_dir, args)
668
+ if uses_narration(args):
534
669
  if not narration_json.exists():
535
670
  # PASS 2: rebuild the brief (now an OUTPUT-timeline variant) and pause for narration.
536
- _rebuild_output_brief()
671
+ _rebuild_understanding_brief(_single_source_record(video, args), work_dir, args)
537
672
  _write_phase_ledger(
538
- work_dir, clip_plan_fingerprint=cp_fp, edited_source_rendered=True
673
+ work_dir, clip_plan_identity=cp_identity, edited_source_rendered=True
539
674
  )
540
675
  _pause(
541
676
  f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
542
- inspect_hint=(
543
- f"python3 {inspect_py} --work-dir {work_dir} "
544
- "clip-map --output-start <s> --output-end <e> # 核对输出↔原片时间轴"
545
- ),
677
+ f"python3 {inspect_py} --work-dir {work_dir} "
678
+ "clip-map --output-start <s> --output-end <e> # 核对输出↔原片时间轴",
546
679
  )
547
680
  return
548
- if _cut_narration_is_stale(_read_phase_ledger(work_dir), cp_fp):
549
- raise SystemExit(
550
- "clip_plan.json 已改变,但 narration.json 仍是对旧剪辑写的,会与剪后画面对不上。"
551
- "请删除 narration.json,重跑后按新成片重新写解说。"
552
- )
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:
553
685
  _write_phase_ledger(
554
686
  work_dir,
555
- clip_plan_fingerprint=cp_fp,
556
- narration_fingerprint=_file_md5(narration_json),
557
- narration_written=True,
558
- )
559
- output_duration = _read_video_duration_or_raise(edited_source)
560
- _run(
561
- "video-script",
562
- "validate.py",
563
- "--work-dir",
564
- work_dir,
565
- "--mode",
566
- "cut_output",
567
- "--output-duration",
568
- f"{output_duration:.3f}",
569
- )
570
- narration_for_tts = narration_json
571
- assemble_video_path = edited_source
572
- review_ran = run_narration_review(
573
- work_dir,
574
- args,
575
- run=_run,
576
- timeline="cut_output" if cut else "source",
577
- )
578
- _write_shift_left_stage_qc(
579
- work_dir,
580
- "pre_tts",
581
- metadata={
582
- "review_ran": review_ran,
583
- "timeline": "cut_output" if cut else "source",
584
- },
585
- )
586
- _run(
587
- "video-voiceover",
588
- "voiceover.py",
589
- *_voiceover_args(work_dir, narration_for_tts, args),
590
- )
591
- _write_shift_left_stage_qc(
592
- work_dir, "post_tts", metadata=_tts_qc_metadata(work_dir)
593
- )
594
- overlays_path = _write_canonical_visual_overlays(work_dir, narration_for_tts)
595
- _write_shift_left_stage_qc(
596
- work_dir, "pre_assemble", metadata={"visual_overlays": str(overlays_path)}
597
- )
598
- _run_mimo_qc_stage(work_dir, args, "pre_assemble")
599
-
600
- aargs = [
601
- str(assemble_video_path),
602
- "--work-dir",
603
- str(work_dir),
604
- "--recap-stem",
605
- video.stem,
606
- ]
607
- if args.output_dir:
608
- aargs += ["--output-dir", args.output_dir]
609
- if args.burn_subtitles is not None:
610
- aargs.append(
611
- "--burn-subtitles" if args.burn_subtitles else "--no-burn-subtitles"
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,
612
691
  )
613
- if args.subtitle_y_top is not None:
614
- aargs += [
615
- "--subtitle-y-top",
616
- str(args.subtitle_y_top),
617
- "--subtitle-y-bot",
618
- str(args.subtitle_y_bot),
619
- ]
620
- # env-only burn intent (BURN_SUBTITLES) is propagated implicitly: assemble re-derives it
621
- # via the same env_bool default the preflight used, so the two agree by shared env.
622
- if cut:
623
- # let the timeline / 剪映 export reference the original clips, not edited_source.mp4
624
- aargs += ["--source-video", str(video)]
625
- if args.export_jianying: # env EXPORT_JIANYING is honored by assemble.py itself
626
- aargs.append("--export-jianying")
627
- if args.jianying_bundle_media:
628
- aargs.append("--jianying-bundle-media")
629
- if args.jianying_no_bundle_media:
630
- aargs.append("--jianying-no-bundle-media")
631
- _run("video-assemble", "assemble.py", *aargs)
632
-
633
- final_output = _read_assembly_output(work_dir)
634
- _write_shift_left_stage_qc(
635
- work_dir,
636
- "post_render",
637
- metadata=_post_render_qc_metadata(work_dir, final_output),
692
+ # let the timeline / 剪映 export reference the original clips, not edited_source.mp4
693
+ _deliver(
694
+ work_dir, args, edited_source, video.stem, "cut_output",
695
+ ["--source-video", str(video)],
638
696
  )
639
- _run_mimo_qc_stage(work_dir, args, "post_render", final_output=final_output)
640
- final_qc_result = _write_final_qc_reports(work_dir, final_output)
641
- print(f"[video-recap] ✅ 完成: {final_output}")
642
- _print_final_qc_pointer(final_qc_result)
643
- _print_narration_review_pointer(work_dir, review_ran=review_ran)