@oh-story/dsh 0.1.4 → 0.1.6

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 (479) hide show
  1. package/README.md +56 -12
  2. package/lib/client.js +2707 -268
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +136 -101
  5. package/lib/drama/skills/short-drama/SKILL.md +61 -1
  6. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
  8. package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
  9. package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
  10. package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
  11. package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
  12. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  13. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  14. package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
  15. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  16. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
  17. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
  18. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  19. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
  21. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
  22. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
  23. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
  24. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
  25. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  26. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  27. package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
  28. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  29. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  30. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  31. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  32. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  33. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  34. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
  35. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  36. package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
  37. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  38. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
  39. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  40. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
  41. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
  42. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
  43. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  44. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
  45. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  46. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  47. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
  48. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  49. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
  50. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
  51. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  52. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  53. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
  54. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  55. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  56. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  57. package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
  58. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  59. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  60. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  61. package/lib/index.js +865 -33
  62. package/lib/index.js.map +4 -4
  63. package/lib/novel-to-game/LICENSE +21 -0
  64. package/lib/novel-to-game/README.md +180 -0
  65. package/lib/novel-to-game/README_ZH.md +178 -0
  66. package/lib/novel-to-game/VERSION +1 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  91. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  92. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  93. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  94. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  95. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  96. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  97. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  98. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  99. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  100. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  101. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  102. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  103. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  104. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  128. package/lib/novel-to-game/manifest.json +508 -0
  129. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  130. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  131. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  132. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  133. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  134. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  135. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  136. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  137. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  138. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  139. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  140. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  141. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  142. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  143. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  144. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  145. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  146. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  147. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  148. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  149. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  150. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  151. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  152. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  153. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  154. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  155. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  156. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  157. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  158. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  159. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  160. package/lib/oh-story/manifest.json +415 -305
  161. package/lib/oh-story/roles/consistency-checker.md +1 -1
  162. package/lib/oh-story/roles/narrative-writer.md +32 -33
  163. package/lib/oh-story/roles/story-architect.md +16 -26
  164. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  165. package/lib/oh-story/skills/story/SKILL.md +16 -9
  166. package/lib/oh-story/skills/story/VERSION +1 -1
  167. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  168. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  169. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  170. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  171. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  172. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  173. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  174. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  175. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  176. package/lib/oh-story/skills/story-import/SKILL.md +19 -10
  177. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  180. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  181. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  182. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  183. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  184. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  185. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  186. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  187. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  188. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  189. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  190. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  191. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  192. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  193. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  194. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  195. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  196. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  197. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  198. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
  199. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
  200. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  201. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  202. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  203. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  204. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  205. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  206. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
  207. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  208. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
  209. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  210. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  211. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  212. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  213. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  214. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  215. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  216. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  217. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  218. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  219. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  220. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  221. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  222. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  223. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  224. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  225. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  226. package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
  227. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  228. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  229. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  230. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  236. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  237. package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
  238. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  239. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  243. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  244. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  245. package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
  246. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  247. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  248. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  249. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  250. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  251. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  252. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  253. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  254. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  255. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  257. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  258. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  259. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  260. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  261. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  262. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  263. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  264. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  265. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  266. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  267. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  268. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  270. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  271. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  272. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  273. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  274. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  276. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  277. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  278. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  279. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  280. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  281. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  282. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  283. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  284. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  285. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  286. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  287. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  288. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  289. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  290. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  291. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  292. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  293. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  294. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  295. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  296. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  297. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  298. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  299. package/lib/types/client/drama-production-view.d.ts +34 -0
  300. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  301. package/lib/types/client/drama-production.d.ts +92 -0
  302. package/lib/types/client/drama-production.d.ts.map +1 -0
  303. package/lib/types/client/file-activity.d.ts +4 -2
  304. package/lib/types/client/file-activity.d.ts.map +1 -1
  305. package/lib/types/client/index.d.ts +1 -1
  306. package/lib/types/client/index.d.ts.map +1 -1
  307. package/lib/types/client/production-intents.d.ts +9 -0
  308. package/lib/types/client/production-intents.d.ts.map +1 -0
  309. package/lib/types/client/production-prompts.d.ts +9 -0
  310. package/lib/types/client/production-prompts.d.ts.map +1 -0
  311. package/lib/types/client/production-runtime.d.ts +58 -0
  312. package/lib/types/client/production-runtime.d.ts.map +1 -0
  313. package/lib/types/client/video-studio.d.ts +41 -0
  314. package/lib/types/client/video-studio.d.ts.map +1 -0
  315. package/lib/types/client/workbench-ui.d.ts +5 -0
  316. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  317. package/lib/types/game-verification.d.ts +16 -0
  318. package/lib/types/game-verification.d.ts.map +1 -0
  319. package/lib/types/index.d.ts +3 -1
  320. package/lib/types/index.d.ts.map +1 -1
  321. package/lib/types/production-intent.d.ts +21 -0
  322. package/lib/types/production-intent.d.ts.map +1 -0
  323. package/lib/types/production-tool.d.ts +5 -0
  324. package/lib/types/production-tool.d.ts.map +1 -0
  325. package/lib/types/role-tool.d.ts.map +1 -1
  326. package/lib/types/skill-provider.d.ts +7 -0
  327. package/lib/types/skill-provider.d.ts.map +1 -1
  328. package/lib/types/video-project.d.ts +42 -0
  329. package/lib/types/video-project.d.ts.map +1 -0
  330. package/lib/types/workspace-request-trust.d.ts +6 -0
  331. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  332. package/lib/types/workspace-route.d.ts +20 -0
  333. package/lib/types/workspace-route.d.ts.map +1 -1
  334. package/lib/video-recap/CHANGELOG.md +282 -0
  335. package/lib/video-recap/LICENSE +21 -0
  336. package/lib/video-recap/README.en.md +257 -0
  337. package/lib/video-recap/README.md +255 -0
  338. package/lib/video-recap/manifest.json +684 -0
  339. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  340. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  341. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  342. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  343. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  344. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  345. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  346. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  347. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  348. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  349. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  350. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  351. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  352. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  353. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  354. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  355. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  356. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  357. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  358. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  359. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  360. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  361. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  362. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  363. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  364. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  365. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  366. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  367. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  368. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  369. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  370. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  371. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  372. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  373. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  374. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  375. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  376. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  377. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  378. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  379. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  380. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  381. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  382. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  383. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  384. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  385. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  386. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  387. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  388. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  389. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  390. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  391. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  392. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  393. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  394. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  395. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  396. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  397. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  398. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  399. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  400. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  401. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  402. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  403. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  404. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  405. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  406. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  407. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  408. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  409. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  410. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  411. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  412. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  413. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  414. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  415. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  416. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  417. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  418. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  419. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  420. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  421. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  422. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  423. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  424. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  425. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  426. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  427. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  428. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  429. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  430. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  431. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  432. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  433. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  434. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  435. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  436. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  437. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  438. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  439. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  440. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  441. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  442. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  443. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  444. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  445. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  446. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  447. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  448. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  449. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  450. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  451. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  452. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  453. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  454. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  455. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  456. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  457. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  458. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  459. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  460. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  461. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  462. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  463. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  464. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  465. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  466. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  467. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  468. package/package.json +55 -40
  469. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  470. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  471. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  472. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  473. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  474. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  475. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  476. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  477. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  478. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  479. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
@@ -0,0 +1,335 @@
1
+ """Provide subprocess, manifest, review, and preflight runtime helpers."""
2
+
3
+ import hashlib
4
+
5
+ import json
6
+
7
+ import math
8
+
9
+ import os
10
+
11
+ import shlex
12
+
13
+ import subprocess
14
+
15
+ import sys
16
+
17
+ from pathlib import Path
18
+
19
+
20
+ from doctor import ffmpeg_has_subtitles_filter
21
+
22
+ import materials as material_lib
23
+
24
+ BUNDLE = Path(__file__).resolve().parents[2] # the skills/ directory
25
+
26
+ RUN_MANIFEST = "recap_run_manifest.json"
27
+
28
+ MULTI_SOURCE_MANIFEST = "multi_source_manifest.json"
29
+
30
+
31
+ def _run(skill, script, *cli_args):
32
+ cmd = [sys.executable, str(_entry(skill, script)), *map(str, cli_args)]
33
+ print(f"[video-recap] ▶ {skill}/{script}", flush=True)
34
+ res = subprocess.run(cmd)
35
+ if res.returncode != 0:
36
+ raise SystemExit(f"{skill}/{script} 失败 (exit {res.returncode})")
37
+
38
+
39
+ def _env_bool(name, default=False):
40
+ raw = os.environ.get(name)
41
+ if raw is None or raw == "":
42
+ return default
43
+ return raw.strip().lower() in {"1", "true", "yes", "y", "on"}
44
+
45
+
46
+ def _optional_env_int(name):
47
+ raw = os.environ.get(name)
48
+ if raw is None or raw.strip() == "":
49
+ return None
50
+ try:
51
+ value = int(raw)
52
+ except ValueError as exc:
53
+ raise ValueError(f"{name} must be an integer; got {raw!r}") from exc
54
+ return None if value == -1 else value
55
+
56
+
57
+ def _read_video_duration_or_raise(path):
58
+ """Return media duration via ffprobe, or hard-fail before downstream TTS/render."""
59
+ path = Path(path)
60
+ cmd = [
61
+ "ffprobe",
62
+ "-v",
63
+ "quiet",
64
+ "-show_entries",
65
+ "format=duration",
66
+ "-of",
67
+ "csv=p=0",
68
+ str(path),
69
+ ]
70
+ res = subprocess.run(cmd, capture_output=True, text=True)
71
+ if res.returncode != 0:
72
+ detail = (res.stderr or res.stdout or "ffprobe failed").strip()
73
+ raise SystemExit(f"无法读取成片时长: {path} ({detail})")
74
+ try:
75
+ duration = float(res.stdout.strip())
76
+ except (TypeError, ValueError):
77
+ raise SystemExit(f"无法读取成片时长: {path} (ffprobe 输出无效: {res.stdout!r})")
78
+ if not math.isfinite(duration) or duration <= 0:
79
+ raise SystemExit(f"无法读取成片时长: {path} (duration={duration:.3f})")
80
+ return duration
81
+
82
+
83
+ def _probe_display_height_or_raise(path, *, require_square_pixels=False):
84
+ """Return ffmpeg's display-coordinate height, accounting for rotation and SAR."""
85
+ cmd = [
86
+ "ffprobe",
87
+ "-v",
88
+ "error",
89
+ "-select_streams",
90
+ "v:0",
91
+ "-show_entries",
92
+ "stream=width,height,sample_aspect_ratio:stream_tags=rotate:stream_side_data=rotation",
93
+ "-of",
94
+ "json",
95
+ str(path),
96
+ ]
97
+ res = subprocess.run(cmd, capture_output=True, text=True)
98
+ try:
99
+ stream = (json.loads(res.stdout or "{}").get("streams") or [])[0]
100
+ width, height = int(stream["width"]), int(stream["height"])
101
+ except (IndexError, KeyError, TypeError, ValueError, json.JSONDecodeError):
102
+ detail = (res.stderr or res.stdout or "ffprobe failed").strip()
103
+ raise SystemExit(f"无法读取视频画布: {path} ({detail})")
104
+ sar = str(stream.get("sample_aspect_ratio") or "1:1")
105
+ try:
106
+ num, den = sar.split(":", 1)
107
+ sar_ratio = float(num) / float(den)
108
+ display_width = max(1, int(round(width * sar_ratio)))
109
+ except (TypeError, ValueError, ZeroDivisionError):
110
+ sar_ratio = math.nan
111
+ display_width = width
112
+ if require_square_pixels and (
113
+ not math.isfinite(sar_ratio) or abs(sar_ratio - 1.0) >= 1e-9
114
+ ):
115
+ raise SystemExit(
116
+ f"subtitle Y coordinates currently require square-pixel video (SAR 1:1); got {sar}"
117
+ )
118
+ rotation_values = [
119
+ (stream.get("tags") or {}).get("rotate"),
120
+ *(
121
+ item.get("rotation")
122
+ for item in stream.get("side_data_list") or []
123
+ if isinstance(item, dict)
124
+ ),
125
+ ]
126
+ rotation = 0
127
+ for value in rotation_values:
128
+ if value not in (None, ""):
129
+ try:
130
+ rotation = int(round(float(value))) % 360
131
+ break
132
+ except (TypeError, ValueError):
133
+ pass
134
+ return display_width if rotation in {90, 270} else height
135
+
136
+
137
+ _FILE_FINGERPRINT_MEMO = {}
138
+
139
+
140
+ def _file_identity(path):
141
+ """(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
142
+ st = os.stat(os.fspath(path))
143
+ return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
144
+
145
+
146
+ def _file_fingerprint(path, chunk_size=1024 * 1024):
147
+ """Return a full-content fingerprint for cache-correct identity checks.
148
+
149
+ The digest covers CONTENT only — never the path or mtime — so a copied video or
150
+ artifact is still recognised as the same asset, while any byte change invalidates
151
+ the cache even if timestamps, size, head, or tail bytes are misleading.
152
+
153
+ Identity metadata is used ONLY to memoize within a single process. One understanding
154
+ run fingerprints the same source video 8-10 times and the whole extracted frame set
155
+ 2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
156
+ any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
157
+ re-hashed, so the memo can never serve a stale digest.
158
+ """
159
+ key = _file_identity(path)
160
+ memoized = _FILE_FINGERPRINT_MEMO.get(key)
161
+ if memoized is not None:
162
+ return memoized
163
+ h = hashlib.sha256()
164
+ with open(os.fspath(path), "rb") as f:
165
+ for chunk in iter(lambda: f.read(chunk_size), b""):
166
+ h.update(chunk)
167
+ digest = h.hexdigest()
168
+ _FILE_FINGERPRINT_MEMO[key] = digest
169
+ return digest
170
+
171
+
172
+ def _analysis_settings(args):
173
+ return {
174
+ "context": args.context,
175
+ "scene_threshold": args.scene_threshold,
176
+ "style": args.style,
177
+ "edit_mode": args.edit_mode,
178
+ "target_duration": args.target_duration,
179
+ "skip_asr": bool(args.skip_asr),
180
+ "mimo_video_overview": bool(args.mimo_video_overview),
181
+ "consolidate": bool(args.consolidate),
182
+ "consolidate_asr": bool(args.consolidate_asr),
183
+ }
184
+
185
+
186
+ def _material_settings_fingerprint(args):
187
+ return material_lib.settings_fingerprint(_analysis_settings(args))
188
+
189
+
190
+ def _coerce_videos(video_or_videos):
191
+ if isinstance(video_or_videos, (list, tuple)):
192
+ return [Path(v).resolve() for v in video_or_videos]
193
+ return [Path(video_or_videos).resolve()]
194
+
195
+
196
+ def _run_manifest_payload(video, args):
197
+ return {
198
+ "schema_version": 1,
199
+ "source_video": str(Path(video).resolve()),
200
+ "source_video_fingerprint": _file_fingerprint(video),
201
+ "settings": _analysis_settings(args),
202
+ }
203
+
204
+
205
+ def _write_run_manifest(work_dir, video, args):
206
+ payload = _run_manifest_payload(video, args)
207
+ (work_dir / RUN_MANIFEST).write_text(
208
+ json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
209
+ )
210
+
211
+
212
+ def _build_multi_source_records(videos, args):
213
+ records = []
214
+ for video in _coerce_videos(videos):
215
+ fp = _file_fingerprint(video)
216
+ records.append(
217
+ {
218
+ "source_path": str(video),
219
+ "source_name": video.name,
220
+ "source_video_fingerprint": fp,
221
+ "settings_fingerprint": _material_settings_fingerprint(args),
222
+ "material_id": material_lib.material_id_for(video, fp),
223
+ }
224
+ )
225
+ records = material_lib.assign_source_ids(records)
226
+ for record in records:
227
+ record["source_work_dir"] = f"sources/{record['source_id']}"
228
+ return records
229
+
230
+
231
+ def _multi_run_manifest_payload(videos, args, source_records):
232
+ return {
233
+ "schema_version": 2,
234
+ "mode": "multi_source",
235
+ "sources": [
236
+ {
237
+ "source_id": s.get("source_id"),
238
+ "source_path": s.get("source_path"),
239
+ "source_video_fingerprint": s.get("source_video_fingerprint"),
240
+ "source_work_dir": s.get("source_work_dir"),
241
+ "material_id": s.get("material_id"),
242
+ }
243
+ for s in source_records
244
+ ],
245
+ "source_videos": [str(v) for v in _coerce_videos(videos)],
246
+ "settings": _analysis_settings(args),
247
+ }
248
+
249
+
250
+ def _write_project_run_manifest(work_dir, videos, args, source_records):
251
+ payload = _multi_run_manifest_payload(videos, args, source_records)
252
+ (work_dir / RUN_MANIFEST).write_text(
253
+ json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
254
+ )
255
+
256
+
257
+ def _write_multi_source_manifest(work_dir, source_records):
258
+ path = Path(work_dir) / MULTI_SOURCE_MANIFEST
259
+ payload = {
260
+ "schema_version": 1,
261
+ "sources": [
262
+ {
263
+ "source_id": s["source_id"],
264
+ "source_path": s["source_path"],
265
+ "source_name": s["source_name"],
266
+ "source_video_fingerprint": s["source_video_fingerprint"],
267
+ "source_work_dir": s["source_work_dir"],
268
+ "material_id": s.get("material_id"),
269
+ }
270
+ for s in source_records
271
+ ],
272
+ }
273
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
274
+ return path
275
+
276
+
277
+ def _load_run_manifest(work_dir):
278
+ path = Path(work_dir) / RUN_MANIFEST
279
+ if not path.exists():
280
+ return None
281
+ try:
282
+ return json.loads(path.read_text(encoding="utf-8"))
283
+ except (OSError, ValueError, TypeError):
284
+ return None
285
+
286
+
287
+ def _load_json(path):
288
+ try:
289
+ return json.loads(Path(path).read_text(encoding="utf-8"))
290
+ except (OSError, ValueError, TypeError):
291
+ return None
292
+
293
+
294
+ def _burn_subtitles_intended(args):
295
+ """Effective burn-subtitles state at orchestrator level. Mirrors video-assemble's
296
+ CONFIG default `env_bool("BURN_SUBTITLES", True)` (burn is ON by default); an explicit
297
+ CLI flag (--burn-subtitles / --no-burn-subtitles) overrides the env."""
298
+ if getattr(args, "burn_subtitles", None) is not None:
299
+ return bool(args.burn_subtitles)
300
+ raw = os.environ.get("BURN_SUBTITLES")
301
+ if raw is None or raw == "":
302
+ return True
303
+ return raw.strip().lower() in {"1", "true", "yes", "y", "on"}
304
+
305
+
306
+ def _ffmpeg_present_but_cannot_burn():
307
+ """True only when ffmpeg EXISTS but lacks the libass `subtitles` filter — the specific
308
+ "subtitle-burn environment unsupported" case. Returns False when ffmpeg is absent
309
+ entirely: that is a more fundamental problem that surfaces at the first stage (understand
310
+ calls ffprobe/ffmpeg) and is reported by doctor, so this guard stays narrow — and does
311
+ not fire in mocked, ffmpeg-less test environments."""
312
+ import shutil
313
+
314
+ if shutil.which("ffmpeg") is None:
315
+ return False
316
+ return not ffmpeg_has_subtitles_filter()
317
+
318
+
319
+ def _preflight_burn_subtitles(args):
320
+ """Fail fast BEFORE any understanding/VLM/ASR/TTS spend when subtitle burn-in is on but
321
+ this ffmpeg can't burn it. Without it the run only dies at the final assemble
322
+ `-vf subtitles=` step — after the whole expensive pipeline has run."""
323
+ if not _burn_subtitles_intended(args):
324
+ return
325
+ if _ffmpeg_present_but_cannot_burn():
326
+ raise SystemExit(
327
+ "字幕烧录已开启,但当前 ffmpeg 不支持 subtitles/libass 滤镜,整条流程会跑到最后渲染才失败。\n"
328
+ " 解决其一:(1) 安装带 libass 的 ffmpeg;(2) 加 --no-burn-subtitles 关闭烧录"
329
+ "(仍输出 .srt 外挂字幕)。\n"
330
+ f" 自检:python3 {shlex.quote(str(_entry('video-recap', 'doctor.py')))}"
331
+ )
332
+
333
+
334
+ def _entry(skill, script):
335
+ return BUNDLE / skill / "scripts" / script
@@ -0,0 +1,217 @@
1
+ """Write shift-left, MiMo, and final QC stage reports."""
2
+
3
+ import json
4
+
5
+
6
+ from pathlib import Path
7
+
8
+ import qc_contract
9
+
10
+ import final_qc
11
+
12
+ import mimo_qc
13
+
14
+
15
+ from recap_runtime import _load_json
16
+
17
+ ASSEMBLY_MANIFEST = "assembly_manifest.json"
18
+
19
+
20
+ def _json_safe(value):
21
+ """Return a JSON-serializable, secret-redacted copy for local QC metadata."""
22
+
23
+ def convert(item):
24
+ if isinstance(item, Path):
25
+ return str(item)
26
+ if isinstance(item, dict):
27
+ return {str(k): convert(v) for k, v in item.items()}
28
+ if isinstance(item, (list, tuple)):
29
+ return [convert(v) for v in item]
30
+ return item
31
+
32
+ return qc_contract.redact_secrets(convert(value))
33
+
34
+
35
+ def _load_preflight_stage_reports(work_dir):
36
+ path = Path(work_dir) / "preflight_qc.json"
37
+ if not path.exists():
38
+ return {}
39
+ data = _load_json(path)
40
+ if not isinstance(data, dict):
41
+ raise qc_contract.QCContractError("preflight_qc.json must be a JSON object")
42
+ qc_contract.validate_report(data)
43
+ stages = None
44
+ metadata = data.get("metadata")
45
+ if isinstance(metadata, dict) and isinstance(metadata.get("stages"), dict):
46
+ stages = metadata.get("stages")
47
+ elif isinstance(data.get("stages"), dict):
48
+ stages = data.get("stages")
49
+ if stages is None:
50
+ return {data["stage"]: data}
51
+ stage_reports = {}
52
+ for stage, report in stages.items():
53
+ if not isinstance(report, dict):
54
+ raise qc_contract.QCContractError(
55
+ f"preflight stage report must be an object: {stage}"
56
+ )
57
+ qc_contract.validate_report(report)
58
+ if report.get("stage") != stage:
59
+ raise qc_contract.QCContractError(
60
+ f"preflight stage key does not match report stage: {stage}"
61
+ )
62
+ stage_reports[stage] = report
63
+ return stage_reports
64
+
65
+
66
+ def _write_shift_left_stage_qc(work_dir, stage, metadata=None, findings=None):
67
+ """Write/roll up local shift-left QC for one pipeline stage.
68
+
69
+ This is a local contract artifact only: no MiMo/deep eval calls, no repair, and
70
+ no credential persistence. Any validation/write failure is allowed to raise.
71
+ """
72
+ work_dir = Path(work_dir)
73
+ path = work_dir / "preflight_qc.json"
74
+ stage_report = qc_contract.build_report(
75
+ artifact="preflight_qc.json",
76
+ stage=stage,
77
+ findings=findings or [],
78
+ metadata=_json_safe(metadata or {}),
79
+ )
80
+ stage_reports = _load_preflight_stage_reports(work_dir)
81
+ stage_reports[stage] = stage_report
82
+ aggregate_findings = []
83
+ for report in stage_reports.values():
84
+ aggregate_findings.extend(dict(f) for f in report.get("findings", []))
85
+ top_metadata = dict(stage_report.get("metadata") or {})
86
+ top_metadata["latest_stage"] = stage
87
+ top_metadata["stages"] = stage_reports
88
+ top_report = qc_contract.build_report(
89
+ artifact="preflight_qc.json",
90
+ stage=stage,
91
+ findings=aggregate_findings,
92
+ metadata=_json_safe(top_metadata),
93
+ )
94
+ qc_contract.validate_report(top_report)
95
+ path.write_text(
96
+ json.dumps(top_report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
97
+ )
98
+ qc_contract.validate_report(_load_json(path))
99
+ return top_report
100
+
101
+
102
+ def _tts_qc_metadata(work_dir):
103
+ work_dir = Path(work_dir)
104
+ metadata = {}
105
+ tts_meta = _load_json(work_dir / "tts_meta.json")
106
+ if tts_meta is not None:
107
+ metadata["tts_meta"] = tts_meta
108
+ tts_dir = work_dir / "tts_segments"
109
+ if tts_dir.exists() and tts_dir.is_dir():
110
+ metadata["tts_segments"] = [
111
+ p.relative_to(work_dir).as_posix()
112
+ for p in sorted(tts_dir.iterdir())
113
+ if p.is_file()
114
+ ]
115
+ return metadata
116
+
117
+
118
+ def _post_render_qc_metadata(work_dir, final_output):
119
+ work_dir = Path(work_dir)
120
+ metadata = {"final_output": str(final_output) if final_output is not None else None}
121
+ assembly_manifest = _load_json(work_dir / ASSEMBLY_MANIFEST)
122
+ if assembly_manifest is not None:
123
+ metadata["assembly_manifest"] = assembly_manifest
124
+ return metadata
125
+
126
+
127
+ def _write_final_qc_reports(work_dir, final_output):
128
+ """Write report-only final QC artifacts after render.
129
+
130
+ final_qc.run converts ffprobe unavailability/failure into deterministic
131
+ blockers; only unexpected schema/write errors propagate.
132
+ """
133
+ return final_qc.run(work_dir, final_output=final_output)
134
+
135
+
136
+ def _print_final_qc_pointer(result):
137
+ """Surface a report-only final_qc/golden_eval FAIL so the shift-left QC is
138
+ not a silent no-op. Advisory only: it never changes the exit status."""
139
+ if not isinstance(result, dict):
140
+ return
141
+ problems = []
142
+ for key in ("final_qc", "golden_eval"):
143
+ section = result.get(key)
144
+ if isinstance(section, dict) and section.get("ok") is False:
145
+ problems.append(f"{key} blocker_count={section.get('blocker_count', '?')}")
146
+ if problems:
147
+ print(
148
+ "[video-recap] ⚠️ 最终 QC 未通过(仅报告,不阻断): "
149
+ + "; ".join(problems)
150
+ + ";详见 final_qc.json / golden_eval.json"
151
+ )
152
+
153
+
154
+ def _mimo_qc_stage_enabled(args, stage):
155
+ mode = getattr(args, "mimo_qc", "off") or "off"
156
+ return (
157
+ mode == "both"
158
+ or (mode == "pre-assemble" and stage == "pre_assemble")
159
+ or (mode == "post-render" and stage == "post_render")
160
+ )
161
+
162
+
163
+ def _prepare_mimo_qc(work_dir, args):
164
+ """Remove an old advisory artifact when this run has MiMo QC disabled."""
165
+ if getattr(args, "mimo_qc", "off") == "off":
166
+ mimo_qc.clear_report(work_dir)
167
+
168
+
169
+ def _print_mimo_qc_pointer(result, stage):
170
+ report = result.get("report", {}) if isinstance(result, dict) else {}
171
+ metadata = report.get("metadata", {}) if isinstance(report, dict) else {}
172
+ status = metadata.get("status", "unknown")
173
+ path = (
174
+ result.get("path", "mimo_qc.json")
175
+ if isinstance(result, dict)
176
+ else "mimo_qc.json"
177
+ )
178
+ stage_findings = [
179
+ finding
180
+ for finding in report.get("findings", [])
181
+ if isinstance(finding, dict) and finding.get("stage") == stage
182
+ ]
183
+ if status in {"failed", "unavailable"}:
184
+ reason = metadata.get("error") or "unavailable"
185
+ print(
186
+ f"[video-recap] ⚠ MiMo QC {stage}: {status} ({reason});建议性检查不可用,继续流水线"
187
+ )
188
+ return
189
+ print(
190
+ f"[video-recap] ℹ MiMo QC {stage}: {status}, {len(stage_findings)} 条建议;详见 {path}"
191
+ )
192
+ for finding in stage_findings[:5]:
193
+ print(
194
+ f"[video-recap] - {finding.get('message') or finding.get('decision_reason')}"
195
+ )
196
+
197
+
198
+ def _run_mimo_qc_stage(work_dir, args, stage, *, final_output=None):
199
+ """Run one selected advisory stage and never propagate a failure."""
200
+ if not _mimo_qc_stage_enabled(args, stage):
201
+ return None
202
+ try:
203
+ result = mimo_qc.run(
204
+ work_dir,
205
+ stage=stage,
206
+ live=True,
207
+ refresh=bool(getattr(args, "mimo_qc_refresh", False)),
208
+ final_output=final_output,
209
+ )
210
+ except Exception as exc:
211
+ print(
212
+ f"[video-recap] ⚠ MiMo QC {stage}: {type(exc).__name__};"
213
+ "建议性检查失败,继续流水线"
214
+ )
215
+ return None
216
+ _print_mimo_qc_pointer(result, stage)
217
+ return result