@oh-story/dsh 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +209 -73
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +248 -168
  5. package/lib/drama/skills/short-drama/SKILL.md +16 -4
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  10. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  11. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  12. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  13. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  14. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  15. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  16. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  17. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  18. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  19. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  20. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  21. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  22. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  23. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  25. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  26. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  27. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  28. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  29. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  30. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  31. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  32. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  33. package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  46. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  47. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  48. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  49. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  50. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
  51. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
  52. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
  53. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
  54. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
  55. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
  56. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
  57. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
  58. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  59. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  60. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  61. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  62. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  63. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  64. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  65. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  66. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  67. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  68. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  69. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  70. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  71. package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
  72. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  73. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  74. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  75. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  76. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
  77. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  78. package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
  79. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  80. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  81. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  82. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
  83. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
  84. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
  85. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  87. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  88. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
  89. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
  90. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
  91. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
  92. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  93. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  94. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  95. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
  96. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  97. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  98. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  99. package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
  100. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  101. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  102. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  103. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  104. package/lib/index.js +346 -98
  105. package/lib/index.js.map +4 -4
  106. package/lib/novel-to-game/README.md +299 -59
  107. package/lib/novel-to-game/README_ZH.md +299 -56
  108. package/lib/novel-to-game/VERSION +1 -1
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  118. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  119. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  120. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  121. package/lib/novel-to-game/manifest.json +77 -87
  122. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  123. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  124. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  125. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  126. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  127. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  128. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  129. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  130. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  131. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  132. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  133. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  134. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  135. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  136. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  137. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  138. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  139. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  140. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  141. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  142. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  143. package/lib/oh-story/manifest.json +607 -327
  144. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  145. package/lib/oh-story/roles/character-designer.md +1 -2
  146. package/lib/oh-story/roles/consistency-checker.md +15 -13
  147. package/lib/oh-story/roles/narrative-writer.md +36 -38
  148. package/lib/oh-story/roles/story-architect.md +9 -61
  149. package/lib/oh-story/roles/story-explorer.md +20 -125
  150. package/lib/oh-story/roles/story-researcher.md +0 -1
  151. package/lib/oh-story/skills/story/SKILL.md +15 -9
  152. package/lib/oh-story/skills/story/VERSION +1 -1
  153. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  154. package/lib/oh-story/skills/story/references/author-memory.md +46 -94
  155. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  157. package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
  158. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  159. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
  160. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  161. package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
  162. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  163. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
  164. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  165. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  166. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  167. package/lib/oh-story/skills/story-import/SKILL.md +64 -584
  168. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  169. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  170. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  171. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  172. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  173. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  174. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  175. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  176. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  177. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  180. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  181. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
  182. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  183. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
  184. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
  185. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
  186. package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
  187. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
  188. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
  189. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  190. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  191. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  192. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
  193. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  194. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  195. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  196. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  197. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  198. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  199. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
  200. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
  201. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  202. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  203. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  204. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  205. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  206. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  207. package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
  208. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  209. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  210. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  211. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  212. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  213. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  214. package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
  215. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  216. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
  217. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
  218. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  219. package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
  220. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  221. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
  222. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  223. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  224. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  225. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  226. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  227. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  228. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  229. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  230. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
  231. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  232. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  234. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  235. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  236. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  237. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  238. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  239. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  240. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  241. package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
  242. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
  243. package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
  244. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  245. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  246. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  247. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  248. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  249. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
  250. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
  251. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
  252. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  253. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
  254. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
  255. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  256. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
  257. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
  258. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  259. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  260. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
  261. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  262. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
  263. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  264. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
  265. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
  266. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
  267. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
  268. package/lib/oh-story/skills/story-review/SKILL.md +31 -302
  269. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  270. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
  271. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  272. package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
  273. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  274. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  275. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  276. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  277. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
  278. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  279. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  280. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  281. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  282. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
  283. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  284. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  285. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
  286. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
  287. package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
  288. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  289. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
  290. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  291. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  292. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  293. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
  294. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  295. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  296. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  297. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  298. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  299. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  300. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  301. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  302. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  303. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  304. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  305. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  306. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  307. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  308. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  309. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  310. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  311. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  312. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  313. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
  314. package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
  315. package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
  316. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  317. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  318. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  319. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  320. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  321. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  322. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  323. package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  324. package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
  325. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  326. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  327. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  328. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  329. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
  330. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  331. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  332. package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
  333. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  335. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  336. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  337. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  338. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  339. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
  340. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  341. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  342. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  343. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  344. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  345. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  346. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  347. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  348. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
  349. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
  350. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
  351. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  352. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  353. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  354. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  355. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  356. package/lib/types/client/file-activity.d.ts +19 -1
  357. package/lib/types/client/file-activity.d.ts.map +1 -1
  358. package/lib/types/client/index.d.ts.map +1 -1
  359. package/lib/types/client/production-prompts.d.ts +7 -0
  360. package/lib/types/client/production-prompts.d.ts.map +1 -1
  361. package/lib/types/client/production-runtime.d.ts +16 -0
  362. package/lib/types/client/production-runtime.d.ts.map +1 -1
  363. package/lib/types/client/video-studio.d.ts.map +1 -1
  364. package/lib/types/drama-adapters.d.ts +5 -3
  365. package/lib/types/drama-adapters.d.ts.map +1 -1
  366. package/lib/types/native-hooks.d.ts +53 -1
  367. package/lib/types/native-hooks.d.ts.map +1 -1
  368. package/lib/types/role-identity.d.ts +17 -0
  369. package/lib/types/role-identity.d.ts.map +1 -0
  370. package/lib/types/role-tool.d.ts.map +1 -1
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts.map +1 -1
  373. package/lib/types/workspace-route.d.ts.map +1 -1
  374. package/lib/video-recap/CHANGELOG.md +60 -1
  375. package/lib/video-recap/README.en.md +348 -137
  376. package/lib/video-recap/README.md +340 -136
  377. package/lib/video-recap/manifest.json +406 -251
  378. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  379. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  380. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  381. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  382. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  383. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  384. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  385. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  386. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  387. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  388. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  389. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  390. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  391. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  392. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  393. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  394. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  395. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  396. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  397. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  398. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  399. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  401. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  403. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  404. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  407. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  408. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  409. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  410. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  411. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  412. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  415. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  417. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  419. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  420. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  423. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  424. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  425. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  426. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  428. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  429. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  430. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  431. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  432. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  433. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  434. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  435. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  436. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  437. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  438. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  439. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  440. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  441. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  442. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  443. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  444. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  445. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  447. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  448. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  449. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  450. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  451. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  452. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  453. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  454. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  455. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  456. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  457. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  458. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  459. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  460. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  461. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  462. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  463. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  464. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  465. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  466. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  467. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  468. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  471. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  472. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  473. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  474. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  475. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  476. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  477. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  478. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  479. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  480. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  481. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  482. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  483. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  484. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  485. package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
  486. package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
  487. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  488. package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
  489. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  490. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  491. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  492. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  493. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  494. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  495. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  496. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  497. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  498. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  499. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  500. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  501. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  502. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  503. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  504. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  505. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  506. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  508. package/package.json +45 -45
  509. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  510. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  511. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  512. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
  513. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
  514. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  515. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
  516. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
  517. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  518. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  519. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  520. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  521. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  522. package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
  523. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  524. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
  525. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  526. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
  527. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  528. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  529. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -0,0 +1,147 @@
1
+ # Source and score bed producer
2
+
3
+ `source_score.py` is a standalone producer for explicitly authored source-audio
4
+ intervals and one continuous score playhead. It does not mix narration, inspect old
5
+ masters, infer dialogue, choose music, retime sound, normalize loudness, limit peaks,
6
+ or publish a final video.
7
+
8
+ ```bash
9
+ python3 scripts/source_score.py source_score_plan.json --output-dir a-new-directory
10
+ ```
11
+
12
+ The output directory must not exist. Success creates:
13
+
14
+ - `source_bed.wav` — reordered/pre-gained source intervals plus explicit silence;
15
+ - `score_bed.wav` — one continuous raw-score window or an adopted frozen stem;
16
+ - `prepared_bed.wav` — source plus score, without master gain/limiting;
17
+ - `prepared_bed_receipt.json` — input paths, clocks, sample ranges, PCM facts and QC facts.
18
+
19
+ All three beds are `pcm_f32le`, 48 kHz, stereo. Float output avoids repeated integer
20
+ quantization and preserves values outside `[-1,1]`; the receipt records actual peak,
21
+ finite-sample status, and `FLOAT_PRESERVED_NO_MASTER`. A later explicitly adopted
22
+ consumer owns final master gain and delivery encoding.
23
+
24
+ ## Strict plan schema v1
25
+
26
+ ```json
27
+ {
28
+ "artifact": "source_score_plan",
29
+ "schema_version": 1,
30
+ "output": {"sample_rate": 48000, "channels": 2, "total_samples": 1920000},
31
+ "source_segments": [
32
+ {
33
+ "id": "protected-dialogue-1",
34
+ "path": "/local/source.mov",
35
+ "audio_stream": 0,
36
+ "source_fps": "24/1",
37
+ "source_start_frame": 120,
38
+ "source_end_frame": 180,
39
+ "output_start_sample": 240000,
40
+ "gain": 1.0,
41
+ "fade_in_samples": 0,
42
+ "fade_out_samples": 96,
43
+ "fade_shape": "linear",
44
+ "role": "protected_original"
45
+ }
46
+ ],
47
+ "source_silence": [
48
+ {"output_start_sample": 0, "output_end_sample": 240000, "role": "silence"},
49
+ {"output_start_sample": 360000, "output_end_sample": 1920000, "role": "silence"}
50
+ ],
51
+ "score": {
52
+ "kind": "raw",
53
+ "path": "/local/score.wav",
54
+ "audio_stream": 0,
55
+ "source_offset_sample": 384000,
56
+ "gain": 0.18,
57
+ "fade_in_samples": 33600,
58
+ "fade_out_samples": 120000,
59
+ "fade_shape": "half_cosine"
60
+ }
61
+ }
62
+ ```
63
+
64
+ Every source range is half-open in CFR picture frames. Version 1 accepts H.264 and
65
+ HEVC picture sources whose complete packet PTS/duration grid proves one zero-origin
66
+ packet per same-speed frame. Frame boundaries are projected onto the 48 kHz clock by
67
+ rounding the exact position half-up once, so `48000/fps` need not be an integer and a
68
+ broadcast rate such as 30000/1001 is accepted; every derived bound comes from those
69
+ rounded positions. Other codecs, incomplete packet timing, VFR, retime fields,
70
+ nominal time seeks, and unknown fields are rejected rather than called verified
71
+ (legacy `sha256` keys are ignored).
72
+ Each unique selected source stream is decoded from its actual PTS clock to canonical
73
+ 48 kHz stereo exactly once. All authored intervals are then cut from that canonical
74
+ sample array—never independently decoded or `-ss`-seeked per edit.
75
+
76
+ The source segment duration is `(source_end_frame-source_start_frame)*48000/fps`.
77
+ Its output end is derived, not declared. Source segments and explicit silence ranges
78
+ must form an exact, nonoverlapping partition of `[0,total_samples)`. There are no
79
+ implicit gaps. Supported roles are `protected_original` and
80
+ `mixed_original_under_narration`; their gains and labels are retained unchanged.
81
+
82
+ Source fades are explicitly `linear`. A fade of `N>=2` samples uses inclusive
83
+ endpoints: fade-in gain is `i/(N-1)`, and fade-out gain is `(N-1-i)/(N-1)`. Thus the
84
+ first/last samples are exactly zero and the opposite endpoints exactly one. Zero
85
+ means no fade; a one-sample fade is rejected as ambiguous. Fade windows may not
86
+ overlap within a segment.
87
+
88
+ ## Score modes
89
+
90
+ `raw` decodes the selected score stream once, advances one playhead from
91
+ `source_offset_sample`, and takes exactly `total_samples`. The input must be long
92
+ enough; there is no implicit loop. Gain and one pair of whole-score fades are applied
93
+ once, so score playback never resets at picture/source cuts. `linear` and
94
+ `half_cosine` fades use inclusive endpoints.
95
+
96
+ When the adopted source bed is already the complete soundtrack and no additional
97
+ music is selected, use the exact no-score form:
98
+
99
+ ```json
100
+ {"kind":"none"}
101
+ ```
102
+
103
+ It accepts no other fields. `score_bed.wav` is real all-zero float PCM at the exact
104
+ output length, while `prepared_bed.wav` is an exact canonical PCM copy of
105
+ `source_bed.wav`; the receipt records `kind:none` rather than inventing a music asset.
106
+
107
+ An adopted score that already contains its chosen offset/gain/fades uses the exact
108
+ alternative schema:
109
+
110
+ ```json
111
+ {"kind":"frozen","path":"/local/frozen.wav","audio_stream":0}
112
+ ```
113
+
114
+ Frozen input must be PCM16, PCM24, or PCM float, 48 kHz stereo, and exactly
115
+ `total_samples`. PCM16/24 values convert exactly to float canonical representation.
116
+ No offset, gain, fade, loop, normalization or other processing fields are accepted.
117
+ The receipt records the frozen file path and its probed PCM facts.
118
+
119
+ ## Receipt and failure boundary
120
+
121
+ `prepared_bed_receipt` schema version 1 records the plan path, each input path,
122
+ selected stream, actual CFR/audio clocks, canonical decoded PCM facts, resolved
123
+ input/output sample ranges, gains, fades, roles, and each output's path, byte size,
124
+ format, sample count, finite status and peak.
125
+
126
+ Every declared input must exist and probe as declared before work starts. A selected
127
+ source range must fit the actual canonical decoded samples; silence padding can never
128
+ conceal a short source. WAVs remain hidden staging artifacts until every output
129
+ validates. FFmpeg failure, invalid ranges, non-finite PCM, or an existing target
130
+ directory cannot publish final-named beds or a receipt.
131
+ Diagnostic command/log/intermediate files may remain in the unique failed directory.
132
+
133
+ To consume a completed receipt with explicitly adopted narration, use the strict
134
+ `--audio-mix-adoption` path in `explicit-audio-mix.md`. To deliver an already complete
135
+ prepared bed without narration, a dedicated prepared-audio renderer will follow in a
136
+ later release. Do not feed `prepared_bed.wav` through legacy source ducking or
137
+ ambient BGM/loudness settings.
138
+
139
+ ## 与旧入口的关系(中文摘要)
140
+
141
+ 原片完整解码一次再切样本,分别执行保留对白、低位原声和明确静音;渐变必须显式给定。
142
+ 已处理的音乐轨走 `frozen`,不能再次偏移、调增益或加渐变。若采用的原声底轨本身已包含
143
+ 完整音乐决定且不再叠加配乐,使用严格的 `score:{"kind":"none"}`;它生成真实全零 score,
144
+ 并保持 prepared 与 source 逐字节相同,不伪造静音音乐资产。
145
+
146
+ 这一步仅输出声音底轨和来源回执,不是最终视频。旧入口保留兼容行为;调用方须区分
147
+ “底轨已验证”“配音已验证”和“完整混音已验证”三种状态,不能相互冒充。
@@ -0,0 +1,202 @@
1
+ # Independent subtitle track contract (schema v1)
2
+
3
+ `scripts/subtitles/track.py` loads a subtitle track whose cue times already use
4
+ the final **output clock**. It validates the track against picture, edit, audio,
5
+ and duration facts independently supplied by the caller. It does not align
6
+ speech, remap source time, split text, repair cue boundaries, or prove that a
7
+ subtitle is perceptually synchronized.
8
+
9
+ ## Loader API
10
+
11
+ ```python
12
+ from fractions import Fraction
13
+ from subtitles.track import load_subtitle_track
14
+
15
+ loaded = load_subtitle_track(
16
+ "subtitle_track.json",
17
+ expected_picture_identity={
18
+ "path": "/project/paired.mp4",
19
+ "edit_plan": "/project/edit_plan.json",
20
+ },
21
+ expected_audio_identity={
22
+ "selected_stream": 1,
23
+ "sample_rate": 48000,
24
+ "packet_count": 4700,
25
+ },
26
+ expected_duration_seconds=Fraction(duration_ts) * stream_time_base,
27
+ reject_legacy_estimate=True,
28
+ )
29
+ metadata = loaded["metadata"]
30
+ entries = loaded["entries"]
31
+ ```
32
+
33
+ The input may also be an in-memory mapping. `entries` is a list of dictionaries
34
+ ready for the existing seconds-based subtitle renderer:
35
+
36
+ ```json
37
+ {
38
+ "start": 1.0,
39
+ "end": 3.0,
40
+ "text": "example",
41
+ "source": "narration",
42
+ "source_ref": "narration:7",
43
+ "timing_evidence": {
44
+ "kind": "asr_boundary_calibrated",
45
+ "evidence_refs": ["alignment-run:example"],
46
+ "calibration": "asr_energy",
47
+ "word_alignment": "none"
48
+ }
49
+ }
50
+ ```
51
+
52
+ The loader preserves cue text and boundaries. The only conversion is exact
53
+ integer ticks to renderer-facing seconds. `metadata.timing_evidence_kinds`
54
+ reports the labels present; it is not an aggregate precision verdict.
55
+
56
+ ## Schema v1
57
+
58
+ ```json
59
+ {
60
+ "schema_version": 1,
61
+ "clock": {
62
+ "kind": "output",
63
+ "timebase": {"numerator": 1, "denominator": 30},
64
+ "duration_ticks": 300
65
+ },
66
+ "overlap_policy": "forbid",
67
+ "bindings": {
68
+ "picture": {
69
+ "path": "/project/paired.mp4",
70
+ "edit_plan": "/project/edit_plan.json"
71
+ },
72
+ "audio": {
73
+ "selected_stream": 1,
74
+ "sample_rate": 48000,
75
+ "packet_count": 4700
76
+ }
77
+ },
78
+ "cues": [
79
+ {
80
+ "start_tick": 30,
81
+ "end_tick": 90,
82
+ "text": "example",
83
+ "attribution": {"kind": "narration", "ref": "narration:7"},
84
+ "timing_evidence": {
85
+ "kind": "asr_boundary_calibrated",
86
+ "evidence_refs": ["alignment-run:example"],
87
+ "calibration": "asr_energy",
88
+ "word_alignment": "none"
89
+ }
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ - `timebase` is rational seconds per tick. Cue intervals are half-open
96
+ `[start_tick, end_tick)`.
97
+ - `duration_ticks`, cue bounds, and stream indexes are nonnegative integers
98
+ (booleans are not integers for this contract).
99
+ - Cues must be ordered, non-overlapping, nonempty, and contained by the output
100
+ duration. Adjacent half-open cues may touch.
101
+ - Distinct tick bounds must remain a positive, finite interval after conversion
102
+ to the renderer's float seconds. Tracks beyond that projection precision fail
103
+ closed rather than becoming a zero-length rendered cue.
104
+ - `overlap_policy` must be `forbid`. Schema v1 has no permissive overlap mode.
105
+ - `attribution.kind` is `source` or `narration`; `ref` identifies the source
106
+ utterance or narration item without changing its text.
107
+ - Unknown fields and schema versions other than integer `1` are rejected, so a
108
+ newer producer cannot be silently interpreted as v1. Legacy `sha256` /
109
+ `edit_sha256` binding keys are the one exception: they are ignored.
110
+
111
+ ## Independent binding checks
112
+
113
+ The loader compares track declarations with the caller's current facts; it never
114
+ treats a track's own binding as evidence that the track is fresh.
115
+
116
+ - `picture.path` is mandatory and must resolve to the caller's current picture
117
+ path. `edit_plan` is optional for inputs without a separately materialized
118
+ edit plan; if the track contains it, the caller must supply the same path.
119
+ - `audio.selected_stream`, `sample_rate` and `packet_count` describe the
120
+ **actually adopted** audio stream, not merely a source filename or an intended
121
+ mix manifest. Pass the facts probed from the current media; this module
122
+ deliberately does not run ffprobe itself.
123
+ - `expected_duration_seconds` is mandatory and checked against
124
+ `duration_ticks * timebase`. Prefer `Fraction(duration_ts) * time_base` from
125
+ the actual output stream to avoid decimal/container rounding. `int`, finite
126
+ `float`, and finite `Decimal` are also accepted.
127
+
128
+ ## Timing evidence labels
129
+
130
+ Evidence labels describe how a cue boundary was obtained. They do not change
131
+ the exact burn time represented by its ticks.
132
+
133
+ | `kind` | Required calibration | Required word alignment | Evidence refs |
134
+ |---|---|---|---|
135
+ | `legacy_estimate` | `none` | `none` | optional |
136
+ | `asr_boundary_calibrated` | `asr_energy` | `none` | required |
137
+ | `word_timestamps` | `none` or `asr_energy` | `asr_words` | required |
138
+ | `human_verified` | `human_boundary` | `none` or `human_words` | required |
139
+
140
+ `asr_boundary_calibrated` is the label for a coarse-ASR boundary adjusted with
141
+ ASR context and/or energy evidence. Energy onset is not proof of a phoneme, so
142
+ this label cannot claim word alignment or human verification. Missing evidence
143
+ references cannot claim any non-legacy kind. A strict caller may reject
144
+ `legacy_estimate`; accepting another label still does not automatically call it
145
+ strong alignment.
146
+
147
+ Because cue intervals are half-open, a cue is not visible at any tick before its
148
+ `start_tick`; the tick immediately preceding it belongs to whatever came before.
149
+
150
+ ## Deliberate limits
151
+
152
+ - Schema v1 has no source-to-cut/edit map and cannot map source-clock cues.
153
+ - The module does not inspect media, select audio streams, or tolerate a
154
+ self-declared binding without current caller facts.
155
+ - Validation proves schema consistency and the requested bindings only. Actual
156
+ rendered first/last subtitle frames and perceptual speech alignment require
157
+ separate render/media review.
158
+
159
+ ## Current assembly integration (bounded, not automatic alignment)
160
+
161
+ Place `subtitle_track.json` in `work_dir` and select
162
+ `assemble.py --audio-mode adopted-packet-copy`. A present track is a **complete
163
+ replacement** of all generated narration/original-dialogue subtitles, not a
164
+ partial patch and not merged with legacy subtitles. Carry every cue that should
165
+ remain; an empty `cues` array intentionally removes all generated subtitles.
166
+ Unknown patch/merge modes are rejected by schema v1. This does not detect words
167
+ missing from the authored full track: acoustic/coverage review remains required.
168
+
169
+ The assembly integration independently probes the chosen input stream for its
170
+ sample rate and packet count, resolves the input path for the picture binding,
171
+ and records the `{size, mtime_ns}` of the track, video and optional edit plan in
172
+ `subtitle_track_validation.json`. It does not use the track's own declarations
173
+ as current facts.
174
+
175
+ Actual decoded frame PTS are read before projection. Integer cue ticks and the
176
+ rational timebase are retained until each boundary is resolved to the first
177
+ frame at or after it. `subtitle_track_validation.json` records original ticks,
178
+ resolved frame indexes/PTS, quantization deltas, ASS thresholds, validation
179
+ schema and projector version. SRT and timeline consume resolved frame seconds;
180
+ ASS thresholds are chosen to switch on those same frames despite ASS's 10ms
181
+ clock. A cue with no visible frame, or a boundary that ASS cannot distinguish,
182
+ is rejected rather than silently dropped. The original author file is not
183
+ rewritten. Legacy subtitles keep their previous rendering behavior.
184
+
185
+ Each consumption re-checks the track/media/optional edit plan `{size, mtime_ns}`
186
+ and the consumer duration; a rewritten input is stale and must be prepared
187
+ again. Deleting the explicit track clears its previous validation record.
188
+ An invalid/stale track never falls back to character-proportional timing.
189
+
190
+ Current limits:
191
+ - Integration is for output media starting at zero with an adopted AAC track,
192
+ not raw-source-to-edited-output mapping or a newly mixed narration track.
193
+ - The low-level preparation API accepts `edit_plan_path`, but the assembly CLI
194
+ does not yet expose it. CLI tracks must omit `edit_plan`; that path binds the
195
+ input media, not edit-plan ancestry.
196
+ - The low-level policy `reject_legacy_estimate=True` is available. There is not
197
+ yet a wired commercial-profile CLI gate. The current CLI preserves timing
198
+ evidence labels and does not call every accepted cue precisely aligned.
199
+ - The actual FFmpeg regression demonstrates frame timing with a synthetic cue,
200
+ not phoneme alignment. `direct_listening` and `acoustic_alignment` remain
201
+ `NOT_CHECKED`; evidence labels are declarations, not automatically verified
202
+ human approval or proof that an external evidence reference is true.
@@ -0,0 +1 @@
1
+ """Adoption family: narration/audio-mix binding + strict publish for adopted-source audio."""
@@ -0,0 +1,279 @@
1
+ """Validate, render, and record an explicitly adopted prepared-bed plus narration mix."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ import subprocess
6
+
7
+ from assemble_constants import frame_clock_samples
8
+ from adoption.frozen_audio import probe_audio_packets
9
+ import adoption.narration_binding as narration_binding
10
+ from pair_media import probe_picture, validate_pair_timing
11
+ import source_score
12
+ from adoption.strict_inputs import (
13
+ read_json_bytes, require_fields, require_integer, require_local_path, require_number,
14
+ run_logged, without_digests, write_json_atomic,
15
+ )
16
+
17
+
18
+ ARTIFACT = "audio_mix_binding"
19
+ FILENAME = "audio_mix_binding.json"
20
+ RATE = 48_000
21
+ CHANNELS = 2
22
+ CODEC = "pcm_f32le"
23
+
24
+
25
+ def _picture_format(picture):
26
+ samples = frame_clock_samples(picture["frame_count"], picture["fps"], RATE)
27
+ return samples, {
28
+ "fps": picture["fps"], "frame_count": picture["frame_count"],
29
+ "duration": picture["duration"], "start": picture["start"],
30
+ "decoder": picture["decoder"],
31
+ }
32
+
33
+
34
+ def load_adoption(path, *, input_video, narration_adoption_path, tts_segments):
35
+ """Strict read-only preflight; no work artifacts are created here."""
36
+ adoption_path, _, value = read_json_bytes(path, "audio mix adoption")
37
+ value = without_digests(value, "audio mix adoption")
38
+ require_fields(value, ["artifact", "schema_version", "prepared_receipt", "format", "segments",
39
+ "master_gain_db"], "audio mix adoption")
40
+ if value["artifact"] != "audio_mix_adoption" or type(value["schema_version"]) is not int \
41
+ or value["schema_version"] != 1:
42
+ raise ValueError("unsupported audio_mix_adoption schema")
43
+ input_video = require_local_path(input_video, "picture")
44
+ picture = probe_picture(input_video)
45
+ picture_samples, picture_summary = _picture_format(picture)
46
+
47
+ narration_path = require_local_path(narration_adoption_path, "narration adoption")
48
+ require_fields(value["format"], ["sample_rate", "channels", "total_samples"], "mix format")
49
+ mix_format = value["format"]
50
+ if mix_format != {"sample_rate": RATE, "channels": CHANNELS,
51
+ "total_samples": picture_samples}:
52
+ raise ValueError("mix format differs from the actual picture sample clock")
53
+ prepared_receipt = source_score.validate_prepared_receipt(
54
+ value["prepared_receipt"], mix_format
55
+ )
56
+
57
+ if not isinstance(value["segments"], list) or len(value["segments"]) != len(tts_segments):
58
+ raise ValueError("audio mix segments must exactly cover narration segments")
59
+ normalized = []
60
+ seen = set()
61
+ for adopted, segment in zip(value["segments"], tts_segments):
62
+ adopted = without_digests(adopted, "audio mix segment")
63
+ require_fields(adopted, ["index", "output_start_sample", "gain"], "audio mix segment")
64
+ index = require_integer(adopted["index"], "audio mix segment index")
65
+ if index in seen or index != segment["index"]:
66
+ raise ValueError("audio mix segment index/order differs from narration")
67
+ seen.add(index)
68
+ normalized.append({
69
+ "index": index,
70
+ "output_start_sample": require_integer(
71
+ adopted["output_start_sample"], "output_start_sample"),
72
+ "gain": require_number(adopted["gain"], "narration gain", 0, 16),
73
+ })
74
+ return {
75
+ "path": str(adoption_path),
76
+ "picture": {"path": str(input_video), "clock": picture_summary},
77
+ "picture_identity": picture,
78
+ "prepared_receipt": prepared_receipt["reference"],
79
+ "prepared": prepared_receipt["outputs"],
80
+ "narration_adoption": {"path": str(narration_path)},
81
+ "format": dict(mix_format), "segments": normalized,
82
+ "master_gain_db": require_number(value["master_gain_db"], "master_gain_db", -24, 24),
83
+ "runtime": None,
84
+ }
85
+
86
+
87
+ def _run(command, work_dir, label):
88
+ run_logged(command, work_dir, label, prefix="explicit_")
89
+
90
+
91
+ def _channels(path):
92
+ result = subprocess.run(
93
+ ["ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries",
94
+ "stream=channels", "-of", "default=nw=1:nk=1", str(path)],
95
+ capture_output=True, text=True, timeout=600,
96
+ )
97
+ try:
98
+ channels = int(result.stdout.strip())
99
+ except ValueError as exc:
100
+ raise ValueError("narration channel probe failed") from exc
101
+ if result.returncode or channels not in {1, 2}:
102
+ raise ValueError("explicit narration supports only mono or stereo input")
103
+ return channels
104
+
105
+
106
+ def render_explicit_mix(context, narration_context, tts_segments, work_dir):
107
+ """Render complete direct 48 kHz placements, voice bus, premaster, and fixed master."""
108
+ directory = Path(work_dir).resolve() / ".explicit_audio_mix"
109
+ directory.mkdir(parents=True, exist_ok=False)
110
+ by_index = {item["index"]: item for item in narration_context["segments"]}
111
+ segment_memory = {item["index"]: item for item in tts_segments}
112
+ rendered = []
113
+ for position, adopted in enumerate(context["segments"]):
114
+ source = by_index[adopted["index"]]["snapshot"]["path"]
115
+ channels = _channels(source)
116
+ placed = directory / f"placed_{position:04d}_{adopted['index']}.wav"
117
+ if channels == 1:
118
+ channel_filter = (
119
+ "aformat=sample_fmts=flt,"
120
+ "pan=stereo|c0=0.7071067811865476*c0|c1=0.7071067811865476*c0,"
121
+ "aresample=48000:async=0:first_pts=0,aformat=sample_fmts=flt:"
122
+ "sample_rates=48000:channel_layouts=stereo"
123
+ )
124
+ matrix = "mono_equal_power"
125
+ else:
126
+ channel_filter = (
127
+ "aresample=48000:async=0:first_pts=0,aformat=sample_fmts=flt:"
128
+ "sample_rates=48000:channel_layouts=stereo"
129
+ )
130
+ matrix = "stereo_identity"
131
+ _run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(source),
132
+ "-map", "0:a:0", "-af", channel_filter, "-c:a", CODEC, str(placed)],
133
+ directory, f"place_{position:04d}")
134
+ facts = source_score._output_facts(placed)
135
+ start = adopted["output_start_sample"]
136
+ end = start + facts["pcm"]["samples"]
137
+ if end > context["format"]["total_samples"]:
138
+ raise ValueError("complete narration segment does not fit the adopted mix clock")
139
+ rendered.append({**adopted, "output_end_sample": end, "input_channels": channels,
140
+ "channel_matrix": matrix, "placed": facts})
141
+ memory = segment_memory[adopted["index"]]
142
+ memory.update({
143
+ "placed_audio_path": str(placed),
144
+ "narration_conversion_path": str(placed),
145
+ "audio_duration": facts["pcm"]["samples"] / RATE,
146
+ "placed_audio_duration": facts["pcm"]["samples"] / RATE,
147
+ "actual_place_start": start / RATE, "actual_place_end": end / RATE,
148
+ "output_start_sample": start, "output_end_sample": end,
149
+ "adopted_gain": adopted["gain"],
150
+ "global_narration_speed": 1.0, "segment_tempo_factor": 1.0,
151
+ "effective_tempo": 1.0, "fit_status": "placed", "blocking": False,
152
+ "truncated": False, "truncate_reason": "none",
153
+ })
154
+ ordered = sorted(rendered, key=lambda item: item["output_start_sample"])
155
+ if any(current["output_start_sample"] < previous["output_end_sample"]
156
+ for previous, current in zip(ordered, ordered[1:])):
157
+ raise ValueError("explicit narration placements overlap")
158
+
159
+ total = context["format"]["total_samples"]
160
+ voice_bus = directory / "voice_bus.wav"
161
+ command = ["ffmpeg", "-nostdin", "-v", "error", "-n"]
162
+ for item in rendered:
163
+ command += ["-i", item["placed"]["path"]]
164
+ filters = [f"anullsrc=r={RATE}:cl=stereo,atrim=end_sample={total}[base]"]
165
+ labels = ["[base]"]
166
+ for position, item in enumerate(rendered):
167
+ filters.append(
168
+ f"[{position}:a]volume={item['gain']:.17g},"
169
+ f"adelay={item['output_start_sample']}S:all=1[v{position}]"
170
+ )
171
+ labels.append(f"[v{position}]")
172
+ filters.append("".join(labels) + f"amix=inputs={len(labels)}:duration=first:normalize=0,"
173
+ f"atrim=end_sample={total},aformat=sample_fmts=flt:sample_rates={RATE}:"
174
+ "channel_layouts=stereo[out]")
175
+ command += ["-filter_complex", ";".join(filters), "-map", "[out]", "-c:a", CODEC,
176
+ str(voice_bus)]
177
+ _run(command, directory, "voice_bus")
178
+ voice_facts = source_score._output_facts(voice_bus)
179
+ narration_binding.seal_render_inputs(narration_context, tts_segments, voice_bus)
180
+ narration_context["sealed"]["narration_bus"]["consumption_status"] = \
181
+ "CONSUMED_BY_EXPLICIT_MIX"
182
+
183
+ prepared = context["prepared"]["prepared_bed.wav"]["path"]
184
+ premaster = directory / "premaster.wav"
185
+ _run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", prepared, "-i", str(voice_bus),
186
+ "-filter_complex", f"[0:a][1:a]amix=inputs=2:duration=first:normalize=0,"
187
+ f"atrim=end_sample={total},aformat=sample_fmts=flt:sample_rates={RATE}:"
188
+ "channel_layouts=stereo[out]", "-map", "[out]", "-c:a", CODEC, str(premaster)],
189
+ directory, "premaster")
190
+ master = directory / "master.wav"
191
+ gain = 10 ** (context["master_gain_db"] / 20)
192
+ _run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(premaster),
193
+ "-af", f"volume={gain:.17g},aformat=sample_fmts=flt:sample_rates={RATE}:"
194
+ "channel_layouts=stereo", "-c:a", CODEC, str(master)], directory, "master")
195
+ runtime = {
196
+ "segments": rendered, "voice_bus": voice_facts,
197
+ "premaster": source_score._output_facts(premaster),
198
+ "master": source_score._output_facts(master), "master_gain_linear": gain,
199
+ }
200
+ if any(runtime[key]["pcm"]["samples"] != total
201
+ for key in ("voice_bus", "premaster", "master")):
202
+ raise ValueError("explicit mix derivative sample count differs from picture clock")
203
+ context["runtime"] = runtime
204
+ return runtime
205
+
206
+
207
+ def finalize_binding(context, narration_record, rendered_output, final_output, work_dir):
208
+ """Probe the rendered candidate and write ``audio_mix_binding.json`` into work_dir."""
209
+ if not context.get("runtime"):
210
+ raise RuntimeError("explicit audio mix must be rendered before finalization")
211
+ rendered = require_local_path(rendered_output, "rendered output")
212
+ output_picture = probe_picture(rendered)
213
+ input_clock = _picture_format(context["picture_identity"])[1]
214
+ output_clock = _picture_format(output_picture)[1]
215
+ for key in ("fps", "frame_count", "duration", "start"):
216
+ if output_clock[key] != input_clock[key]:
217
+ raise ValueError("rendered output picture frame clock changed")
218
+ packet_identity = (
219
+ "EXACT" if output_picture == context["picture_identity"]
220
+ else "REENCODED_CLOCK_MATCH"
221
+ )
222
+ audio = probe_audio_packets(rendered, 0)
223
+ validate_pair_timing(output_picture, audio)
224
+ decoded = Path(context["runtime"]["master"]["path"]).parent / "final_aac_decoded.wav"
225
+ _run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(rendered),
226
+ "-map", "0:a:0", "-af", "aformat=sample_fmts=flt:sample_rates=48000:"
227
+ "channel_layouts=stereo", "-c:a", CODEC, str(decoded)], decoded.parent,
228
+ "final_decode")
229
+ context["runtime"]["final_decoded_pcm"] = source_score._output_facts(decoded)
230
+ report = {
231
+ "artifact": ARTIFACT, "schema_version": 1, "status": "FINALIZED",
232
+ "adoption": {"path": context["path"]},
233
+ "picture": context["picture"],
234
+ "output_picture": {
235
+ **output_clock, "packet_identity": packet_identity,
236
+ },
237
+ "prepared_receipt": context["prepared_receipt"],
238
+ "prepared": context["prepared"], "format": context["format"],
239
+ "segments": context["runtime"]["segments"],
240
+ "voice_bus": context["runtime"]["voice_bus"],
241
+ "premaster": context["runtime"]["premaster"],
242
+ "master": {**context["runtime"]["master"], "gain_db": context["master_gain_db"],
243
+ "gain_linear": context["runtime"]["master_gain_linear"]},
244
+ "narration_input_binding": {"path": narration_record["path"], "status": "FINALIZED"},
245
+ "final_output": {
246
+ "path": str(Path(final_output).resolve()),
247
+ "decoded_pcm": context["runtime"]["final_decoded_pcm"],
248
+ "audio_stream": {
249
+ "decoder": audio["decoder"], "packet_count": audio["packet_count"],
250
+ "payload_bytes": audio["payload_bytes"],
251
+ "start_time": audio["start_time"], "duration": audio["duration"],
252
+ },
253
+ },
254
+ "direct_listening": "NOT_CHECKED", "normal_speed_review": "NOT_CHECKED",
255
+ "release_approved": False,
256
+ }
257
+ write_json_atomic(Path(work_dir).resolve() / FILENAME, report)
258
+ return report
259
+
260
+
261
+ def binding_record(work_dir):
262
+ """The finalized mix binding's path and status; None when absent or malformed."""
263
+ path = Path(work_dir) / FILENAME
264
+ if not path.is_file():
265
+ return None
266
+ try:
267
+ report = json.loads(path.read_text(encoding="utf-8"))
268
+ except (UnicodeDecodeError, json.JSONDecodeError):
269
+ return None
270
+ if not isinstance(report, dict) or report.get("artifact") != ARTIFACT \
271
+ or report.get("schema_version") != 1 or report.get("status") != "FINALIZED":
272
+ return None
273
+ final = report.get("final_output")
274
+ if not isinstance(final, dict) or not Path(str(final.get("path", ""))).is_file():
275
+ return None
276
+ narration = report.get("narration_input_binding")
277
+ if not isinstance(narration, dict) or not Path(str(narration.get("path", ""))).is_file():
278
+ return None
279
+ return {"path": str(path.resolve()), "status": "FINALIZED"}