@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
@@ -13,7 +13,7 @@ from extract import (
13
13
  parse_frame_number,
14
14
  )
15
15
  from lib import CONFIG
16
- from lib import log, api_call, load_prompt, mimo_video_api_call, run_cmd, file_fingerprint, stable_hash
16
+ from lib import log, api_call, load_prompt, mimo_video_api_call, run_cmd, file_identity
17
17
 
18
18
  # ── Step 4: VLM 视觉分析 ─────────────────────────────────────────────
19
19
 
@@ -55,8 +55,8 @@ def _parse_vlm_depth_response(raw_text):
55
55
  def _max_frames_for_duration(duration):
56
56
  """Frames the VLM sees for one scene: ~1 per `vlm_seconds_per_frame`, floor 3, capped by
57
57
  `vlm_max_frames`. Replaces the old hard cap of 6 that starved long/merged scenes."""
58
- spf = float(CONFIG.get("vlm_seconds_per_frame", 4.0) or 4.0)
59
- ceiling = int(CONFIG.get("vlm_max_frames", 16) or 16)
58
+ spf = float(CONFIG["vlm_seconds_per_frame"])
59
+ ceiling = int(CONFIG["vlm_max_frames"])
60
60
  return max(3, min(ceiling, round(max(0.0, float(duration)) / spf)))
61
61
 
62
62
 
@@ -64,24 +64,33 @@ def _vlm_scene_cache_path(work_dir):
64
64
  return Path(work_dir) / "vlm_scene_cache.json"
65
65
 
66
66
 
67
- def _load_vlm_scene_cache(work_dir):
68
- """Per-scene VLM resume cache (scene_key -> analysis). Tolerant: {} if absent/corrupt."""
67
+ def _load_vlm_scene_cache(work_dir, settings):
68
+ """Per-scene VLM resume cache (scene_key -> analysis) written under `settings`.
69
+
70
+ Tolerant: {} if absent/corrupt, and {} when the stored request settings differ, so a
71
+ partial-failure cache is never resumed after an output-affecting setting changed."""
69
72
  path = _vlm_scene_cache_path(work_dir)
70
73
  if not path.exists():
71
74
  return {}
72
75
  try:
73
76
  data = json.loads(path.read_text(encoding="utf-8"))
74
- return data if isinstance(data, dict) else {}
75
77
  except (OSError, ValueError):
76
78
  return {}
79
+ if not isinstance(data, dict) or data.get("settings") != settings:
80
+ return {}
81
+ scenes = data.get("scenes")
82
+ return scenes if isinstance(scenes, dict) else {}
77
83
 
78
84
 
79
- def _flush_vlm_scene_cache(work_dir, cache):
85
+ def _flush_vlm_scene_cache(work_dir, settings, cache):
80
86
  """Persist the resume cache atomically (temp + rename) so an abort/crash never corrupts it."""
81
87
  path = _vlm_scene_cache_path(work_dir)
82
88
  try:
83
89
  tmp = path.with_name(path.name + ".tmp")
84
- tmp.write_text(json.dumps(cache, ensure_ascii=False), encoding="utf-8")
90
+ tmp.write_text(
91
+ json.dumps({"settings": settings, "scenes": cache}, ensure_ascii=False),
92
+ encoding="utf-8",
93
+ )
85
94
  tmp.replace(path)
86
95
  except OSError as exc:
87
96
  log(f"VLM 场景缓存写入失败(忽略): {exc}")
@@ -114,7 +123,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
114
123
  if not vlm_prompt:
115
124
  vlm_prompt = "仔细观察这些视频帧。分两部分输出:\n【描述】不超过80字,描述画面中正在发生什么。\n【深层分析】不超过120字,分析角色情绪、关系动态、潜台词。"
116
125
 
117
- ctx = CONFIG.get("context_info", "")
126
+ ctx = CONFIG["context_info"]
118
127
  if ctx:
119
128
  vlm_prompt = f"已知信息:{ctx}\n\n{vlm_prompt}"
120
129
 
@@ -132,7 +141,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
132
141
  # 相邻场景才会复用同一帧,容量取「并发数 × 单场景最大帧数」就够,再多也命中不了。
133
142
  b64_capacity = max(
134
143
  8,
135
- int(CONFIG.get("vlm_max_frames", 16) or 16) * max(1, int(CONFIG.get("vlm_workers", 4) or 4)),
144
+ int(CONFIG["vlm_max_frames"]) * int(CONFIG["vlm_workers"]),
136
145
  )
137
146
  b64_cache = OrderedDict()
138
147
  b64_lock = Lock()
@@ -184,7 +193,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
184
193
  payload = {
185
194
  "model": CONFIG["vlm_model"],
186
195
  "messages": [{"role": "user", "content": content_parts}],
187
- "max_tokens": int(CONFIG.get("vlm_max_tokens", 1500) or 1500),
196
+ "max_tokens": int(CONFIG["vlm_max_tokens"]),
188
197
  }
189
198
 
190
199
  log(f"VLM 分析场景 {i+1}/{len(scenes)} ({len(scene_frames)} 帧)...")
@@ -208,7 +217,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
208
217
  payload = {
209
218
  "model": CONFIG["vlm_model"],
210
219
  "messages": [{"role": "user", "content": retry_parts}],
211
- "max_tokens": int(CONFIG.get("vlm_max_tokens", 1500) or 1500),
220
+ "max_tokens": int(CONFIG["vlm_max_tokens"]),
212
221
  }
213
222
 
214
223
  if not raw_response.strip():
@@ -230,29 +239,34 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
230
239
  return i, result
231
240
 
232
241
  # 断点续传:逐场景持久化分析结果,避免少数场景失败(多为 429 限流)时整批画面理解全部作废。
233
- cache = _load_vlm_scene_cache(work_dir) if resume else {}
234
- prompt_fp = stable_hash(vlm_prompt)
242
+ # The resume cache is valid only under the SAME output-affecting settings the outer stage
243
+ # gate tracks (understanding_cache._vlm_cache_payload). vlm_prompt already folds in
244
+ # context_info (and background_research). The request/endpoint settings below are NOT in
245
+ # vlm_prompt, so they are stored explicitly — otherwise a partial-failure cache reused after
246
+ # flipping e.g. mimo_disable_thinking would yield a stale/mixed analysis.
247
+ cache_settings = {
248
+ "vlm_prompt": vlm_prompt,
249
+ "vlm_model": CONFIG.get("vlm_model"),
250
+ "vlm_max_tokens": CONFIG.get("vlm_max_tokens"),
251
+ "vlm_seconds_per_frame": CONFIG.get("vlm_seconds_per_frame"),
252
+ "vlm_max_frames": CONFIG.get("vlm_max_frames"),
253
+ "fps": round(float(fps), 3),
254
+ "frame_time_convention": FRAME_TIME_CONVENTION_VERSION,
255
+ "api_url": CONFIG.get("api_url"),
256
+ "mimo_disable_thinking": CONFIG.get("mimo_disable_thinking", True),
257
+ "mimo_media_resolution": CONFIG.get("mimo_media_resolution"),
258
+ }
259
+ cache = _load_vlm_scene_cache(work_dir, cache_settings) if resume else {}
235
260
 
236
261
  def _scene_cache_key(i, scene):
237
- # Must invalidate on the SAME output-affecting settings the outer stage gate tracks
238
- # (understand.py:_vlm_cache_payload). prompt_fp already covers vlm_prompt + context_info
239
- # (which folds in background_research). The three below change the request/endpoint but
240
- # are NOT in vlm_prompt, so they must be keyed explicitly — otherwise a partial-failure
241
- # cache reused after flipping e.g. mimo_disable_thinking yields a stale/mixed analysis.
242
262
  return "|".join(str(x) for x in (
243
263
  i, round(float(scene["start"]), 3), round(float(scene["end"]), 3),
244
- CONFIG.get("vlm_model"), prompt_fp, CONFIG.get("vlm_max_tokens"),
245
- CONFIG.get("vlm_seconds_per_frame"), CONFIG.get("vlm_max_frames"),
246
- round(float(fps), 3), FRAME_TIME_CONVENTION_VERSION,
247
- CONFIG.get("api_url"), CONFIG.get("mimo_disable_thinking", True),
248
- CONFIG.get("mimo_media_resolution"),
249
264
  ))
250
265
 
251
266
  analyses = [None] * len(scenes)
252
267
  todo = []
253
268
  for i, s in enumerate(scenes):
254
- cached = cache.get(_scene_cache_key(i, s))
255
- analyses[i] = cached if isinstance(cached, dict) else None
269
+ analyses[i] = cache.get(_scene_cache_key(i, s))
256
270
  if analyses[i] is None:
257
271
  todo.append(i)
258
272
  if todo and len(todo) < len(scenes):
@@ -282,7 +296,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
282
296
  cache[_scene_cache_key(idx, scenes[idx])] = result
283
297
  pending += 1
284
298
  if pending >= flush_every:
285
- _flush_vlm_scene_cache(work_dir, cache)
299
+ _flush_vlm_scene_cache(work_dir, cache_settings, cache)
286
300
  pending = 0
287
301
  except Exception as e: # noqa: BLE001 - 单个场景失败不能拖垮其余已完成的
288
302
  log(f"VLM 场景 {i+1} 分析失败: {e}")
@@ -290,10 +304,10 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
290
304
  finally:
291
305
  # 无论正常结束、失败还是中断,都把这一轮已完成的场景落盘。
292
306
  if pending:
293
- _flush_vlm_scene_cache(work_dir, cache)
307
+ _flush_vlm_scene_cache(work_dir, cache_settings, cache)
294
308
  return failures
295
309
 
296
- base_workers = min(len(todo) or 1, int(CONFIG.get("vlm_workers", 4) or 4))
310
+ base_workers = min(len(todo) or 1, int(CONFIG["vlm_workers"]))
297
311
  log(f"VLM 并行分析 {len(todo)} 个场景 (workers={base_workers})...")
298
312
  failures = _run_pass(todo, base_workers)
299
313
  if failures:
@@ -323,7 +337,7 @@ def analyze_scenes(scenes, frames, work_dir, *, resume=True):
323
337
 
324
338
  def _video_data_url(video_path):
325
339
  """Return a MiMo-compatible data URL for a local video chunk, or None when too large."""
326
- max_bytes = int(float(CONFIG.get("mimo_video_base64_max_mb", 45.0)) * 1024 * 1024)
340
+ max_bytes = int(float(CONFIG["mimo_video_base64_max_mb"]) * 1024 * 1024)
327
341
  encoded_size = int(video_path.stat().st_size * 4 / 3) + 128
328
342
  if encoded_size > max_bytes:
329
343
  log(
@@ -342,18 +356,15 @@ def _mimo_video_chunks(scenes):
342
356
  if not scenes:
343
357
  raise RuntimeError("MiMo 视频分片理解需要 scenes;请先运行 ffmpeg scene/scdet 场景检测")
344
358
 
345
- max_seconds = float(CONFIG.get("mimo_video_chunk_max_seconds", 20.0) or 20.0)
346
- min_seconds = float(CONFIG.get("mimo_video_chunk_min_seconds", 1.0) or 1.0)
359
+ max_seconds = float(CONFIG["mimo_video_chunk_max_seconds"])
360
+ min_seconds = float(CONFIG["mimo_video_chunk_min_seconds"])
347
361
  chunks = []
348
362
  for scene_index, scene in enumerate(scenes):
349
- try:
350
- start = float(scene.get("start", 0.0))
351
- end = float(scene.get("end", start))
352
- except (TypeError, ValueError, AttributeError):
353
- continue
363
+ start = float(scene["start"])
364
+ end = float(scene["end"])
354
365
  if end <= start:
355
366
  continue
356
- scene_id = scene.get("scene_id", scene_index) if isinstance(scene, dict) else scene_index
367
+ scene_id = scene.get("scene_id", scene_index)
357
368
  cursor = start
358
369
  while cursor < end:
359
370
  chunk_end = min(end, cursor + max_seconds)
@@ -376,7 +387,7 @@ def _extract_video_chunk(video_path, chunk, output_path):
376
387
  """Cut one scene-based chunk into a compact local MP4 for MiMo video_url data URL."""
377
388
  start = float(chunk["start"])
378
389
  duration = max(0.1, float(chunk["end"]) - start)
379
- fps = float(CONFIG.get("mimo_video_fps", 2.0) or 2.0)
390
+ fps = float(CONFIG["mimo_video_fps"])
380
391
  cmd = [
381
392
  "ffmpeg", "-y",
382
393
  "-ss", f"{start:.3f}",
@@ -392,7 +403,7 @@ def _extract_video_chunk(video_path, chunk, output_path):
392
403
  "-movflags", "+faststart",
393
404
  str(output_path),
394
405
  ]
395
- result = run_cmd(cmd, timeout=CONFIG.get("mimo_video_chunk_timeout", 180))
406
+ result = run_cmd(cmd, timeout=CONFIG["mimo_video_chunk_timeout"])
396
407
  if result.returncode != 0:
397
408
  raise RuntimeError(f"MiMo 视频分片裁剪失败: {result.stderr[-500:]}")
398
409
  return output_path
@@ -402,7 +413,7 @@ def _mimo_chunk_prompt(chunk):
402
413
  return (
403
414
  f"这是原视频 {chunk['start']:.1f}s-{chunk['end']:.1f}s 的场景分片,"
404
415
  f"scene_id={chunk['scene_id']}。"
405
- f"{CONFIG.get('mimo_video_prompt', '请用中文概括这个视频分片。')}"
416
+ f"{CONFIG['mimo_video_prompt']}"
406
417
  )
407
418
 
408
419
 
@@ -411,7 +422,7 @@ def _mimo_video_model():
411
422
  return CONFIG.get("mimo_video_model") or CONFIG.get("mimo_model") or CONFIG["vlm_model"]
412
423
 
413
424
 
414
- def mimo_video_settings_fingerprint():
425
+ def mimo_video_settings():
415
426
  """Return non-secret MiMo video-overview settings that affect generated content."""
416
427
  return {
417
428
  "model": _mimo_video_model(),
@@ -434,19 +445,9 @@ def _mimo_chunk_cache_key(chunk):
434
445
  )
435
446
 
436
447
 
437
- def _mimo_cached_chunks_fingerprint(done):
438
- return stable_hash(done)
439
-
440
-
441
- def _mimo_overview_payload_fingerprint(overview):
442
- payload = dict(overview)
443
- payload.pop("overview_fingerprint", None)
444
- return stable_hash(payload)
445
-
446
-
447
448
  def _mimo_partial_provenance(video_path, scenes):
448
449
  return {
449
- "source_video_fingerprint": file_fingerprint(video_path),
450
+ "source_video_identity": file_identity(video_path),
450
451
  "chunks": [_mimo_chunk_cache_key(chunk) for chunk in _mimo_video_chunks(scenes)],
451
452
  }
452
453
 
@@ -465,7 +466,7 @@ def _load_mimo_partial(partial_path, video_path=None, scenes=None):
465
466
  return {}
466
467
  if not isinstance(partial, dict):
467
468
  return {}
468
- if partial.get("settings") != mimo_video_settings_fingerprint():
469
+ if partial.get("settings") != mimo_video_settings():
469
470
  return {}
470
471
  if video_path is not None or scenes is not None:
471
472
  try:
@@ -474,20 +475,14 @@ def _load_mimo_partial(partial_path, video_path=None, scenes=None):
474
475
  except (OSError, RuntimeError, TypeError, ValueError):
475
476
  return {}
476
477
  done = partial.get("chunks")
477
- if not isinstance(done, dict):
478
- return {}
479
- recorded = partial.get("chunks_fingerprint")
480
- if not recorded or recorded != _mimo_cached_chunks_fingerprint(done):
481
- return {}
482
- return done
478
+ return done if isinstance(done, dict) else {}
483
479
 
484
480
 
485
481
  def _save_mimo_partial(partial_path, done, video_path=None, scenes=None):
486
482
  """Persist completed chunk results incrementally so paid chunks survive a mid-loop failure."""
487
483
  payload = {
488
- "settings": mimo_video_settings_fingerprint(),
484
+ "settings": mimo_video_settings(),
489
485
  "chunks": done,
490
- "chunks_fingerprint": _mimo_cached_chunks_fingerprint(done),
491
486
  }
492
487
  if video_path is not None or scenes is not None:
493
488
  payload["provenance"] = _mimo_partial_provenance(video_path, scenes)
@@ -507,7 +502,7 @@ def _mimo_chunks_match(cached_chunks, expected_chunks):
507
502
 
508
503
  def mimo_video_overview_cache_fresh(overview_path, video_path, scenes):
509
504
  """Return True only when the final MiMo overview matches current inputs/settings."""
510
- overview_path = Path(overview_path) if not hasattr(overview_path, "read_text") else overview_path
505
+ overview_path = Path(overview_path)
511
506
  if not overview_path.exists():
512
507
  return False
513
508
  try:
@@ -516,24 +511,16 @@ def mimo_video_overview_cache_fresh(overview_path, video_path, scenes):
516
511
  return False
517
512
  if not isinstance(overview, dict) or overview.get("input") != "scene_chunks":
518
513
  return False
519
- if overview.get("settings") != mimo_video_settings_fingerprint():
520
- return False
521
- overview_fingerprint = overview.get("overview_fingerprint")
522
- if not overview_fingerprint or overview_fingerprint != _mimo_overview_payload_fingerprint(overview):
523
- return False
524
- recorded = overview.get("chunks_fingerprint")
525
- if not recorded:
514
+ if overview.get("settings") != mimo_video_settings():
526
515
  return False
527
516
  chunks = overview.get("chunks")
528
517
  if not isinstance(chunks, list) or not all(
529
518
  isinstance(chunk, dict) and _is_mimo_chunk_usable(chunk.get("content"))
530
519
  for chunk in chunks
531
520
  ):
532
- return False
533
- if recorded != _mimo_cached_chunks_fingerprint(chunks):
534
- return False
521
+ return False # a moderation-rejected chunk is retried, never served from cache
535
522
  try:
536
- if overview.get("source_video_fingerprint") != file_fingerprint(video_path):
523
+ if overview.get("source_video_identity") != file_identity(video_path):
537
524
  return False
538
525
  expected_chunks = _mimo_video_chunks(scenes)
539
526
  except (OSError, RuntimeError, TypeError, ValueError):
@@ -550,8 +537,8 @@ def _analyze_mimo_video_chunk(chunk_path, chunk):
550
537
  {
551
538
  "type": "video_url",
552
539
  "video_url": {"url": video_url},
553
- "fps": CONFIG.get("mimo_video_fps", 2.0),
554
- "media_resolution": CONFIG.get("mimo_media_resolution", "default"),
540
+ "fps": CONFIG["mimo_video_fps"],
541
+ "media_resolution": CONFIG["mimo_media_resolution"],
555
542
  },
556
543
  {"type": "text", "text": _mimo_chunk_prompt(chunk)},
557
544
  ]
@@ -596,9 +583,9 @@ def _is_mimo_chunk_usable(content):
596
583
 
597
584
  def analyze_video_overview(video_path, work_dir, scenes=None):
598
585
  """Use MiMo video understanding over local ffmpeg scene chunks."""
599
- if not CONFIG.get("mimo_video_overview", False):
586
+ if not CONFIG["mimo_video_overview"]:
600
587
  return None
601
- if not CONFIG.get("mimo_video_api_key"):
588
+ if not CONFIG["mimo_video_api_key"]:
602
589
  log("MiMo 视频概览已启用,但未设置 MIMO_VIDEO_API_KEY/MIMO_API_KEY,跳过")
603
590
  return None
604
591
 
@@ -613,20 +600,16 @@ def analyze_video_overview(video_path, work_dir, scenes=None):
613
600
  log(f"MiMo 视频理解:按 ffmpeg scene 分片分析 {len(chunks)} 段...")
614
601
  chunk_results = []
615
602
  unusable_chunks = []
616
- removed_stale_cache = False
617
603
  for chunk in chunks:
618
604
  cache_key = _mimo_chunk_cache_key(chunk)
619
605
  cached = done.get(cache_key)
620
- if cached is not None:
621
- if _is_mimo_chunk_usable(cached.get("content")):
622
- log(
623
- f" MiMo 分片 {chunk['chunk_id'] + 1}/{len(chunks)}: "
624
- f"{chunk['start']:.1f}-{chunk['end']:.1f}s(命中增量缓存,跳过)"
625
- )
626
- chunk_results.append(cached)
627
- continue
628
- done.pop(cache_key, None)
629
- removed_stale_cache = True
606
+ if cached is not None: # the partial only ever stores usable chunks
607
+ log(
608
+ f" MiMo 分片 {chunk['chunk_id'] + 1}/{len(chunks)}: "
609
+ f"{chunk['start']:.1f}-{chunk['end']:.1f}s(命中增量缓存,跳过)"
610
+ )
611
+ chunk_results.append(cached)
612
+ continue
630
613
  chunk_path = chunks_dir / (
631
614
  f"chunk_{chunk['chunk_id']:03d}_scene_{chunk['scene_id']}_"
632
615
  f"{chunk['start']:.2f}-{chunk['end']:.2f}.mp4"
@@ -637,7 +620,7 @@ def analyze_video_overview(video_path, work_dir, scenes=None):
637
620
  f"{chunk['start']:.1f}-{chunk['end']:.1f}s"
638
621
  )
639
622
  chunk_result = _analyze_mimo_video_chunk(chunk_path, chunk)
640
- if _is_mimo_chunk_usable(chunk_result.get("content")):
623
+ if _is_mimo_chunk_usable(chunk_result["content"]):
641
624
  chunk_results.append(chunk_result)
642
625
  done[cache_key] = chunk_result
643
626
  _save_mimo_partial(partial_path, done, video_path, scenes)
@@ -645,15 +628,9 @@ def analyze_video_overview(video_path, work_dir, scenes=None):
645
628
  unusable_chunks.append(chunk)
646
629
  log(f" MiMo 分片 {chunk['chunk_id'] + 1}: 未返回有效内容,保留为待重试")
647
630
 
648
- if removed_stale_cache:
649
- _save_mimo_partial(partial_path, done, video_path, scenes)
650
-
651
631
  if not chunk_results:
652
632
  log(f"MiMo 视频概览:{len(chunks)} 段均无有效内容(疑似被内容审核拦截),跳过概览")
653
- try:
654
- partial_path.unlink()
655
- except OSError:
656
- pass
633
+ partial_path.unlink(missing_ok=True)
657
634
  return None
658
635
 
659
636
  if unusable_chunks:
@@ -680,23 +657,18 @@ def analyze_video_overview(video_path, work_dir, scenes=None):
680
657
  "content": content,
681
658
  "chunks": chunk_results,
682
659
  "chunk_count": len(chunk_results),
683
- "fps": CONFIG.get("mimo_video_fps", 2.0),
684
- "media_resolution": CONFIG.get("mimo_media_resolution", "default"),
660
+ "fps": CONFIG["mimo_video_fps"],
661
+ "media_resolution": CONFIG["mimo_media_resolution"],
685
662
  "input": "scene_chunks",
686
663
  "partial": bool(unusable_chunks),
687
664
  "unusable_chunk_count": len(unusable_chunks),
688
- "source_video_fingerprint": file_fingerprint(video_path),
689
- "chunk_max_seconds": CONFIG.get("mimo_video_chunk_max_seconds", 20.0),
690
- "settings": mimo_video_settings_fingerprint(),
691
- "chunks_fingerprint": _mimo_cached_chunks_fingerprint(chunk_results),
665
+ "source_video_identity": file_identity(video_path),
666
+ "chunk_max_seconds": CONFIG["mimo_video_chunk_max_seconds"],
667
+ "settings": mimo_video_settings(),
692
668
  }
693
- overview["overview_fingerprint"] = _mimo_overview_payload_fingerprint(overview)
694
669
  overview_path = work_dir / "mimo_video_overview.json"
695
670
  overview_path.write_text(json.dumps(overview, ensure_ascii=False, indent=2), encoding="utf-8")
696
671
  # 所有分片完成后清理增量缓存,保持 work_dir 仅有规范产物
697
- try:
698
- partial_path.unlink()
699
- except OSError:
700
- pass
672
+ partial_path.unlink(missing_ok=True)
701
673
  log(f"MiMo 分片视频概览完成: {overview_path}")
702
674
  return overview
@@ -3,9 +3,9 @@ name: video-voiceover
3
3
  user-invocable: false
4
4
  description: >
5
5
  把带时间戳的 narration.json 合成为中文解说音频。使用 MiMo TTS(mimo-v2.5-tts)或
6
- Fish Audio(s2.1-pro-free)逐段生成语音,
6
+ Fish Audio(s2.1-pro-free)或显式配置的通用 IndexTTS HTTP 服务逐段生成语音,
7
7
  按时间窗动态适配语速并处理响度;输入输出时间线上的旁白,产出 tts_segments 与 tts_meta.json。
8
- 旧版直接剪辑路径也可显式传入 narration_mapped.json。触发词:配音、语音合成、TTS、解说配音、
8
+ 触发词:配音、语音合成、TTS、解说配音、
9
9
  voiceover、text to speech、旁白配音。
10
10
  ---
11
11
 
@@ -23,51 +23,61 @@ export MIMO_API_KEY=*** # 也可使用仅供 TTS 的 MIMO_TTS_API_KEY
23
23
  export TTS_PROVIDER=fish-audio
24
24
  export FISH_API_KEY=***
25
25
  export FISH_TTS_REFERENCE_ID=<voice-model-id> # 可选;覆盖内置“娱乐扒妹”音色
26
+
27
+ # 或显式选择自托管 index-tts 端点,配置见 references/index-tts.md
28
+ export TTS_PROVIDER=index-tts
26
29
  ```
27
30
 
28
31
  下面的 `scripts/...` 均相对于本技能目录。若执行器从仓库根目录启动,请给脚本路径加上本技能的绝对目录。
29
- 脚本不从其他技能目录读取文件;外部输入仅限命令显式传入的稿件、音频、参数与 `work_dir` 产物。
30
32
 
31
33
  ## 3. 输入契约
32
34
 
33
35
  默认输入为 `work_dir/narration.json`。每段必须包含 `start`、`end` 与 `narration`,可选字段包括
34
36
  `pause_after_ms` 和 `overlaps_speech`。时间统一表示音频最终放置的**输出时间线秒数**。
35
37
 
36
- 编排式 cut 流程直接使用输出时间的 `narration.json`。只有旧版直接剪辑路径需要显式传入
37
- `narration_mapped.json`。
38
+ cut 流程先剪后配:`narration.json` 本身就是按剪后成片的输出时间写的,不存在另一份映射稿。
38
39
 
39
40
  ## 4. 运行命令
40
41
 
41
42
  ```bash
42
43
  python3 scripts/voiceover.py --work-dir <work_dir> --narration <narration.json> \
43
- [--tts-provider auto|mimo-tts|fish-audio] \
44
- [--mimo-voice 冰糖 | --voice-ref <reference-audio>]
44
+ [--tts-provider auto|mimo-tts|fish-audio|index-tts] \
45
+ [--mimo-voice 冰糖 | --voice-ref <reference-audio>] \
46
+ [--preserve-approved-text]
45
47
  ```
46
48
 
47
- 单独运行且省略 `--narration` 时,默认读取 `work_dir/narration.json`。旧版路径如需映射后的稿件,必须显式传入:
48
-
49
- ```bash
50
- python3 scripts/voiceover.py --work-dir <work_dir> \
51
- --narration <work_dir/narration_mapped.json>
52
- ```
49
+ 单独运行且省略 `--narration` 时,默认读取 `work_dir/narration.json`;`--narration` 只用于指定其他路径的同格式稿件。
53
50
 
54
51
  ## 5. 输出契约
55
52
 
56
53
  - `tts_segments/*.wav`:每段旁白对应一个音频文件。
57
- - `tts_meta.json`:包含 `segments`、`engine` 与 `narration`。每段记录 `audio_path`、时间、
54
+ - `tts_meta.json`:包含 `segments`、`engine`、`voice`(实际使用的 provider、模型、音色或参考音频)与 `narration`。每段记录 `audio_path`、时间、
58
55
  `pause_after_ms` 和放置字段。
59
56
  - 干净运行写入 `partial: false` 与 `failures: []`。
60
57
  - 使用 `--allow-partial-tts` 跳过失败段时,写入 `partial: true` 和
61
58
  `failures: [{index,start,end,text,error}]`,让缺失语音保持可见。
59
+ - `--preserve-approved-text` 是显式的批准稿保护策略。每段保留原始 `authored_text`
60
+ 证据;TTS 实际读取的 `spoken_text` 只经过既有的格式/舞台提示清理。若完整语音超过时间窗及
61
+ 累计语速预算,命令失败并报告段序号、原稿、实读文本、语音时长和窗口证据,不写成功的
62
+ `tts_meta.json`。严格模式下任何必需段失败(包括供应商失败)都不能被
63
+ `--allow-partial-tts` 降级为可交付的部分成功;异常记录标为 `required: true` 并带策略 ID。
64
+ 仅含 `[停顿]` 等清理标记、清理后无实读文本的作者段也属于必需段错误。
62
65
 
63
66
  ## 6. 运行规则
64
67
 
65
- - 重跑只复用内容与 TTS 设置均匹配的分段音频;修改旁白或合成参数后,只重生成受影响的 WAV。
68
+ - 重跑只复用分段 sidecar 中记录的文本、TTS 设置与该 WAV 的 `size`/`mtime_ns` 均与当前相等的分段音频;
69
+ 修改旁白或合成参数后,只重生成受影响的 WAV。
70
+ - 批准稿保护策略属于缓存设置:严格模式不会命中旧的自动缩稿缓存;只有同一严格策略下、
71
+ `spoken_text` 完整匹配且 WAV 存在非空的缓存才可离线复用。
72
+ - 严格 CLI 在本轮合成前把旧 `tts_meta.json` 按时间戳归档至 `tts_meta.history/`,因此失败时
73
+ 当前路径不会继续冒充本轮成功;成功元数据通过同目录临时文件原子替换。
66
74
  - `auto` 优先使用已配置的 MiMo,MiMo key 缺失且设置了 `FISH_API_KEY` 时使用 Fish Audio;需要可复现的 provider 选择时显式传 `--tts-provider`。
75
+ - 自托管 index-tts 端点只能由 `--tts-provider index-tts` 或 `TTS_PROVIDER=index-tts` 显式选择,`auto`
76
+ 永不兜底选择它。协议、请求体、receipt 语义与缓存失效规则见 `references/index-tts.md`。
67
77
  - Fish Audio 直接请求 WAV;默认使用“娱乐扒妹”音色(`5653cea4ac83480aaf2bf45406556185`),`FISH_TTS_REFERENCE_ID` 可覆盖。模型、音色 ID、API URL、动态语速或归一化设置变化时会重新生成缓存。当前免费模型无 SLA,受 Fair Use 和官方免费期限约束。
68
78
  - `--voice-ref` 仅用于 full/cut 解说克隆,切换到 `mimo-v2.5-tts-voiceclone`。仅在确需新合成时惰性规范化一次;
69
- - dub voiceclone 原始 WAV 也会用模型、提示、台词和参考音频指纹缓存;匹配重跑不再重复请求或计费,`dub_manifest.json` 逐行记录 `tts_cache=hit|miss`;
70
- 参考音频内容或预处理指纹变化会使旧缓存失效。仅在获得授权后使用,参考音频会发送到 MiMo。
79
+ - dub voiceclone 原始 WAV 也会按模型、提示、台词和参考音频的 `size`/`mtime_ns` 缓存;匹配重跑不再重复请求或计费,`dub_manifest.json` 逐行记录 `tts_cache=hit|miss`;
80
+ 参考音频文件变化会使旧缓存失效。仅在获得授权后使用,参考音频会发送到 MiMo。
71
81
  - `TTS_WORKERS`、`TTS_TIMEOUT`、`TTS_RETRIES`、`ALLOW_PARTIAL_TTS` 用于调整并发、超时、重试与部分成功策略。
72
82
  - dub 模式有独立的确定性门禁:`dub_lint.json` 会在语音克隆前阻止空行、重叠或越界译文;
73
83
  `dub_review.json` 用于记录忠实度、语气、时长和平台适配复核。可通过
@@ -75,7 +85,7 @@ python3 scripts/voiceover.py --work-dir <work_dir> \
75
85
 
76
86
  ## 7. 能力边界
77
87
 
78
- - 不撰写或修改旁白文本。
88
+ - 超窗时默认在句界自动缩稿并在 `spoken_text/truncated` 留痕;批准稿加 `--preserve-approved-text`。
79
89
  - 不混流、不压低原声、不渲染字幕。
80
90
  - 不分析视频,也不选择时间点;只为输入稿件中的既定分段配音。
81
- - Fish Audio 路径不接受本地 `--voice-ref`;使用已创建的 `FISH_TTS_REFERENCE_ID` 选择音色。
91
+ - Fish Audio 与 IndexTTS 路径都不接受本地 `--voice-ref`;前者用已创建的 `FISH_TTS_REFERENCE_ID` 选择音色。
@@ -0,0 +1,31 @@
1
+ # 自托管 IndexTTS 端点
2
+
3
+ `--tts-provider index-tts` 或 `TTS_PROVIDER=index-tts` 显式选择自托管的 index-tts 协议服务(JSON→WAV)。
4
+ `auto` 永不兜底选择它。
5
+
6
+ ## 配置
7
+
8
+ ```bash
9
+ export TTS_PROVIDER=index-tts
10
+ export INDEX_TTS_ENDPOINT=http://127.0.0.1:<port>/tts
11
+ export INDEX_TTS_VOICE=<authorized-voice-name>
12
+ ```
13
+
14
+ - 缺 `INDEX_TTS_ENDPOINT` 或 `INDEX_TTS_VOICE` 时在缓存/请求前失败。
15
+ - endpoint 只接受无 userinfo/query/fragment 的 HTTP(S) URL,且请求禁止重定向,避免把批准文本转发至其他主机。
16
+ - 不接受 `--voice-ref` / `--mimo-voice`。
17
+
18
+ ## 请求契约
19
+
20
+ 请求体固定为 `{"voice": voice, "text": spoken_text}`。当前段 schema 的可选 `emotion`
21
+ 以及额外 style、非默认 rate/pitch 控制会显式失败,不会静默忽略。原始合成使用 provider 默认速度,
22
+ 不按标点、长度或位置自动计算 rate;这不等于下游放置阶段的端到端 tempo 锁。
23
+
24
+ ## Receipt 与缓存
25
+
26
+ 每段的 `provider_receipt` 只记录 `provider` 与“请求的 voice”(`requested_voice`)。它说明请求参数,
27
+ 不是该声线的声学验证,也不代表已做人耳听审或音色身份验证。endpoint 不写入任何文件。
28
+
29
+ 缓存复用条件:sidecar 中的文本与设置(含 `index_tts_voice`)与当前相等、WAV 的 `size`/`mtime_ns` 未变,
30
+ 且 sidecar 的 receipt 对应当前 voice;缓存命中复用 sidecar 中的 receipt,不能现场补造。
31
+ 服务端模型或部署变化不会被自动察觉;更换声线实现后请删除 `tts_segments/*.cache.json` 强制重合成。