@gentbajko/slopify 2.5.0 → 3.0.1

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 (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,492 @@
1
+ import { remark } from "remark";
2
+ import remarkGfm from "remark-gfm";
3
+ import { runFfmpeg } from "./ffmpeg.js";
4
+ // The 3.0 design tokens (`packages/web/src/styles/index.css`): the surface graphite, ink,
5
+ // the second ink, Slopify lime and the line colour, in the bundled Barlow.
6
+ export const defaultCardStyle = {
7
+ ground: "#19191c",
8
+ ink: "#ece9e2",
9
+ muted: "#a9a69e",
10
+ accent: "#a6d45c",
11
+ line: "#2a2a30",
12
+ font: "Barlow",
13
+ mono: "DejaVu Sans Mono",
14
+ };
15
+ // ceiling: the owner's eye at 1080p. Text never smaller than 2.4% of the short side (26 px on
16
+ // a 1080-line frame), so a card stays readable on a phone; a table with more rows than fit at
17
+ // that size says how many it left out.
18
+ const minShare = 0.024;
19
+ const rowSpacing = 1.55;
20
+ const lineSpacing = 1.35;
21
+ // Barlow's average advance is about half its size; a monospace face's is 0.6.
22
+ const textAdvance = 0.52;
23
+ const monoAdvance = 0.6;
24
+ export function cardLayout(input) {
25
+ const { width, height } = input;
26
+ const short = Math.min(width, height);
27
+ const margin = Math.round(short * 0.07);
28
+ const events = [];
29
+ let top = margin;
30
+ const label = labelOf(input);
31
+ if (label !== "") {
32
+ const size = Math.round(short * 0.03);
33
+ events.push(text(margin, top, 7, `{\\fs${String(size)}\\c${colour(input.style.accent)}}${assText(label)}`));
34
+ top += Math.round(size * 1.9);
35
+ }
36
+ const area = { x: margin, y: top, width: width - margin * 2, height: height - top - margin };
37
+ let box;
38
+ switch (input.kind) {
39
+ case "table":
40
+ events.push(...tableEvents(input, area));
41
+ break;
42
+ case "figure": {
43
+ const caption = figureCaption(input.source);
44
+ const size = Math.round(short * 0.032);
45
+ const lines = wrap(caption, Math.floor(area.width / (size * textAdvance)), 3);
46
+ const captionHeight = lines.length === 0 ? 0 : Math.round(lines.length * size * lineSpacing);
47
+ if (input.picture) {
48
+ box = {
49
+ x: area.x,
50
+ y: area.y,
51
+ width: area.width,
52
+ height: Math.max(1, area.height - captionHeight - (lines.length === 0 ? 0 : size)),
53
+ };
54
+ for (const [at, line] of lines.entries())
55
+ events.push(text(width / 2, area.y + area.height - captionHeight + at * size * lineSpacing, 8, `{\\fs${String(size)}\\c${colour(input.style.muted)}}${assText(line)}`));
56
+ }
57
+ else {
58
+ // No picture to show: the caption itself, large, as the figure's card.
59
+ const big = Math.round(short * 0.05);
60
+ const said = wrap(caption || "Figure", Math.floor(area.width / (big * textAdvance)), 6);
61
+ const start = area.y + (area.height - said.length * big * lineSpacing) / 2;
62
+ for (const [at, line] of said.entries())
63
+ events.push(text(width / 2, start + at * big * lineSpacing, 8, `{\\fs${String(big)}\\c${colour(input.style.ink)}}${assText(line)}`));
64
+ }
65
+ break;
66
+ }
67
+ case "math":
68
+ events.push(...mathEvents(input, area));
69
+ break;
70
+ case "code":
71
+ case "diagram":
72
+ events.push(...codeEvents(input, area));
73
+ break;
74
+ }
75
+ return { ass: script(input, events), ...(box === undefined ? {} : { box }) };
76
+ }
77
+ // The ffmpeg arguments that draw the card to `output` (a PNG) in `cwd`, which holds
78
+ // `card.ass` and a `fonts` folder.
79
+ export function cardArgs(input, layout, output, picture) {
80
+ const ground = `color=c=0x${input.style.ground.slice(1)}:s=${String(input.width)}x${String(input.height)}:d=1`;
81
+ const box = layout.box;
82
+ if (picture === undefined || box === undefined)
83
+ return [
84
+ ...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-f", "lavfi", "-i", ground],
85
+ ...["-vf", "ass=filename=card.ass:fontsdir=fonts", "-frames:v", "1", output],
86
+ ];
87
+ return [
88
+ ...[
89
+ "-hide_banner",
90
+ "-nostdin",
91
+ "-loglevel",
92
+ "error",
93
+ "-y",
94
+ "-f",
95
+ "lavfi",
96
+ "-i",
97
+ ground,
98
+ "-i",
99
+ picture,
100
+ ],
101
+ "-filter_complex",
102
+ [
103
+ `[1:v]scale=${String(box.width)}:${String(box.height)}:force_original_aspect_ratio=decrease:flags=lanczos,format=rgba[pic]`,
104
+ `[0:v][pic]overlay=x=${String(box.x)}+(${String(box.width)}-overlay_w)/2:y=${String(box.y)}+(${String(box.height)}-overlay_h)/2:format=auto,ass=filename=card.ass:fontsdir=fonts[v]`,
105
+ ].join(";"),
106
+ ...["-map", "[v]", "-frames:v", "1", output],
107
+ ];
108
+ }
109
+ export async function renderCard(run, write) {
110
+ const layout = cardLayout(run.input);
111
+ write(layout.ass);
112
+ await runFfmpeg({
113
+ bin: run.bin,
114
+ args: cardArgs(run.input, layout, run.output, run.picture),
115
+ cwd: run.cwd,
116
+ signal: run.signal,
117
+ log: run.log,
118
+ onProgress: () => undefined,
119
+ });
120
+ }
121
+ function labelOf(input) {
122
+ const section = input.section?.trim() ?? "";
123
+ if (input.kind === "code") {
124
+ const lang = /^```\s*([\w+#.-]+)/.exec(input.source)?.[1];
125
+ return [section, lang].filter((part) => part !== undefined && part !== "").join(" · ");
126
+ }
127
+ return section;
128
+ }
129
+ // A table as a clean grid: the header in the accent colour over a rule, a hairline between
130
+ // rows, numbers right-aligned. Sized to fit; past the smallest readable size the rows that do
131
+ // not fit are left out and counted.
132
+ function tableEvents(input, area) {
133
+ const rows = tableRows(input.source);
134
+ const header = rows[0] ?? [];
135
+ const body = rows.slice(1);
136
+ const columns = Math.max(1, ...rows.map((row) => row.length));
137
+ const short = Math.min(input.width, input.height);
138
+ const smallest = Math.round(short * minShare);
139
+ const largest = Math.round(short * 0.045);
140
+ // Every row but the header, and a line for what was left out when some are.
141
+ let shown = body.length;
142
+ let size = Math.floor(area.height / ((shown + 1) * rowSpacing));
143
+ if (size < smallest) {
144
+ size = smallest;
145
+ shown = Math.max(1, Math.floor(area.height / (size * rowSpacing)) - 2);
146
+ }
147
+ size = Math.min(size, largest);
148
+ const cells = (row) => Array.from({ length: columns }, (_value, at) => row[at] ?? "");
149
+ const kept = [cells(header), ...body.slice(0, shown).map(cells)];
150
+ // Each column as wide as its widest cell (at most 28 letters); cut to fit if need be.
151
+ const letters = Array.from({ length: columns }, (_value, at) => Math.min(28, Math.max(3, ...kept.map((row) => (row[at] ?? "").length))));
152
+ const gap = 1.6;
153
+ const wanted = (letters.reduce((sum, n) => sum + n, 0) + gap * columns) * size * textAdvance;
154
+ if (wanted > area.width)
155
+ size = Math.max(smallest, Math.floor((size * area.width) / wanted));
156
+ const scale = area.width /
157
+ Math.max(area.width, (letters.reduce((sum, n) => sum + n, 0) + gap * columns) * size * textAdvance);
158
+ const widths = letters.map((n) => (n + gap) * size * textAdvance * scale);
159
+ const room = (at) => Math.max(3, Math.floor((widths[at] ?? 0) / (size * textAdvance)) - 1);
160
+ const rowHeight = Math.round(size * rowSpacing);
161
+ const events = [];
162
+ // Centred across the card; a narrow table is not pushed into a corner.
163
+ const tableWidth = widths.reduce((sum, n) => sum + n, 0);
164
+ const left = area.x + Math.max(0, (area.width - tableWidth) / 2);
165
+ const right = left + tableWidth;
166
+ kept.forEach((row, index) => {
167
+ const y = area.y + index * rowHeight;
168
+ let x = left;
169
+ row.forEach((cell, at) => {
170
+ const value = clip(cell, room(at));
171
+ const numeric = index > 0 && /^[-+−]?[\d.,]+\s*%?$|^[-+−]?[\d.,]+\s*[a-zA-Zµ%/]{0,4}$/.test(cell.trim());
172
+ const cellWidth = widths[at] ?? 0;
173
+ const colourTag = index === 0 ? colour(input.style.accent) : colour(input.style.ink);
174
+ events.push(numeric
175
+ ? text(x + cellWidth - size * 0.8, y + rowHeight / 2, 6, `{\\fs${String(size)}\\c${colourTag}}${assText(value)}`)
176
+ : text(x, y + rowHeight / 2, 4, `{\\fs${String(size)}\\c${colourTag}${index === 0 ? "\\b1" : ""}}${assText(value)}`));
177
+ x += cellWidth;
178
+ });
179
+ // The rule under the header is the accent's; the rest are hairlines.
180
+ const under = y + rowHeight;
181
+ const thick = index === 0 ? Math.max(2, Math.round(size / 10)) : 1;
182
+ events.push(rule(left, under, right - left, thick, index === 0 ? input.style.accent : input.style.line));
183
+ });
184
+ const leftOut = body.length - Math.min(shown, body.length);
185
+ if (leftOut > 0)
186
+ events.push(text(left, area.y + kept.length * rowHeight + rowHeight / 2, 4, `{\\fs${String(size)}\\c${colour(input.style.muted)}\\i1}${assText(`…and ${String(leftOut)} more ${leftOut === 1 ? "row" : "rows"}`)}`));
187
+ return events;
188
+ }
189
+ function tableRows(markdown) {
190
+ const tree = remark().use(remarkGfm).parse(markdown);
191
+ const table = tree.children.find((node) => node.type === "table");
192
+ if (table === undefined)
193
+ return [];
194
+ return table.children.map((row) => row.children.map((cell) => plain(cell).trim()));
195
+ }
196
+ function plain(node) {
197
+ if ("value" in node && typeof node.value === "string")
198
+ return node.value;
199
+ if (node.type === "image")
200
+ return node.alt ?? "";
201
+ if ("children" in node)
202
+ return node.children.map((child) => plain(child)).join("");
203
+ return "";
204
+ }
205
+ // Code and drawings in the monospace face, one line per line, with simple colouring: comments
206
+ // muted, strings and numbers in the ink's warm second tone, keywords in the accent.
207
+ function codeEvents(input, area) {
208
+ const lines = fenced(input.source).split("\n");
209
+ const longest = Math.max(12, ...lines.map((line) => line.length));
210
+ const short = Math.min(input.width, input.height);
211
+ const smallest = Math.round(short * minShare * 0.9);
212
+ let size = Math.min(Math.round(short * 0.04), Math.floor(area.width / (longest * monoAdvance)), Math.floor(area.height / (lines.length * lineSpacing)));
213
+ let shown = lines.length;
214
+ if (size < smallest) {
215
+ size = smallest;
216
+ shown = Math.max(1, Math.floor(area.height / (size * lineSpacing)) - 1);
217
+ }
218
+ const room = Math.floor(area.width / (size * monoAdvance));
219
+ // A short program sits in the middle of the card rather than at its top.
220
+ const top = area.y + Math.max(0, (area.height - shown * size * lineSpacing) / 2);
221
+ const events = lines
222
+ .slice(0, shown)
223
+ .map((line, at) => text(area.x, top + at * size * lineSpacing, 7, `{\\fn${input.style.mono}\\fs${String(size)}\\c${colour(input.style.ink)}}${input.kind === "code"
224
+ ? coloured(clip(line, room), input.style)
225
+ : assText(clip(line, room), true)}`));
226
+ if (shown < lines.length)
227
+ events.push(text(area.x, top + shown * size * lineSpacing, 7, `{\\fs${String(size)}\\c${colour(input.style.muted)}\\i1}${assText(`…and ${String(lines.length - shown)} more lines`)}`));
228
+ return events;
229
+ }
230
+ const keywords = new Set("fn let mut pub use mod impl struct enum trait match if else for while loop return break continue in as where const static async await function var class extends new this def import from export type interface yield lambda try catch except finally raise throw public private protected void int float double bool boolean char string true false null None nil self Self package func go defer select case switch default".split(" "));
231
+ function coloured(line, style) {
232
+ const comment = /(\/\/|#(?!\[)|--\s).*$/.exec(line);
233
+ const code = comment === null ? line : line.slice(0, comment.index);
234
+ const out = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\b\d[\d._]*\b|\b[A-Za-z_]\w*\b|[^"'\w]+)/g, (token) => {
235
+ if (/^["']/.test(token))
236
+ return `{\\c${colour(style.muted)}}${assText(token, true)}{\\c${colour(style.ink)}}`;
237
+ if (/^\d/.test(token))
238
+ return `{\\c${colour(style.muted)}}${assText(token, true)}{\\c${colour(style.ink)}}`;
239
+ if (keywords.has(token))
240
+ return `{\\c${colour(style.accent)}}${token}{\\c${colour(style.ink)}}`;
241
+ return assText(token, true);
242
+ });
243
+ return comment === null
244
+ ? out
245
+ : `${out}{\\c${colour(style.muted)}\\i1}${assText(comment[0], true)}`;
246
+ }
247
+ // An equation set in readable text: TeX's commands as the symbols they stand for, powers
248
+ // and indices as superscripts and subscripts where Unicode has them, centred and large.
249
+ function mathEvents(input, area) {
250
+ const tex = input.source
251
+ .replace(/^\$\$|\$\$$/g, "")
252
+ .replace(/^\\\[|\\\]$/g, "")
253
+ .replace(/^```\s*\w*\n?|```$/g, "")
254
+ .trim();
255
+ // A paragraph with formulas in it shows the paragraph with its formulas set.
256
+ const said = /\$[^$]+\$/.test(tex)
257
+ ? tex.replace(/\$\$?([^$]+)\$\$?/g, (_all, inner) => typeset(inner))
258
+ : typeset(tex);
259
+ const short = Math.min(input.width, input.height);
260
+ const size = Math.round(short * (said.length > 60 ? 0.04 : 0.06));
261
+ const lines = wrap(said, Math.floor(area.width / (size * textAdvance)), 6);
262
+ const start = area.y + (area.height - lines.length * size * lineSpacing) / 2;
263
+ return lines.map((line, at) => text(input.width / 2, start + at * size * lineSpacing, 8, `{\\fs${String(size)}\\c${colour(input.style.ink)}}${assText(line)}`));
264
+ }
265
+ const symbols = {
266
+ alpha: "α",
267
+ beta: "β",
268
+ gamma: "γ",
269
+ delta: "δ",
270
+ epsilon: "ε",
271
+ varepsilon: "ε",
272
+ zeta: "ζ",
273
+ eta: "η",
274
+ theta: "θ",
275
+ iota: "ι",
276
+ kappa: "κ",
277
+ lambda: "λ",
278
+ mu: "μ",
279
+ nu: "ν",
280
+ xi: "ξ",
281
+ pi: "π",
282
+ rho: "ρ",
283
+ sigma: "σ",
284
+ tau: "τ",
285
+ upsilon: "υ",
286
+ phi: "φ",
287
+ varphi: "φ",
288
+ chi: "χ",
289
+ psi: "ψ",
290
+ omega: "ω",
291
+ Gamma: "Γ",
292
+ Delta: "Δ",
293
+ Theta: "Θ",
294
+ Lambda: "Λ",
295
+ Xi: "Ξ",
296
+ Pi: "Π",
297
+ Sigma: "Σ",
298
+ Phi: "Φ",
299
+ Psi: "Ψ",
300
+ Omega: "Ω",
301
+ cdot: "·",
302
+ times: "×",
303
+ div: "÷",
304
+ pm: "±",
305
+ mp: "∓",
306
+ le: "≤",
307
+ leq: "≤",
308
+ ge: "≥",
309
+ geq: "≥",
310
+ neq: "≠",
311
+ ne: "≠",
312
+ approx: "≈",
313
+ sim: "∼",
314
+ equiv: "≡",
315
+ propto: "∝",
316
+ infty: "∞",
317
+ partial: "∂",
318
+ nabla: "∇",
319
+ sum: "Σ",
320
+ prod: "Π",
321
+ int: "∫",
322
+ to: "→",
323
+ rightarrow: "→",
324
+ leftarrow: "←",
325
+ Rightarrow: "⇒",
326
+ in: "∈",
327
+ forall: "∀",
328
+ exists: "∃",
329
+ ldots: "…",
330
+ cdots: "⋯",
331
+ degree: "°",
332
+ circ: "°",
333
+ };
334
+ const superscripts = {
335
+ "0": "⁰",
336
+ "1": "¹",
337
+ "2": "²",
338
+ "3": "³",
339
+ "4": "⁴",
340
+ "5": "⁵",
341
+ "6": "⁶",
342
+ "7": "⁷",
343
+ "8": "⁸",
344
+ "9": "⁹",
345
+ "+": "⁺",
346
+ "-": "⁻",
347
+ "=": "⁼",
348
+ "(": "⁽",
349
+ ")": "⁾",
350
+ n: "ⁿ",
351
+ i: "ⁱ",
352
+ };
353
+ const subscripts = {
354
+ "0": "₀",
355
+ "1": "₁",
356
+ "2": "₂",
357
+ "3": "₃",
358
+ "4": "₄",
359
+ "5": "₅",
360
+ "6": "₆",
361
+ "7": "₇",
362
+ "8": "₈",
363
+ "9": "₉",
364
+ "+": "₊",
365
+ "-": "₋",
366
+ "=": "₌",
367
+ "(": "₍",
368
+ ")": "₎",
369
+ a: "ₐ",
370
+ e: "ₑ",
371
+ o: "ₒ",
372
+ x: "ₓ",
373
+ i: "ᵢ",
374
+ j: "ⱼ",
375
+ k: "ₖ",
376
+ n: "ₙ",
377
+ t: "ₜ",
378
+ s: "ₛ",
379
+ r: "ᵣ",
380
+ m: "ₘ",
381
+ p: "ₚ",
382
+ };
383
+ export function typeset(tex) {
384
+ let out = tex
385
+ .replace(/\\(left|right|big|Big|bigg|Bigg|displaystyle|,|;|!|quad|qquad)\b/g, " ")
386
+ .replace(/\\(mathrm|mathbf|mathit|text|operatorname)\{([^}]*)\}/g, "$2");
387
+ // Innermost first, so a fraction inside a root comes out whole.
388
+ for (let pass = 0; pass < 4; pass++)
389
+ out = out
390
+ .replace(/\\frac\{([^{}]*)\}\{([^{}]*)\}/g, (_all, a, b) => `${group(a)}/${group(b)}`)
391
+ .replace(/\\sqrt\{([^{}]*)\}/g, (_all, a) => `√${group(a)}`);
392
+ out = out
393
+ .replace(/\\([A-Za-z]+)/g, (_all, name) => symbols[name] ?? name)
394
+ .replace(/\^\{([^{}]*)\}|\^(\S)/g, (_all, a, b) => scripted(a ?? b ?? "", superscripts, "^"))
395
+ .replace(/_\{([^{}]*)\}|_(\S)/g, (_all, a, b) => scripted(a ?? b ?? "", subscripts, "_"))
396
+ .replace(/[{}]/g, "")
397
+ .replace(/\s*([=<>≤≥≈≠+×·−])\s*/g, " $1 ")
398
+ .replace(/\s+/g, " ")
399
+ .trim();
400
+ return out;
401
+ }
402
+ function group(text) {
403
+ const clean = text.trim();
404
+ return /^[\w.]+$/.test(clean) ? clean : `(${clean})`;
405
+ }
406
+ function scripted(text, map, mark) {
407
+ const letters = [...text];
408
+ return letters.every((letter) => map[letter] !== undefined)
409
+ ? letters.map((letter) => map[letter]).join("")
410
+ : `${mark}(${text})`;
411
+ }
412
+ function figureCaption(source) {
413
+ const lines = source.split("\n");
414
+ const captions = lines
415
+ .filter((line) => line.startsWith("Caption: "))
416
+ .map((line) => line.slice(9));
417
+ const alt = lines.find((line) => line.startsWith("Alt text: "))?.slice(10);
418
+ return (captions.length > 0 ? captions.join(" ") : (alt ?? "")).trim();
419
+ }
420
+ function fenced(source) {
421
+ return source
422
+ .replace(/^\s*(```|~~~)[^\n]*\n?/, "")
423
+ .replace(/\n?(```|~~~)\s*$/, "")
424
+ .replace(/\t/g, " ")
425
+ .replace(/\s+$/, "");
426
+ }
427
+ function wrap(textValue, width, most) {
428
+ const words = textValue.split(/\s+/).filter(Boolean);
429
+ const lines = [];
430
+ let line = "";
431
+ for (const word of words) {
432
+ if (line === "")
433
+ line = word;
434
+ else if (line.length + 1 + word.length <= width)
435
+ line += ` ${word}`;
436
+ else {
437
+ lines.push(line);
438
+ line = word;
439
+ }
440
+ }
441
+ if (line !== "")
442
+ lines.push(line);
443
+ if (lines.length <= most)
444
+ return lines;
445
+ const kept = lines.slice(0, most);
446
+ kept[most - 1] = clip(`${kept[most - 1] ?? ""} …`, width);
447
+ return kept;
448
+ }
449
+ function clip(value, room) {
450
+ return value.length <= room ? value : `${value.slice(0, Math.max(1, room - 1))}…`;
451
+ }
452
+ // Text as libass draws it literally: braces and backslashes escaped rather than read as
453
+ // override tags; code keeps its spaces, prose has them collapsed.
454
+ function assText(value, keepSpaces = false) {
455
+ const escaped = value.replace(/\\/g, "\\\\").replace(/\{/g, "\\{").replace(/\}/g, "\\}");
456
+ return keepSpaces
457
+ ? escaped.replace(/ {2,}/g, (run) => "\\h".repeat(run.length))
458
+ : escaped.replace(/\s+/g, " ");
459
+ }
460
+ function colour(hex) {
461
+ const match = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/.exec(hex);
462
+ if (match === null)
463
+ return "&HFFFFFF&";
464
+ const [, r, g, b] = match;
465
+ return `&H${(b ?? "").toUpperCase()}${(g ?? "").toUpperCase()}${(r ?? "").toUpperCase()}&`;
466
+ }
467
+ function text(x, y, align, body) {
468
+ return `Dialogue: 0,0:00:00.00,0:00:10.00,Card,,0,0,0,,{\\an${String(align)}\\pos(${String(Math.round(x))},${String(Math.round(y))})}${body}`;
469
+ }
470
+ function rule(x, y, width, thick, hex) {
471
+ const [x0, y0, x1, y1] = [x, y, x + width, y + thick].map((n) => String(Math.round(n)));
472
+ return `Dialogue: 0,0:00:00.00,0:00:10.00,Card,,0,0,0,,{\\an7\\pos(0,0)\\p1\\bord0\\shad0\\c${colour(hex)}}m ${x0} ${y0} l ${x1} ${y0} l ${x1} ${y1} l ${x0} ${y1}{\\p0}`;
473
+ }
474
+ function script(input, events) {
475
+ const { width, height, style } = input;
476
+ return `${[
477
+ "[Script Info]",
478
+ "ScriptType: v4.00+",
479
+ `PlayResX: ${String(width)}`,
480
+ `PlayResY: ${String(height)}`,
481
+ "WrapStyle: 2",
482
+ "ScaledBorderAndShadow: yes",
483
+ "",
484
+ "[V4+ Styles]",
485
+ "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding",
486
+ `Style: Card,${style.font.replace(/,/g, " ")},${String(Math.round(Math.min(width, height) * 0.03))},&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1`,
487
+ "",
488
+ "[Events]",
489
+ "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text",
490
+ ...events,
491
+ ].join("\n")}\n`;
492
+ }
@@ -0,0 +1,51 @@
1
+ // ceiling: the words compared at each end of a passage, and how many of them must match.
2
+ const edgeWords = 4;
3
+ const matchShare = 0.75;
4
+ export function passageSpans(words, passages) {
5
+ const flat = [];
6
+ for (const [index, word] of words.entries())
7
+ for (const token of tokens(word.text))
8
+ flat.push({ token, word: index });
9
+ let cursor = 0;
10
+ return passages.map((passage) => {
11
+ const wanted = tokens(passage);
12
+ if (wanted.length === 0)
13
+ return undefined;
14
+ const k = Math.min(edgeWords, wanted.length);
15
+ const head = wanted.slice(0, k);
16
+ const start = find(flat, head, cursor, flat.length);
17
+ if (start === undefined)
18
+ return undefined;
19
+ const tail = wanted.slice(-k);
20
+ const from = start + Math.max(0, Math.floor(wanted.length / 2) - k);
21
+ const to = Math.min(flat.length, start + Math.ceil(wanted.length * 1.5) + k);
22
+ const last = find(flat, tail, from, to);
23
+ const endToken = Math.min(flat.length - 1, last === undefined ? start + wanted.length - 1 : last + k - 1);
24
+ cursor = endToken + 1;
25
+ const first = words[flat[start]?.word ?? 0];
26
+ const final = words[flat[endToken]?.word ?? 0];
27
+ return first === undefined || final === undefined
28
+ ? undefined
29
+ : { start: first.start, end: Math.max(first.start, final.end) };
30
+ });
31
+ }
32
+ function find(flat, wanted, from, to) {
33
+ const need = Math.ceil(wanted.length * matchShare);
34
+ for (let at = from; at + wanted.length <= Math.max(to, from + wanted.length) && at + wanted.length <= flat.length; at++) {
35
+ let hits = 0;
36
+ for (const [offset, token] of wanted.entries())
37
+ if (flat[at + offset]?.token === token)
38
+ hits += 1;
39
+ if (hits >= need)
40
+ return at;
41
+ }
42
+ return undefined;
43
+ }
44
+ function tokens(text) {
45
+ return text
46
+ .toLowerCase()
47
+ .normalize("NFKD")
48
+ .replace(/\p{M}/gu, "")
49
+ .split(/[^\p{L}\p{N}]+/u)
50
+ .filter(Boolean);
51
+ }