@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
@@ -1,20 +1,11 @@
1
- """Normalize cut plans and maintain render-cache provenance."""
2
-
3
- import hashlib
1
+ """Normalize cut plans and maintain the render-cache sidecar."""
4
2
 
5
3
  import json
6
- import os
7
-
8
4
  import re
9
5
 
10
-
11
6
  from pathlib import Path
12
7
 
13
- from lib import CONFIG, get_video_duration, log
14
-
15
- EDITED_SOURCE_RENDER_ALGORITHM_VERSION = "edited-source-render-v3"
16
-
17
- GEOMETRY_RENDER_ALGORITHM_VERSION = "geometry-weighted-orientation-area-fps-v2"
8
+ from lib import CONFIG, file_identity, get_video_duration, log
18
9
 
19
10
 
20
11
  def parse_duration_seconds(value):
@@ -101,111 +92,42 @@ def load_clip_plan(path):
101
92
  return json.loads(Path(path).read_text(encoding="utf-8"))
102
93
 
103
94
 
104
- def _stable_json_dumps(value):
105
- return json.dumps(
106
- value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str
107
- )
108
-
109
-
110
- def value_fingerprint(value):
111
- """Return a stable fingerprint for JSON-serializable non-secret values."""
112
- return hashlib.md5(_stable_json_dumps(value).encode("utf-8")).hexdigest()
113
-
114
-
115
- def cut_plan_fingerprint(validated_plan):
116
- """Hash the exact normalized clip plan that determines edited_source.mp4 bytes."""
117
- payload = dict(validated_plan)
118
- # Provenance for raw-plan freshness is not part of the edited media bytes.
119
- payload.pop("raw_plan_fingerprint", None)
120
- # QC is observability derived from the media plan, not an input range decision.
121
- payload.pop("qc", None)
122
- return value_fingerprint(payload)
123
-
124
-
125
- _FILE_FINGERPRINT_MEMO = {}
126
-
127
-
128
- def _file_identity(path):
129
- """(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
130
- st = os.stat(os.fspath(path))
131
- return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
132
-
133
-
134
- def file_fingerprint(path, chunk_size=1024 * 1024):
135
- """Return a full-content fingerprint for cache-correct identity checks.
136
-
137
- The digest covers CONTENT only — never the path or mtime — so a copied video or
138
- artifact is still recognised as the same asset, while any byte change invalidates
139
- the cache even if timestamps, size, head, or tail bytes are misleading.
140
-
141
- Identity metadata is used ONLY to memoize within a single process. One understanding
142
- run fingerprints the same source video 8-10 times and the whole extracted frame set
143
- 2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
144
- any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
145
- re-hashed, so the memo can never serve a stale digest.
146
- """
147
- key = _file_identity(path)
148
- memoized = _FILE_FINGERPRINT_MEMO.get(key)
149
- if memoized is not None:
150
- return memoized
151
- h = hashlib.sha256()
152
- with open(os.fspath(path), "rb") as f:
153
- for chunk in iter(lambda: f.read(chunk_size), b""):
154
- h.update(chunk)
155
- digest = h.hexdigest()
156
- _FILE_FINGERPRINT_MEMO[key] = digest
157
- return digest
158
-
159
-
160
95
  def _edited_source_meta_path(output_path):
161
96
  return Path(str(output_path) + ".meta.json")
162
97
 
163
98
 
164
99
  def _load_edited_source_meta(output_path):
100
+ """None when never rendered; a sidecar this skill wrote but cannot parse raises."""
165
101
  meta_path = _edited_source_meta_path(output_path)
166
102
  if not meta_path.exists():
167
103
  return None
168
- try:
169
- meta = json.loads(meta_path.read_text(encoding="utf-8"))
170
- except (OSError, ValueError):
171
- return None
172
- return meta if isinstance(meta, dict) else None
104
+ return json.loads(meta_path.read_text(encoding="utf-8"))
173
105
 
174
106
 
175
- def _source_fingerprints_for_plan(validated_plan, input_video=None):
176
- """Fingerprint every media file that can affect edited_source.mp4 bytes."""
107
+ def _source_identities_for_plan(validated_plan, input_video=None):
108
+ """{path: {size, mtime_ns}} for every media file that can affect edited_source.mp4."""
177
109
  # Single-source plans carry no per-clip source_path; the CLI video is the only input.
178
110
  paths = {clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip}
179
111
  if not paths and input_video is not None:
180
112
  paths.add(str(input_video))
181
- return {str(Path(path)): file_fingerprint(path) for path in sorted(paths)}
113
+ return {str(Path(path)): file_identity(path) for path in sorted(paths)}
182
114
 
183
115
 
184
116
  def edited_source_render_cache_payload():
185
117
  """Render-affecting settings that invalidate edited_source.mp4 cache reuse.
186
118
 
187
- Keep this payload limited to inputs/algorithms that can change rendered media bytes.
119
+ Keep this payload limited to inputs that can change rendered media bytes.
188
120
  Observational QC produced after validation/render is intentionally excluded.
189
121
  """
190
- return {
191
- "render_algorithm_version": EDITED_SOURCE_RENDER_ALGORITHM_VERSION,
192
- "geometry_render_algorithm_version": GEOMETRY_RENDER_ALGORITHM_VERSION,
193
- "clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3),
194
- }
195
-
196
-
197
- def edited_source_render_fingerprint():
198
- return value_fingerprint(edited_source_render_cache_payload())
122
+ return {"clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3)}
199
123
 
200
124
 
201
125
  def _write_edited_source_meta(output_path, validated_plan, input_video=None):
202
126
  meta = {
203
- "schema_version": 2,
204
- "clip_plan_fingerprint": cut_plan_fingerprint(validated_plan),
205
- "render_fingerprint": edited_source_render_fingerprint(),
127
+ "schema_version": 3,
128
+ "plan": validated_plan["clips"],
206
129
  "render_cache": edited_source_render_cache_payload(),
207
- "source_fingerprints": _source_fingerprints_for_plan(validated_plan, input_video),
208
- "edited_source_fingerprint": file_fingerprint(output_path),
130
+ "sources": _source_identities_for_plan(validated_plan, input_video),
209
131
  "total_duration": validated_plan["total_duration"],
210
132
  "clip_count": len(validated_plan["clips"]),
211
133
  }
@@ -215,25 +137,18 @@ def _write_edited_source_meta(output_path, validated_plan, input_video=None):
215
137
 
216
138
 
217
139
  def should_reuse_edited_source(output_path, validated_plan, input_video=None):
218
- """Return True only when edited_source.mp4 matches source media and cut params."""
140
+ """True only when a non-empty edited_source.mp4 matches the plan, settings and sources."""
219
141
  output_path = Path(output_path)
220
- if not output_path.exists():
142
+ if not output_path.exists() or output_path.stat().st_size == 0:
221
143
  return False
222
144
  meta = _load_edited_source_meta(output_path)
223
- if meta is None:
224
- return False
225
- if (
226
- meta.get("clip_plan_fingerprint") != cut_plan_fingerprint(validated_plan)
227
- or meta.get("render_fingerprint") != edited_source_render_fingerprint()
228
- or meta.get("source_fingerprints")
229
- != _source_fingerprints_for_plan(validated_plan, input_video)
230
- ):
231
- return False
232
- try:
233
- output_fingerprint = file_fingerprint(output_path)
234
- except OSError:
235
- return False
236
- return meta.get("edited_source_fingerprint") == output_fingerprint
145
+ if not isinstance(meta, dict) or meta.get("schema_version") != 3:
146
+ return False # never rendered, or a sidecar from an older schema: re-render
147
+ return (
148
+ meta["plan"] == validated_plan["clips"]
149
+ and meta["render_cache"] == edited_source_render_cache_payload()
150
+ and meta["sources"] == _source_identities_for_plan(validated_plan, input_video)
151
+ )
237
152
 
238
153
 
239
154
  def _manifest_source_entries(sources_manifest):
@@ -5,10 +5,10 @@ import json
5
5
 
6
6
  from pathlib import Path
7
7
 
8
- from lib import CONFIG, get_video_duration, log, run_cmd
8
+ from lib import CONFIG, filter_file_args, get_video_duration, log, run_cmd
9
9
 
10
10
  from cut_contract import _write_edited_source_meta
11
- from media_geometry import _has_audio_stream, _select_output_geometry
11
+ from media_geometry import _has_audio_stream
12
12
  from sentence_boundaries import _continuous_source_join
13
13
 
14
14
 
@@ -129,10 +129,20 @@ def write_cut_delivery_qc(work_dir, validated_plan):
129
129
 
130
130
 
131
131
  def build_edited_source_video(input_video, validated_plan, work_dir, output_path=None):
132
- """Build `edited_source.mp4` by concatenating validated source ranges."""
132
+ """Build `edited_source.mp4` by concatenating validated source ranges.
133
+
134
+ `validated_plan["qc"]["output_geometry"]` is required: the caller (cut_cli, or any
135
+ public user of this API) selects the canvas with `_select_output_geometry` first, so the
136
+ same geometry is recorded in clip_plan_validated.json and used for the render."""
133
137
  work_dir = Path(work_dir)
134
138
  output_path = Path(output_path or work_dir / "edited_source.mp4")
135
139
  clips = validated_plan["clips"]
140
+ qc = validated_plan["qc"]
141
+ if "output_geometry" not in qc:
142
+ raise KeyError(
143
+ "validated_plan['qc']['output_geometry'] is required: select the canvas with "
144
+ "media_geometry._select_output_geometry before build_edited_source_video"
145
+ )
136
146
 
137
147
  source_paths = []
138
148
  for clip in clips:
@@ -142,12 +152,7 @@ def build_edited_source_video(input_video, validated_plan, work_dir, output_path
142
152
  source_index = {path: idx for idx, path in enumerate(source_paths)}
143
153
  audio_by_input = {path: _has_audio_stream(path) for path in source_paths}
144
154
  join_fade_ms = CONFIG["clip_join_audio_fade_ms"]
145
- qc = validated_plan.setdefault("qc", {})
146
155
  qc["join_fade_ms"] = round(join_fade_ms, 3)
147
- if "output_geometry" not in qc:
148
- _, _, _, geometry_qc = _select_output_geometry(source_paths, clips)
149
- qc["output_geometry"] = geometry_qc
150
- qc["output_geometry_reason"] = geometry_qc["reason"]
151
156
 
152
157
  parts = []
153
158
  concat_inputs = []
@@ -250,7 +255,7 @@ def build_edited_source_video(input_video, validated_plan, work_dir, output_path
250
255
  if len(filter_complex.encode("utf-8")) > 7000:
251
256
  filter_script = work_dir / "edit_filter_complex.txt"
252
257
  filter_script.write_text(filter_complex, encoding="utf-8")
253
- filter_args = ["-filter_complex_script", str(filter_script)]
258
+ filter_args = filter_file_args("filter_complex", filter_script)
254
259
  else:
255
260
  filter_args = ["-filter_complex", filter_complex]
256
261
 
@@ -1,7 +1,10 @@
1
1
  """Self-contained utilities for the video-cut skill (no cross-skill imports)."""
2
+ import functools
2
3
  import math
3
4
  import os
5
+ import shutil
4
6
  import subprocess
7
+ import tempfile
5
8
 
6
9
 
7
10
  def log(msg):
@@ -56,6 +59,12 @@ CONFIG = {
56
59
  }
57
60
 
58
61
 
62
+ def file_identity(path):
63
+ """{size, mtime_ns} — the cache identity of an input file (no content read)."""
64
+ st = os.stat(os.fspath(path))
65
+ return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
66
+
67
+
59
68
  def run_cmd(cmd, **kwargs):
60
69
  """Run a command list and return the CompletedProcess (stdout/stderr captured)."""
61
70
  display = " ".join(
@@ -65,6 +74,36 @@ def run_cmd(cmd, **kwargs):
65
74
  return subprocess.run(cmd, capture_output=True, text=True, **kwargs)
66
75
 
67
76
 
77
+ # ffmpeg 7 added `-/option path` to read any option's value from a file; ffmpeg 9 removed the
78
+ # older `-filter_complex_script` / `-filter_script` spellings, which are all ffmpeg <= 6 knows.
79
+ _LEGACY_FILTER_FILE_OPTIONS = {
80
+ "filter_complex": "-filter_complex_script",
81
+ "filter:v:0": "-filter_script:v:0",
82
+ }
83
+
84
+
85
+ @functools.lru_cache(maxsize=None)
86
+ def _ffmpeg_reads_option_files():
87
+ """Whether the ffmpeg on PATH accepts `-/option path` (asked once per process)."""
88
+ if shutil.which("ffmpeg") is None:
89
+ return False
90
+ with tempfile.TemporaryDirectory() as tmp:
91
+ graph = os.path.join(tmp, "probe_filter.txt")
92
+ with open(graph, "w", encoding="utf-8") as fh:
93
+ fh.write("null")
94
+ result = subprocess.run(["ffmpeg", "-hide_banner", "-/filter_complex", graph],
95
+ stdin=subprocess.DEVNULL, capture_output=True, text=True,
96
+ timeout=20)
97
+ return "Unrecognized option" not in result.stderr
98
+
99
+
100
+ def filter_file_args(option, path):
101
+ """ffmpeg arguments that load `option`'s filtergraph from `path`, spelled for this ffmpeg."""
102
+ if _ffmpeg_reads_option_files():
103
+ return [f"-/{option}", str(path)]
104
+ return [_LEGACY_FILTER_FILE_OPTIONS[option], str(path)]
105
+
106
+
68
107
  def get_video_duration(video_path):
69
108
  """Return media duration in seconds via ffprobe."""
70
109
  cmd = ["ffprobe", "-v", "error", "-show_entries", "format=duration",
@@ -1,157 +1,4 @@
1
- """Map source-time narration onto the edited output timeline."""
2
-
3
- from lib import log
4
-
5
-
6
- def source_time_to_output_time(source_time, clips):
7
- """Map a source timestamp into the post-concat output timeline (None when cut away)."""
8
- for clip in clips:
9
- start = clip["source_start"]
10
- end = clip["source_end"]
11
- if start <= source_time <= end:
12
- mapped = clip["output_start"] + (source_time - start)
13
- return round(max(clip["output_start"], min(mapped, clip["output_end"])), 3)
14
- return None
15
-
16
-
17
- def _clips_for_midpoint(start, end, clips):
18
- mid = (start + end) / 2
19
- return [clip for clip in clips if clip["source_start"] <= mid <= clip["source_end"]]
20
-
21
-
22
- def map_narration_to_clips(narration, validated_plan, min_duration=0.3):
23
- """Convert source-time narration segments to edited-output timeline segments."""
24
- clips = validated_plan["clips"]
25
- mapped = []
26
- for raw in narration:
27
- source_start = raw["start"]
28
- source_end = raw["end"]
29
- if raw.get("source_clip_id") is not None:
30
- requested_clip_id = int(raw["source_clip_id"])
31
- clip = next((c for c in clips if c["clip_id"] == requested_clip_id), None)
32
- if clip and not (
33
- clip["source_start"] <= (source_start + source_end) / 2 <= clip["source_end"]
34
- ):
35
- clip = None
36
- else:
37
- matches = _clips_for_midpoint(source_start, source_end, clips)
38
- if len(matches) > 1:
39
- log(
40
- f" 丢弃重复片段中未标 source_clip_id 的解说: {source_start:.1f}-{source_end:.1f}s"
41
- )
42
- continue
43
- clip = matches[0] if matches else None
44
- if not clip:
45
- log(f" 丢弃未落入剪辑片段的解说: {source_start:.1f}-{source_end:.1f}s")
46
- continue
47
- clipped_source_start = max(source_start, clip["source_start"])
48
- clipped_source_end = min(source_end, clip["source_end"])
49
- if clipped_source_end - clipped_source_start < min_duration:
50
- log(f" 丢弃过短映射解说: {source_start:.1f}-{source_end:.1f}s")
51
- continue
52
- item = dict(raw)
53
- item["source_start"] = round(clipped_source_start, 3)
54
- item["source_end"] = round(clipped_source_end, 3)
55
- item["source_clip_id"] = clip["clip_id"]
56
- item["start"] = source_time_to_output_time(clipped_source_start, [clip])
57
- item["end"] = source_time_to_output_time(clipped_source_end, [clip])
58
- # Tag beats trimmed to a clip edge: their TEXT was written for a longer span and may
59
- # now describe footage that was cut away (a stale-text desync the lint surfaces).
60
- item["clamped"] = bool(
61
- clipped_source_start > source_start + 1e-3
62
- or clipped_source_end < source_end - 1e-3
63
- )
64
- mapped.append(item)
65
-
66
- mapped.sort(key=lambda seg: seg["start"])
67
- return mapped
68
-
69
-
70
- def lint_mapped_narration(
71
- mapped,
72
- original_count,
73
- output_duration,
74
- *,
75
- min_spm=6.0,
76
- max_gap_seconds=12.0,
77
- drop_ratio_limit=0.3,
78
- ):
79
- """Advisory re-lint of narration AFTER it is mapped onto the cut OUTPUT timeline.
80
-
81
- The mapper silently drops beats whose midpoint is outside every kept clip and clamps
82
- boundary-crossers, so a narration authored against the full source can pass the
83
- source-time validate yet leave the cut sparse or describing footage the viewer never
84
- sees. This surfaces that on the real output timeline (narration_mapped_lint.json) and
85
- returns a `blocking` verdict (heavy drop / too sparse / long gap) that the cut stage
86
- enforces unless --allow-sparse-cut. Clamped beats are always blocking because their TTS
87
- sentence would be cut at a clip edge; sparse-cut intent never authorizes speech truncation.
88
- """
89
- mapped = sorted(mapped, key=lambda s: s["start"])
90
- mapped_count = len(mapped)
91
- dropped = max(0, original_count - mapped_count)
92
- drop_ratio = dropped / original_count if original_count else 0.0
93
- spm = mapped_count / (output_duration / 60) if output_duration > 0 else 0.0
94
- gaps = [b["start"] - a["end"] for a, b in zip(mapped, mapped[1:])]
95
- max_gap = max(gaps) if gaps else 0.0
96
- covered = sum(max(0.0, b["end"] - b["start"]) for b in mapped)
97
- coverage = covered / output_duration if output_duration > 0 else 0.0
98
-
99
- warnings = []
100
- if drop_ratio >= drop_ratio_limit:
101
- warnings.append(
102
- {
103
- "code": "many_beats_dropped",
104
- "message": "大量解说段落落在保留片段之外被丢弃——请按保留的片段写解说,而不是整段原片。",
105
- "dropped": dropped,
106
- "original": original_count,
107
- "drop_ratio": round(drop_ratio, 2),
108
- }
109
- )
110
- if mapped_count >= 2 and spm and spm < min_spm:
111
- warnings.append(
112
- {
113
- "code": "low_density_output",
114
- "message": "映射后的解说在成片里偏稀疏——在保留片段内补充解说 beat。",
115
- "segments_per_minute": round(spm, 2),
116
- "min_segments_per_minute": min_spm,
117
- }
118
- )
119
- if max_gap > max_gap_seconds:
120
- warnings.append(
121
- {
122
- "code": "long_gap_output",
123
- "message": "成片里有一长段没有解说。",
124
- "max_gap_seconds": round(max_gap, 2),
125
- "max_gap_limit_seconds": max_gap_seconds,
126
- }
127
- )
128
- clamped = [b for b in mapped if b["clamped"]]
129
- if clamped:
130
- warnings.append(
131
- {
132
- "code": "clamped_beats",
133
- "message": "有解说段被裁到片段边界,旁白句子会被截断——必须移动片段边界或重写整句。",
134
- "count": len(clamped),
135
- }
136
- )
137
- blocking_codes = {
138
- "many_beats_dropped",
139
- "low_density_output",
140
- "long_gap_output",
141
- "clamped_beats",
142
- }
143
- return {
144
- "mapped_count": mapped_count,
145
- "dropped": dropped,
146
- "drop_ratio": round(drop_ratio, 2),
147
- "output_duration": round(output_duration, 2),
148
- "segments_per_minute": round(spm, 2),
149
- "max_gap_seconds": round(max_gap, 2),
150
- "coverage": round(coverage, 2),
151
- "clamped_count": len(clamped),
152
- "warnings": warnings,
153
- "blocking": any(w["code"] in blocking_codes for w in warnings),
154
- }
1
+ """Cut QC bookkeeping for clip_plan_validated.json."""
155
2
 
156
3
 
157
4
  def update_cut_qc(plan, *, allow_duration_drift=False, duration_drift_allowed_by=None):
@@ -169,7 +16,7 @@ def update_cut_qc(plan, *, allow_duration_drift=False, duration_drift_allowed_by
169
16
  "total_duration": round(total, 3),
170
17
  }
171
18
  else:
172
- ratio = total / target if target > 0 else 0.0
19
+ ratio = total / target # parse_duration_seconds guarantees target > 0
173
20
  if ratio < 0.85:
174
21
  target_status = "under"
175
22
  elif ratio > 1.15: