@oh-story/dsh 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +11 -11
  2. package/lib/client.js +7 -3
  3. package/lib/client.js.map +2 -2
  4. package/lib/drama/manifest.json +212 -142
  5. package/lib/drama/skills/short-drama/SKILL.md +14 -2
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  11. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  13. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  14. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  15. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  16. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  17. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  18. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  19. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  20. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  21. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  22. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  23. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  25. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  26. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  27. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  28. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  29. package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
  30. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  31. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  32. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  33. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  46. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
  47. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
  48. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
  49. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
  50. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
  51. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
  53. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
  54. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  55. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  56. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  57. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  58. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  59. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  60. package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
  61. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  62. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  63. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  64. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
  65. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  66. package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
  67. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  68. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
  70. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
  71. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
  72. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
  73. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  74. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  75. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
  77. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
  78. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
  79. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  80. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  81. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  82. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  83. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  84. package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
  85. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  86. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  87. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  88. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  89. package/lib/index.js +71 -21
  90. package/lib/index.js.map +3 -3
  91. package/lib/oh-story/manifest.json +545 -310
  92. package/lib/oh-story/roles/chapter-extractor.md +11 -11
  93. package/lib/oh-story/roles/character-designer.md +1 -2
  94. package/lib/oh-story/roles/consistency-checker.md +11 -9
  95. package/lib/oh-story/roles/narrative-writer.md +24 -25
  96. package/lib/oh-story/roles/story-architect.md +9 -61
  97. package/lib/oh-story/roles/story-explorer.md +20 -125
  98. package/lib/oh-story/roles/story-researcher.md +0 -1
  99. package/lib/oh-story/skills/story/SKILL.md +6 -6
  100. package/lib/oh-story/skills/story/VERSION +1 -1
  101. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  102. package/lib/oh-story/skills/story/references/author-memory.md +40 -118
  103. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
  104. package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
  105. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  106. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
  107. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  108. package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
  109. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  110. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
  111. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  112. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
  113. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  114. package/lib/oh-story/skills/story-import/SKILL.md +63 -579
  115. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  116. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  117. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  118. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  119. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  120. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  121. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  122. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  123. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
  124. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  125. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  126. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
  127. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  128. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
  129. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
  130. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
  131. package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
  132. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
  133. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
  134. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  135. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  136. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
  137. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  139. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  141. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  142. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
  143. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
  144. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  145. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
  146. package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
  147. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  148. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  149. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  150. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  151. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  152. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  153. package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
  154. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  155. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
  156. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
  157. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  158. package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
  159. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  160. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
  161. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  162. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  163. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  164. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  165. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  166. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
  167. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  168. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  169. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  170. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  171. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  172. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  173. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  174. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  175. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  176. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  177. package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
  178. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
  179. package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
  180. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  181. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  182. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  183. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  184. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  185. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
  186. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
  187. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
  188. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  189. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
  190. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
  191. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  192. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
  193. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
  194. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
  195. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  196. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
  197. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  198. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
  199. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  200. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
  201. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
  202. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
  203. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
  204. package/lib/oh-story/skills/story-review/SKILL.md +19 -273
  205. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  206. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
  207. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  208. package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
  209. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  210. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  211. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  212. package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
  213. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  214. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  215. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  216. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  217. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
  218. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
  219. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  220. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
  221. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
  222. package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
  223. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  224. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
  225. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  226. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  227. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  228. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
  229. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  230. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  234. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  236. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  237. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  238. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  239. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  240. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  241. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  242. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  243. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  244. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  245. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  246. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
  247. package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
  248. package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
  249. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  250. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  251. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  252. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  253. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  254. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  255. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
  257. package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
  258. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  259. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  260. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  261. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  262. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
  263. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  264. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  265. package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
  266. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  267. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  268. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  270. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  271. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
  272. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  273. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  274. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  276. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  277. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  278. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  279. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  280. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
  281. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
  282. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
  283. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  284. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
  285. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  286. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  287. package/lib/types/client/video-studio.d.ts.map +1 -1
  288. package/lib/types/native-hooks.d.ts +2 -2
  289. package/lib/types/native-hooks.d.ts.map +1 -1
  290. package/lib/types/role-tool.d.ts.map +1 -1
  291. package/lib/types/skill-provider.d.ts.map +1 -1
  292. package/lib/types/video-project.d.ts.map +1 -1
  293. package/lib/types/workspace-route.d.ts.map +1 -1
  294. package/lib/video-recap/CHANGELOG.md +60 -1
  295. package/lib/video-recap/README.en.md +348 -137
  296. package/lib/video-recap/README.md +340 -136
  297. package/lib/video-recap/manifest.json +406 -251
  298. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  299. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  300. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  301. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  302. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  303. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  304. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  305. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  306. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  307. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  308. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  309. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  310. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  311. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  312. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  313. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  314. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  315. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  316. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  317. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  318. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  319. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  320. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  321. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  322. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  323. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  324. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  325. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  326. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  327. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  328. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  329. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  330. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  331. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  332. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  333. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  334. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  335. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  336. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  337. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  338. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  339. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  340. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  341. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  342. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  343. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  344. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  345. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  346. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  347. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  348. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  349. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  350. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  351. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  352. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  353. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  354. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  355. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  356. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  357. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  358. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  359. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  360. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  361. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  362. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  363. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  364. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  365. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  366. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  367. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  368. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  369. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  370. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  371. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  372. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  373. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  374. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  375. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  376. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  377. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  378. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  379. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  380. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  381. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  382. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  383. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  384. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  385. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  386. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  387. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  388. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  389. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  390. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  391. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  392. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  393. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  394. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  395. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  396. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  397. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  398. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  399. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  400. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  401. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  402. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  403. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  404. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  405. package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
  406. package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
  407. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  408. package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
  409. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  410. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  411. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  412. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  413. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  414. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  415. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  416. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  417. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  418. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  419. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  420. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  421. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  422. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  423. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  424. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  425. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  426. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  427. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  428. package/package.json +41 -41
  429. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
  430. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
  431. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  432. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  433. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  435. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  436. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  437. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
  438. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
  439. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  440. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
  441. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  442. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  443. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  444. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -4,22 +4,20 @@ import json
4
4
  import os
5
5
  from pathlib import Path
6
6
 
7
- from artifacts import _explicit_source_video, _value_fingerprint
7
+ from artifacts import _explicit_source_video
8
8
  from lib import log, run_cmd
9
9
 
10
10
  def _load_cut_timeline_plan(work_dir):
11
- """The cut plan, preferring clip_plan_validated.json when it matches the raw plan; None in full mode."""
11
+ """The cut plan, preferring clip_plan_validated.json unless the raw plan is newer; None in full mode."""
12
12
  raw_plan_path = Path(work_dir) / "clip_plan.json"
13
13
  validated_plan_path = Path(work_dir) / "clip_plan_validated.json"
14
14
  if not validated_plan_path.exists():
15
15
  return json.loads(raw_plan_path.read_text(encoding="utf-8")) if raw_plan_path.exists() else None
16
16
  if not raw_plan_path.exists():
17
17
  return json.loads(validated_plan_path.read_text(encoding="utf-8"))
18
- raw_plan = json.loads(raw_plan_path.read_text(encoding="utf-8"))
19
- validated_plan = json.loads(validated_plan_path.read_text(encoding="utf-8"))
20
- if validated_plan.get("raw_plan_fingerprint") == _value_fingerprint(raw_plan):
21
- return validated_plan
22
- return raw_plan
18
+ if validated_plan_path.stat().st_mtime_ns >= raw_plan_path.stat().st_mtime_ns:
19
+ return json.loads(validated_plan_path.read_text(encoding="utf-8"))
20
+ return json.loads(raw_plan_path.read_text(encoding="utf-8"))
23
21
 
24
22
 
25
23
  def _plan_clip_spans(work_dir):
@@ -120,13 +118,13 @@ def _canvas_from_stream(stream):
120
118
  }
121
119
 
122
120
 
123
- def _probe_canvas(video_path, *, command_runner=run_cmd):
121
+ def _probe_canvas(video_path):
124
122
  """Return rotation/SAR/DAR-aware canvas facts for a video.
125
123
 
126
124
  ``width``/``height`` are the display canvas used by subtitle/overlay geometry.
127
125
  For legacy square-pixel landscape sources, these remain the raw storage dimensions.
128
126
  """
129
- res = command_runner([
127
+ res = run_cmd([
130
128
  "ffprobe", "-v", "error", "-select_streams", "v:0",
131
129
  "-show_entries",
132
130
  "stream=width,height,r_frame_rate,avg_frame_rate,sample_aspect_ratio,display_aspect_ratio:stream_tags=rotate:stream_side_data=rotation",
@@ -140,23 +138,16 @@ def _probe_canvas(video_path, *, command_runner=run_cmd):
140
138
  return _canvas_from_stream(streams[0])
141
139
 
142
140
 
143
- def _has_audio_stream(video_path, *, command_runner=run_cmd):
141
+ def _has_audio_stream(video_path):
144
142
  """Return True when the input has an audio stream usable as [0:a]."""
145
- result = command_runner([
143
+ result = run_cmd([
146
144
  "ffprobe", "-v", "error", "-select_streams", "a:0",
147
145
  "-show_entries", "stream=index", "-of", "csv=p=0", str(video_path),
148
146
  ])
149
147
  return result.returncode == 0 and bool(result.stdout.strip())
150
148
 
151
149
 
152
- def _build_video_clips(
153
- input_video,
154
- work_dir,
155
- duration_s,
156
- *,
157
- logger=log,
158
- source_video_getter=_explicit_source_video,
159
- ):
150
+ def _build_video_clips(input_video, work_dir, duration_s):
160
151
  """Video-track clips for the timeline.
161
152
 
162
153
  In cut mode each plan entry becomes a clip referencing the ORIGINAL source
@@ -164,7 +155,7 @@ def _build_video_clips(
164
155
  require an explicit ambient --source-video. Without any declared source (full
165
156
  mode, or cut mode rendered without --source-video) the rendered input is one clip.
166
157
  """
167
- explicit_source_video = source_video_getter()
158
+ explicit_source_video = _explicit_source_video()
168
159
  spans = _plan_clip_spans(work_dir)
169
160
  multi_source = spans is not None and any(span["entry"].get("source_path") for span in spans)
170
161
  if spans is None or not (explicit_source_video or multi_source):
@@ -180,7 +171,7 @@ def _build_video_clips(
180
171
  # Degrade ONLY this clip — point it at the rendered cut for its own output
181
172
  # window — and keep real provenance for every present source, instead of
182
173
  # collapsing the whole multi-source timeline.
183
- logger(f" 时间线: source_path 不存在,该片段降级为剪后成片片段: {source_path or '(unset)'}")
174
+ log(f" 时间线: source_path 不存在,该片段降级为剪后成片片段: {source_path or '(unset)'}")
184
175
  clips.append({"source_id": entry.get("source_id"),
185
176
  "source_path": str(input_video),
186
177
  "source_start": timeline_start,
@@ -10,9 +10,7 @@ def _apply_narration_speed(
10
10
  tts_segments,
11
11
  work_dir,
12
12
  *,
13
- command_runner=run_cmd,
14
- duration_probe=get_video_duration,
15
- logger=log,
13
+ tempo_policy=None,
16
14
  ):
17
15
  """Globally speed up narration audio via atempo (CONFIG['narration_speed']).
18
16
 
@@ -20,7 +18,7 @@ def _apply_narration_speed(
20
18
  snappier without the chipmunk effect. Rewrites each segment's audio_path/duration
21
19
  to the sped copy so the rest of assembly is unchanged. No-op at speed 1.0.
22
20
  """
23
- speed = CONFIG["narration_speed"]
21
+ speed = tempo_policy["global_atempo"] if tempo_policy else CONFIG["narration_speed"]
24
22
  if abs(speed - 1.0) <= 1e-3:
25
23
  return
26
24
  done = 0
@@ -29,14 +27,15 @@ def _apply_narration_speed(
29
27
  if not os.path.exists(src):
30
28
  continue # reported as a skipped segment during placement
31
29
  out = str(Path(work_dir) / f"_spd_{seg['index']}.wav")
32
- res = command_runner(["ffmpeg", "-y", "-i", src, "-filter:a", f"atempo={speed:.3f}",
30
+ res = run_cmd(["ffmpeg", "-y", "-i", src, "-filter:a", f"atempo={speed:.3f}",
33
31
  "-ar", "44100", "-ac", "1", "-acodec", "pcm_s16le", out])
34
32
  if res.returncode != 0:
35
33
  raise RuntimeError(f"解说提速失败 {src}: {res.stderr}")
36
34
  seg["audio_path"] = out
37
- seg["audio_duration"] = duration_probe(out)
35
+ seg["narration_conversion_path"] = out
36
+ seg["audio_duration"] = get_video_duration(out)
38
37
  done += 1
39
- logger(f"解说整体提速: atempo={speed:.2f} ({done} 段)")
38
+ log(f"解说整体提速: atempo={speed:.2f} ({done} 段)")
40
39
 
41
40
 
42
41
  def _adjust_tts_speed(
@@ -44,9 +43,7 @@ def _adjust_tts_speed(
44
43
  target_duration,
45
44
  tts_rate_offset=0.0,
46
45
  *,
47
- command_runner=run_cmd,
48
- duration_probe=get_video_duration,
49
- logger=log,
46
+ tempo_policy=None,
50
47
  ):
51
48
  """Fit overlong TTS with bounded atempo; never time-trim speech in assemble.
52
49
 
@@ -55,8 +52,16 @@ def _adjust_tts_speed(
55
52
  untouched for QC to block instead of guessing a spoken_text truncation.
56
53
  """
57
54
  audio_path = Path(audio_path)
58
- current_dur = duration_probe(audio_path)
55
+ current_dur = get_video_duration(audio_path)
59
56
  budget = narration_tempo_budget(tts_rate_offset)
57
+ if tempo_policy:
58
+ budget.update({
59
+ "global_narration_speed": tempo_policy["global_atempo"],
60
+ "tts_rate_factor": 1.0,
61
+ "segment_tempo_max": tempo_policy["segment_tempo_max"],
62
+ "cumulative_tempo_max": tempo_policy["cumulative_tempo_max"],
63
+ "cumulative_tempo_hard_max": tempo_policy["cumulative_tempo_hard_max"],
64
+ })
60
65
  meta = {
61
66
  "fit_status": "fits",
62
67
  "blocking": False,
@@ -75,6 +80,14 @@ def _adjust_tts_speed(
75
80
  if current_dur <= target_duration:
76
81
  return (str(audio_path), current_dur, meta)
77
82
 
83
+ if tempo_policy and not tempo_policy["bounded_segment_fit"]:
84
+ meta.update({
85
+ "fit_status": "no_safe_fit", "blocking": True,
86
+ "truncate_reason": "no_safe_boundary", "placed_audio_duration": 0.0,
87
+ "needed_tempo_factor": current_dur / target_duration,
88
+ })
89
+ return (str(audio_path), current_dur, meta)
90
+
78
91
  ratio = current_dur / target_duration
79
92
  effective_max = budget["segment_tempo_max"]
80
93
  if ratio > effective_max:
@@ -85,7 +98,7 @@ def _adjust_tts_speed(
85
98
  "placed_audio_duration": 0.0,
86
99
  "needed_tempo_factor": ratio,
87
100
  })
88
- logger(
101
+ log(
89
102
  f" TTS 无安全放置: {current_dur:.1f}s 需 x{ratio:.2f},"
90
103
  f"超过段内预算 x{effective_max:.2f}(assemble 不按时间硬切)"
91
104
  )
@@ -98,10 +111,10 @@ def _adjust_tts_speed(
98
111
  cmd = ["ffmpeg", "-y", "-i", str(audio_path),
99
112
  "-filter:a", f"atempo={tempo:.6f}",
100
113
  "-ar", "44100", "-ac", "1", str(adjusted_path)]
101
- result = command_runner(cmd)
114
+ result = run_cmd(cmd)
102
115
  if result.returncode != 0:
103
116
  raise RuntimeError(f"TTS 加速失败 {audio_path}: {result.stderr}")
104
- new_dur = duration_probe(adjusted_path)
117
+ new_dur = get_video_duration(adjusted_path)
105
118
  if new_dur > target_duration + (1.0 / 44100.0):
106
119
  adjusted_path.unlink(missing_ok=True)
107
120
  meta.update({
@@ -111,7 +124,7 @@ def _adjust_tts_speed(
111
124
  "placed_audio_duration": 0.0,
112
125
  "needed_tempo_factor": new_dur / target_duration,
113
126
  })
114
- logger(
127
+ log(
115
128
  f" TTS 加速后仍超出安全窗口 {new_dur - target_duration:.3f}s;"
116
129
  "禁止裁尾,交由 Agent 缩短/移动文本"
117
130
  )
@@ -123,7 +136,7 @@ def _adjust_tts_speed(
123
136
  "placed_audio_duration": new_dur,
124
137
  "effective_tempo": budget["global_narration_speed"] * budget["tts_rate_factor"] * tempo,
125
138
  })
126
- logger(f" TTS 温和加速: {current_dur:.1f}s → {new_dur:.1f}s (x{tempo:.2f})")
139
+ log(f" TTS 温和加速: {current_dur:.1f}s → {new_dur:.1f}s (x{tempo:.2f})")
127
140
  return (str(adjusted_path), new_dur, meta)
128
141
 
129
142
 
@@ -170,9 +183,7 @@ def _build_timed_narration(
170
183
  video_duration,
171
184
  work_dir,
172
185
  *,
173
- adjust_speed=_adjust_tts_speed,
174
- command_runner=run_cmd,
175
- logger=log,
186
+ tempo_policy=None,
176
187
  ):
177
188
  """将 TTS 片段按时间轴放置到一条与视频等长的音轨上"""
178
189
  sample_rate = 44100
@@ -194,7 +205,7 @@ def _build_timed_narration(
194
205
 
195
206
  for seg in tts_segments:
196
207
  wav_path = seg["audio_path"]
197
- pause_samples = int(seg.get("pause_after_ms", CONFIG["breath_ms"]) * sample_rate / 1000)
208
+ pause_samples = int(seg["pause_after_ms"] * sample_rate / 1000)
198
209
  # 段落收紧:同一段落内(与上一句作者留白 <= run_gap)把这一句紧贴上一句的实际收尾播放,
199
210
  # 句间间隔固定为 tight_pause,不受 slot 内居中延迟 / TTS 时长波动影响。段落之间(作者特意留
200
211
  # 的大留白,让精彩原声透出)才放回原声。这样句间间隔稳定、不会出现"一句解说一段空白"。
@@ -210,12 +221,17 @@ def _build_timed_narration(
210
221
  continue
211
222
 
212
223
  original_wav_path = wav_path
213
- with wave.open(wav_path, "rb") as wf_check:
214
- needs_resample = (
215
- wf_check.getnchannels(), wf_check.getsampwidth(), wf_check.getframerate()
216
- ) != (1, 2, sample_rate)
217
-
218
- tts_rate_offset = seg.get("tts_rate_offset", 0.0)
224
+ try:
225
+ with wave.open(wav_path, "rb") as wf_check:
226
+ needs_resample = (
227
+ wf_check.getnchannels(), wf_check.getsampwidth(), wf_check.getframerate()
228
+ ) != (1, 2, sample_rate)
229
+ except (wave.Error, EOFError):
230
+ # Valid post-processed WAV may use IEEE float, which Python's wave
231
+ # reader does not support. FFmpeg performs the explicit PCM conversion.
232
+ needs_resample = True
233
+
234
+ tts_rate_offset = seg["tts_rate_offset"]
219
235
  tts_dur = seg["audio_duration"]
220
236
 
221
237
  slot_duration = max(0.0, float(seg["end"]) - float(seg["start"]))
@@ -240,7 +256,15 @@ def _build_timed_narration(
240
256
  available_samples = end_boundary - actual_start
241
257
  available_duration = max(available_samples / sample_rate, 0)
242
258
  if tts_dur > available_duration > 0:
243
- wav_path, _actual_dur, fit_meta = adjust_speed(wav_path, available_duration, tts_rate_offset)
259
+ if tempo_policy:
260
+ wav_path, _actual_dur, fit_meta = _adjust_tts_speed(
261
+ wav_path, available_duration, tts_rate_offset,
262
+ tempo_policy=tempo_policy,
263
+ )
264
+ else:
265
+ wav_path, _actual_dur, fit_meta = _adjust_tts_speed(
266
+ wav_path, available_duration, tts_rate_offset
267
+ )
244
268
  seg.update({
245
269
  "fit_status": fit_meta["fit_status"],
246
270
  "segment_tempo_factor": fit_meta["segment_tempo_factor"],
@@ -257,6 +281,11 @@ def _build_timed_narration(
257
281
  continue
258
282
  else:
259
283
  budget = narration_tempo_budget(tts_rate_offset)
284
+ if tempo_policy:
285
+ budget.update({
286
+ "global_narration_speed": tempo_policy["global_atempo"],
287
+ "tts_rate_factor": 1.0,
288
+ })
260
289
  seg.update({
261
290
  "fit_status": "fits",
262
291
  "segment_tempo_factor": 1.0,
@@ -270,16 +299,17 @@ def _build_timed_narration(
270
299
  needs_resample = False
271
300
  if needs_resample:
272
301
  tmp_path = str(Path(work_dir) / f"_rs_{seg['index']}.wav")
273
- rs_result = command_runner(["ffmpeg", "-y", "-i", wav_path,
302
+ rs_result = run_cmd(["ffmpeg", "-y", "-i", wav_path,
274
303
  "-ar", str(sample_rate), "-ac", "1",
275
304
  "-acodec", "pcm_s16le", tmp_path])
276
305
  if rs_result.returncode != 0:
277
- logger(f" 跳过: 重采样失败 {wav_path}: {rs_result.stderr}")
306
+ log(f" 跳过: 重采样失败 {wav_path}: {rs_result.stderr}")
278
307
  _unplaced(seg, seg["start"], "skipped", "resample_failed")
279
308
  prev_pause_samples = pause_samples
280
309
  skipped_count += 1
281
310
  continue
282
311
  wav_path = tmp_path
312
+ seg["narration_conversion_path"] = tmp_path
283
313
 
284
314
  with wave.open(wav_path, "rb") as wf:
285
315
  wf_data = bytearray(wf.readframes(wf.getnframes()))
@@ -290,7 +320,7 @@ def _build_timed_narration(
290
320
  write_samples = audio_samples
291
321
 
292
322
  if write_samples <= 0 or available <= 0:
293
- logger(f" 跳过: {seg['start']:.1f}s-{seg['end']:.1f}s (无空间)")
323
+ log(f" 跳过: {seg['start']:.1f}s-{seg['end']:.1f}s (无空间)")
294
324
  _unplaced(seg, seg["start"], "no_safe_fit", "no_room", blocking=True)
295
325
  prev_pause_samples = pause_samples
296
326
  no_safe_fit_count += 1
@@ -301,7 +331,7 @@ def _build_timed_narration(
301
331
  # consonant/vowel release. _adjust_tts_speed must produce a complete file
302
332
  # that fits; otherwise block and ask the Agent to shorten/move the block.
303
333
  over = (audio_samples - available) / sample_rate
304
- logger(f" TTS 无安全放置: 段 {seg['index']} 超出可用窗口 {over:.3f}s;禁止裁尾,交由 QC 阻断")
334
+ log(f" TTS 无安全放置: 段 {seg['index']} 超出可用窗口 {over:.3f}s;禁止裁尾,交由 QC 阻断")
305
335
  _unplaced(seg, actual_start / sample_rate, "no_safe_fit", "no_safe_boundary", blocking=True)
306
336
  prev_pause_samples = pause_samples
307
337
  skipped_count += 1
@@ -312,7 +342,7 @@ def _build_timed_narration(
312
342
  if actual_start < last_written_end:
313
343
  overlap_ms = (last_written_end - actual_start) * 1000 / sample_rate
314
344
  if last_written_end >= actual_start + write_samples:
315
- logger(f" 跳过重叠段: {actual_start/sample_rate:.1f}s "
345
+ log(f" 跳过重叠段: {actual_start/sample_rate:.1f}s "
316
346
  f"(与前段重叠 {overlap_ms:.0f}ms)")
317
347
  _unplaced(seg, seg["start"], "no_safe_fit", "no_room", blocking=True)
318
348
  prev_pause_samples = pause_samples
@@ -321,7 +351,7 @@ def _build_timed_narration(
321
351
  actual_start = last_written_end
322
352
  available = end_boundary - actual_start
323
353
  if write_samples > available:
324
- logger(f" 重叠 {overlap_ms:.0f}ms 后无安全完整窗口,跳过")
354
+ log(f" 重叠 {overlap_ms:.0f}ms 后无安全完整窗口,跳过")
325
355
  _unplaced(seg, actual_start / sample_rate, "no_safe_fit", "no_safe_boundary", blocking=True)
326
356
  prev_pause_samples = pause_samples
327
357
  skipped_count += 1
@@ -380,4 +410,4 @@ def _build_timed_narration(
380
410
  "已中止以避免生成无解说视频"
381
411
  )
382
412
 
383
- logger(f"解说音轨: {video_duration:.1f}s, {len(tts_segments)} 段")
413
+ log(f"解说音轨: {video_duration:.1f}s, {len(tts_segments)} 段")
@@ -0,0 +1,122 @@
1
+ """Static packaging layers (frame / header / logo images) burned over the whole recap.
2
+
3
+ The caller writes ``work_dir/packaging_layers.json`` (the orchestrator does so from a bound
4
+ ``packaging`` template). Each layer is a local image scaled into a rect on a declared
5
+ canvas. ffmpeg reads the images with ``movie=`` sources, so the render stays a single-input
6
+ video filter; ``timeline.json`` gets matching image segments for editable export.
7
+ """
8
+
9
+ import json
10
+ from pathlib import Path
11
+
12
+ import lib
13
+ from artifacts import file_identity
14
+ from visual_render import _escape_subtitle_filter_path
15
+
16
+ PACKAGING_LAYERS = "packaging_layers.json"
17
+ _IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
18
+
19
+
20
+ def load_packaging_layers(work_dir, canvas):
21
+ """Validated layers for this canvas, or [] when the work_dir declares none."""
22
+ path = Path(work_dir) / PACKAGING_LAYERS
23
+ if not path.exists():
24
+ return []
25
+ plan = json.loads(path.read_text(encoding="utf-8"))
26
+ declared = plan["canvas"]
27
+ if (declared["width"], declared["height"]) != (canvas["width"], canvas["height"]):
28
+ raise RuntimeError(
29
+ f"{PACKAGING_LAYERS} 按 {declared['width']}x{declared['height']} 设计,"
30
+ f"成片画布是 {canvas['width']}x{canvas['height']}"
31
+ )
32
+ layers = []
33
+ for layer in plan["layers"]:
34
+ image = Path(layer["path"]).expanduser().resolve()
35
+ rect = layer["rect"]
36
+ if not image.is_file() or image.suffix.lower() not in _IMAGE_EXTS:
37
+ raise RuntimeError(f"包装图层 {layer['name']} 的图片不可用: {image}")
38
+ if (min(rect["x"], rect["y"]) < 0 or rect["width"] <= 0 or rect["height"] <= 0
39
+ or rect["x"] + rect["width"] > canvas["width"]
40
+ or rect["y"] + rect["height"] > canvas["height"]):
41
+ raise RuntimeError(f"包装图层 {layer['name']} 超出画布")
42
+ layers.append({"name": layer["name"], "path": str(image), "rect": dict(rect),
43
+ "image_size": _image_size(image)})
44
+ return layers
45
+
46
+
47
+ def _image_size(image):
48
+ """Pixel size of a layer image; the editor fits by it, the render stretches to the rect."""
49
+ res = lib.run_cmd(["ffprobe", "-v", "error", "-select_streams", "v:0",
50
+ "-show_entries", "stream=width,height", "-of", "json", str(image)])
51
+ try:
52
+ stream = json.loads(res.stdout)["streams"][0]
53
+ return {"width": int(stream["width"]), "height": int(stream["height"])}
54
+ except (ValueError, KeyError, IndexError):
55
+ raise RuntimeError(f"无法读取包装图层尺寸: {image}") from None
56
+
57
+
58
+ def compose_video_filter(chain, layers, *, mask_first):
59
+ """Join the existing filter chain, inserting packaging layers after the source mask.
60
+
61
+ Order: source-subtitle mask → packaging layers → text overlays / burned subtitles →
62
+ scaling. Without layers this is the plain comma-joined chain.
63
+ """
64
+ if not layers:
65
+ return ",".join(chain)
66
+ head, tail = (chain[:1], chain[1:]) if mask_first else ([], list(chain))
67
+ graph = [
68
+ f"movie=filename='{_escape_subtitle_filter_path(layer['path'])}',"
69
+ f"scale={layer['rect']['width']}:{layer['rect']['height']},format=rgba[pk{i}]"
70
+ for i, layer in enumerate(layers)
71
+ ]
72
+ current = "in"
73
+ if head:
74
+ graph.append(f"[in]{head[0]}[pm]")
75
+ current = "pm"
76
+ for i, layer in enumerate(layers):
77
+ out = "out" if i == len(layers) - 1 and not tail else f"po{i}"
78
+ graph.append(f"[{current}][pk{i}]overlay=x={layer['rect']['x']}:y={layer['rect']['y']}[{out}]")
79
+ current = out
80
+ if tail:
81
+ graph.append(f"[{current}]{','.join(tail)}[out]")
82
+ return ";".join(graph)
83
+
84
+
85
+ def timeline_image_segments(layers, canvas, duration_s):
86
+ """Full-length image segments in the timeline's center-origin, Y-up transform.
87
+
88
+ The editor shows an image fitted inside the canvas (keeping its own aspect) at scale 1;
89
+ the render stretches it to the rect, so x and y scales are derived separately.
90
+ """
91
+ width, height = canvas["width"], canvas["height"]
92
+ segments = []
93
+ for layer in layers:
94
+ rect, size = layer["rect"], layer["image_size"]
95
+ fit = min(width / size["width"], height / size["height"])
96
+ segments.append({
97
+ "source_path": layer["path"],
98
+ "timeline_start": 0.0,
99
+ "timeline_end": duration_s,
100
+ "scale": {"x": round(rect["width"] / (size["width"] * fit), 6),
101
+ "y": round(rect["height"] / (size["height"] * fit), 6)},
102
+ "position": {
103
+ "x": round((rect["x"] + rect["width"] / 2 - width / 2) / (width / 2), 6),
104
+ "y": round((height / 2 - rect["y"] - rect["height"] / 2) / (height / 2), 6),
105
+ },
106
+ })
107
+ return segments
108
+
109
+
110
+ def packaging_settings(work_dir):
111
+ """What the manifest records: the plan file and each layer image's identity."""
112
+ path = Path(work_dir) / PACKAGING_LAYERS if work_dir is not None else None
113
+ if path is None or not path.exists():
114
+ return {"artifact": PACKAGING_LAYERS, "present": False, "layers": []}
115
+ plan = json.loads(path.read_text(encoding="utf-8"))
116
+ layers = []
117
+ for layer in plan["layers"]:
118
+ image = Path(layer["path"]).expanduser().resolve()
119
+ layers.append({"name": layer["name"], "path": str(image), "rect": layer["rect"],
120
+ **(file_identity(image) if image.is_file() else {})})
121
+ return {"artifact": PACKAGING_LAYERS, "present": True, "identity": file_identity(path),
122
+ "template": plan.get("template"), "layers": layers}