@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
@@ -9,46 +9,160 @@ import assemble_constants as constants
9
9
  import assembly_contract
10
10
  import assembly_settings
11
11
  import audio_mix
12
+ import adoption.audio_mix_binding as audio_mix_binding
13
+ import adoption.frozen_audio as frozen_audio
12
14
  import media
13
15
  import narration_audio
16
+ import adoption.narration_binding as narration_binding
17
+ import pair_media
14
18
  import render_preflight
15
- import subtitle_render
19
+ import adoption.strict_publish as strict_publish
20
+ import subtitles.render as subtitle_render
21
+ import subtitles.track_binding as subtitle_track_binding
16
22
  import timeline_emit
23
+ import packaging
17
24
  import visual_render
18
25
  import lib
19
- from assembly_settings import assembly_settings_fingerprint
20
- from audio_mix import final_loudnorm_filter
21
26
 
22
27
  __all__ = [
23
28
  "assemble_video",
24
- "assembly_settings_fingerprint",
25
- "final_loudnorm_filter",
26
29
  "main",
27
30
  ]
28
31
 
29
32
 
30
- def assemble_video(input_video, tts_segments, work_dir, output_path):
33
+ AUDIO_MODES = ("narration", "source-mix", "adopted-packet-copy")
34
+
35
+
36
+ _current_narration_binding = strict_publish.current_narration_binding
37
+ _current_audio_mix_binding = strict_publish.current_audio_mix_binding
38
+
39
+
40
+ def assemble_video(input_video, tts_segments, work_dir, output_path, *,
41
+ audio_mode="narration", audio_stream_index=0,
42
+ narration_adoption_path=None, tts_meta_path=None,
43
+ audio_mix_adoption_path=None):
31
44
  """组装最终视频"""
32
- if not tts_segments:
45
+ if audio_mode not in AUDIO_MODES:
46
+ raise RuntimeError(f"不支持的 audio_mode: {audio_mode}")
47
+ if isinstance(audio_stream_index, bool) or not isinstance(audio_stream_index, int) or audio_stream_index < 0:
48
+ raise RuntimeError("audio_stream_index 必须是非负整数")
49
+ if audio_mode == "narration" and not tts_segments:
33
50
  raise RuntimeError("tts_meta.json 没有有效解说音频,已中止以避免生成无解说视频")
51
+ if audio_mode == "narration" and audio_stream_index != 0:
52
+ raise RuntimeError("narration 当前不支持非零 audio_stream_index")
53
+ if audio_mode == "source-mix" and tts_segments:
54
+ raise RuntimeError("source-mix 与 TTS 解说不兼容")
55
+ if audio_mode != "narration" and tts_meta_path is not None:
56
+ raise RuntimeError(f"tts_meta 与 audio_mode {audio_mode} 不兼容")
57
+ bgm_path = lib.CONFIG["bgm_path"]
58
+ has_bgm = bool(bgm_path) and os.path.exists(bgm_path)
59
+ if audio_mode == "source-mix" and bgm_path and not has_bgm:
60
+ raise RuntimeError(f"source-mix 声明的 BGM 文件不存在: {bgm_path}")
61
+ if (
62
+ audio_mode != "narration"
63
+ and audio_stream_index != 0
64
+ and lib.CONFIG["export_jianying"]
65
+ ):
66
+ raise RuntimeError("剪映导出当前不支持选择非零音频流")
67
+ if audio_mode == "adopted-packet-copy":
68
+ if tts_segments:
69
+ raise RuntimeError("adopted-packet-copy 与 TTS 解说不兼容")
70
+ if bgm_path:
71
+ raise RuntimeError("adopted-packet-copy 与 BGM 混音不兼容")
72
+ if audio_mode != "narration" and narration_adoption_path is not None:
73
+ raise RuntimeError("narration adoption 仅适用于 narration audio_mode")
74
+ if audio_mix_adoption_path is not None and (
75
+ audio_mode != "narration" or narration_adoption_path is None or tts_meta_path is None
76
+ ):
77
+ raise RuntimeError("audio mix adoption 要求 narration 模式及显式 narration adoption/tts_meta")
78
+
79
+ published_output = Path(output_path)
80
+ if audio_mix_adoption_path is not None and published_output.exists():
81
+ raise RuntimeError("显式音频混合要求新的 output_path,不能覆盖已有成片")
82
+ explicit_mix = (
83
+ audio_mix_binding.load_adoption(
84
+ audio_mix_adoption_path, input_video=input_video,
85
+ narration_adoption_path=narration_adoption_path, tts_segments=tts_segments,
86
+ )
87
+ if audio_mix_adoption_path is not None else None
88
+ )
34
89
 
90
+ binding = narration_binding.prepare_binding(
91
+ tts_segments, work_dir,
92
+ narration_adoption_path=narration_adoption_path,
93
+ tts_meta_path=tts_meta_path,
94
+ ) if audio_mode == "narration" else None
95
+ render_output = published_output
96
+ if binding and binding["active"]:
97
+ if published_output.exists():
98
+ raise RuntimeError("身份约束渲染要求新的 output_path,不能覆盖已有成片")
99
+ render_output = published_output.with_name(
100
+ f".{published_output.stem}.narration-rendering{published_output.suffix}"
101
+ )
102
+ render_output.unlink(missing_ok=True)
103
+ output_path = render_output
35
104
  video_duration = lib.get_video_duration(input_video)
36
105
  canvas = media._probe_canvas(input_video) # drives subtitle PlayRes/scale so 竖屏 text isn't stretched
37
106
  burn_subtitles = lib.CONFIG["burn_subtitles"]
107
+ subtitle_track_binding.prepare_subtitle_track(
108
+ input_video,
109
+ work_dir,
110
+ video_duration,
111
+ audio_mode=audio_mode,
112
+ selected_audio_stream=audio_stream_index,
113
+ )
114
+ adopted_source_audio = None
115
+ if audio_mode == "adopted-packet-copy":
116
+ adopted_source_audio = frozen_audio.validate_adopted_source(
117
+ input_video, audio_stream_index
118
+ )
119
+ if type(adopted_source_audio["sample_rate"]) is not int \
120
+ or adopted_source_audio["sample_rate"] <= 0:
121
+ raise RuntimeError("adopted audio sample rate must be a positive integer")
122
+ elif audio_mode == "source-mix":
123
+ frozen_audio.probe_audio_packets(input_video, audio_stream_index)
38
124
 
39
125
  # 解说整体提速(可选)后,将所有 TTS 片段按时间位置合成到与视频等长的音轨上
40
- narration_audio._apply_narration_speed(tts_segments, work_dir)
41
- narration_wav = work_dir / "narration.wav"
42
- narration_audio._build_timed_narration(tts_segments, narration_wav, video_duration, work_dir)
43
- handoffs = audio_mix._apply_source_sentence_handoffs(tts_segments, work_dir, video_duration)
44
- if handoffs:
45
- lib.log(
46
- "原声句末交接: "
47
- + ", ".join(
48
- f"{item['end']:.2f}s→{item.get('restore_at', item['end']):.2f}s({item['status']})"
49
- for item in handoffs
50
- )
126
+ narration_wav = None
127
+ if audio_mode == "narration" and explicit_mix is not None:
128
+ explicit_runtime = audio_mix_binding.render_explicit_mix(
129
+ explicit_mix, binding, tts_segments, work_dir
51
130
  )
131
+ narration_wav = Path(explicit_runtime["voice_bus"]["path"])
132
+ elif audio_mode == "narration":
133
+ if binding["tempo_policy"]:
134
+ narration_audio._apply_narration_speed(
135
+ tts_segments, work_dir, tempo_policy=binding["tempo_policy"]
136
+ )
137
+ else:
138
+ narration_audio._apply_narration_speed(tts_segments, work_dir)
139
+ narration_wav = work_dir / "narration.wav"
140
+ if binding["tempo_policy"]:
141
+ narration_audio._build_timed_narration(
142
+ tts_segments, narration_wav, video_duration, work_dir,
143
+ tempo_policy=binding["tempo_policy"],
144
+ )
145
+ if any(
146
+ segment.get("blocking") or segment.get("fit_status") == "no_safe_fit"
147
+ for segment in tts_segments
148
+ ):
149
+ raise RuntimeError(
150
+ "严格 narration adoption 存在 no_safe_fit,禁止提速或裁尾渲染"
151
+ )
152
+ else:
153
+ narration_audio._build_timed_narration(
154
+ tts_segments, narration_wav, video_duration, work_dir
155
+ )
156
+ handoffs = audio_mix._apply_source_sentence_handoffs(tts_segments, work_dir, video_duration)
157
+ if handoffs:
158
+ lib.log(
159
+ "原声句末交接: "
160
+ + ", ".join(
161
+ f"{item['end']:.2f}s→{item.get('restore_at', item['end']):.2f}s({item['status']})"
162
+ for item in handoffs
163
+ )
164
+ )
165
+ narration_binding.seal_render_inputs(binding, tts_segments, narration_wav)
52
166
 
53
167
  # 始终生成 SRT 字幕文件(原声留白处补烧原声字幕,传入成片时长以计算留白区间)
54
168
  srt_path = subtitle_render._generate_srt(tts_segments, work_dir, video_duration)
@@ -59,17 +173,24 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
59
173
  lib.log(f"压制字幕文件: {ass_path}")
60
174
 
61
175
  # 可选 BGM:作为一条独立音轨(input [2:a])混入,旁白处自动压低
62
- bgm_path = lib.CONFIG["bgm_path"]
63
- has_bgm = bool(bgm_path) and os.path.exists(bgm_path)
64
- if bgm_path and not has_bgm:
176
+ if explicit_mix is not None:
177
+ lib.log("显式 adopted full-sound:忽略环境 BGM/duck/loudnorm/tempo 配置")
178
+ elif bgm_path and not has_bgm:
65
179
  lib.log(f" ⚠️ BGM 文件不存在,跳过: {bgm_path}")
66
180
  elif has_bgm:
67
181
  lib.log(f"BGM 铺底: {bgm_path} (音量 {lib.CONFIG['bgm_volume']},旁白时 {lib.CONFIG['bgm_ducking_volume']})")
68
182
 
69
183
  # 多轨时间线模型(timeline.json):canonical 渲染仍是 ffmpeg,此模型供检视/可选导出
70
- timeline_emit._emit_timeline(input_video, tts_segments, work_dir, video_duration, canvas, has_bgm)
184
+ timeline_emit._emit_timeline(
185
+ input_video, tts_segments, work_dir, video_duration, canvas, has_bgm,
186
+ audio_mode=audio_mode, selected_audio_stream=audio_stream_index,
187
+ explicit_audio_mix=(
188
+ {**explicit_mix, **explicit_mix["runtime"]} if explicit_mix is not None else None
189
+ ),
190
+ )
71
191
 
72
192
  overlay_filters, overlay_qc = visual_render._visual_overlay_filters(work_dir, canvas, video_duration)
193
+ packaging_layers = packaging.load_packaging_layers(work_dir, canvas)
73
194
  mask_filter = visual_render._source_subtitle_mask_filter(canvas, work_dir, tts_segments, video_duration)
74
195
  visual_qc = visual_render._build_visual_qc(
75
196
  tts_segments,
@@ -86,65 +207,94 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
86
207
  codes = ", ".join(visual_qc["blocking_codes"])
87
208
  raise RuntimeError(f"视觉 QC 失败: {codes};详见 {Path(work_dir) / constants.VISUAL_QC}")
88
209
 
89
- # 混合原始音频 + 解说音频(+ 可选 BGM)
210
+ # Select exactly one of three explicit audio paths. Only narration may synthesize
211
+ # a missing original track; adopted copy never decodes, mixes, normalizes or trims.
90
212
  source_has_audio = media._has_audio_stream(input_video)
91
- if source_has_audio:
92
- original_audio_input = []
93
- original_audio_label = "0:a"
94
- bgm_audio_label = "2:a"
95
- else:
96
- lib.log("源视频无音轨,使用静音原声音轨进行混音")
97
- original_audio_input = [
98
- "-f", "lavfi", "-t", str(video_duration),
99
- "-i", "anullsrc=channel_layout=stereo:sample_rate=48000",
100
- ]
101
- original_audio_label = "2:a"
102
- bgm_audio_label = "3:a"
103
- filter_complex = audio_mix._build_audio_filter_complex(
104
- tts_segments,
105
- has_bgm,
106
- original_audio_label=original_audio_label,
107
- bgm_audio_label=bgm_audio_label,
108
- )
109
-
110
- # BGM is input [2:a]; -stream_loop -1 loops it to cover the whole timeline (amix
111
- # duration=first + -t trim it back to the video length).
112
- bgm_input = ["-stream_loop", "-1", "-i", str(bgm_path)] if has_bgm else []
113
-
114
- # 末端整体响度归一:ducking 只管相对平衡,这一步统一成片绝对响度
115
- loudnorm_measurement = audio_mix._run_loudnorm_first_pass(
116
- input_video,
117
- narration_wav,
118
- original_audio_input,
119
- bgm_input,
120
- filter_complex,
121
- work_dir,
122
- )
123
- final_ln = audio_mix.final_loudnorm_filter(loudnorm_measurement)
124
- filter_complex += f";[aout]{final_ln}[aoutln]"
125
- lib.log(f"成片响度归一: {final_ln}")
126
-
127
- # 对于超长 volume 表达式(多段解说),使用 -filter_complex_script 避免命令行溢出
213
+ adopted_audio = None
214
+ loudnorm_measurement = None
215
+ original_audio_input = []
216
+ bgm_input = []
217
+ filter_complex = None
218
+ filter_args = []
219
+ audio_input_args = []
128
220
  fc_script = None
129
- if len(filter_complex.encode("utf-8")) > constants.FILTER_SCRIPT_THRESHOLD_BYTES:
130
- fc_script = Path(work_dir) / ".filter_complex.txt"
131
- fc_script.write_text(filter_complex, encoding="utf-8")
132
- lib.log(f"使用 filter_complex_script (表达式长度 {len(filter_complex.encode('utf-8'))} bytes)")
133
- filter_args = ["-filter_complex_script", str(fc_script)]
221
+ if audio_mode == "adopted-packet-copy":
222
+ audio_map = f"0:a:{audio_stream_index}"
223
+ elif audio_mode == "source-mix":
224
+ audio_map = "[aoutln]"
225
+ source_label = f"0:a:{audio_stream_index}"
226
+ filter_complex = f"[{source_label}]volume={lib.CONFIG['idle_orig_volume']}[source]"
227
+ if has_bgm:
228
+ bgm_input = ["-stream_loop", "-1", "-i", str(bgm_path)]
229
+ filter_complex += (
230
+ f";[1:a]volume={lib.CONFIG['bgm_volume']}[bgm]"
231
+ ";[source][bgm]amix=inputs=2:duration=first:dropout_transition=0[aout]"
232
+ )
233
+ else:
234
+ filter_complex += ";[source]anull[aout]"
235
+ final_ln = audio_mix.final_loudnorm_filter()
236
+ filter_complex += f";[aout]{final_ln}[aoutln]"
237
+ lib.log(f"source-mix 音频处理: source volume + {final_ln}")
238
+ elif explicit_mix is not None:
239
+ audio_map = "1:a:0"
240
+ audio_input_args = ["-i", explicit_mix["runtime"]["master"]["path"]]
134
241
  else:
135
- filter_args = ["-filter_complex", filter_complex]
242
+ # 混合原始音频 + 解说音频(+ 可选 BGM)
243
+ if source_has_audio:
244
+ original_audio_label = "0:a"
245
+ bgm_audio_label = "2:a"
246
+ else:
247
+ lib.log("源视频无音轨,使用静音原声音轨进行混音")
248
+ original_audio_input = [
249
+ "-f", "lavfi", "-t", str(video_duration),
250
+ "-i", "anullsrc=channel_layout=stereo:sample_rate=48000",
251
+ ]
252
+ original_audio_label = "2:a"
253
+ bgm_audio_label = "3:a"
254
+ filter_complex = audio_mix._build_audio_filter_complex(
255
+ tts_segments,
256
+ has_bgm,
257
+ original_audio_label=original_audio_label,
258
+ bgm_audio_label=bgm_audio_label,
259
+ )
260
+ # BGM is input [2:a]; -stream_loop -1 loops it to cover the whole timeline (amix
261
+ # duration=first + -t trim it back to the video length).
262
+ bgm_input = ["-stream_loop", "-1", "-i", str(bgm_path)] if has_bgm else []
263
+ # 末端整体响度归一:ducking 只管相对平衡,这一步统一成片绝对响度
264
+ loudnorm_measurement = audio_mix._run_loudnorm_first_pass(
265
+ input_video,
266
+ narration_wav,
267
+ original_audio_input,
268
+ bgm_input,
269
+ filter_complex,
270
+ work_dir,
271
+ )
272
+ final_ln = audio_mix.final_loudnorm_filter(loudnorm_measurement)
273
+ filter_complex += f";[aout]{final_ln}[aoutln]"
274
+ lib.log(f"成片响度归一: {final_ln}")
275
+ audio_map = "[aoutln]"
276
+ audio_input_args = ["-i", str(narration_wav), *original_audio_input]
277
+
278
+ # 对于超长 volume 表达式(多段解说),从脚本文件读取 filter_complex 避免命令行溢出
279
+ if filter_complex is not None:
280
+ if len(filter_complex.encode("utf-8")) > constants.FILTER_SCRIPT_THRESHOLD_BYTES:
281
+ fc_script = Path(work_dir) / ".filter_complex.txt"
282
+ fc_script.write_text(filter_complex, encoding="utf-8")
283
+ lib.log(f"使用 filter_complex 脚本文件 (表达式长度 {len(filter_complex.encode('utf-8'))} bytes)")
284
+ filter_args = lib.filter_file_args("filter_complex", fc_script)
285
+ else:
286
+ filter_args = ["-filter_complex", filter_complex]
136
287
  cmd = [
137
288
  "ffmpeg", "-y",
138
289
  "-i", str(input_video),
139
- "-i", str(narration_wav),
140
- *original_audio_input,
290
+ *audio_input_args,
141
291
  *bgm_input,
142
292
  *filter_args,
143
293
  # 0:v:0 (not 0:v): sources with attached cover art carry a second video stream.
144
294
  # -vf only ever applies to the first one, so mapping all of them makes ffmpeg
145
295
  # abort with "Could not write header (incorrect codec parameters ?)" and leave
146
296
  # an unreadable file — after the whole pipeline has already run.
147
- "-map", "0:v:0", "-map", "[aoutln]",
297
+ "-map", "0:v:0", "-map", audio_map,
148
298
  ]
149
299
 
150
300
  # Video filter chain: mask source subtitles first (drawbox), then burn our subtitles
@@ -168,17 +318,19 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
168
318
  # needs EVEN width AND height, so normalize odd dims (4:2:2/4:4:4 permit them) before the
169
319
  # encode — otherwise libx264 aborts to a 0-byte file. The downscale helper already evens out.
170
320
  even = "scale=trunc(iw/2)*2:trunc(ih/2)*2"
171
- reencode = bool(vf_chain) or lib.CONFIG["force_video_reencode"]
321
+ reencode = bool(vf_chain or packaging_layers) or lib.CONFIG["force_video_reencode"]
172
322
  notes = []
173
323
  video_filter_script = None
174
- if vf_chain:
324
+ if vf_chain or packaging_layers:
175
325
  if max_h <= 0: # no downscale in the chain to force even dims
176
326
  vf_chain.append(even)
177
- video_filter = ",".join(vf_chain)
327
+ video_filter = packaging.compose_video_filter(
328
+ vf_chain, packaging_layers, mask_first=bool(mask_filter)
329
+ )
178
330
  if len(video_filter.encode("utf-8")) > constants.FILTER_SCRIPT_THRESHOLD_BYTES:
179
331
  video_filter_script = Path(work_dir) / ".video_filter.txt"
180
332
  video_filter_script.write_text(video_filter, encoding="utf-8")
181
- cmd += ["-filter_script:v:0", str(video_filter_script)]
333
+ cmd += lib.filter_file_args("filter:v:0", video_filter_script)
182
334
  lib.log(
183
335
  "使用 video filter script "
184
336
  f"(表达式长度 {len(video_filter.encode('utf-8'))} bytes)"
@@ -187,6 +339,7 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
187
339
  cmd += ["-vf", video_filter]
188
340
  cmd += ["-c:v", "libx264", "-preset", preset, "-crf", crf, "-pix_fmt", "yuv420p"]
189
341
  notes = ((["遮挡原字幕"] if mask_filter else [])
342
+ + ([f"包装图层×{len(packaging_layers)}"] if packaging_layers else [])
190
343
  + ([f"视觉叠加×{len(overlay_filters)}"] if overlay_filters else [])
191
344
  + (["压制解说字幕"] if burn_subtitles else [])
192
345
  + ([f"缩放≤{max_h}p"] if max_h > 0 else []))
@@ -199,8 +352,18 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
199
352
 
200
353
  # +faststart relocates the moov atom to the front so web/social players can start
201
354
  # before the full file downloads; valid (and beneficial) on the copy path too.
202
- cmd += ["-c:a", "aac", "-b:a", "192k", "-ar", "48000", "-movflags", "+faststart",
203
- "-t", str(video_duration), str(output_path)]
355
+ if audio_mode == "adopted-packet-copy":
356
+ # No -t/-shortest: either would discard valid AAC priming or tail packets.
357
+ cmd += ["-c:a", "copy", "-movie_timescale",
358
+ str(adopted_source_audio["sample_rate"]),
359
+ "-movflags", "+faststart", str(output_path)]
360
+ elif explicit_mix is not None:
361
+ cmd += ["-c:a", "aac", "-b:a", "192k", "-ar", "48000",
362
+ "-movie_timescale", "48000", "-movflags", "+faststart",
363
+ str(output_path)]
364
+ else:
365
+ cmd += ["-c:a", "aac", "-b:a", "192k", "-ar", "48000", "-movflags", "+faststart",
366
+ "-t", str(video_duration), str(output_path)]
204
367
  try:
205
368
  result = lib.run_cmd(cmd)
206
369
  if result.returncode != 0:
@@ -212,25 +375,61 @@ def assemble_video(input_video, tts_segments, work_dir, output_path):
212
375
  if video_filter_script is not None:
213
376
  video_filter_script.unlink(missing_ok=True)
214
377
 
215
- lib.log(f"最终视频: {output_path} ({output_path.stat().st_size / 1024 / 1024:.1f}MB)")
216
- assembly_contract._write_assembly_qc(
217
- work_dir,
218
- assembly_contract._build_assembly_qc(
219
- tts_segments,
220
- video_duration,
221
- output_path=output_path,
222
- source_has_audio=source_has_audio,
223
- loudnorm_measurement=loudnorm_measurement,
224
- visual_qc=visual_qc,
225
- render_delivery={
226
- "video_encode_passes": 1 if reencode else 0,
227
- "reencode_reason": notes,
228
- "audio_sample_rate": 48000,
229
- "final_compat_notes": ["yuv420p"] if reencode else ["video_copy", "aac_48000", "faststart"],
230
- },
378
+ if audio_mode == "adopted-packet-copy":
379
+ # Either check failing means the file at the final path is unverified: never leave it.
380
+ try:
381
+ adopted_audio = frozen_audio.verify_adopted_audio(
382
+ input_video, output_path, audio_stream_index
383
+ )
384
+ pair_media.validate_aac_packet_interval(adopted_audio["output"])
385
+ except (RuntimeError, ValueError):
386
+ output_path.unlink(missing_ok=True)
387
+ raise
388
+ subtitle_track_binding.verify_rendered_picture(work_dir, output_path)
389
+ audio_operations = {
390
+ "narration": audio_mode == "narration",
391
+ "source_mix": audio_mode == "source-mix",
392
+ "bgm_mix": has_bgm and audio_mode != "adopted-packet-copy" and explicit_mix is None,
393
+ "ducking": audio_mode == "narration" and explicit_mix is None,
394
+ "loudness_normalization": (
395
+ audio_mode != "adopted-packet-copy" and explicit_mix is None
396
+ and lib.CONFIG["final_loudnorm"]
397
+ ),
398
+ "limiter": audio_mode != "adopted-packet-copy" and explicit_mix is None,
399
+ "resample": audio_mode != "adopted-packet-copy",
400
+ "tempo": audio_mode == "narration" and explicit_mix is None,
401
+ "packet_copy": audio_mode == "adopted-packet-copy",
402
+ }
403
+ if explicit_mix is not None:
404
+ audio_operations["explicit_audio_mix"] = True
405
+ render_delivery = {
406
+ "video_encode_passes": 1 if reencode else 0,
407
+ "reencode_reason": notes,
408
+ "audio_sample_rate": (
409
+ adopted_audio["output"]["sample_rate"] if adopted_audio else 48000
231
410
  ),
411
+ "final_compat_notes": (
412
+ (["yuv420p"] if reencode else ["video_copy"])
413
+ + (["aac_packet_copy", "faststart"] if adopted_audio else ["aac_48000", "faststart"])
414
+ ),
415
+ }
416
+ loudness_mode = (
417
+ "not_run" if audio_mode == "adopted-packet-copy" else
418
+ "fixed_master_gain_no_loudnorm" if explicit_mix is not None else None
419
+ )
420
+ source_audio_status = "prepared_bed_adopted" if explicit_mix is not None else None
421
+ render_output = strict_publish.publish_render(
422
+ work_dir=work_dir, binding=binding, explicit_mix=explicit_mix,
423
+ tts_segments=tts_segments, narration_wav=narration_wav,
424
+ render_output=render_output, published_output=published_output,
425
+ audio_mode=audio_mode, audio_operations=audio_operations,
426
+ adopted_audio=adopted_audio, loudness_mode=loudness_mode,
427
+ loudnorm_measurement=loudnorm_measurement, visual_qc=visual_qc,
428
+ source_has_audio=source_has_audio, video_duration=video_duration,
429
+ render_delivery=render_delivery, source_audio_status=source_audio_status,
232
430
  )
233
- return output_path
431
+ lib.log(f"最终视频: {render_output} ({render_output.stat().st_size / 1024 / 1024:.1f}MB)")
432
+ return render_output
234
433
 
235
434
 
236
435
  def main():
@@ -241,6 +440,18 @@ def main():
241
440
  ap.add_argument("video", help="source video (edited_source.mp4 in cut mode, else the original)")
242
441
  ap.add_argument("--work-dir", required=True)
243
442
  ap.add_argument("--tts-meta", default=None, help="tts_meta.json (default: <work-dir>/tts_meta.json)")
443
+ ap.add_argument("--narration-adoption", default=None,
444
+ help="strict narration_adoption v1 bound to an explicit --tts-meta")
445
+ ap.add_argument("--audio-mix-adoption", default=None,
446
+ help="strict audio_mix_adoption v1 for adopted prepared bed and narration")
447
+ ap.add_argument(
448
+ "--audio-mode", choices=AUDIO_MODES,
449
+ default="narration", help="audio path (default: narration)",
450
+ )
451
+ ap.add_argument(
452
+ "--audio-stream-index", type=int, default=0,
453
+ help="zero-based input audio stream ordinal for source/adopted modes",
454
+ )
244
455
  ap.add_argument("--recap-stem", default=None, help="final recap filename stem (default: video stem)")
245
456
  ap.add_argument("--output-dir", default=None)
246
457
  ap.add_argument("--burn-subtitles", action=argparse.BooleanOptionalAction, default=None,
@@ -254,10 +465,12 @@ def main():
254
465
  ap.add_argument("--export-jianying", action="store_true",
255
466
  help="also export an OPTIONAL 剪映/JianYing draft from timeline.json after rendering")
256
467
  ap.add_argument("--jianying-out", default=None, help="parent dir for the 剪映 draft (default: work-dir)")
257
- ap.add_argument("--jianying-bundle-media", action="store_true",
258
- help="copy media into the 剪映 draft folder (default on; portable/self-contained)")
259
- ap.add_argument("--jianying-no-bundle-media", action="store_true",
260
- help="do NOT copy media into the draft — reference in place (only if 剪映 can read those paths; macOS 剪映 usually cannot)")
468
+ bundle_group = ap.add_mutually_exclusive_group()
469
+ bundle_group.add_argument("--jianying-bundle-media", dest="jianying_bundle_media", action="store_true",
470
+ help="copy media into the 剪映 draft folder (default on; portable/self-contained)")
471
+ bundle_group.add_argument("--jianying-no-bundle-media", dest="jianying_bundle_media", action="store_false",
472
+ help="do NOT copy media into the draft — reference in place (only if 剪映 can read those paths; macOS 剪映 usually cannot)")
473
+ ap.set_defaults(jianying_bundle_media=None)
261
474
  args = ap.parse_args()
262
475
  work_dir = Path(args.work_dir)
263
476
  if args.burn_subtitles is not None:
@@ -291,38 +504,84 @@ def main():
291
504
  lib.CONFIG["source_video_explicit"] = False
292
505
  if args.export_jianying:
293
506
  lib.CONFIG["export_jianying"] = True
294
- if args.jianying_bundle_media:
295
- lib.CONFIG["jianying_bundle_media"] = True
296
- if args.jianying_no_bundle_media:
297
- lib.CONFIG["jianying_bundle_media"] = False
507
+ if args.jianying_bundle_media is not None:
508
+ lib.CONFIG["jianying_bundle_media"] = args.jianying_bundle_media
298
509
  render_preflight._preflight_burn_subtitles() # fail before the render if burn-in is on but ffmpeg lacks libass
299
- tts_meta = Path(args.tts_meta) if args.tts_meta else work_dir / "tts_meta.json"
300
- tts_segments = json.loads(tts_meta.read_text(encoding="utf-8"))["segments"]
301
- output_path = work_dir / "output.mp4"
302
- assemble_video(args.video, tts_segments, work_dir, output_path)
303
- assembly_qc = artifacts._load_work_json(work_dir, constants.ASSEMBLY_QC)
304
- if assembly_qc["blocking"]:
305
- codes = ", ".join(assembly_qc["blocking_codes"])
306
- raise SystemExit(f"组装 QC 阻断交付: {codes};详见 {work_dir / constants.ASSEMBLY_QC}")
510
+ # Argument combinations are validated once, by assemble_video.
511
+ tts_meta = Path(args.tts_meta) if args.tts_meta else None
512
+ tts_segments = []
513
+ if args.audio_mode == "narration":
514
+ tts_meta = tts_meta or work_dir / "tts_meta.json"
515
+ tts_segments = json.loads(tts_meta.read_text(encoding="utf-8"))["segments"]
307
516
  stem = args.recap_stem or Path(args.video).stem
308
517
  base = Path(args.output_dir) if args.output_dir else work_dir.parent
309
- base.mkdir(parents=True, exist_ok=True)
310
518
  final_output = assembly_contract._resolve_final_output(base, stem)
311
- shutil.copy2(str(output_path), str(final_output))
312
- manifest = assembly_contract._assembly_manifest_payload(
313
- args.video, tts_segments, work_dir, output_path,
314
- tts_meta_path=tts_meta,
315
- final_output=final_output,
316
- settings_fingerprint=assembly_settings.assembly_settings_fingerprint,
317
- )
318
- assembly_contract._write_assembly_manifest(work_dir, manifest)
519
+ if args.audio_mix_adoption is not None and final_output.exists():
520
+ ap.error("explicit audio mix requires a new final delivery path")
521
+ delivery_stage = None
522
+ owned_alias = None
523
+ output_path = work_dir / "output.mp4"
524
+ try:
525
+ assemble_video(
526
+ args.video, tts_segments, work_dir, output_path,
527
+ audio_mode=args.audio_mode, audio_stream_index=args.audio_stream_index,
528
+ narration_adoption_path=args.narration_adoption, tts_meta_path=tts_meta,
529
+ audio_mix_adoption_path=args.audio_mix_adoption,
530
+ )
531
+ assembly_qc = artifacts._load_work_json(work_dir, constants.ASSEMBLY_QC)
532
+ if assembly_qc["blocking"]:
533
+ codes = ", ".join(assembly_qc["blocking_codes"])
534
+ raise SystemExit(
535
+ f"组装 QC 阻断交付: {codes};详见 {work_dir / constants.ASSEMBLY_QC}"
536
+ )
537
+ base.mkdir(parents=True, exist_ok=True)
538
+ if args.audio_mix_adoption is not None:
539
+ # Publish the delivery alias only when this process created it: stage a copy,
540
+ # hard-link it into place (fails if the alias appeared meanwhile), remember the
541
+ # inode, and roll back only an alias we own.
542
+ delivery_stage = final_output.with_name(
543
+ f".{final_output.name}.rendering-{os.getpid()}"
544
+ )
545
+ with output_path.open("rb") as source, delivery_stage.open("xb") as target:
546
+ shutil.copyfileobj(source, target)
547
+ target.flush()
548
+ os.fsync(target.fileno())
549
+ try:
550
+ os.link(delivery_stage, final_output)
551
+ except FileExistsError as exc:
552
+ raise RuntimeError("explicit audio mix delivery alias appeared during render") from exc
553
+ stat = final_output.stat()
554
+ owned_alias = (stat.st_dev, stat.st_ino)
555
+ delivery_stage.unlink()
556
+ delivery_stage = None
557
+ else:
558
+ shutil.copy2(str(output_path), str(final_output))
559
+ manifest = assembly_contract._assembly_manifest_payload(
560
+ args.video, tts_segments, work_dir, output_path,
561
+ tts_meta_path=tts_meta,
562
+ narration_input_binding=_current_narration_binding(work_dir, args.audio_mode),
563
+ audio_mix_binding=_current_audio_mix_binding(work_dir, args.audio_mode),
564
+ final_output=final_output,
565
+ settings_payload=assembly_settings.assembly_settings_payload,
566
+ audio_mode=args.audio_mode,
567
+ audio_stream_index=args.audio_stream_index,
568
+ )
569
+ assembly_contract._write_assembly_manifest(work_dir, manifest)
570
+ except BaseException:
571
+ if delivery_stage is not None:
572
+ delivery_stage.unlink(missing_ok=True)
573
+ if owned_alias is not None and final_output.exists():
574
+ stat = final_output.stat()
575
+ if (stat.st_dev, stat.st_ino) == owned_alias:
576
+ final_output.unlink()
577
+ raise
319
578
  lib.log(f"组装完成: {final_output}")
320
579
 
321
580
  # OPTIONAL, decoupled: export a 剪映 draft from the timeline (lazy import; never
322
581
  # required by the core render path).
323
582
  if lib.CONFIG["export_jianying"]:
324
- from jianying_optional import _maybe_export_jianying
325
- _maybe_export_jianying(work_dir, args.jianying_out, stem)
583
+ from jianying.optional import maybe_export_jianying
584
+ maybe_export_jianying(work_dir, args.jianying_out, stem)
326
585
 
327
586
  print(json.dumps({"status": "assembled", "output": str(final_output), "work_dir": str(work_dir)},
328
587
  ensure_ascii=False))