@oh-story/dsh 0.1.3 → 0.1.5

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 (524) hide show
  1. package/README.md +58 -12
  2. package/lib/client.js +2970 -537
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +218 -178
  5. package/lib/drama/skills/short-drama/SKILL.md +110 -117
  6. package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
  7. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
  8. package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
  9. package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
  10. package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
  11. package/lib/drama/skills/short-drama/references/look-development.md +2 -3
  12. package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
  13. package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
  14. package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
  15. package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
  16. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  17. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  18. package/lib/drama/skills/short-drama-assets/SKILL.md +59 -170
  19. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
  20. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
  21. package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
  22. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  23. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +6 -12
  24. package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
  25. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
  26. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
  27. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
  28. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  29. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
  30. package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
  31. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
  32. package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
  33. package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
  34. package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
  35. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
  36. package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
  37. package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
  38. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
  39. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
  40. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  41. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  42. package/lib/drama/skills/short-drama-produce/SKILL.md +67 -15
  43. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  44. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  45. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  46. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  47. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  48. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  49. package/lib/drama/skills/short-drama-review/SKILL.md +61 -123
  50. package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
  51. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
  52. package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
  53. package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
  54. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
  55. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
  56. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
  57. package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
  58. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  59. package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
  60. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  61. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
  62. package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
  63. package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
  64. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
  65. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  66. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +27 -62
  67. package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
  68. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
  70. package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
  71. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
  72. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  73. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
  74. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  75. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +10 -8
  77. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
  78. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
  79. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  80. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +35 -27
  81. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
  82. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
  83. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
  84. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  85. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +17 -73
  87. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  88. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  89. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  90. package/lib/drama/skills/short-drama-write/SKILL.md +74 -268
  91. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
  92. package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
  93. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
  94. package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
  95. package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
  96. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  97. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  98. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  99. package/lib/index.js +971 -46
  100. package/lib/index.js.map +4 -4
  101. package/lib/novel-to-game/LICENSE +21 -0
  102. package/lib/novel-to-game/README.md +180 -0
  103. package/lib/novel-to-game/README_ZH.md +178 -0
  104. package/lib/novel-to-game/VERSION +1 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  128. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  129. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  130. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  131. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  132. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  133. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  134. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  135. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  136. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  137. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  138. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  139. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  140. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  141. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  142. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  143. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  144. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  145. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  146. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  147. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  148. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  149. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  150. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  151. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  152. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  153. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  154. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  155. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  156. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  157. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  158. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  159. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  160. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  161. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  162. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  163. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  164. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  165. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  166. package/lib/novel-to-game/manifest.json +508 -0
  167. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  168. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  169. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  170. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  171. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  172. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  173. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  174. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  175. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  176. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  177. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  178. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  179. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  180. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  181. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  182. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  183. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  184. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  185. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  186. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  187. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  188. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  189. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  190. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  191. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  192. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  193. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  194. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  195. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  197. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  198. package/lib/oh-story/manifest.json +419 -309
  199. package/lib/oh-story/roles/character-designer.md +4 -4
  200. package/lib/oh-story/roles/consistency-checker.md +1 -1
  201. package/lib/oh-story/roles/narrative-writer.md +41 -42
  202. package/lib/oh-story/roles/story-architect.md +19 -23
  203. package/lib/oh-story/roles/story-explorer.md +8 -5
  204. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  205. package/lib/oh-story/skills/story/SKILL.md +16 -9
  206. package/lib/oh-story/skills/story/VERSION +1 -1
  207. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  208. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  209. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  210. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  211. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  212. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  213. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  214. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  215. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  216. package/lib/oh-story/skills/story-import/SKILL.md +21 -8
  217. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  218. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  219. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  220. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  221. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  222. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  223. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  224. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  225. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  226. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  227. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  228. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  229. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  230. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  232. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  236. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  237. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  238. package/lib/oh-story/skills/story-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  239. package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  243. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  244. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  245. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  246. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +5 -4
  247. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  248. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +17 -11
  249. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  250. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  251. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  252. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  253. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  254. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  255. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  256. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  257. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  258. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  259. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  260. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  261. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  262. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  263. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  264. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  265. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  266. package/lib/oh-story/skills/story-setup/SKILL.md +85 -41
  267. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  268. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  269. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  270. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  271. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  272. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  273. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  274. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  275. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  276. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  277. package/lib/oh-story/skills/story-setup/references/agent-references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  278. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  279. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/long-reversal.md} +18 -32
  280. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  281. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  282. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  283. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  284. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  285. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → short-reversal.md} +29 -23
  286. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  287. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  288. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  289. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  290. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  291. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  292. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  293. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  294. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  295. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  296. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  297. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  298. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  299. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  300. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  301. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  302. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  303. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  304. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  305. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  306. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  307. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  308. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  309. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  310. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  311. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  312. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  313. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  314. 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
  315. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  316. 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
  317. 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
  318. 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
  319. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  320. 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
  321. 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
  322. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  323. 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
  324. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  325. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  326. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  327. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  328. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  329. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  330. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  331. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  332. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  333. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  335. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  336. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  337. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  338. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  339. package/lib/types/client/drama-production-view.d.ts +34 -0
  340. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  341. package/lib/types/client/drama-production.d.ts +92 -0
  342. package/lib/types/client/drama-production.d.ts.map +1 -0
  343. package/lib/types/client/file-activity.d.ts +9 -2
  344. package/lib/types/client/file-activity.d.ts.map +1 -1
  345. package/lib/types/client/index.d.ts +1 -1
  346. package/lib/types/client/index.d.ts.map +1 -1
  347. package/lib/types/client/production-intents.d.ts +9 -0
  348. package/lib/types/client/production-intents.d.ts.map +1 -0
  349. package/lib/types/client/production-prompts.d.ts +9 -0
  350. package/lib/types/client/production-prompts.d.ts.map +1 -0
  351. package/lib/types/client/production-runtime.d.ts +58 -0
  352. package/lib/types/client/production-runtime.d.ts.map +1 -0
  353. package/lib/types/client/video-studio.d.ts +41 -0
  354. package/lib/types/client/video-studio.d.ts.map +1 -0
  355. package/lib/types/client/workbench-ui.d.ts +5 -0
  356. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  357. package/lib/types/game-verification.d.ts +16 -0
  358. package/lib/types/game-verification.d.ts.map +1 -0
  359. package/lib/types/index.d.ts +5 -2
  360. package/lib/types/index.d.ts.map +1 -1
  361. package/lib/types/production-intent.d.ts +21 -0
  362. package/lib/types/production-intent.d.ts.map +1 -0
  363. package/lib/types/production-tool.d.ts +5 -0
  364. package/lib/types/production-tool.d.ts.map +1 -0
  365. package/lib/types/reference-tool.d.ts +6 -0
  366. package/lib/types/reference-tool.d.ts.map +1 -0
  367. package/lib/types/role-provider.d.ts.map +1 -1
  368. package/lib/types/role-tool.d.ts +3 -1
  369. package/lib/types/role-tool.d.ts.map +1 -1
  370. package/lib/types/skill-provider.d.ts +7 -0
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts +42 -0
  373. package/lib/types/video-project.d.ts.map +1 -0
  374. package/lib/types/workspace-request-trust.d.ts +6 -0
  375. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  376. package/lib/types/workspace-route.d.ts +20 -0
  377. package/lib/types/workspace-route.d.ts.map +1 -1
  378. package/lib/video-recap/CHANGELOG.md +282 -0
  379. package/lib/video-recap/LICENSE +21 -0
  380. package/lib/video-recap/README.en.md +257 -0
  381. package/lib/video-recap/README.md +255 -0
  382. package/lib/video-recap/manifest.json +684 -0
  383. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  384. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  385. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  386. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  387. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  388. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  389. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  390. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  391. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  392. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  393. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  394. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  395. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  396. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  397. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  398. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  399. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  401. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  403. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  404. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  407. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  408. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  409. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  410. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  411. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  412. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  415. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  417. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  419. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  420. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  423. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  424. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  425. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  426. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  428. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  429. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  430. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  431. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  432. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  433. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  435. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  436. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  437. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  438. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  439. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  440. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  441. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  442. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  443. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  444. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  445. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  447. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  448. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  449. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  450. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  451. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  452. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  453. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  454. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  455. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  456. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  457. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  458. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  459. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  460. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  461. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  462. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  463. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  464. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  465. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  466. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  467. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  468. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  471. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  472. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  473. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  474. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  475. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  476. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  477. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  478. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  479. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  480. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  481. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  482. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  483. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  484. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  485. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  486. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  487. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  488. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  489. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  490. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  491. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  492. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  493. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  494. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  495. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  496. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  497. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  498. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  499. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  500. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  501. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  502. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  503. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  504. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  505. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  506. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  508. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  509. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  510. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  511. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  512. package/package.json +57 -41
  513. package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
  514. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  515. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  516. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  517. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  518. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  519. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  520. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  521. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  522. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  523. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  524. /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,410 @@
1
+ """Normalize, split, budget, and de-duplicate narration text."""
2
+
3
+ import importlib.util
4
+
5
+ import copy
6
+
7
+
8
+ import re
9
+
10
+ from pathlib import Path
11
+
12
+ from lib import CONFIG
13
+
14
+ from lib import log
15
+
16
+ try:
17
+ from deslop_qc import analyze_deslop_qc
18
+ except ModuleNotFoundError:
19
+ _deslop_qc_path = Path(__file__).with_name("deslop_qc.py")
20
+ _deslop_qc_spec = importlib.util.spec_from_file_location(
21
+ "deslop_qc", _deslop_qc_path
22
+ )
23
+ if _deslop_qc_spec is None or _deslop_qc_spec.loader is None:
24
+ raise
25
+ _deslop_qc_module = importlib.util.module_from_spec(_deslop_qc_spec)
26
+ _deslop_qc_spec.loader.exec_module(_deslop_qc_module)
27
+ analyze_deslop_qc = _deslop_qc_module.analyze_deslop_qc
28
+
29
+
30
+ def _format_frame_facts(scene):
31
+ """将帧动作描述格式化为可注入 agent brief 的文本。"""
32
+ facts = scene.get("frame_facts", {})
33
+ if not facts:
34
+ return ""
35
+ lines = []
36
+ for ts in sorted(facts.keys(), key=lambda x: float(x)):
37
+ actions = facts[ts]
38
+ lines.append(f" {ts}s: {'; '.join(actions)}")
39
+ return "\n 帧动作:\n" + "\n".join(lines)
40
+
41
+
42
+ def _text_char_count(text):
43
+ """计算文本的有效字数(去除标点和空白,这些不占 TTS 朗读时间)。"""
44
+ return len(
45
+ re.sub(
46
+ r'[,。!?、;:…“”‘’《》〈〉\s"\'「」『』()()【】\[\]—~·,.!?;:\\-]',
47
+ "",
48
+ text or "",
49
+ )
50
+ )
51
+
52
+
53
+ def _contains_cjk(text):
54
+ return bool(re.search(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]", text or ""))
55
+
56
+
57
+ def _writing_text_units(text):
58
+ """Length unit used for ASR writing chunks: CJK chars, otherwise words."""
59
+ text = str(text or "")
60
+ if _contains_cjk(text):
61
+ return len(re.sub(r"\s+", "", text))
62
+ return len(re.findall(r"\b\w+\b", text))
63
+
64
+
65
+ def _sentence_pieces(text):
66
+ """Split text into sentence-like pieces while keeping terminal punctuation."""
67
+ text = re.sub(r"\s+", " ", str(text or "")).strip()
68
+ if not text:
69
+ return []
70
+ parts = re.split(r"([。!?!?;;.])", text)
71
+ cleaned = []
72
+ for idx in range(0, len(parts), 2):
73
+ body = parts[idx].strip()
74
+ punct = parts[idx + 1] if idx + 1 < len(parts) else ""
75
+ if body or punct:
76
+ cleaned.append((body + punct).strip())
77
+ return cleaned or [text]
78
+
79
+
80
+ def _split_text_by_sentence_windows(text, min_chars=500, max_chars=800):
81
+ """Clipto-style three-tier sentence boundary splitting for long ASR text."""
82
+ text = re.sub(r"\s+", " ", str(text or "")).strip()
83
+ if not text:
84
+ return []
85
+ if _writing_text_units(text) <= max_chars:
86
+ return _sentence_pieces(text)
87
+
88
+ result = []
89
+ rest = text
90
+ sentence_marks = "。!?!?;;."
91
+ while _writing_text_units(rest) > max_chars:
92
+ # The min/max thresholds are unit-based but punctuation is char-indexed.
93
+ # For Chinese (the dominant recap target) these are nearly identical; for
94
+ # non-CJK this remains a safe sentence-boundary heuristic around words.
95
+ char_min = min(len(rest), max(1, min_chars))
96
+ char_max = min(len(rest), max(1, max_chars))
97
+ window = rest[:char_max]
98
+ cut = max(window.rfind(mark) for mark in sentence_marks)
99
+ if cut + 1 < char_min:
100
+ outside = -1
101
+ for i, ch in enumerate(rest[char_max:], start=char_max):
102
+ if ch in sentence_marks:
103
+ outside = i
104
+ break
105
+ if outside >= 0 and outside + 1 <= len(rest):
106
+ cut = outside
107
+ else:
108
+ cut = char_max - 1
109
+ piece = rest[: cut + 1].strip()
110
+ if not piece:
111
+ piece = rest[:char_max].strip()
112
+ cut = char_max - 1
113
+ result.append(piece)
114
+ rest = rest[cut + 1 :].strip()
115
+ if rest:
116
+ result.append(rest)
117
+ return result
118
+
119
+
120
+ def _timed_sentence_pieces(seg, min_chars, max_chars):
121
+ """Split one ASR segment into timed sentence pieces with approximate spans."""
122
+ text = str(seg.get("text", "")).strip()
123
+ if not text:
124
+ return []
125
+ try:
126
+ start = float(seg.get("start", 0))
127
+ end = float(seg.get("end", start))
128
+ except (TypeError, ValueError):
129
+ start = end = 0.0
130
+ if end < start:
131
+ end = start
132
+ pieces = []
133
+ for sentence in _sentence_pieces(text):
134
+ if _writing_text_units(sentence) > max_chars:
135
+ pieces.extend(
136
+ _split_text_by_sentence_windows(
137
+ sentence, min_chars=min_chars, max_chars=max_chars
138
+ )
139
+ )
140
+ else:
141
+ pieces.append(sentence)
142
+ total_units = sum(max(1, _writing_text_units(piece)) for piece in pieces) or 1
143
+ duration = max(0.0, end - start)
144
+ cursor = start
145
+ timed = []
146
+ for idx, piece in enumerate(pieces):
147
+ units = max(1, _writing_text_units(piece))
148
+ piece_duration = duration * units / total_units if duration else 0.0
149
+ piece_end = end if idx == len(pieces) - 1 else cursor + piece_duration
150
+ timed.append(
151
+ {
152
+ "start": round(cursor, 2),
153
+ "end": round(piece_end, 2),
154
+ "text": piece,
155
+ "char_count": _writing_text_units(piece),
156
+ }
157
+ )
158
+ cursor = piece_end
159
+ return timed
160
+
161
+
162
+ def _scene_ids_for_range(scenes, start, end):
163
+ scene_ids = []
164
+ duration = max(0.001, float(end) - float(start))
165
+ for scene in scenes or []:
166
+ try:
167
+ s_start = float(scene.get("start", 0))
168
+ s_end = float(scene.get("end", s_start))
169
+ except (TypeError, ValueError):
170
+ continue
171
+ overlap = _overlap_seconds(start, end, s_start, s_end)
172
+ # Ignore tiny boundary tails from approximate ASR sentence timing. A
173
+ # scene id should mean the chunk materially belongs to that scene.
174
+ if overlap and (overlap >= duration * 0.2 or overlap >= 3.0):
175
+ scene_ids.append(scene.get("scene_id"))
176
+ return [sid for sid in scene_ids if sid is not None]
177
+
178
+
179
+ def _chunk_asr_for_writing(
180
+ segments, scenes_analysis=None, min_chars=None, max_chars=None
181
+ ):
182
+ """Chunk ASR into semantic windows before an agent writes long-dialogue recaps.
183
+
184
+ The strategy mirrors Clipto's segment splitter: accumulate a window, prefer
185
+ the last sentence boundary inside max length, allow a slightly longer first
186
+ boundary outside the window, and fall back to the remaining text. CJK text is
187
+ measured by characters; non-CJK text is measured by words.
188
+ """
189
+ min_chars = int(min_chars or CONFIG.get("asr_chunk_min_chars", 500))
190
+ max_chars = int(max_chars or CONFIG.get("asr_chunk_max_chars", 800))
191
+ min_chars = max(1, min(min_chars, max_chars))
192
+ max_chars = max(min_chars, max_chars)
193
+
194
+ pieces = []
195
+ for seg in segments or []:
196
+ if isinstance(seg, dict):
197
+ pieces.extend(_timed_sentence_pieces(seg, min_chars, max_chars))
198
+
199
+ chunks = []
200
+ current = []
201
+ current_units = 0
202
+ current_scene_ids = set()
203
+
204
+ def flush():
205
+ nonlocal current, current_units, current_scene_ids
206
+ if not current:
207
+ return
208
+ chunks.append(
209
+ {
210
+ "chunk_id": len(chunks),
211
+ "start": round(float(current[0]["start"]), 2),
212
+ "end": round(float(current[-1]["end"]), 2),
213
+ "scene_ids": sorted(
214
+ current_scene_ids, key=lambda sid: (isinstance(sid, str), sid)
215
+ ),
216
+ "char_count": current_units,
217
+ "text": " ".join(piece["text"] for piece in current).strip(),
218
+ "segments": current,
219
+ }
220
+ )
221
+ current = []
222
+ current_units = 0
223
+ current_scene_ids = set()
224
+
225
+ for piece in pieces:
226
+ units = max(
227
+ 1, int(piece.get("char_count", _writing_text_units(piece.get("text", ""))))
228
+ )
229
+ piece_scene_ids = set(
230
+ _scene_ids_for_range(scenes_analysis, piece["start"], piece["end"])
231
+ )
232
+ crosses_scene = (
233
+ current
234
+ and current_scene_ids
235
+ and piece_scene_ids
236
+ and not (current_scene_ids & piece_scene_ids)
237
+ )
238
+ if crosses_scene and current_units >= min_chars:
239
+ flush()
240
+ if current and current_units >= min_chars and current_units + units > max_chars:
241
+ flush()
242
+ current.append(piece)
243
+ current_scene_ids.update(piece_scene_ids)
244
+ current_units += units
245
+ if current_units >= max_chars:
246
+ flush()
247
+ flush()
248
+ return chunks
249
+
250
+
251
+ def _truncate_at_sentence(text, max_chars):
252
+ """在句子边界截断,不产生残句。max_chars 按有效字符计(不含标点空白)。"""
253
+ if _text_char_count(text) <= max_chars:
254
+ return text
255
+ eff = 0
256
+ cutoff = len(text)
257
+ for i, ch in enumerate(text):
258
+ eff += 1 if _text_char_count(ch) else 0
259
+ if eff > max_chars:
260
+ cutoff = i + 1
261
+ break
262
+ idx = max(text[:cutoff].rfind(sep) for sep in ["。", "!", "?", "!", "?"])
263
+ if idx > 0:
264
+ return text[: idx + 1]
265
+ idx = max(text[:cutoff].rfind(sep) for sep in [",", "、", ";", ","])
266
+ if idx > 3:
267
+ return text[:idx] + "。"
268
+ return ""
269
+
270
+
271
+ def _char_bigrams(text):
272
+ return {text[i : i + 2] for i in range(len(text) - 1) if text[i : i + 2].strip()}
273
+
274
+
275
+ def _post_dedup_narration(narration):
276
+ """去除相邻相似解说段(bigram 重叠 >60% 则合并)。"""
277
+ if len(narration) < 2:
278
+ return narration
279
+ result = [narration[0]]
280
+ for seg in narration[1:]:
281
+ prev = result[-1]
282
+ if (
283
+ not prev.get("narration", "").strip()
284
+ or not seg.get("narration", "").strip()
285
+ ):
286
+ result.append(seg)
287
+ continue
288
+ set_a, set_b = _char_bigrams(prev["narration"]), _char_bigrams(seg["narration"])
289
+ if not set_a or not set_b:
290
+ result.append(seg)
291
+ continue
292
+ overlap = len(set_a & set_b) / min(len(set_a), len(set_b))
293
+ # Only merge near-identical adjacent beats. Short Chinese beats share many
294
+ # bigrams by chance, so a low threshold collapses intentional parallel beats
295
+ # ("他不再试探" / "他直接赌上全力") and silently drops density below target.
296
+ if overlap > 0.6:
297
+ # Validation is also the handoff boundary for renderer metadata. When two
298
+ # near-identical narration beats are merged, retain overlays authored on either
299
+ # beat instead of silently discarding the second beat's visual instructions.
300
+ merged_overlays = []
301
+ for candidate in (prev, seg):
302
+ raw_overlays = candidate.get("visual_overlays")
303
+ if isinstance(raw_overlays, list):
304
+ merged_overlays.extend(copy.deepcopy(raw_overlays))
305
+ if len(seg["narration"]) > len(prev["narration"]):
306
+ prev["narration"] = seg["narration"]
307
+ prev["end"] = seg["end"]
308
+ prev["pause_after_ms"] = seg.get(
309
+ "pause_after_ms", prev.get("pause_after_ms", 600)
310
+ )
311
+ if merged_overlays:
312
+ prev["visual_overlays"] = merged_overlays
313
+ log(f" 去重合并: {prev['start']:.0f}-{prev['end']:.0f}s")
314
+ else:
315
+ result.append(seg)
316
+ removed = len(narration) - len(result)
317
+ if removed:
318
+ log(f" 去重: {len(narration)} → {len(result)} 段 (合并 {removed} 段)")
319
+ return result
320
+
321
+
322
+ def _scene_available_seconds(start, end, pause_after_ms=None):
323
+ del pause_after_ms # pause_after_ms affects the next segment gap during assembly, not current speech capacity.
324
+ tail_pad = max(0.0, float(CONFIG.get("narration_tail_pad_seconds", 0.1) or 0.0))
325
+ return max(0.0, float(end) - float(start) - tail_pad)
326
+
327
+
328
+ def _recommended_char_budget(start, end, pause_after_ms=None):
329
+ # account for the global narration atempo (CONFIG['narration_speed']) so a beat's text
330
+ # is budgeted against the FINAL sped-up audio, not the raw TTS rate — otherwise windows
331
+ # are over-sized and the bed shows long silent gaps between sentences.
332
+ effective_rate = (
333
+ CONFIG["speech_rate"]
334
+ * CONFIG["speech_safety_margin"]
335
+ * float(CONFIG.get("narration_speed", 1.0) or 1.0)
336
+ )
337
+ available = _scene_available_seconds(start, end, pause_after_ms)
338
+ return max(0, int(available * effective_rate))
339
+
340
+
341
+ def _find_scene_for_midpoint(scenes_analysis, start, end):
342
+ mid = (float(start) + float(end)) / 2
343
+ for scene in scenes_analysis:
344
+ if scene["start"] <= mid <= scene["end"]:
345
+ return scene
346
+ return None
347
+
348
+
349
+ def _normalise_narration_segment(seg, scenes_analysis=None):
350
+ if not isinstance(seg, dict):
351
+ return None
352
+ try:
353
+ start = float(seg.get("start"))
354
+ end = float(seg.get("end"))
355
+ except (TypeError, ValueError):
356
+ return None
357
+ if end <= start:
358
+ return None
359
+ text = str(seg.get("narration", "")).strip()
360
+ if not text:
361
+ return None
362
+ pause = seg.get("pause_after_ms", CONFIG.get("breath_ms", 250))
363
+ try:
364
+ pause = int(pause)
365
+ except (TypeError, ValueError):
366
+ pause = CONFIG.get("breath_ms", 250)
367
+ item = {
368
+ "start": round(start, 2),
369
+ "end": round(end, 2),
370
+ "narration": text,
371
+ "pause_after_ms": pause,
372
+ "overlaps_speech": bool(seg.get("overlaps_speech", True)),
373
+ }
374
+ for optional_key in (
375
+ "source_start",
376
+ "source_end",
377
+ "source_clip_id",
378
+ "source_entry_policy",
379
+ "source_entry_reason",
380
+ ):
381
+ if optional_key in seg:
382
+ item[optional_key] = seg[optional_key]
383
+ # carry the per-beat emotion/tone tag (MiMo TTS instruct) through lint untouched
384
+ emotion = seg.get("emotion")
385
+ if isinstance(emotion, str) and emotion.strip():
386
+ item["emotion"] = emotion.strip()
387
+ # Preserve renderer-owned metadata across the full-mode validation rewrite. The recap
388
+ # orchestrator filters supported overlay kinds later; validation must not erase authored
389
+ # overlays merely because it normalizes narration timing/text fields.
390
+ visual_overlays = seg.get("visual_overlays")
391
+ if isinstance(visual_overlays, list):
392
+ item["visual_overlays"] = copy.deepcopy(visual_overlays)
393
+ # Authored timing is a delivery contract. Scene boundaries are approximate
394
+ # visual-analysis buckets, while source sentence anchors are measured audio
395
+ # handoff points. Lint may warn when a block crosses a scene, but validation
396
+ # must never silently clamp start/end and invalidate an audio-safe handoff.
397
+ return item
398
+
399
+
400
+ def _clean_narration_punctuation(text):
401
+ text = re.sub(r"\s+", " ", text or "").strip()
402
+ text = re.sub(r'[,:、;,]["\']?[。!?]', "。", text)
403
+ text = re.sub(r'["\']。$', "。", text)
404
+ return text
405
+
406
+
407
+ def _overlap_seconds(start, end, other_start, other_end):
408
+ return max(
409
+ 0.0, min(float(end), float(other_end)) - max(float(start), float(other_start))
410
+ )
@@ -0,0 +1,260 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ import re
5
+ import time
6
+ from pathlib import Path
7
+
8
+ from lib import CONFIG
9
+ from lib import log, run_cmd, get_video_duration, mimo_asr_api_call, file_fingerprint
10
+
11
+ # ── Step 3: ASR 转录(MiMo mimo-v2.5-asr,云端 API)────────────────────────
12
+
13
+ _ASR_AUDIO_MIME = "audio/wav"
14
+
15
+
16
+ def _load_name_glossary(work_dir):
17
+ """从 background_research.json 收集已知人名(characters 键 + character_details 键及别名)。
18
+
19
+ 返回去重后、长度 >=2 的人名列表(按长度降序,长名优先匹配)。文件缺失或无名字时返回 []。
20
+ """
21
+ research_path = Path(work_dir) / "background_research.json"
22
+ if not research_path.exists():
23
+ return []
24
+ try:
25
+ data = json.loads(research_path.read_text(encoding="utf-8"))
26
+ except (OSError, ValueError):
27
+ return []
28
+ if not isinstance(data, dict):
29
+ return []
30
+
31
+ names = set()
32
+ characters = data.get("characters")
33
+ if isinstance(characters, dict):
34
+ names.update(characters.keys())
35
+ details = data.get("character_details")
36
+ if isinstance(details, dict):
37
+ for key, val in details.items():
38
+ names.add(key)
39
+ if isinstance(val, dict):
40
+ aliases = val.get("aliases")
41
+ if isinstance(aliases, list):
42
+ names.update(a for a in aliases if isinstance(a, str))
43
+
44
+ cleaned = {n for n in names if isinstance(n, str) and len(n) >= 2}
45
+ return sorted(cleaned, key=len, reverse=True)
46
+
47
+
48
+ def _correct_text_with_glossary(text, names):
49
+ """用人名表修正 ASR 同音字错误(如 叶青眉 → 叶轻眉)。
50
+
51
+ 对每个已知人名,扫描文本中所有等长窗口;若某窗口与人名恰好相差一个字符(仅一处不同),
52
+ 则替换为该人名。严格约束为「恰好一字之差」,避免过度纠正(叶轻风 与 叶轻眉 也是一字之差,
53
+ 但这正是限制为单字替换的边界——只有当窗口本身不是任何已知人名时才会被改写)。
54
+ """
55
+ if not text or not names:
56
+ return text
57
+ name_set = set(names)
58
+ for name in names:
59
+ n = len(name)
60
+ if n < 2 or len(text) < n:
61
+ continue
62
+ i = 0
63
+ while i <= len(text) - n:
64
+ window = text[i:i + n]
65
+ # Only rewrite a one-char-off window when it is NOT itself a known name —
66
+ # otherwise a distinct real name one char away (叶轻风 vs 叶轻眉) would be corrupted.
67
+ if window != name and window not in name_set and _one_char_diff(window, name):
68
+ text = text[:i] + name + text[i + n:]
69
+ i += n
70
+ else:
71
+ i += 1
72
+ return text
73
+
74
+
75
+ def _one_char_diff(a, b):
76
+ """两个等长字符串是否恰好相差一个字符(仅一处不同)。"""
77
+ if len(a) != len(b):
78
+ return False
79
+ diff = 0
80
+ for ca, cb in zip(a, b):
81
+ if ca != cb:
82
+ diff += 1
83
+ if diff > 1:
84
+ return False
85
+ return diff == 1
86
+
87
+
88
+ def _apply_glossary_corrections(segments, work_dir):
89
+ """对已转录的 segments 就地应用人名表修正;无人名表时为 no-op。"""
90
+ names = _load_name_glossary(work_dir)
91
+ if not names:
92
+ return segments
93
+ for seg in segments:
94
+ original = seg.get("text") or ""
95
+ corrected = _correct_text_with_glossary(original, names)
96
+ if corrected != original:
97
+ seg["text"] = corrected
98
+ return segments
99
+
100
+
101
+ def _audio_meta_path(work_dir):
102
+ return Path(work_dir) / "audio.wav.meta.json"
103
+
104
+
105
+ def _write_audio_meta(work_dir, video_path):
106
+ _audio_meta_path(work_dir).write_text(
107
+ json.dumps({
108
+ "schema_version": 1,
109
+ "source_video_fingerprint": file_fingerprint(video_path),
110
+ "audio": "audio.wav",
111
+ }, ensure_ascii=False, indent=2),
112
+ encoding="utf-8",
113
+ )
114
+
115
+
116
+ def transcribe_audio(video_path, work_dir):
117
+ """提取音频并用 MiMo ASR 分段转录,通过分段合成时间戳。"""
118
+ asr_file = work_dir / "asr_result.json"
119
+
120
+ if not CONFIG.get("mimo_asr_api_key"):
121
+ key_name = CONFIG.get("mimo_asr_api_key_source", "MIMO_API_KEY")
122
+ log(f"ASR 跳过:未设置 {key_name}(MiMo ASR 需要;VLM/TTS 也需要同一个 key)。"
123
+ f"如不需要对白可加 --skip-asr")
124
+ asr_file.write_text(json.dumps([], ensure_ascii=False, indent=2), encoding="utf-8")
125
+ return []
126
+
127
+ # 提取音频
128
+ audio_wav = work_dir / "audio.wav"
129
+ cmd = ["ffmpeg", "-y", "-i", str(video_path), "-vn",
130
+ "-ar", "16000", "-ac", "1", str(audio_wav)]
131
+ result = run_cmd(cmd)
132
+ if result.returncode != 0:
133
+ raise RuntimeError(f"音频提取失败: {result.stderr}")
134
+ _write_audio_meta(work_dir, video_path)
135
+
136
+ # 获取音频时长
137
+ duration = get_video_duration(video_path)
138
+ if duration <= 0:
139
+ # ffprobe 失败时不再伪造 180s 时长,否则会向 asr_result.json 写入虚构时间戳
140
+ log("ASR 警告: 无法获取音频时长(ffprobe 失败),跳过 ASR 转录")
141
+ asr_file.write_text(json.dumps([], ensure_ascii=False, indent=2), encoding="utf-8")
142
+ return []
143
+
144
+ segments_dir = work_dir / "audio_segments"
145
+ segments_dir.mkdir(exist_ok=True)
146
+
147
+ segment_length = max(5, int(CONFIG.get("asr_segment_seconds", 30) or 30))
148
+ if duration <= segment_length:
149
+ # 短音频,整段转录
150
+ text = _run_asr(audio_wav)
151
+ asr_result = [{"start": 0.0, "end": round(duration, 2), "text": text}]
152
+ else:
153
+ # 长音频,分段转录(更细的窗口 → 更精细的对白时间戳)
154
+ asr_result = _segment_and_transcribe(audio_wav, segments_dir, duration, segment_length)
155
+
156
+ # 用 background_research.json 的人名表修正 ASR 同音字错误(如 叶青眉 → 叶轻眉);无人名表时为 no-op
157
+ _apply_glossary_corrections(asr_result, work_dir)
158
+
159
+ # 保存
160
+ asr_file.write_text(json.dumps(asr_result, ensure_ascii=False, indent=2), encoding="utf-8")
161
+
162
+ total_text = " ".join(s["text"] for s in asr_result if s["text"])
163
+ empty = sum(1 for s in asr_result if not s["text"])
164
+ suffix = f"({empty} 段无文本:静音/切分失败/超限被跳过)" if empty else ""
165
+ log(f"ASR 转录完成: {len(asr_result)} 段, 共 {len(total_text)} 字{suffix}")
166
+ return asr_result
167
+
168
+
169
+ def _strip_reasoning_residue(text):
170
+ """Remove MiMo reasoning-model <think>…</think> leakage from ASR content.
171
+
172
+ Thinking-disable is not applied to -asr models (lib._prepare_api_payload), so the reasoning
173
+ model can leak a <think> block — full, truncated/unclosed, or a leading orphan/residual tag
174
+ (a bare "think>" prefix) — into the transcript. The dubbing implementation carries its own
175
+ copy of this policy; keep behavior aligned through tests.
176
+ """
177
+ text = re.sub(r"(?is)<think\b.*?</think\s*>", "", text) # full <think>…</think> block
178
+ text = re.sub(r"(?is)<think\b.*\Z", "", text) # unclosed/truncated <think tail
179
+ text = re.sub(r"(?i)^\s*<?/?think\s*>\s*", "", text) # leading orphan/residual think tag
180
+ return text
181
+
182
+
183
+ def _run_asr(wav_path):
184
+ """用 MiMo ASR (mimo-v2.5-asr) 转录单个 wav 文件,返回纯文本。
185
+
186
+ 音频以 base64 data-URI 放进 OpenAI 风格的 chat/completions 消息里,转写文本回到
187
+ choices[0].message.content。API/响应结构失败会抛错,避免把瞬时失败缓存成空转写;
188
+ 只有无音频、超体积等确定不可发送的片段返回空串。
189
+ """
190
+ try:
191
+ raw = Path(wav_path).read_bytes()
192
+ except OSError as e:
193
+ log(f"ASR 警告: 无法读取音频 {wav_path}: {e}")
194
+ return ""
195
+ if not raw:
196
+ return ""
197
+
198
+ b64 = base64.b64encode(raw).decode("ascii")
199
+ max_b64_bytes = int(float(CONFIG.get("mimo_asr_base64_max_mb", 10.0)) * 1024 * 1024)
200
+ if len(b64) > max_b64_bytes:
201
+ log(f"ASR 警告: 分片 base64 体积 {len(b64) / 1024 / 1024:.1f}MB 超过 MiMo 上限 "
202
+ f"{CONFIG.get('mimo_asr_base64_max_mb')}MB,跳过该段;可调小 ASR_SEGMENT_SECONDS")
203
+ return ""
204
+
205
+ payload = {
206
+ "model": CONFIG.get("mimo_asr_model", "mimo-v2.5-asr"),
207
+ "messages": [{
208
+ "role": "user",
209
+ "content": [{
210
+ "type": "input_audio",
211
+ "input_audio": {"data": f"data:{_ASR_AUDIO_MIME};base64,{b64}"},
212
+ }],
213
+ }],
214
+ "asr_options": {"language": CONFIG.get("mimo_asr_language", "auto")},
215
+ }
216
+ try:
217
+ resp = mimo_asr_api_call(payload)
218
+ except Exception as e:
219
+ raise RuntimeError(f"MiMo ASR 调用失败: {e}") from e
220
+ try:
221
+ return _strip_reasoning_residue(str(resp["choices"][0]["message"]["content"] or "")).strip()
222
+ except (KeyError, IndexError, TypeError):
223
+ raise RuntimeError(f"MiMo ASR 返回结构异常: {json.dumps(resp, ensure_ascii=False)[:200]}")
224
+
225
+
226
+ def _segment_and_transcribe(audio_wav, segments_dir, total_duration, segment_length=None):
227
+ """分段转录长音频"""
228
+ if segment_length is None:
229
+ segment_length = max(5, int(CONFIG.get("asr_segment_seconds", 30) or 30))
230
+ # 长视频 ASR 是顺序调用;可选节流让调用间隔开,降低踩到集群限流的频率(默认 0=不节流)
231
+ try:
232
+ throttle = max(0.0, float(os.environ.get("ASR_THROTTLE_SECONDS", "0") or 0))
233
+ except ValueError:
234
+ throttle = 0.0
235
+ results = []
236
+
237
+ for i, start in enumerate(range(0, int(total_duration), segment_length)):
238
+ if throttle and i:
239
+ time.sleep(throttle)
240
+ end = min(start + segment_length, total_duration)
241
+ seg_wav = segments_dir / f"seg_{i:03d}.wav"
242
+
243
+ cmd = ["ffmpeg", "-y", "-i", str(audio_wav),
244
+ "-ss", str(start), "-to", str(end),
245
+ "-ar", "16000", "-ac", "1", str(seg_wav)]
246
+ cut = run_cmd(cmd)
247
+ if cut.returncode != 0:
248
+ # 切分失败时不要对磁盘上的陈旧/残缺音频转录,否则会得到错位文本
249
+ log(f" 段 {i+1}: 切分失败,跳过转录 ({cut.stderr.strip()[:200]})")
250
+ text = ""
251
+ else:
252
+ text = _run_asr(seg_wav)
253
+ results.append({
254
+ "start": round(start, 2),
255
+ "end": round(end, 2),
256
+ "text": text,
257
+ })
258
+ log(f" 段 {i+1}: {start:.0f}s-{end:.0f}s => {len(text)} 字")
259
+
260
+ return results
@@ -0,0 +1,12 @@
1
+ """Public narration-brief API for this self-contained skill."""
2
+
3
+ from agent_brief import build_agent_brief
4
+ from brief_context import assess_understanding_substrate
5
+ from narration_lint import lint_narration, validate_narration_or_raise
6
+
7
+ __all__ = [
8
+ "assess_understanding_substrate",
9
+ "build_agent_brief",
10
+ "lint_narration",
11
+ "validate_narration_or_raise",
12
+ ]