@oh-story/dsh 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +7 -7
  2. package/lib/client.js +7 -3
  3. package/lib/client.js.map +2 -2
  4. package/lib/drama/manifest.json +212 -142
  5. package/lib/drama/skills/short-drama/SKILL.md +14 -2
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  11. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  13. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  14. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  15. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  16. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  17. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  18. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  19. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  20. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  21. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  22. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  23. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  25. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  26. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  27. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  28. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  29. package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
  30. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  31. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  32. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  33. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  46. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
  47. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
  48. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
  49. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
  50. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
  51. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
  53. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
  54. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  55. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  56. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  57. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  58. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  59. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  60. package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
  61. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  62. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  63. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  64. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
  65. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  66. package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
  67. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  68. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
  70. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
  71. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
  72. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
  73. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  74. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  75. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
  77. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
  78. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
  79. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  80. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  81. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  82. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  83. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  84. package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
  85. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  86. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  87. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  88. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  89. package/lib/index.js +71 -21
  90. package/lib/index.js.map +3 -3
  91. package/lib/oh-story/manifest.json +545 -310
  92. package/lib/oh-story/roles/chapter-extractor.md +11 -11
  93. package/lib/oh-story/roles/character-designer.md +1 -2
  94. package/lib/oh-story/roles/consistency-checker.md +11 -9
  95. package/lib/oh-story/roles/narrative-writer.md +24 -25
  96. package/lib/oh-story/roles/story-architect.md +9 -61
  97. package/lib/oh-story/roles/story-explorer.md +20 -125
  98. package/lib/oh-story/roles/story-researcher.md +0 -1
  99. package/lib/oh-story/skills/story/SKILL.md +6 -6
  100. package/lib/oh-story/skills/story/VERSION +1 -1
  101. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  102. package/lib/oh-story/skills/story/references/author-memory.md +40 -118
  103. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
  104. package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
  105. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  106. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
  107. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  108. package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
  109. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  110. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
  111. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  112. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
  113. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  114. package/lib/oh-story/skills/story-import/SKILL.md +63 -579
  115. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  116. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  117. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  118. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  119. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  120. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  121. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  122. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  123. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
  124. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  125. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  126. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
  127. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  128. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
  129. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
  130. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
  131. package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
  132. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
  133. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
  134. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  135. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  136. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
  137. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  139. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  141. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  142. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
  143. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
  144. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  145. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
  146. package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
  147. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  148. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  149. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  150. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  151. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  152. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  153. package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
  154. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  155. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
  156. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
  157. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  158. package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
  159. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  160. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
  161. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  162. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  163. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  164. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  165. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  166. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
  167. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  168. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  169. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  170. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  171. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  172. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  173. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  174. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  175. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  176. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  177. package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
  178. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
  179. package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
  180. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  181. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  182. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  183. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  184. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  185. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
  186. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
  187. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
  188. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  189. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
  190. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
  191. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  192. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
  193. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
  194. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
  195. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  196. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
  197. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  198. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
  199. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  200. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
  201. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
  202. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
  203. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
  204. package/lib/oh-story/skills/story-review/SKILL.md +19 -273
  205. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  206. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
  207. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  208. package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
  209. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  210. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  211. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  212. package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
  213. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  214. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  215. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  216. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  217. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
  218. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
  219. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  220. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
  221. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
  222. package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
  223. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  224. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
  225. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  226. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  227. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  228. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
  229. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  230. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  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/long-genre-catalog.md +1 -1
  234. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  236. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  237. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  238. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  239. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  240. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  241. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  242. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  243. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  244. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  245. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  246. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
  247. package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
  248. package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
  249. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  250. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  251. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  252. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  253. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  254. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  255. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
  257. package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
  258. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  259. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  260. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  261. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  262. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
  263. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  264. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  265. package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
  266. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  267. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  268. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  270. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  271. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
  272. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  273. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  274. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  276. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  277. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  278. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  279. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  280. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
  281. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
  282. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
  283. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  284. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
  285. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  286. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  287. package/lib/types/client/video-studio.d.ts.map +1 -1
  288. package/lib/types/native-hooks.d.ts +2 -2
  289. package/lib/types/native-hooks.d.ts.map +1 -1
  290. package/lib/types/role-tool.d.ts.map +1 -1
  291. package/lib/types/skill-provider.d.ts.map +1 -1
  292. package/lib/types/video-project.d.ts.map +1 -1
  293. package/lib/types/workspace-route.d.ts.map +1 -1
  294. package/lib/video-recap/CHANGELOG.md +60 -1
  295. package/lib/video-recap/README.en.md +348 -137
  296. package/lib/video-recap/README.md +340 -136
  297. package/lib/video-recap/manifest.json +406 -251
  298. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  299. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  300. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  301. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  302. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  303. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  304. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  305. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  306. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  307. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  308. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  309. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  310. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  311. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  312. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  313. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  314. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  315. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  316. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  317. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  318. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  319. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  320. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  321. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  322. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  323. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  324. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  325. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  326. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  327. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  328. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  329. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  330. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  331. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  332. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  333. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  334. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  335. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  336. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  337. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  338. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  339. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  340. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  341. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  342. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  343. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  344. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  345. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  346. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  347. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  348. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  349. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  350. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  351. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  352. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  353. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  354. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  355. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  356. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  357. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  358. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  359. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  360. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  361. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  362. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  363. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  364. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  365. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  366. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  367. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  368. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  369. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  370. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  371. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  372. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  373. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  374. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  375. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  376. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  377. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  378. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  379. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  380. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  381. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  382. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  383. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  384. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  385. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  386. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  387. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  388. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  389. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  390. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  391. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  392. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  393. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  394. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  395. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  396. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  397. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  398. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  399. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  400. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  401. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  402. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  403. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  404. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  405. package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
  406. package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
  407. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  408. package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
  409. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  410. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  411. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  412. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  413. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  414. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  415. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  416. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  417. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  418. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  419. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  420. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  421. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  422. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  423. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  424. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  425. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  426. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  427. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  428. package/package.json +1 -1
  429. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
  430. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
  431. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  432. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  433. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  435. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  436. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  437. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
  438. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
  439. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  440. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
  441. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  442. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  443. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  444. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -6,43 +6,29 @@ import time
6
6
  from pathlib import Path
7
7
 
8
8
  from lib import CONFIG
9
- from lib import log, run_cmd, get_video_duration, mimo_asr_api_call, file_fingerprint
9
+ from lib import log, run_cmd, get_video_duration, mimo_asr_api_call
10
+ from detect import _audio_meta_path, _write_audio_meta
11
+ from asr_timing_evidence import (
12
+ EVIDENCE_FILENAME,
13
+ load_glossary_names,
14
+ write_asr_timing_evidence,
15
+ )
10
16
 
11
17
  # ── Step 3: ASR 转录(MiMo mimo-v2.5-asr,云端 API)────────────────────────
12
18
 
13
19
  _ASR_AUDIO_MIME = "audio/wav"
14
20
 
15
21
 
22
+ class ASRProviderError(RuntimeError):
23
+ """Provider/API response failed, so an empty transcript is not cacheable success."""
24
+
25
+
16
26
  def _load_name_glossary(work_dir):
17
27
  """从 background_research.json 收集已知人名(characters 键 + character_details 键及别名)。
18
28
 
19
29
  返回去重后、长度 >=2 的人名列表(按长度降序,长名优先匹配)。文件缺失或无名字时返回 []。
20
30
  """
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)
31
+ return load_glossary_names(work_dir)
46
32
 
47
33
 
48
34
  def _correct_text_with_glossary(text, names):
@@ -91,77 +77,109 @@ def _apply_glossary_corrections(segments, work_dir):
91
77
  if not names:
92
78
  return segments
93
79
  for seg in segments:
94
- original = seg.get("text") or ""
80
+ original = seg["text"]
95
81
  corrected = _correct_text_with_glossary(original, names)
96
82
  if corrected != original:
97
83
  seg["text"] = corrected
98
84
  return segments
99
85
 
100
86
 
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
87
 
116
88
  def transcribe_audio(video_path, work_dir):
117
89
  """提取音频并用 MiMo ASR 分段转录,通过分段合成时间戳。"""
90
+ work_dir = Path(work_dir)
118
91
  asr_file = work_dir / "asr_result.json"
92
+ (work_dir / EVIDENCE_FILENAME).unlink(missing_ok=True)
119
93
 
120
- if not CONFIG.get("mimo_asr_api_key"):
121
- key_name = CONFIG.get("mimo_asr_api_key_source", "MIMO_API_KEY")
94
+ if not CONFIG["mimo_asr_api_key"]:
95
+ key_name = CONFIG["mimo_asr_env_var"]
122
96
  log(f"ASR 跳过:未设置 {key_name}(MiMo ASR 需要;VLM/TTS 也需要同一个 key)。"
123
97
  f"如不需要对白可加 --skip-asr")
124
98
  asr_file.write_text(json.dumps([], ensure_ascii=False, indent=2), encoding="utf-8")
99
+ write_asr_timing_evidence(
100
+ work_dir, video_path, "UNAVAILABLE_NO_KEY", final_segments=[]
101
+ )
125
102
  return []
126
103
 
127
104
  # 提取音频
128
105
  audio_wav = work_dir / "audio.wav"
106
+ audio_meta = _audio_meta_path(work_dir)
107
+ audio_wav.unlink(missing_ok=True)
108
+ audio_meta.unlink(missing_ok=True)
129
109
  cmd = ["ffmpeg", "-y", "-i", str(video_path), "-vn",
130
110
  "-ar", "16000", "-ac", "1", str(audio_wav)]
131
- result = run_cmd(cmd)
111
+ try:
112
+ result = run_cmd(cmd)
113
+ except Exception as exc:
114
+ audio_wav.unlink(missing_ok=True)
115
+ audio_meta.unlink(missing_ok=True)
116
+ asr_file.unlink(missing_ok=True)
117
+ write_asr_timing_evidence(work_dir, video_path, "FAILED_AUDIO_EXTRACTION")
118
+ raise RuntimeError("音频提取失败: ffmpeg 无法完成") from exc
132
119
  if result.returncode != 0:
120
+ audio_wav.unlink(missing_ok=True)
121
+ audio_meta.unlink(missing_ok=True)
122
+ asr_file.unlink(missing_ok=True)
123
+ write_asr_timing_evidence(work_dir, video_path, "FAILED_AUDIO_EXTRACTION")
133
124
  raise RuntimeError(f"音频提取失败: {result.stderr}")
134
125
  _write_audio_meta(work_dir, video_path)
135
126
 
136
- # 获取音频时长
137
- duration = get_video_duration(video_path)
138
- if duration <= 0:
139
- # ffprobe 失败时不再伪造 180s 时长,否则会向 asr_result.json 写入虚构时间戳
140
- log("ASR 警告: 无法获取音频时长(ffprobe 失败),跳过 ASR 转录")
127
+ # 获取音频时长;ffprobe 失败时不伪造时长(否则会向 asr_result.json 写入虚构时间戳),
128
+ # 而是记录 UNAVAILABLE_NO_DURATION 证据并跳过转录
129
+ try:
130
+ duration = get_video_duration(audio_wav)
131
+ except RuntimeError as exc:
132
+ log(f"ASR 警告: 无法获取音频时长,跳过 ASR 转录: {exc}")
141
133
  asr_file.write_text(json.dumps([], ensure_ascii=False, indent=2), encoding="utf-8")
134
+ write_asr_timing_evidence(
135
+ work_dir,
136
+ video_path,
137
+ "UNAVAILABLE_NO_DURATION",
138
+ final_segments=[],
139
+ audio_path=audio_wav,
140
+ )
142
141
  return []
143
142
 
144
143
  segments_dir = work_dir / "audio_segments"
145
144
  segments_dir.mkdir(exist_ok=True)
146
145
 
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)
146
+ segment_length = int(CONFIG["asr_segment_seconds"])
147
+ try:
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(
155
+ audio_wav, segments_dir, duration, segment_length
156
+ )
157
+ except ASRProviderError:
158
+ asr_file.unlink(missing_ok=True)
159
+ write_asr_timing_evidence(
160
+ work_dir, video_path, "FAILED_PROVIDER", audio_path=audio_wav
161
+ )
162
+ raise
155
163
 
156
164
  # 用 background_research.json 的人名表修正 ASR 同音字错误(如 叶青眉 → 叶轻眉);无人名表时为 no-op
165
+ observed_result = [dict(segment) for segment in asr_result]
157
166
  _apply_glossary_corrections(asr_result, work_dir)
158
167
 
159
168
  # 保存
160
169
  asr_file.write_text(json.dumps(asr_result, ensure_ascii=False, indent=2), encoding="utf-8")
170
+ status = "AVAILABLE_COARSE" if any(s["text"] for s in asr_result) else "EMPTY_UNKNOWN"
171
+ write_asr_timing_evidence(
172
+ work_dir,
173
+ video_path,
174
+ status,
175
+ observed_segments=observed_result,
176
+ final_segments=asr_result,
177
+ audio_path=audio_wav,
178
+ )
161
179
 
162
180
  total_text = " ".join(s["text"] for s in asr_result if s["text"])
163
181
  empty = sum(1 for s in asr_result if not s["text"])
164
- suffix = f"({empty} 段无文本:静音/切分失败/超限被跳过)" if empty else ""
182
+ suffix = f"({empty} 段无文本:原因未知,不代表已证实静音)" if empty else ""
165
183
  log(f"ASR 转录完成: {len(asr_result)} 段, 共 {len(total_text)} 字{suffix}")
166
184
  return asr_result
167
185
 
@@ -195,14 +213,14 @@ def _run_asr(wav_path):
195
213
  return ""
196
214
 
197
215
  b64 = base64.b64encode(raw).decode("ascii")
198
- max_b64_bytes = int(float(CONFIG.get("mimo_asr_base64_max_mb", 10.0)) * 1024 * 1024)
216
+ max_b64_bytes = int(float(CONFIG["mimo_asr_base64_max_mb"]) * 1024 * 1024)
199
217
  if len(b64) > max_b64_bytes:
200
218
  log(f"ASR 警告: 分片 base64 体积 {len(b64) / 1024 / 1024:.1f}MB 超过 MiMo 上限 "
201
- f"{CONFIG.get('mimo_asr_base64_max_mb')}MB,跳过该段;可调小 ASR_SEGMENT_SECONDS")
219
+ f"{CONFIG['mimo_asr_base64_max_mb']}MB,跳过该段;可调小 ASR_SEGMENT_SECONDS")
202
220
  return ""
203
221
 
204
222
  payload = {
205
- "model": CONFIG.get("mimo_asr_model", "mimo-v2.5-asr"),
223
+ "model": CONFIG["mimo_asr_model"],
206
224
  "messages": [{
207
225
  "role": "user",
208
226
  "content": [{
@@ -210,22 +228,24 @@ def _run_asr(wav_path):
210
228
  "input_audio": {"data": f"data:{_ASR_AUDIO_MIME};base64,{b64}"},
211
229
  }],
212
230
  }],
213
- "asr_options": {"language": CONFIG.get("mimo_asr_language", "auto")},
231
+ "asr_options": {"language": CONFIG["mimo_asr_language"]},
214
232
  }
215
233
  try:
216
234
  resp = mimo_asr_api_call(payload)
217
235
  except Exception as e:
218
- raise RuntimeError(f"MiMo ASR 调用失败: {e}") from e
236
+ raise ASRProviderError(f"MiMo ASR 调用失败: {e}") from e
219
237
  try:
220
238
  return _strip_reasoning_residue(str(resp["choices"][0]["message"]["content"] or "")).strip()
221
239
  except (KeyError, IndexError, TypeError):
222
- raise RuntimeError(f"MiMo ASR 返回结构异常: {json.dumps(resp, ensure_ascii=False)[:200]}")
240
+ raise ASRProviderError(
241
+ f"MiMo ASR 返回结构异常: {json.dumps(resp, ensure_ascii=False)[:200]}"
242
+ )
223
243
 
224
244
 
225
245
  def _segment_and_transcribe(audio_wav, segments_dir, total_duration, segment_length=None):
226
246
  """分段转录长音频"""
227
247
  if segment_length is None:
228
- segment_length = max(5, int(CONFIG.get("asr_segment_seconds", 30) or 30))
248
+ segment_length = int(CONFIG["asr_segment_seconds"])
229
249
  # 长视频 ASR 是顺序调用;可选节流让调用间隔开,降低踩到集群限流的频率(默认 0=不节流)
230
250
  try:
231
251
  throttle = max(0.0, float(os.environ.get("ASR_THROTTLE_SECONDS", "0") or 0))
@@ -0,0 +1,270 @@
1
+ """Write and validate honest timing/provenance evidence for coarse MiMo ASR."""
2
+
3
+ import json
4
+ import math
5
+ import os
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ from lib import file_identity, load_background_research
10
+
11
+
12
+ EVIDENCE_FILENAME = "asr_timing_evidence.json"
13
+ SCHEMA_VERSION = 2
14
+ VALID_STATUSES = {
15
+ "AVAILABLE_COARSE",
16
+ "EXPLICITLY_SKIPPED",
17
+ "UNAVAILABLE_NO_KEY",
18
+ "UNAVAILABLE_NO_DURATION",
19
+ "FAILED_AUDIO_EXTRACTION",
20
+ "FAILED_PROVIDER",
21
+ "EMPTY_UNKNOWN",
22
+ "LEGACY_UNVERIFIED",
23
+ }
24
+ _TOP_KEYS = {
25
+ "schema_version", "status", "source_video", "audio", "asr_result",
26
+ "glossary", "precision", "windows",
27
+ }
28
+ _WINDOW_KEYS = {
29
+ "index", "start", "end", "text_availability", "observed_text",
30
+ "post_glossary_text", "glossary_modified",
31
+ }
32
+ _GLOSSARY_KEYS = {"names", "name_count"}
33
+ _PRECISION = {
34
+ "window_timing": "COARSE_SEGMENT_WINDOWS",
35
+ "dialogue_boundaries": "NOT_VERIFIED",
36
+ "word_alignment": "NOT_PERFORMED",
37
+ "empty_text_meaning": "UNKNOWN_NOT_PROVEN_SILENCE",
38
+ }
39
+
40
+
41
+ def _identity(path):
42
+ """{size, mtime_ns} of a bound file, or None when it does not exist."""
43
+ path = Path(path)
44
+ return file_identity(path) if path.exists() else None
45
+
46
+
47
+ def load_glossary_names(work_dir):
48
+ """Return normalized names that can actually affect ASR correction."""
49
+ data = load_background_research(work_dir)
50
+ names = set()
51
+ characters = data.get("characters")
52
+ if isinstance(characters, dict):
53
+ names.update(characters.keys())
54
+ details = data.get("character_details")
55
+ if isinstance(details, dict):
56
+ for name, info in details.items():
57
+ names.add(name)
58
+ if isinstance(info, dict) and isinstance(info.get("aliases"), list):
59
+ names.update(alias for alias in info["aliases"] if isinstance(alias, str))
60
+ return sorted(
61
+ {name for name in names if isinstance(name, str) and len(name) >= 2},
62
+ key=lambda value: (-len(value), value),
63
+ )
64
+
65
+
66
+ def _glossary_binding(work_dir, *, legacy=False):
67
+ """The glossary names that could have corrected this transcription (legacy: unknown)."""
68
+ if legacy:
69
+ return {"names": None, "name_count": None}
70
+ names = load_glossary_names(work_dir)
71
+ return {"names": names, "name_count": len(names)}
72
+
73
+
74
+ def _atomic_json_write(path, payload):
75
+ path = Path(path)
76
+ path.parent.mkdir(parents=True, exist_ok=True)
77
+ fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
78
+ try:
79
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
80
+ json.dump(payload, handle, ensure_ascii=False, indent=2)
81
+ handle.write("\n")
82
+ os.replace(temporary, path)
83
+ finally:
84
+ try:
85
+ os.unlink(temporary)
86
+ except FileNotFoundError:
87
+ pass
88
+
89
+
90
+ def _window_evidence(observed_segments, final_segments, legacy):
91
+ observed_segments = observed_segments or []
92
+ windows = []
93
+ for index, final in enumerate(final_segments or []):
94
+ observed = observed_segments[index] if index < len(observed_segments) else None
95
+ observed_text = None if legacy or observed is None else str(observed.get("text") or "")
96
+ final_text = str(final.get("text") or "")
97
+ windows.append({
98
+ "index": index,
99
+ "start": final.get("start"),
100
+ "end": final.get("end"),
101
+ "text_availability": "AVAILABLE" if final_text else "UNAVAILABLE_UNKNOWN",
102
+ "observed_text": observed_text,
103
+ "post_glossary_text": final_text,
104
+ "glossary_modified": None if observed_text is None else observed_text != final_text,
105
+ })
106
+ return windows
107
+
108
+
109
+ def write_asr_timing_evidence(
110
+ work_dir, video_path, status, *, observed_segments=None, final_segments=None,
111
+ audio_path=None,
112
+ ):
113
+ """Write a sidecar recording which source/audio/result files it describes, without
114
+ changing the legacy result."""
115
+ if status not in VALID_STATUSES:
116
+ raise ValueError(f"unknown ASR timing evidence status: {status}")
117
+ work_dir = Path(work_dir)
118
+ legacy = status == "LEGACY_UNVERIFIED"
119
+ payload = {
120
+ "schema_version": SCHEMA_VERSION,
121
+ "status": status,
122
+ "source_video": _identity(video_path),
123
+ "audio": _identity(audio_path) if audio_path else None,
124
+ "asr_result": _identity(work_dir / "asr_result.json"),
125
+ "glossary": _glossary_binding(work_dir, legacy=legacy),
126
+ "precision": dict(_PRECISION),
127
+ "windows": _window_evidence(observed_segments, final_segments, legacy),
128
+ }
129
+ path = work_dir / EVIDENCE_FILENAME
130
+ _atomic_json_write(path, payload)
131
+ return path
132
+
133
+
134
+ def _is_int(value):
135
+ return isinstance(value, int) and not isinstance(value, bool)
136
+
137
+
138
+ def _is_number(value):
139
+ return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)
140
+
141
+
142
+ def _is_identity(value):
143
+ return (
144
+ isinstance(value, dict) and set(value) == {"size", "mtime_ns"}
145
+ and _is_int(value["size"]) and _is_int(value["mtime_ns"])
146
+ )
147
+
148
+
149
+ def _valid_glossary(payload, work_dir, legacy):
150
+ if not isinstance(payload, dict) or set(payload) != _GLOSSARY_KEYS:
151
+ return False
152
+ if legacy:
153
+ return payload == _glossary_binding(work_dir, legacy=True)
154
+ return payload == _glossary_binding(work_dir) and _is_int(payload.get("name_count"))
155
+
156
+
157
+ def _valid_status_relationships(status, result, audio):
158
+ texts = [str(segment.get("text") or "") for segment in result]
159
+ if status == "AVAILABLE_COARSE":
160
+ return bool(result) and any(texts) and _is_identity(audio)
161
+ if status == "EMPTY_UNKNOWN":
162
+ return bool(result) and not any(texts) and _is_identity(audio)
163
+ if status == "UNAVAILABLE_NO_DURATION":
164
+ return result == [] and _is_identity(audio)
165
+ if status in {"EXPLICITLY_SKIPPED", "UNAVAILABLE_NO_KEY", "LEGACY_UNVERIFIED"}:
166
+ return audio is None and (status == "LEGACY_UNVERIFIED" or result == [])
167
+ return False
168
+
169
+
170
+ def validate_asr_timing_evidence(evidence_path, video_path, asr_result_path):
171
+ """Return whether a sidecar still describes the current source/audio/result files
172
+ (by size + mtime_ns) and the limited timing it claims."""
173
+ evidence_path = Path(evidence_path)
174
+ result_path = Path(asr_result_path)
175
+ try:
176
+ evidence = json.loads(evidence_path.read_text(encoding="utf-8"))
177
+ result = json.loads(result_path.read_text(encoding="utf-8"))
178
+ except (OSError, ValueError, TypeError):
179
+ return False
180
+ if not isinstance(evidence, dict) or set(evidence) != _TOP_KEYS:
181
+ return False
182
+ if not isinstance(result, list) or not all(isinstance(item, dict) for item in result):
183
+ return False
184
+ if not _is_int(evidence.get("schema_version")) or evidence["schema_version"] != SCHEMA_VERSION:
185
+ return False
186
+ status = evidence.get("status")
187
+ if status not in VALID_STATUSES or status.startswith("FAILED_"):
188
+ return False
189
+ if evidence.get("precision") != _PRECISION:
190
+ return False
191
+ source_identity = _identity(video_path)
192
+ result_identity = _identity(result_path)
193
+ if not _is_identity(source_identity) or evidence.get("source_video") != source_identity:
194
+ return False
195
+ if not _is_identity(result_identity) or evidence.get("asr_result") != result_identity:
196
+ return False
197
+ audio = evidence.get("audio")
198
+ if audio is not None and not _is_identity(audio):
199
+ return False
200
+ if not _valid_status_relationships(status, result, audio):
201
+ return False
202
+ legacy = status == "LEGACY_UNVERIFIED"
203
+ if not _valid_glossary(evidence.get("glossary"), evidence_path.parent, legacy):
204
+ return False
205
+
206
+ if audio is not None:
207
+ audio_path = evidence_path.parent / "audio.wav"
208
+ if audio != _identity(audio_path):
209
+ return False
210
+ try:
211
+ audio_meta = json.loads(
212
+ (evidence_path.parent / "audio.wav.meta.json").read_text(encoding="utf-8")
213
+ )
214
+ except (OSError, ValueError, TypeError):
215
+ return False
216
+ if not isinstance(audio_meta, dict):
217
+ return False
218
+ if audio_meta.get("source_video_identity") != source_identity:
219
+ return False
220
+
221
+ windows = evidence.get("windows")
222
+ if not isinstance(windows, list) or len(windows) != len(result):
223
+ return False
224
+ any_modified = False
225
+ for index, (window, segment) in enumerate(zip(windows, result)):
226
+ if not isinstance(window, dict) or set(window) != _WINDOW_KEYS:
227
+ return False
228
+ start, end = segment.get("start"), segment.get("end")
229
+ text = str(segment.get("text") or "")
230
+ observed = window.get("observed_text")
231
+ modified = window.get("glossary_modified")
232
+ if not _is_number(start) or not _is_number(end) or end <= start:
233
+ return False
234
+ if (
235
+ not _is_int(window.get("index")) or window["index"] != index
236
+ or not _is_number(window.get("start")) or not _is_number(window.get("end"))
237
+ or window["start"] != start or window["end"] != end
238
+ or not isinstance(window.get("post_glossary_text"), str)
239
+ or window["post_glossary_text"] != text
240
+ or window.get("text_availability") != ("AVAILABLE" if text else "UNAVAILABLE_UNKNOWN")
241
+ ):
242
+ return False
243
+ if legacy:
244
+ if observed is not None or modified is not None:
245
+ return False
246
+ elif (
247
+ not isinstance(observed, str) or not isinstance(modified, bool)
248
+ or modified != (observed != text)
249
+ ):
250
+ return False
251
+ any_modified = any_modified or modified is True
252
+ if any_modified and evidence["glossary"]["name_count"] == 0:
253
+ return False
254
+ return True
255
+
256
+
257
+ def asr_evidence_summary_for_brief(work_dir, video_path):
258
+ """Return a compact validated banner; never upgrade missing/stale evidence."""
259
+ work_dir = Path(work_dir)
260
+ evidence_path = work_dir / EVIDENCE_FILENAME
261
+ result_path = work_dir / "asr_result.json"
262
+ valid = bool(video_path) and validate_asr_timing_evidence(
263
+ evidence_path, video_path, result_path
264
+ )
265
+ if valid:
266
+ payload = json.loads(evidence_path.read_text(encoding="utf-8"))
267
+ return {"status": payload["status"],
268
+ "glossary_modifications": None if payload["status"] == "LEGACY_UNVERIFIED"
269
+ else sum(w["glossary_modified"] is True for w in payload["windows"])}
270
+ return {"status": "MISSING_OR_STALE", "glossary_modifications": None}
@@ -1,12 +1,9 @@
1
1
  """Public narration-brief API for this self-contained skill."""
2
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
3
+ from briefing.builder import build_agent_brief
4
+ from briefing.context import assess_understanding_substrate
6
5
 
7
6
  __all__ = [
8
7
  "assess_understanding_substrate",
9
8
  "build_agent_brief",
10
- "lint_narration",
11
- "validate_narration_or_raise",
12
9
  ]
@@ -0,0 +1 @@
1
+ """Brief-generation subpackage: builds the creative agent brief from ASR/scene/timeline evidence."""