@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
@@ -44,12 +44,13 @@ license: MIT
44
44
  3. **在可用区间内选叙事入出点。** 按动作、反应或停顿的需要选择入点,出点取信息完成之后、缺陷
45
45
  开始之前。逐段写清入点理由和出点理由。做法见 [入出点与镜序](references/cut-craft.md)。
46
46
  4. **排镜序并检查相邻关系。** 检查动作、空间、揭示与声音是否连贯;重复信息没有新增作用时再删。
47
- 5. **比较相邻段的画面。** 按同场景、同光态比较可比区域;有可见偏差时在对应段写 `画面`。
48
- 做法见 [接镜与色彩一致性](references/generated-footage.md#接镜与色彩一致性)。
47
+ 5. **比较相邻段的画面。** render 会在同一场景内自动接镜;按同场景、同光态比较可比区域,
48
+ 自动接镜不够或不该动的段写 `画面`。做法见 [接镜与色彩一致性](references/generated-footage.md#接镜与色彩一致性)。
49
49
  6. **处理声音接缝与响度。** 拼接后回听;需要混音时通过外部工具执行并记录。
50
50
  做法见 [声音、字幕与音乐](references/sound-and-subtitles.md)。
51
- 7. **写字幕。** 字幕的每一个字都取自 `剧本.md` 的台词原文,不取自转写结果;标点可按字幕惯例调整,
52
- 字不能改。转写只用来定位时间。
51
+ 7. **写字幕、画面文字与音效。** 字幕的每一个字都取自 `剧本.md` 的台词原文,不取自转写结果;
52
+ 标点照抄剧本,字不能改。转写只用来定位时间。画面留白给后期的 `[画面文字]` 写成 `画面文字` 行,
53
+ 需要落在画面文字出现时的提示音写成 `音效` 行。
53
54
  8. **写出 `剪辑单.md`,再渲染。** 先有文档后有成片:文档是可审查、可修订、可复现的那一份,
54
55
  成片是它的产物。
55
56
  9. **按可测项验收成片,再交付。** 见 [交付与验收](references/delivery-verify.md)。
@@ -67,12 +68,31 @@ license: MIT
67
68
  - `- 取舍:入点=<理由>;出点=<理由>`——理由写画面上发生了什么,不写「节奏需要」。
68
69
  理由应能对应实际素材与本镜叙事需要。
69
70
  - `- 声音:<本段声音怎么处理>`——记录保留原声或外部处理。此行不驱动内置 render 的混音。
70
- - `- 画面:<接镜校正>` 或 `- 画面:无`——只在这一段与相邻段明显对不上时写,
71
- 格式是「亮度 <数>」「饱和 <数>」「色温 <数>」三项的任意组合,用 `;` 分隔。
72
- 详见 [接镜与色彩一致性](references/generated-footage.md#接镜与色彩一致性)。
71
+ - `- 画面:<接镜校正>`、`- 画面:无` 或 `- 画面:不校`——校正是「亮度 <数>」「饱和 <数>」「色温 <数>」
72
+ 三项的任意组合,用 `;` 分隔,写了就替代这一段的自动接镜;`无` 表示不另写校正;`不校` 表示连自动接镜
73
+ 也不做。详见 [接镜与色彩一致性](references/generated-footage.md#接镜与色彩一致性)。
73
74
  - `- 字幕:<逐字台词原文>` 或 `- 字幕:无`。本段有多句台词时改用编号写法,
74
75
  每句自带时间:`- 字幕 1:0.20-1.50 请进`、`- 字幕 2:2.30-4.30 门没有锁`。
75
- 编号从 1 连续,时间相对本 CUT 入点,且不得重叠。
76
+ 编号从 1 连续,时间相对本 CUT 入点,且不得重叠。句末可加 `(重点:<词>|<词>)`,
77
+ 这些词须出现在本句里,烧录时标黄。字色按 `剧本.md` 里这句的来源决定:对白白色,
78
+ `[VO] 系统:` 青色,其他 `[VO]` 浅青,剪辑单里不另写。
79
+ - `- 画面文字 N:<起>-<止> <样式> <项>|<项>…`——视频模型写不对字,系统面板、倒计时、屏幕上的
80
+ 粉丝数在画面里留白,由 render 叠上去。每一项都要能在 `剧本.md` 的 `[画面文字]` 行里找到。
81
+ 单条可不编号;编号与时间规则同字幕。三种面板共用画面顶部,角标在右上角,同一位置不得重叠。
82
+
83
+ | 样式 | 各项的位置 |
84
+ |---|---|
85
+ | `卡片` | 每项一行:第一个词是名称,最后一个词是数值,中间的词是单位(`主号 粉丝 2|小号 1`) |
86
+ | `系统面板` | 第一项是状态行,逐字打出;其余是奖励行 |
87
+ | `任务面板` | 标题、目标(逐字打出);其后写成 `<数> / <数>` 的一项是进度,其余是奖励标签 |
88
+ | `角标` | 各项连成一行标签 |
89
+
90
+ 奖励可在项末写稀有度 `(传说)` `(史诗)` `(稀有)`,不写按稀有;稀有度不是文字,不参与核对。
91
+ `任务面板` 和 `角标` 末尾可加 `(倒计时:<秒>)`,显示成 `4天 23:59:58` 并逐秒走动;
92
+ `(倒计时:接续)` 接着成片里上一个倒计时走。相邻两段写同一条角标且首尾相接时,
93
+ 成片里是同一个角标,切镜时不重新入场。面板自带的 `【系统提示】` `【新任务】` `进度` 属于样式,不写进剪辑单。
94
+ - `- 音效 N:<起>-<止> <项目相对路径>(增益:<dB>)`——从声音文件开头取 `止 - 起` 秒,
95
+ 混进本段该时刻的原声;增益可省,范围 -40 到 +6。单条可不编号。
76
96
  - 素材里没有被采用的镜头,在正文开头写一行 `- 未采用镜头:<MOTION-...>(理由:……)`,
77
97
  多项用 `;` 连接。理由区分两类:**文件缺失**(还没生产 / 生产失败)和**质量不可用**
78
98
  (生成结果不能用),也可说明叙事取舍。补素材前先判断需要重出还是修订上游。
@@ -102,21 +122,24 @@ python3 <本技能目录>/scripts/edit_tool.py render <剧集/EP001> --project-r
102
122
  python3 <本技能目录>/scripts/edit_tool.py verify <剧集/EP001> --project-root <project>
103
123
  ```
104
124
 
105
- `check` 只核对不渲染:素材是否比剪辑单新(重出之后字幕时间必须重测)、区间自洽、`MOTION-...` 解析、文件存在、区间不越过素材时长、
106
- 同源区间不重叠、字幕文字能在 `剧本.md` 里找到。其中「区间不越过素材时长」需要 `ffprobe`,
107
- 没有时这一项报「未测」并说明原因,不静默跳过。`render` 按剪辑单切段、拼接、烧字幕、统一响度,
108
- 输出 `剧集/<EP>/制作成果/成片/`。`verify` 只测量已经渲染出来的成片并回报数字,不改文件、
109
- 不给质量结论。
125
+ `check` 只核对不渲染:素材是否比剪辑单新(重出之后字幕、画面文字与音效时间必须重测)、区间自洽、
126
+ `MOTION-...` 解析、文件存在、区间不越过素材时长、同源区间不重叠、字幕文字能在 `剧本.md` 里找到、
127
+ 画面文字能在 `[画面文字]` 行里找到、「接续」之前确有倒计时、音效文件存在且落在本段内。
128
+ 其中「区间不越过素材时长」需要 `ffprobe`,没有时这一项报「未测」并说明原因,不静默跳过。
129
+ `render` 按剪辑单切段、同场景自动接镜、拼接、混入音效、烧字幕与画面文字、统一响度,输出 `剧集/<EP>/制作成果/成片/`。
130
+ `verify` 只测量已经渲染出来的成片并回报数字,另列出画面文字与音效在成片里的起止秒、切点亮度变化和疑似坏帧,
131
+ 供抽帧和回听;不改文件、不给质量结论。
110
132
 
111
133
  字幕默认由带 libass 的 ffmpeg 烧录。`--subtitles remotion` 换成一条排版可调的
112
134
  路线,代价是装一次 Node 依赖,而且叠层要**逐帧过无头浏览器**渲整部成片——慢且吃内存,
113
- 并发默认压在 2(`--remotion-concurrency` 可调,调高前先量本机)。取舍见
114
- [声音、字幕与音乐](references/sound-and-subtitles.md),装法见
115
- [Remotion 字幕叠层](assets/remotion/README.md)。
135
+ 并发默认压在 2(`--remotion-concurrency` 可调,调高前先量本机)。画面文字只有 Remotion 一条路线:
136
+ 剪辑单里有画面文字时,无论字幕走哪条路线 render 都需要它,没装就报错并给出安装命令;
137
+ 走 ffmpeg 的字幕压在画面文字之上。取舍见 [声音、字幕与音乐](references/sound-and-subtitles.md),
138
+ 装法见 [Remotion 叠层](assets/remotion/README.md)。
116
139
 
117
- `render` 保留切好的中间文件。它只切段、硬拼接、统一字幕样式、接镜校正和整片响度,
118
- 不执行 `声音` 行中的混音方案,也不按交付规格自动缩放或改帧率。需要混音、逐镜字幕避让、
119
- 转场或画幅转换时用外部工具完成并记录,验收实际交付文件。
140
+ `render` 保留切好的中间文件。它只切段、硬拼接、混入 `音效` 行、统一字幕样式、画面文字、
141
+ 接镜校正(写明的与同场景自动的)和整片响度,不执行 `声音` 行中的混音方案,也不按交付规格自动缩放或改帧率。
142
+ 需要混音、逐镜字幕避让、转场或画幅转换时用外部工具完成并记录,验收实际交付文件。
120
143
 
121
144
  ## 按需知识
122
145
 
@@ -128,7 +151,7 @@ python3 <本技能目录>/scripts/edit_tool.py verify <剧集/EP001> --project-r
128
151
  - 声音接缝、字幕与音乐落点:[声音、字幕与音乐](references/sound-and-subtitles.md)
129
152
  - 成片可测项与验收:[交付与验收](references/delivery-verify.md)
130
153
  - 剪辑单成稿样例:[剪辑单样例](assets/剪辑单.md)
131
- - 可选的 Remotion 字幕排版:[Remotion 字幕叠层](assets/remotion/README.md)
154
+ - 画面文字样式与可选的 Remotion 字幕排版:[Remotion 叠层](assets/remotion/README.md)
132
155
 
133
156
  ## 修订
134
157
 
@@ -1,13 +1,15 @@
1
- # Remotion 字幕叠层
1
+ # Remotion 叠层
2
2
 
3
- `$short-drama-edit` 的**可选**字幕渲染路线。默认路线是 ffmpeg + libass,零依赖;这一条排版
4
- 更好,但要装 Node 依赖,而且慢得多、吃内存——先看下面的「代价」。
3
+ `$short-drama-edit` 用它画两样东西:剪辑单里的**画面文字**(只有这一条路线),以及**可选的**
4
+ 字幕排版(默认字幕路线是 ffmpeg + libass,零依赖)。两者在同一遍里渲进一段透明叠层。
5
+ 它要装 Node 依赖,而且慢得多、吃内存——先看下面的「代价」。
5
6
 
6
- ## 为什么会有第二条路
7
+ ## 为什么走这条路
7
8
 
8
9
  ASS 的字号是相对 `PlayRes` 的单位,渲染时再缩放一次,这层间接每次都要重推一遍,推错了
9
10
  在数字上看不出来(见[声音、字幕与音乐](../../references/sound-and-subtitles.md))。这里字号、
10
11
  描边、行距、安全区、折行都是 CSS,写的是画面像素,改完在 `npx remotion studio` 当场能看。
12
+ 面板、发光、逐条入场和逐秒走动的倒计时是版面,不是字幕样式,ASS 画不了。
11
13
 
12
14
  ## 装一次
13
15
 
@@ -18,22 +20,47 @@ ASS 的字号是相对 `PlayRes` 的单位,渲染时再缩放一次,这层
18
20
  cd ~/.cache/short-drama-edit/remotion && npm install
19
21
  ```
20
22
 
21
- `node_modules` 不进项目也不进本仓库。
23
+ `node_modules` 不进项目也不进本仓库。字体也在这一步装上:`@fontsource/noto-sans-sc`
24
+ (思源黑体,用 700 与 900)和 `@fontsource/jetbrains-mono`(数字,用 800)。升级本技能后
25
+ `package.json` 多了依赖时,render 会点名缺的包并给出同一条命令。
22
26
 
23
27
  ## 用
24
28
 
29
+ 剪辑单里有画面文字时,`render` 自动走这一遍。只想换字幕排版时:
30
+
25
31
  ```bash
26
32
  python3 <本技能目录>/scripts/edit_tool.py render <剧集/EP001> --project-root <project> --subtitles remotion
27
33
  ```
28
34
 
29
- 它渲染出一段**透明**的字幕层(VP8 + alpha),再由 ffmpeg 叠到未经改动的画面上。画面本身
30
- 不经过浏览器重绘,所以不多一次画质损失。
35
+ 它渲染出一段**透明**的叠层(VP8 + alpha),再由 ffmpeg 叠到未经改动的画面上。画面本身
36
+ 不经过浏览器重绘,所以不多一次画质损失;也不在浏览器里解码画面,帧时间不规整的拼接素材在那里
37
+ 会解出串进别的镜头画面的坏帧。字幕走 ffmpeg 路线时,这一遍只画画面文字,
38
+ 字幕在合成后烧在它上面。
39
+
40
+ ## 画面文字的样式
41
+
42
+ 每种样式是 `src/screen/` 里的一个组件,接收同样的 `{ text }`;颜色、入场、淡出、
43
+ 面板位置这些共用的值在 `src/screen/tokens.ts`,系统类面板共用的框与部件在 `src/screen/chrome.tsx`。换一种样式的外观只改它自己的文件,
44
+ 不碰剪辑单解析与时间换算。所有尺寸以画面高度的百分之一为单位。
45
+
46
+ 倒计时由帧号直接算出:同一帧每次渲染显示同样的数字。
47
+
48
+ ## 字体
49
+
50
+ 字体随叠层打包,不依赖本机。字体按字符区段拆成许多小文件,浏览器只在排到某段文字时才去取,
51
+ 那时这一帧可能已经截走了。所以叠层挂载时先按整部片子要画的字逐个字重调用
52
+ `document.fonts.load`,全部就绪、组件树提交之后才放出第一帧;十秒内没加载完、某个字重没有声明、
53
+ 或加载后仍未就绪,渲染直接报错。这个等待按挂载注册并设了上限,不再是模块加载时那次
54
+ `document.fonts.ready`——在带视频的渲染页里,那次等待曾挂住整个渲染直到超时。
55
+
56
+ 画每段字之前再测一次宽:把字体栈分别接在 `serif` 与 `monospace` 两个兜底之前,与只有兜底时的
57
+ 宽度相比,两边都一样才算字体没到位,这时同样报错,不悄悄换字体出片。
31
58
 
32
59
  ## 代价
33
60
 
34
61
  叠层**逐帧**渲,长度等于整部成片:一分钟竖屏片一千多帧 1080×1920,每帧一次无头浏览器
35
- 截图,没字幕的帧照渲。透明还要求 PNG 抓帧加 VP8 编码,Remotion 的性能文档把这两样都列为
36
- 慢环节。默认路线是一次滤镜的事,这条是分钟级。
62
+ 截图,没字的帧照渲。透明还要求 PNG 抓帧加 VP8 编码,Remotion 的性能文档把这两样都列为
63
+ 慢环节。默认字幕路线是一次滤镜的事,这条是分钟级。
37
64
 
38
65
  内存才是会出事的地方。Remotion 默认按 CPU 核数开浏览器实例,每个各持一整帧——10 核 8 GB
39
66
  的机器上内存打满,整机停止响应,本套件就这么卡死过一次。所以 `edit_tool.py` 固定传
@@ -47,14 +74,23 @@ cd ~/.cache/short-drama-edit/remotion && npx remotion benchmark
47
74
 
48
75
  Remotion 个人与小团队免费,超出规模需商业授权,条款以 [remotion.dev](https://www.remotion.dev/)
49
76
  为准。本套件是 MIT,不包含也不代理这项授权,也不会替你安装它——缺依赖时只报错并给出命令。
50
- 默认路线不涉及这件事。
77
+ 剪辑单里没有画面文字、字幕走默认路线时,不涉及这件事。
78
+
79
+ 思源黑体(Noto Sans SC)与 JetBrains Mono 都是 SIL Open Font License 1.1:可以随成片使用、
80
+ 嵌入和再分发,不能单独出售字体文件。许可证文本在各自的 `node_modules/@fontsource/*/LICENSE`。
51
81
 
52
82
  ## 文件
53
83
 
54
84
  | 文件 | 作用 |
55
85
  |---|---|
56
- | `src/schema.ts` | 叠层的输入形状:cues、画幅、fps、字号与安全区比例 |
57
- | `src/Subtitles.tsx` | 排版本身。所有尺寸都是画面高度的比例,同一组数值对 768×1344 和 1080×1920 都成立 |
86
+ | `src/schema.ts` | 叠层的输入形状:字幕(文字、来源、重点词)、画面文字(各项与稀有度)、画幅、fps |
87
+ | `src/Overlay.tsx` | 合成本身:引入字体、等字体就绪,画面文字在下,字幕在上 |
88
+ | `src/Subtitles.tsx` | 字幕排版。所有尺寸都是画面高度的比例,同一组数值对 768×1344 和 1080×1920 都成立 |
89
+ | `src/screen/ScreenText.tsx` | 把每条画面文字放到成片时间上,按样式分派 |
90
+ | `src/screen/tokens.ts` | 字幕与各样式共用的字体、颜色、稀有度色、入场、淡出与位置 |
91
+ | `src/screen/chrome.tsx` | 系统类面板共用的框、角标括号、扫描线、标题行、打字机与奖励行 |
92
+ | `src/screen/Card.tsx` 等 | 每种样式一个组件:卡片、系统面板、任务面板、角标 |
93
+ | `src/rules.mjs` | 不需要浏览器的判断:要加载的字、加载结果是否可用、字体族是否缺失、打字进度、进度条、倒计时读数与脉冲。纯 JavaScript,测试直接用 Node 跑 |
94
+ | `src/font.ts` | 在渲染页里执行上面的字体判断 |
58
95
  | `src/Root.tsx` | 合成注册;画幅、帧率、时长由调用方通过 `--props` 传入 |
59
- | `src/font.ts` | 渲染前等字体就绪,并验证字体族真的存在——装不上时报错,而不是悄悄换一款字体出片 |
60
96
  | `remotion.config.ts` | 固定成带 alpha 的输出,叠层必须透明 |
@@ -2,11 +2,13 @@
2
2
  "name": "short-drama-subtitles",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "description": "Remotion subtitle layer for short-drama-edit. Renders a transparent overlay from cues.json.",
5
+ "description": "Remotion overlay for short-drama-edit: subtitles and screen text on transparent frames.",
6
6
  "scripts": {
7
- "render": "remotion render Subtitles"
7
+ "render": "remotion render Overlay"
8
8
  },
9
9
  "dependencies": {
10
+ "@fontsource/jetbrains-mono": "5.3.0",
11
+ "@fontsource/noto-sans-sc": "5.3.0",
10
12
  "@remotion/cli": "4.0.418",
11
13
  "react": "19.0.0",
12
14
  "react-dom": "19.0.0",
@@ -0,0 +1,31 @@
1
+ import React, { useMemo } from "react";
2
+ import { AbsoluteFill } from "remotion";
3
+ // Only the weights drawn: Noto Sans SC 700 and 900, JetBrains Mono 800.
4
+ import "@fontsource/noto-sans-sc/700.css";
5
+ import "@fontsource/noto-sans-sc/900.css";
6
+ import "@fontsource/jetbrains-mono/800.css";
7
+ import { OverlayProps } from "./schema";
8
+ import { useFaces } from "./font";
9
+ import { fontLoadPlan } from "./rules.mjs";
10
+ import { ScreenTextLayer } from "./screen/ScreenText";
11
+ import { Subtitles } from "./Subtitles";
12
+
13
+ /**
14
+ * One transparent pass: screen text first, subtitles on top, over empty frames.
15
+ * ffmpeg composites the result onto the untouched picture.
16
+ */
17
+ export const Overlay: React.FC<OverlayProps> = (props) => {
18
+ const plan = useMemo(
19
+ () => fontLoadPlan(props.cues, props.screenTexts),
20
+ [props.cues, props.screenTexts],
21
+ );
22
+ if (!useFaces(plan)) return null;
23
+ return (
24
+ <AbsoluteFill>
25
+ {props.screenTexts.map((text, index) => (
26
+ <ScreenTextLayer key={index} text={text} />
27
+ ))}
28
+ <Subtitles cues={props.cues} />
29
+ </AbsoluteFill>
30
+ );
31
+ };
@@ -1,13 +1,13 @@
1
1
  import React from "react";
2
2
  import { Composition } from "remotion";
3
- import { Subtitles } from "./Subtitles";
4
- import { defaultProps, SubtitleProps } from "./schema";
3
+ import { Overlay } from "./Overlay";
4
+ import { defaultProps, OverlayProps } from "./schema";
5
5
 
6
6
  export const RemotionRoot: React.FC = () => {
7
7
  return (
8
8
  <Composition
9
- id="Subtitles"
10
- component={Subtitles}
9
+ id="Overlay"
10
+ component={Overlay}
11
11
  defaultProps={defaultProps}
12
12
  // The caller passes the real frame, fps and length through --props; these
13
13
  // stand in only when the composition is opened in the studio by hand.
@@ -15,7 +15,7 @@ export const RemotionRoot: React.FC = () => {
15
15
  fps={defaultProps.fps}
16
16
  width={defaultProps.width}
17
17
  height={defaultProps.height}
18
- calculateMetadata={({ props }: { props: SubtitleProps }) => ({
18
+ calculateMetadata={({ props }: { props: OverlayProps }) => ({
19
19
  durationInFrames: Math.max(1, Math.round(props.durationInSeconds * props.fps)),
20
20
  fps: props.fps,
21
21
  width: props.width,
@@ -1,87 +1,86 @@
1
- import React, { useMemo } from "react";
2
- import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
3
- import { Cue, SubtitleProps } from "./schema";
4
- import { assertFamilyResolves, waitForFonts } from "./font";
1
+ import React from "react";
2
+ import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "remotion";
3
+ import { Cue } from "./schema";
4
+ import { assertFamilyResolves } from "./font";
5
+ import { lineEms } from "./rules.mjs";
6
+ import { SANS, SUBTITLE } from "./screen/tokens";
5
7
 
6
- waitForFonts();
8
+ /** Splits a line into plain and highlighted runs, earliest keyword first. */
9
+ const runs = (text: string, keys: string[]): { text: string; key: boolean }[] => {
10
+ const out: { text: string; key: boolean }[] = [];
11
+ let rest = text;
12
+ while (rest) {
13
+ const hit = keys
14
+ .map((key) => ({ key, at: rest.indexOf(key) }))
15
+ .filter((found) => found.at >= 0)
16
+ .sort((a, b) => a.at - b.at)[0];
17
+ if (!hit) {
18
+ out.push({ text: rest, key: false });
19
+ break;
20
+ }
21
+ if (hit.at) out.push({ text: rest.slice(0, hit.at), key: false });
22
+ out.push({ text: hit.key, key: true });
23
+ rest = rest.slice(hit.at + hit.key.length);
24
+ }
25
+ return out;
26
+ };
7
27
 
8
28
  /**
9
- * A transparent subtitle layer, composited onto untouched picture.
10
- *
11
- * Everything here is expressed as a fraction of frame height, so the same
12
- * numbers hold for 768×1344 and 1080×1920. That is the whole reason this file
13
- * exists in pixels rather than in an ASS style: the subtitle renderer's own
14
- * `PlayRes` scaling was applied twice once already, and type three times too
15
- * large shipped in a sample film.
29
+ * The subtitle layer: Noto Sans SC 900, white or voice-coloured fill over a
30
+ * dark rim drawn beneath it, baseline about a quarter up the frame so the
31
+ * platform's own UI never covers it. Sizes are fractions of frame height.
16
32
  */
17
- export const Subtitles: React.FC<SubtitleProps> = ({
18
- cues,
19
- fontScale,
20
- bottomScale,
21
- fontFamily,
22
- }) => {
33
+ export const Subtitles: React.FC<{ cues: Cue[] }> = ({ cues }) => {
23
34
  const frame = useCurrentFrame();
24
35
  const { fps, height, width } = useVideoConfig();
25
36
  const now = frame / fps;
26
- const active = cues.find((cue: Cue) => now >= cue.start && now < cue.end);
27
-
28
- const fontSize = height * fontScale;
29
- // A dark rim on every side keeps white type legible over a bright frame
30
- // without a caption box, which is what vertical drama expects. It depends
31
- // only on the frame size, so it is built once — and it stays above the early
32
- // return, because a hook may not be skipped on the frames with no line.
33
- const shadow = useMemo(() => {
34
- const rim = Math.max(2, height * 0.0024);
35
- return [
36
- `0 0 ${rim * 3}px rgba(0,0,0,0.85)`,
37
- `${rim}px ${rim}px 0 rgba(0,0,0,0.92)`,
38
- `-${rim}px ${rim}px 0 rgba(0,0,0,0.92)`,
39
- `${rim}px -${rim}px 0 rgba(0,0,0,0.92)`,
40
- `-${rim}px -${rim}px 0 rgba(0,0,0,0.92)`,
41
- ].join(", ");
42
- }, [height]);
43
-
44
- // Checked against a line about to be filmed, so a missing font stops the
37
+ const cue = cues.find((item) => now >= item.start && now < item.end);
38
+ // Checked against a line about to be filmed, so a missing face stops the
45
39
  // render on the first subtitle frame rather than after the whole pass.
46
- assertFamilyResolves(fontFamily, active?.text ?? "");
47
- if (!active) return null;
48
-
49
- // A short lift on entry reads as the line arriving with the delivery. It is
50
- // over well before the first syllable ends, so it never delays reading.
51
- const age = now - active.start;
52
- const lift = Math.min(1, age / 0.12);
53
- const eased = 1 - Math.pow(1 - lift, 3);
40
+ assertFamilyResolves(SANS, cue?.text ?? "");
41
+ if (!cue) return null;
54
42
 
43
+ const u = height / 100;
44
+ // One line: long lines are split upstream; what still does not fit shrinks.
45
+ const size = Math.min(u * 4.3, (width * 0.9) / (lineEms(cue.text) * 1.04));
46
+ const age = now - cue.start;
47
+ const pop = spring({ frame: Math.round(age * fps), fps, config: { damping: 12, stiffness: 260, mass: 0.5 } });
55
48
  return (
56
- <AbsoluteFill
57
- style={{
58
- justifyContent: "flex-end",
59
- alignItems: "center",
60
- paddingBottom: height * bottomScale,
61
- paddingLeft: width * 0.06,
62
- paddingRight: width * 0.06,
63
- }}
64
- >
49
+ <AbsoluteFill style={{ justifyContent: "flex-end", alignItems: "center", paddingBottom: u * 24 }}>
65
50
  <div
66
51
  style={{
67
- fontFamily,
68
- fontSize,
69
- fontWeight: 700,
70
- color: "#ffffff",
52
+ fontFamily: SANS,
53
+ fontWeight: 900,
54
+ fontSize: size,
55
+ color: SUBTITLE.fill[cue.kind],
56
+ letterSpacing: size * 0.04,
71
57
  textAlign: "center",
72
- lineHeight: 1.28,
73
- letterSpacing: fontSize * 0.02,
74
- textShadow: shadow,
75
- opacity: eased,
76
- transform: `translateY(${(1 - eased) * fontSize * 0.25}px)`,
77
- // A long line wraps rather than running off the frame — the failure
78
- // that made the first shipped version unreadable.
79
- maxWidth: "100%",
80
- wordBreak: "break-word",
81
- whiteSpace: "pre-wrap",
58
+ lineHeight: 1.2,
59
+ whiteSpace: "nowrap",
60
+ WebkitTextStroke: `${u * 0.42}px ${SUBTITLE.rim}`,
61
+ paintOrder: "stroke fill",
62
+ textShadow: `0 ${u * 0.25}px ${u * 0.6}px rgba(0,0,0,0.55)`,
63
+ opacity: Math.min(1, age / 0.06),
64
+ transform: `scale(${0.94 + 0.06 * pop})`,
82
65
  }}
83
66
  >
84
- {active.text}
67
+ {runs(cue.text, cue.keys).map((run, index) =>
68
+ run.key ? (
69
+ <span
70
+ key={index}
71
+ style={{
72
+ color: SUBTITLE.keyword,
73
+ display: "inline-block",
74
+ // A 120 ms pop as the word lands.
75
+ transform: `scale(${1.08 + 0.15 * Math.max(0, 1 - age / 0.12)})`,
76
+ }}
77
+ >
78
+ {run.text}
79
+ </span>
80
+ ) : (
81
+ <span key={index}>{run.text}</span>
82
+ ),
83
+ )}
85
84
  </div>
86
85
  </AbsoluteFill>
87
86
  );
@@ -1,46 +1,56 @@
1
- import { continueRender, delayRender } from "remotion";
1
+ import { useEffect, useState } from "react";
2
+ import { cancelRender, continueRender, delayRender } from "remotion";
3
+ import { FONT_WAIT_MS, familyIsMissing, fontLoadProblem, settleWithin } from "./rules.mjs";
4
+
5
+ type Plan = { font: string; text: string }[];
2
6
 
3
7
  /**
4
- * Remotion captures a frame as soon as React has painted, so a font still being
5
- * fetched is simply absent from it. Nothing downstream reports that: the film
6
- * measures correct and reads wrong.
8
+ * Holds the first frame until every face in `plan` has the glyphs it will draw.
9
+ *
10
+ * Registered per mount rather than at module load, the way Remotion expects;
11
+ * bounded by `FONT_WAIT_MS`; and a face that is undeclared, fails, or never
12
+ * finishes cancels the render with the reason instead of shipping a fallback.
13
+ * Returns false until then, so nothing is drawn -- or measured -- early; the
14
+ * frame is released only after the ready tree has been committed.
7
15
  */
8
- export const waitForFonts = (): void => {
9
- const handle = delayRender("等待字体就绪");
10
- document.fonts.ready.then(
11
- () => continueRender(handle),
12
- () => continueRender(handle),
13
- );
16
+ export const useFaces = (plan: Plan): boolean => {
17
+ const [handle] = useState(() => delayRender("加载字体"));
18
+ const [ready, setReady] = useState(false);
19
+ useEffect(() => {
20
+ const loads = Promise.all(plan.map((entry) => document.fonts.load(entry.font, entry.text)));
21
+ settleWithin(loads, FONT_WAIT_MS).then((outcome) => {
22
+ const problem = fontLoadProblem(plan, outcome, (font, text) => document.fonts.check(font, text));
23
+ if (problem) cancelRender(new Error(problem));
24
+ else setReady(true);
25
+ });
26
+ }, [plan]);
27
+ useEffect(() => {
28
+ if (ready) continueRender(handle);
29
+ }, [ready, handle]);
30
+ return ready;
14
31
  };
15
32
 
16
33
  /**
17
- * `document.fonts.ready` promises that loading finished, not that the requested
18
- * family exists — a missing face raises nothing, the browser substitutes, and
19
- * the film ships in the wrong typeface. Measuring is the only reliable test:
20
- * identical widths against a family that cannot exist mean nothing resolved.
34
+ * A second guard at draw time: measuring is the only test of what the frame
35
+ * actually uses, so a stack whose families all fall back stops the render.
21
36
  */
22
- let familyChecked = "";
37
+ const checked = new Set<string>();
23
38
 
24
39
  export const assertFamilyResolves = (fontFamily: string, sample: string): void => {
25
40
  // Runs on every frame, so measure once per family.
26
- if (!sample || familyChecked === fontFamily) return;
27
- const canvas = document.createElement("canvas");
28
- const context = canvas.getContext("2d");
41
+ if (!sample || checked.has(fontFamily)) return;
42
+ const context = document.createElement("canvas").getContext("2d");
29
43
  if (!context) return;
30
44
 
31
- const measure = (family: string): number => {
32
- context.font = `700 64px ${family}`;
33
- return context.measureText(sample).width;
45
+ const measure = (stack: string, text: string): number => {
46
+ context.font = `700 64px ${stack}`;
47
+ return context.measureText(text).width;
34
48
  };
35
- // A name no font can carry, so it always lands on the browser's last resort.
36
- const fallbackOnly = measure('"__no_such_family__"');
37
- const requested = measure(`${fontFamily}, "__no_such_family__"`);
38
- if (requested === fallbackOnly) {
49
+ if (familyIsMissing(measure, fontFamily, sample)) {
39
50
  throw new Error(
40
- `字幕字体 ${fontFamily} 在渲染环境里一个都没装上,` +
41
- "画面会落到浏览器的兜底字体。装一款其中的字体," +
42
- "或给 render 传一个本机确实有的字体族。",
51
+ `字体 ${fontFamily} 在渲染环境里一个都没有,画面会落到浏览器的兜底字体。` +
52
+ "在 Remotion 工作区运行 npm install 装上字体包。",
43
53
  );
44
54
  }
45
- familyChecked = fontFamily;
55
+ checked.add(fontFamily);
46
56
  };