@oh-story/dsh 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +209 -73
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +248 -168
  5. package/lib/drama/skills/short-drama/SKILL.md +16 -4
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  10. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  11. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  12. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  13. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  14. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  15. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  16. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  17. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  18. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  19. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  20. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  21. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  22. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  23. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  25. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  26. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  27. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  28. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  29. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  30. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  31. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  32. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  33. package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  46. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  47. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  48. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  49. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  50. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
  51. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
  52. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
  53. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
  54. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
  55. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
  56. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
  57. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
  58. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  59. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  60. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  61. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  62. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  63. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  64. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  65. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  66. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  67. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  68. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  69. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  70. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  71. package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
  72. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  73. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  74. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  75. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  76. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
  77. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  78. package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
  79. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  80. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  81. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  82. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
  83. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
  84. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
  85. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  87. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  88. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
  89. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
  90. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
  91. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
  92. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  93. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  94. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  95. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
  96. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  97. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  98. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  99. package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
  100. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  101. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  102. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  103. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  104. package/lib/index.js +346 -98
  105. package/lib/index.js.map +4 -4
  106. package/lib/novel-to-game/README.md +299 -59
  107. package/lib/novel-to-game/README_ZH.md +299 -56
  108. package/lib/novel-to-game/VERSION +1 -1
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  118. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  119. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  120. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  121. package/lib/novel-to-game/manifest.json +77 -87
  122. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  123. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  124. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  125. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  126. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  127. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  128. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  129. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  130. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  131. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  132. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  133. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  134. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  135. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  136. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  137. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  138. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  139. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  140. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  141. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  142. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  143. package/lib/oh-story/manifest.json +607 -327
  144. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  145. package/lib/oh-story/roles/character-designer.md +1 -2
  146. package/lib/oh-story/roles/consistency-checker.md +15 -13
  147. package/lib/oh-story/roles/narrative-writer.md +36 -38
  148. package/lib/oh-story/roles/story-architect.md +9 -61
  149. package/lib/oh-story/roles/story-explorer.md +20 -125
  150. package/lib/oh-story/roles/story-researcher.md +0 -1
  151. package/lib/oh-story/skills/story/SKILL.md +15 -9
  152. package/lib/oh-story/skills/story/VERSION +1 -1
  153. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  154. package/lib/oh-story/skills/story/references/author-memory.md +46 -94
  155. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  157. package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
  158. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  159. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
  160. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  161. package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
  162. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  163. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
  164. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  165. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  166. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  167. package/lib/oh-story/skills/story-import/SKILL.md +64 -584
  168. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  169. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  170. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  171. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  172. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  173. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  174. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  175. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  176. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  177. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  180. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  181. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
  182. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  183. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
  184. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
  185. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
  186. package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
  187. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
  188. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
  189. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  190. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  191. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  192. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
  193. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  194. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  195. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  196. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  197. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  198. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  199. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
  200. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
  201. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  202. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  203. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  204. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  205. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  206. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  207. package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
  208. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  209. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  210. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  211. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  212. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  213. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  214. package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
  215. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  216. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
  217. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
  218. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  219. package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
  220. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  221. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
  222. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  223. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  224. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  225. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  226. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  227. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  228. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  229. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  230. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
  231. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  232. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  234. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  235. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  236. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  237. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  238. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  239. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  240. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  241. package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
  242. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
  243. package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
  244. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  245. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  246. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  247. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  248. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  249. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
  250. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
  251. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
  252. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  253. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
  254. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
  255. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  256. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
  257. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
  258. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  259. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  260. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
  261. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  262. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
  263. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  264. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
  265. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
  266. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
  267. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
  268. package/lib/oh-story/skills/story-review/SKILL.md +31 -302
  269. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  270. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
  271. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  272. package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
  273. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  274. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  275. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  276. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  277. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
  278. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  279. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  280. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  281. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  282. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
  283. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  284. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  285. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
  286. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
  287. package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
  288. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  289. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
  290. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  291. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  292. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  293. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
  294. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  295. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  296. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  297. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  298. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  299. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  300. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  301. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  302. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  303. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  304. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  305. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  306. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  307. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  308. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  309. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  310. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  311. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  312. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  313. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
  314. package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
  315. package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
  316. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  317. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  318. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  319. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  320. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  321. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  322. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  323. package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  324. package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
  325. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  326. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  327. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  328. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  329. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
  330. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  331. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  332. package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
  333. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  335. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  336. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  337. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  338. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  339. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
  340. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  341. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  342. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  343. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  344. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  345. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  346. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  347. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  348. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
  349. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
  350. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
  351. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  352. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  353. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  354. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  355. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  356. package/lib/types/client/file-activity.d.ts +19 -1
  357. package/lib/types/client/file-activity.d.ts.map +1 -1
  358. package/lib/types/client/index.d.ts.map +1 -1
  359. package/lib/types/client/production-prompts.d.ts +7 -0
  360. package/lib/types/client/production-prompts.d.ts.map +1 -1
  361. package/lib/types/client/production-runtime.d.ts +16 -0
  362. package/lib/types/client/production-runtime.d.ts.map +1 -1
  363. package/lib/types/client/video-studio.d.ts.map +1 -1
  364. package/lib/types/drama-adapters.d.ts +5 -3
  365. package/lib/types/drama-adapters.d.ts.map +1 -1
  366. package/lib/types/native-hooks.d.ts +53 -1
  367. package/lib/types/native-hooks.d.ts.map +1 -1
  368. package/lib/types/role-identity.d.ts +17 -0
  369. package/lib/types/role-identity.d.ts.map +1 -0
  370. package/lib/types/role-tool.d.ts.map +1 -1
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts.map +1 -1
  373. package/lib/types/workspace-route.d.ts.map +1 -1
  374. package/lib/video-recap/CHANGELOG.md +60 -1
  375. package/lib/video-recap/README.en.md +348 -137
  376. package/lib/video-recap/README.md +340 -136
  377. package/lib/video-recap/manifest.json +406 -251
  378. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  379. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  380. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  381. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  382. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  383. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  384. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  385. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  386. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  387. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  388. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  389. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  390. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  391. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  392. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  393. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  394. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  395. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  396. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  397. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  398. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  399. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  401. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  403. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  404. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  407. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  408. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  409. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  410. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  411. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  412. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  415. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  417. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  419. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  420. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  423. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  424. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  425. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  426. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  428. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  429. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  430. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  431. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  432. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  433. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  434. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  435. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  436. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  437. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  438. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  439. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  440. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  441. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  442. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  443. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  444. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  445. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  447. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  448. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  449. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  450. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  451. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  452. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  453. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  454. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  455. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  456. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  457. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  458. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  459. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  460. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  461. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  462. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  463. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  464. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  465. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  466. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  467. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  468. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  471. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  472. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  473. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  474. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  475. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  476. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  477. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  478. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  479. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  480. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  481. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  482. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  483. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  484. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  485. package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
  486. package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
  487. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  488. package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
  489. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  490. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  491. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  492. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  493. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  494. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  495. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  496. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  497. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  498. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  499. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  500. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  501. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  502. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  503. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  504. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  505. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  506. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  508. package/package.json +45 -45
  509. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  510. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  511. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  512. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
  513. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
  514. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  515. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
  516. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
  517. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  518. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  519. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  520. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  521. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  522. package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
  523. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  524. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
  525. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  526. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
  527. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  528. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  529. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -4,9 +4,9 @@ import json
4
4
  import os
5
5
  from copy import deepcopy
6
6
 
7
- from jianying_schema import scrub_platform_identity, us, validate_material_category
8
- from jianying_templates import template
9
- from jianying_tracks import SEGMENT_RENDER_INDEX
7
+ from jianying.schema import scrub_platform_identity, us
8
+ from jianying.templates import template
9
+ from jianying.tracks import SEGMENT_RENDER_INDEX
10
10
 
11
11
 
12
12
  def timerange(start_us, dur_us):
@@ -83,9 +83,10 @@ def windowed_volume_keyframes(keyframes, seg_start_s, seg_end_s, default_gain, n
83
83
 
84
84
 
85
85
  def clip_from_segment(segment):
86
- scale = segment.get("scale") if isinstance(segment.get("scale"), dict) else {}
87
- position = segment.get("position") if isinstance(segment.get("position"), dict) else {}
88
- flip = segment.get("flip") if isinstance(segment.get("flip"), dict) else {}
86
+ """Map optional authoring transforms (validated as objects by the timeline contract)."""
87
+ scale = segment.get("scale", {})
88
+ position = segment.get("position", {})
89
+ flip = segment.get("flip", {})
89
90
  return {
90
91
  "alpha": round(float(segment.get("opacity", 1.0)), 4),
91
92
  "flip": {
@@ -220,13 +221,6 @@ def rich_text_content(text, base_style=None, words=None, style_presets=None):
220
221
  return content
221
222
 
222
223
 
223
- def unsupported_track_note(kind):
224
- info = validate_material_category(kind)
225
- if info["supported"]:
226
- return None
227
- return info.get("note")
228
-
229
-
230
224
  RESOURCE_TRACKS = {
231
225
  "sound": "audios",
232
226
  "sticker": "stickers",
@@ -237,17 +231,12 @@ RESOURCE_TRACKS = {
237
231
 
238
232
 
239
233
  def _resource_material(segment, kind, new_id):
240
- raw = segment.get("material")
234
+ """The contract guarantees exactly one of material / resource_config (resolved package)."""
241
235
  config = segment.get("resource_config")
242
- if raw is not None and config is not None:
243
- raise ValueError(f"{kind} segment must use either material or resource_config, not both")
244
- if config is not None:
245
- if not isinstance(config, dict):
246
- raise ValueError(f"{kind} resource_config must be an object")
247
- raw = config.get("main_config")
248
- if not isinstance(raw, dict):
249
- raise ValueError(f"{kind} resource_config.main_config must be an object")
250
- raw = deepcopy(raw)
236
+ if config is None:
237
+ raw = deepcopy(segment["material"])
238
+ else:
239
+ raw = deepcopy(config["main_config"])
251
240
  resource_id = config.get("resource_id")
252
241
  if resource_id is not None:
253
242
  raw.setdefault("resource_id", resource_id)
@@ -257,10 +246,6 @@ def _resource_material(segment, kind, new_id):
257
246
  if kind == "sticker" and cover_img:
258
247
  raw["icon_url"] = cover_img
259
248
  raw["preview_cover_url"] = cover_img
260
- elif not isinstance(raw, dict):
261
- raise ValueError(f"{kind} segment requires material or resource_config")
262
- else:
263
- raw = deepcopy(raw)
264
249
  raw["id"] = new_id()
265
250
  return raw
266
251
 
@@ -269,7 +254,7 @@ def build_resource_track(ctx, timeline_track):
269
254
  kind = timeline_track["kind"]
270
255
  materials_key = RESOURCE_TRACKS[kind]
271
256
  track_name = timeline_track.get("name", kind)
272
- for item in timeline_track.get("segments", []):
257
+ for item in timeline_track["segments"]:
273
258
  ts, te = float(item["timeline_start"]), float(item["timeline_end"])
274
259
  duration_us = us(te - ts)
275
260
  authored_item = item
@@ -295,8 +280,6 @@ def build_resource_track(ctx, timeline_track):
295
280
  ctx.materials["effects"].extend(subordinate_effects)
296
281
  seg = base_segment(material["id"], us(ts), duration_us, 1.0, [], ctx.new_id)
297
282
  speed_value = float(item.get("speed", 1.0))
298
- if speed_value <= 0:
299
- raise ValueError(f"{kind} segment speed must be greater than 0")
300
283
  seg["source_timerange"] = timerange(0, round(duration_us * speed_value))
301
284
  seg["track_render_index"] = 0
302
285
  seg["extra_material_refs"] = []
@@ -310,8 +293,6 @@ def build_resource_track(ctx, timeline_track):
310
293
 
311
294
 
312
295
  def _attachment_material(spec, kind, new_id):
313
- if not isinstance(spec, dict):
314
- raise ValueError(f"video {kind} must be an object")
315
296
  material = deepcopy(spec)
316
297
  config = material.pop("main_config", None)
317
298
  resources = material.pop("resources", None)
@@ -400,18 +381,14 @@ def _track_object(ctx, name, track_type, segments, flag):
400
381
 
401
382
  def build_compound_video(ctx, clip, material, foreground_segment, track_name, ts, te):
402
383
  """Build duo-video's nested green-screen/compound draft structure."""
403
- background_spec = clip.get("green_background")
404
- chroma_spec = _resolve_attachment_spec(ctx, clip.get("chroma"), "chroma")
405
- if not isinstance(background_spec, dict) or not isinstance(chroma_spec, dict):
406
- raise ValueError("compound green-screen clips require green_background and chroma objects")
384
+ background_spec = clip["green_background"]
385
+ chroma_spec = _resolve_attachment_spec(ctx, clip["chroma"], "chroma")
407
386
 
408
387
  duration_us = us(te - ts)
409
- full_material_duration_us = int(material.get("duration") or duration_us)
388
+ full_material_duration_us = material["duration"]
410
389
  outer_source_timerange = deepcopy(foreground_segment["source_timerange"])
411
390
  outer_target_timerange = deepcopy(foreground_segment["target_timerange"])
412
- background_path = background_spec.get("source_path")
413
- if not isinstance(background_path, str) or not background_path:
414
- raise ValueError("green_background.source_path must be a non-empty string")
391
+ background_path = background_spec["source_path"]
415
392
 
416
393
  background_id = ctx.new_id()
417
394
  background = template("video")
@@ -495,20 +472,21 @@ def build_compound_video(ctx, clip, material, foreground_segment, track_name, ts
495
472
 
496
473
  def build_video_track(ctx, timeline_track):
497
474
  track_name = timeline_track.get("name", "video")
498
- for clip in timeline_track.get("clips", []):
475
+ for clip in timeline_track["clips"]:
499
476
  ts, te = float(clip["timeline_start"]), float(clip["timeline_end"])
500
477
  ss, se = float(clip["source_start"]), float(clip["source_end"])
501
478
  path = clip["source_path"]
502
479
  speed_value = float(clip.get("speed", 1.0))
503
- if speed_value <= 0:
504
- raise ValueError("video clip speed must be greater than 0")
505
480
  reverse = bool(clip.get("reverse", False))
506
481
  if reverse:
507
- reverse_path = clip.get("reverse_path")
508
- if not isinstance(reverse_path, str) or not reverse_path:
482
+ # The contract allows omitting reverse_path so export_timeline_to_jianying
483
+ # can generate it; building directly from such a clip is an authoring error.
484
+ if "reverse_path" not in clip:
509
485
  raise ValueError("reverse video clips require a local reverse_path")
510
- path = reverse_path
511
- src_dur_us, width, height = ctx.media_duration(path, us(se))
486
+ path = clip["reverse_path"]
487
+ src_dur_us, width, height = ctx.probe(path)
488
+ if src_dur_us <= 0:
489
+ raise ValueError(f"JianYing video source has no probed duration: {path}")
512
490
  mat_id = ctx.new_id()
513
491
  material = template("video")
514
492
  material.update({
@@ -546,17 +524,17 @@ def build_video_track(ctx, timeline_track):
546
524
  def build_audio_track(ctx, timeline_track):
547
525
  role = timeline_track.get("role", timeline_track.get("name", "audio"))
548
526
  track_name = timeline_track.get("name", role)
549
- for segment in timeline_track.get("segments", []):
527
+ for segment in timeline_track["segments"]:
550
528
  ts, te = float(segment["timeline_start"]), float(segment["timeline_end"])
551
529
  path = segment["source_path"]
552
- mat_dur_us, _width, _height = ctx.media_duration(path, us(te - ts))
530
+ mat_dur_us, _width, _height = ctx.probe(path)
531
+ if mat_dur_us <= 0:
532
+ raise ValueError(f"JianYing audio source has no probed duration: {path}")
553
533
  want_us = us(te - ts)
554
534
  speed_value = float(segment.get("speed", 1.0))
555
- if speed_value <= 0:
556
- raise ValueError("audio segment speed must be greater than 0")
557
535
  place_us = want_us
558
536
  required_source_us = int(round(want_us * speed_value))
559
- if mat_dur_us and required_source_us > mat_dur_us:
537
+ if required_source_us > mat_dur_us:
560
538
  if role == "bgm" and timeline_track.get("loop"):
561
539
  place_us = want_us
562
540
  else:
@@ -569,14 +547,14 @@ def build_audio_track(ctx, timeline_track):
569
547
  mat_id = ctx.new_id()
570
548
  material = template("audio")
571
549
  material.update({
572
- "duration": int(mat_dur_us or want_us),
550
+ "duration": int(mat_dur_us),
573
551
  "id": mat_id,
574
552
  "path": path,
575
553
  })
576
554
  ctx.materials["audios"].append(material)
577
555
  keyframes = volume_keyframes(segment.get("volume_keyframes"), ts, ctx.new_id)
578
556
  volume = segment.get("gain", 1.0) if not keyframes else 1.0
579
- if role == "bgm" and timeline_track.get("loop") and mat_dur_us and required_source_us > mat_dur_us:
557
+ if role == "bgm" and timeline_track.get("loop") and required_source_us > mat_dur_us:
580
558
  cursor = 0
581
559
  while cursor < want_us:
582
560
  piece = min(int(mat_dur_us / speed_value), want_us - cursor)
@@ -613,9 +591,9 @@ def build_audio_track(ctx, timeline_track):
613
591
  def build_text_track(ctx, timeline_track):
614
592
  track_name = timeline_track.get("name", "text")
615
593
  track_kind = "subtitle" if track_name == "subtitle" else "text"
616
- for segment in timeline_track.get("segments", []):
594
+ for segment in timeline_track["segments"]:
617
595
  ts, te = float(segment["timeline_start"]), float(segment["timeline_end"])
618
- text = segment.get("text", "")
596
+ text = segment["text"]
619
597
  mat_id = ctx.new_id()
620
598
  authored_style = dict(ctx.style_presets.get(str(segment.get("style_id")), {}))
621
599
  authored_style.update(segment.get("style") or {})
@@ -691,11 +669,11 @@ def build_text_track(ctx, timeline_track):
691
669
  def build_image_track(ctx, timeline_track):
692
670
  """Build local image overlays as JianYing photo materials on video tracks."""
693
671
  track_name = timeline_track.get("name", "image")
694
- for segment in timeline_track.get("segments", []):
672
+ for segment in timeline_track["segments"]:
695
673
  ts, te = float(segment["timeline_start"]), float(segment["timeline_end"])
696
674
  duration_us = us(te - ts)
697
675
  path = segment["source_path"]
698
- _ignored_duration, width, height = ctx.media_duration(path, duration_us)
676
+ _still_image_duration, width, height = ctx.probe(path)
699
677
  mat_id = ctx.new_id()
700
678
  material = template("video")
701
679
  material.update({
@@ -710,8 +688,6 @@ def build_image_track(ctx, timeline_track):
710
688
  ctx.materials["videos"].append(material)
711
689
  seg = base_segment(mat_id, us(ts), duration_us, 1.0, [], ctx.new_id)
712
690
  speed_value = float(segment.get("speed", 1.0))
713
- if speed_value <= 0:
714
- raise ValueError("image segment speed must be greater than 0")
715
691
  seg["source_timerange"] = timerange(0, round(duration_us * speed_value))
716
692
  seg["clip"] = clip_from_segment(segment)
717
693
  seg["speed"] = speed_value
@@ -727,8 +703,8 @@ def build_image_track(ctx, timeline_track):
727
703
 
728
704
 
729
705
  def build_timeline_track(ctx, timeline_track):
730
- kind = timeline_track.get("kind")
731
- if kind in ("audio", "text") and not timeline_track.get("segments"):
706
+ kind = timeline_track["kind"]
707
+ if kind in ("audio", "text") and not timeline_track["segments"]:
732
708
  return
733
709
  if kind == "video":
734
710
  build_video_track(ctx, timeline_track)
@@ -738,9 +714,5 @@ def build_timeline_track(ctx, timeline_track):
738
714
  build_text_track(ctx, timeline_track)
739
715
  elif kind == "image":
740
716
  build_image_track(ctx, timeline_track)
741
- elif kind in RESOURCE_TRACKS:
717
+ else: # normalize_timeline admits only RESOURCE_TRACKS kinds past this point
742
718
  build_resource_track(ctx, timeline_track)
743
- else:
744
- note = unsupported_track_note(kind)
745
- if note:
746
- ctx.note(note)
@@ -1,11 +1,10 @@
1
1
  """Thin internal model used at the JianYing adapter boundary."""
2
2
 
3
- import os
4
3
  from dataclasses import dataclass, field
5
4
  from collections.abc import Callable
6
5
 
7
- from jianying_schema import us
8
- from jianying_tracks import TrackAllocator
6
+ from jianying.schema import us
7
+ from jianying.tracks import TrackAllocator
9
8
 
10
9
 
11
10
  ProbeFn = Callable[[str], tuple[int, int, int]]
@@ -50,7 +49,7 @@ class DraftBuildContext:
50
49
 
51
50
  @classmethod
52
51
  def from_timeline(cls, timeline, new_id, probe):
53
- """Build from a timeline already validated by jianying_timeline_contract."""
52
+ """Build from a timeline already validated by jianying.timeline_contract."""
54
53
  canvas = timeline["canvas"]
55
54
  return cls(
56
55
  width=canvas["width"],
@@ -63,13 +62,6 @@ class DraftBuildContext:
63
62
  style_presets=timeline.get("style_presets", {}),
64
63
  )
65
64
 
66
- def media_duration(self, path, fallback_us):
67
- """(duration_us, width, height) probed from the file; images and missing files fall back."""
68
- if os.path.exists(path):
69
- duration_us, width, height = self.probe(path)
70
- return duration_us or fallback_us, width, height
71
- return fallback_us, 0, 0
72
-
73
65
  def add_segment(self, kind, base_name, start_us, duration_us, segment):
74
66
  allocated = self.track_allocator.allocate(kind, base_name, start_us, duration_us)
75
67
  track = next(
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
 
5
5
  from lib import CONFIG, log
6
6
 
7
- def _maybe_export_jianying(work_dir, out_dir, stem):
7
+ def maybe_export_jianying(work_dir, out_dir, stem):
8
8
  """Lazy-import the optional 剪映 exporter and write a draft from timeline.json.
9
9
 
10
10
  The export is a documented fail-open sidecar: any failure is logged and never
@@ -0,0 +1,53 @@
1
+ """Schema constants and skeleton factories for JianYing export.
2
+
3
+ This module is intentionally data-oriented: it owns draft version metadata and
4
+ the full `materials` parallel-array shape.
5
+ """
6
+
7
+ from jianying.templates import template
8
+
9
+ # The full 剪映 materials object: ~45 parallel arrays. Only arrays backed by a
10
+ # production builder are populated; retaining the full shape preserves compatibility.
11
+ MATERIAL_KEYS = ["ai_translates", "audio_balances", "audio_effects", "audio_fades", "audio_track_indexes", "audios", "beats", "canvases", "chromas", "color_curves", "digital_humans", "drafts", "effects", "flowers", "green_screens", "handwrites", "hsl", "images", "log_color_wheels", "loudnesses", "manual_deformations", "masks", "common_mask", "material_animations", "material_colors", "multi_language_refs", "placeholders", "plugin_effects", "primary_color_wheels", "realtime_denoises", "shapes", "smart_crops", "smart_relights", "sound_channel_mappings", "speeds", "stickers", "tail_leaders", "text_templates", "texts", "time_marks", "transitions", "video_effects", "video_trackings", "videos", "vocal_beautifys", "vocal_separations"]
12
+
13
+
14
+ def us(seconds):
15
+ """Seconds (float) -> integer microseconds. The single seconds->µs boundary."""
16
+ return int(round(float(seconds) * 1_000_000))
17
+
18
+
19
+ def full_materials(filled):
20
+ """Return a complete JianYing `materials` object with all known arrays."""
21
+ out = {k: [] for k in MATERIAL_KEYS}
22
+ out.update(filled)
23
+ return out
24
+
25
+
26
+ def scrub_platform_identity(project):
27
+ """Remove hardware fingerprints carried by the pinned upstream templates."""
28
+ for platform_key in ("last_modified_platform", "platform"):
29
+ for identity_key in ("device_id", "hard_disk_id", "mac_address"):
30
+ project[platform_key][identity_key] = ""
31
+ return project
32
+
33
+
34
+ def draft_content_skeleton(draft_id, width, height, fps, total_us, materials, tracks):
35
+ """Build the root `draft_content.json` / `draft_info.json` skeleton."""
36
+ content = template("project")
37
+ scrub_platform_identity(content)
38
+ content["canvas_config"] = {"width": width, "height": height, "ratio": "original"}
39
+ content["duration"] = int(total_us)
40
+ content["fps"] = float(fps)
41
+ content["id"] = draft_id
42
+ content["materials"] = full_materials(materials)
43
+ content["tracks"] = tracks
44
+ return content
45
+
46
+
47
+ def meta_info(draft_id, total_us):
48
+ """Build the companion `draft_meta_info.json` skeleton."""
49
+ meta = template("meta")
50
+ meta["draft_id"] = draft_id
51
+ meta["draft_timeline_materials_size_"] = 0
52
+ meta["tm_duration"] = int(total_us)
53
+ return meta
@@ -6,7 +6,7 @@ from functools import lru_cache
6
6
  from pathlib import Path
7
7
 
8
8
 
9
- _TEMPLATE_DIR = Path(__file__).resolve().parent.parent / "references" / "jianying"
9
+ _TEMPLATE_DIR = Path(__file__).resolve().parent.parent.parent / "references" / "jianying"
10
10
  _TEMPLATE_FILES = {
11
11
  "project": "empty_jy_project_info.json",
12
12
  "video": "empty_jy_material_video.json",
@@ -45,6 +45,19 @@ def _validate_span(item, path, start_key="timeline_start", end_key="timeline_end
45
45
  _error(f"{path}.{end_key}", f"must be greater than {start_key}")
46
46
 
47
47
 
48
+ def _validate_transform(item, path):
49
+ for field in ("scale", "position", "flip"):
50
+ if field in item and not isinstance(item[field], dict):
51
+ _error(f"{path}.{field}", "must be an object")
52
+
53
+
54
+ def _validate_resources(resources, path):
55
+ if not isinstance(resources, list) or any(not isinstance(item, dict) for item in resources):
56
+ _error(path, "must contain source_path objects")
57
+ for index, item in enumerate(resources):
58
+ _require_string(item, "source_path", f"{path}[{index}]")
59
+
60
+
48
61
  def _validate_video_clip(clip, path):
49
62
  if not isinstance(clip, dict):
50
63
  _error(path, "must be an object")
@@ -68,18 +81,30 @@ def _validate_video_clip(clip, path):
68
81
  )
69
82
  if "reverse" in clip and not isinstance(clip["reverse"], bool):
70
83
  _error(f"{path}.reverse", "must be a boolean")
71
- if clip.get("reverse"):
84
+ # A reversed clip may omit reverse_path: export_timeline_to_jianying generates it.
85
+ if "reverse_path" in clip:
72
86
  _require_string(clip, "reverse_path", path)
73
- for field in ("scale", "position", "flip"):
74
- if field in clip and not isinstance(clip[field], dict):
75
- _error(f"{path}.{field}", "must be an object")
87
+ _validate_transform(clip, path)
76
88
  for field in ("transition", "mask", "lut", "chroma"):
77
- if field in clip and not isinstance(clip[field], (dict, str)):
89
+ if field not in clip:
90
+ continue
91
+ spec = clip[field]
92
+ if isinstance(spec, dict):
93
+ if "resources" in spec:
94
+ _validate_resources(spec["resources"], f"{path}.{field}.resources")
95
+ elif not isinstance(spec, str):
78
96
  _error(f"{path}.{field}", "must be an object or resource-package name")
79
- if "green_background" in clip and not isinstance(clip["green_background"], dict):
80
- _error(f"{path}.green_background", "must be a local media object")
81
97
  if "compound" in clip and not isinstance(clip["compound"], bool):
82
98
  _error(f"{path}.compound", "must be a boolean")
99
+ if clip.get("compound") or "green_background" in clip or "chroma" in clip:
100
+ # Any one of these makes the clip a green-screen compound, which needs both.
101
+ background = clip.get("green_background")
102
+ if not isinstance(background, dict):
103
+ _error(f"{path}.green_background", "must be a local media object")
104
+ _require_string(background, "source_path", f"{path}.green_background")
105
+ _validate_transform(background, f"{path}.green_background")
106
+ if "chroma" not in clip:
107
+ _error(f"{path}.chroma", "compound green-screen clips require a chroma object")
83
108
 
84
109
 
85
110
  def _validate_resource_config(config, path):
@@ -87,18 +112,14 @@ def _validate_resource_config(config, path):
87
112
  _error(path, "must be an object")
88
113
  if not isinstance(config.get("main_config"), dict):
89
114
  _error(f"{path}.main_config", "must be an object")
90
- resources = config.get("resources", [])
91
- if not isinstance(resources, list) or any(
92
- not isinstance(item, dict) or not isinstance(item.get("source_path"), str)
93
- for item in resources
94
- ):
95
- _error(f"{path}.resources", "must contain source_path objects")
115
+ _validate_resources(config.get("resources", []), f"{path}.resources")
96
116
 
97
117
 
98
118
  def _validate_segment(segment, path, kind):
99
119
  if not isinstance(segment, dict):
100
120
  _error(path, "must be an object")
101
121
  _validate_span(segment, path)
122
+ _validate_transform(segment, path)
102
123
  if "speed" in segment:
103
124
  speed = _require_number(segment, "speed", path)
104
125
  if speed <= 0:
@@ -9,7 +9,7 @@ import time
9
9
  import uuid
10
10
  import zipfile
11
11
 
12
- from jianying_templates import template
12
+ from jianying.templates import template
13
13
 
14
14
 
15
15
  DRAFT_PATH_PLACEHOLDER = "##_draftpath_placeholder_0E685133-18CE-45ED-8CB8-2904A212EC80_##"
@@ -98,14 +98,10 @@ def _meta_value(material, relative_path, metetype, copied_path, timestamp_ms):
98
98
 
99
99
  def _material_sets(content):
100
100
  """Yield root and nested compound-draft material dictionaries."""
101
- materials = content.get("materials")
102
- if not isinstance(materials, dict):
103
- return
101
+ materials = content["materials"]
104
102
  yield materials
105
- for draft in materials.get("drafts", []):
106
- nested = draft.get("draft") if isinstance(draft, dict) else None
107
- if isinstance(nested, dict):
108
- yield from _material_sets(nested)
103
+ for draft in materials["drafts"]:
104
+ yield from _material_sets(draft["draft"])
109
105
 
110
106
 
111
107
  def _replace_value(value, old, new):
@@ -184,13 +180,10 @@ def _is_packaged_path(value):
184
180
 
185
181
 
186
182
  def _descriptor(raw, default_kind, *, required):
187
- if not isinstance(raw, dict):
188
- raise ValueError("JianYing resources entries must be objects")
189
- source = raw.get("source_path")
183
+ """`raw` is a contract-validated resource entry: an object with a non-empty source_path."""
184
+ source = raw["source_path"]
190
185
  resource_kind = raw.get("resource_kind", default_kind)
191
186
  target_path = raw.get("target_path")
192
- if not isinstance(source, str) or not source:
193
- raise ValueError("JianYing resource source_path must be a non-empty string")
194
187
  if not isinstance(resource_kind, str) or resource_kind not in {
195
188
  "audio", "effect", "fonts", "image", "lut", "mask", "sticker",
196
189
  "text", "text_template", "transition", "video",
@@ -321,11 +314,8 @@ def bundle_media(content, meta, draft_dir):
321
314
  def strip_internal_resource_fields(content):
322
315
  for materials in _material_sets(content):
323
316
  for entries in materials.values():
324
- if not isinstance(entries, list):
325
- continue
326
317
  for material in entries:
327
- if isinstance(material, dict):
328
- material.pop("_bundle_resources", None)
318
+ material.pop("_bundle_resources", None)
329
319
 
330
320
 
331
321
  def draft_dir_has_user_content(draft_dir):
@@ -1,8 +1,10 @@
1
1
  """Self-contained config + utilities for this skill (no cross-skill imports)."""
2
+ import functools
2
3
  import math
3
4
  import os
5
+ import shutil
4
6
  import subprocess
5
- from pathlib import Path
7
+ import tempfile
6
8
 
7
9
 
8
10
  # ── 配置 ──────────────────────────────────────────────────────────────
@@ -53,7 +55,6 @@ _foreign_under_narration_volume = 0.05 # original volume under narration when s
53
55
 
54
56
  CONFIG = {
55
57
  "fade_ms": env_int("FADE_MS", 120, minimum=0), # 每段 TTS 淡入淡出(ms);过大会让紧凑的句子一顿一顿,120ms 防爆音又不发闷
56
- "breath_ms": 250, # 段间呼吸空间(ms);block recap 块内连贯、块间留原声呼吸
57
58
  "ducking_mode": "fixed", # fixed | sidechaincompress | none
58
59
  "ducking_threshold": 0.15,
59
60
  "ducking_ratio": 3,
@@ -73,6 +74,7 @@ CONFIG = {
73
74
  "duck_bridge_seconds": env_float("DUCK_BRIDGE_SECONDS", 1.5, minimum=0.0), # 仅把间隔小于此值的相邻解说窗口并成一段压低;超过则视为作者特意留的"原声块",原声放回满音量。默认 1.5s:解说块内部连续压低,块与块之间的留白放出满音量原声。该值只控制短间隔合并,不设定旁白/原声配额。调大→更连续铺底、原声块更少;调小→更碎
74
75
  "bgm_path": os.environ.get("BGM_PATH", "").strip(), # 背景音乐文件(可选),留空则不加 BGM
75
76
  "source_video": os.environ.get("SOURCE_VIDEO", "").strip(), # 剪辑模式下的原始视频(可选),用于时间线/剪映导出引用原片片段
77
+ "source_video_explicit": False, # 仅 assemble.py --source-video 显式传入时为 True;环境变量 SOURCE_VIDEO 不算显式
76
78
  "export_jianying": env_bool("EXPORT_JIANYING", False), # 渲染后可选导出剪映草稿(默认关;与核心解耦)
77
79
  "jianying_draft_dir": os.environ.get("JIANYING_DRAFT_DIR", "").strip(), # 剪映草稿输出父目录(留空=work_dir)
78
80
  "jianying_bundle_media": env_bool("JIANYING_BUNDLE_MEDIA", True), # 默认开:macOS 剪映沙箱读不到外部路径,须把素材拷进草稿目录
@@ -117,6 +119,8 @@ CONFIG = {
117
119
  "target_lra": env_float("TARGET_LRA", 11.0), # 目标响度范围 (LU)
118
120
  "final_limiter_peak": env_float("FINAL_LIMITER_PEAK", 0.98, minimum=0.1), # loudnorm 后峰值保护 limiter
119
121
  "subtitle_font_name": os.environ.get("SUBTITLE_FONT_NAME", "Arial"),
122
+ # 可选字体文件:ASS 烧录经 fontsdir 加载,画面文字经 drawtext fontfile 使用;family 名仍由 SUBTITLE_FONT_NAME 指定
123
+ "subtitle_font_file": os.environ.get("SUBTITLE_FONT_FILE", "").strip(),
120
124
  "subtitle_font_size": env_int("SUBTITLE_FONT_SIZE", 42, minimum=8),
121
125
  "subtitle_primary_color": os.environ.get("SUBTITLE_PRIMARY_COLOR", "&H00FFFFFF"),
122
126
  "subtitle_outline_color": os.environ.get("SUBTITLE_OUTLINE_COLOR", "&H00000000"),
@@ -136,8 +140,6 @@ if isinstance(_EXISTING_CONFIG_REF, dict):
136
140
  _EXISTING_CONFIG_REF.update(CONFIG)
137
141
  CONFIG = _EXISTING_CONFIG_REF
138
142
 
139
- SCRIPT_DIR = Path(__file__).parent
140
-
141
143
  def narration_tempo_budget(tts_rate_offset=0.0):
142
144
  """Return the canonical tempo budget shared by voiceover and assemble.
143
145
 
@@ -175,6 +177,37 @@ def run_cmd(cmd, **kwargs):
175
177
  log(f"运行: {display}")
176
178
  return subprocess.run(cmd, capture_output=True, text=True, **kwargs)
177
179
 
180
+
181
+ # ffmpeg 7 added `-/option path` to read any option's value from a file; ffmpeg 9 removed the
182
+ # older `-filter_complex_script` / `-filter_script` spellings, which are all ffmpeg <= 6 knows.
183
+ _LEGACY_FILTER_FILE_OPTIONS = {
184
+ "filter_complex": "-filter_complex_script",
185
+ "filter:v:0": "-filter_script:v:0",
186
+ }
187
+
188
+
189
+ @functools.lru_cache(maxsize=None)
190
+ def _ffmpeg_reads_option_files():
191
+ """Whether the ffmpeg on PATH accepts `-/option path` (asked once per process)."""
192
+ if shutil.which("ffmpeg") is None:
193
+ return False
194
+ with tempfile.TemporaryDirectory() as tmp:
195
+ graph = os.path.join(tmp, "probe_filter.txt")
196
+ with open(graph, "w", encoding="utf-8") as fh:
197
+ fh.write("null")
198
+ result = subprocess.run(["ffmpeg", "-hide_banner", "-/filter_complex", graph],
199
+ stdin=subprocess.DEVNULL, capture_output=True, text=True,
200
+ timeout=20)
201
+ return "Unrecognized option" not in result.stderr
202
+
203
+
204
+ def filter_file_args(option, path):
205
+ """ffmpeg arguments that load `option`'s filtergraph from `path`, spelled for this ffmpeg."""
206
+ if _ffmpeg_reads_option_files():
207
+ return [f"-/{option}", str(path)]
208
+ return [_LEGACY_FILTER_FILE_OPTIONS[option], str(path)]
209
+
210
+
178
211
  def get_video_duration(video_path):
179
212
  """获取视频时长(秒)"""
180
213
  cmd = ["ffprobe", "-v", "quiet", "-show_entries", "format=duration",