@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,352 @@
1
+ import { remark } from "remark";
2
+ import remarkGfm from "remark-gfm";
3
+ // The article as it will be spoken, block by block, for "Describe tables and figures in the
4
+ // narration". Flattening the Markdown (`article/plain.ts`) keeps the words of prose but has
5
+ // nothing to say about a table, a picture, an equation or a program: it drops them or reads
6
+ // their bare cells. Here the document is walked instead, and every block a listener cannot
7
+ // follow read aloud is kept whole for the text model to describe in a sentence or four
8
+ // (`narration/describe.ts`); everything else becomes plain spoken text.
9
+ //
10
+ // Deterministic: the same Markdown always gives the same blocks, so a description is cached by
11
+ // its block and an unchanged article never asks for one again. The article's end matter (the
12
+ // sources and the pronunciation glossary) is cut off before this runs (`article/split.ts`), as
13
+ // it is for the flattened text, so neither is ever narrated.
14
+ export const describedKinds = ["table", "figure", "diagram", "math", "code"];
15
+ // Private-use characters around an equation's number while the document is parsed, so an
16
+ // underscore or asterisk inside an equation is never read as emphasis.
17
+ const open = "\uE000";
18
+ const close = "\uE001";
19
+ const placeholder = /\uE000(\d+)\uE001/g;
20
+ export function narrationBlocks(markdown, options = {}) {
21
+ const { text: protectedText, math } = protectMath(markdown);
22
+ const tree = remark().use(remarkGfm).parse(protectedText);
23
+ const walker = new Walker(protectedText, math, options);
24
+ walker.blocks(tree.children);
25
+ return walker.out;
26
+ }
27
+ export function describedBlocks(blocks) {
28
+ return blocks.filter((block) => block.kind !== "text");
29
+ }
30
+ // The narration: the text blocks as they are and each described block as its spoken passage,
31
+ // one paragraph each, in the flattened text's own shape. Null while any passage is unknown.
32
+ export function spokenNarration(blocks, passage) {
33
+ const paragraphs = [];
34
+ for (const block of blocks) {
35
+ const text = block.kind === "text" ? block.text : passage(block);
36
+ if (text === null)
37
+ return null;
38
+ const clean = text.trim();
39
+ if (clean !== "")
40
+ paragraphs.push(clean);
41
+ }
42
+ return paragraphs.length === 0 ? "" : `${paragraphs.join("\n\n")}\n`;
43
+ }
44
+ function protectMath(markdown) {
45
+ // Code keeps its dollars: a shell variable is not an equation.
46
+ const code = [];
47
+ const visit = (node) => {
48
+ if ((node.type === "code" || node.type === "inlineCode") && node.position) {
49
+ code.push([node.position.start.offset ?? 0, node.position.end.offset ?? 0]);
50
+ return;
51
+ }
52
+ if ("children" in node)
53
+ for (const child of node.children)
54
+ visit(child);
55
+ };
56
+ visit(remark().use(remarkGfm).parse(markdown));
57
+ const math = [];
58
+ const pattern = /\$\$([\s\S]+?)\$\$|\\\[([\s\S]+?)\\\]|\\\(([\s\S]+?)\\\)|(?<![\\$\w])\$(?![\s$])([^$\n]+?)(?<![\s\\])\$(?![\w$])/g;
59
+ const text = markdown.replace(pattern, (written, a, b, c, d, at) => {
60
+ if (code.some(([from, to]) => at < to && at + written.length > from))
61
+ return written;
62
+ const tex = String(a ?? b ?? c ?? d ?? "").trim();
63
+ math.push({ tex, written, display: a !== undefined || b !== undefined });
64
+ return `${open}${String(math.length - 1)}${close}`;
65
+ });
66
+ return { text, math };
67
+ }
68
+ class Walker {
69
+ markdown;
70
+ math;
71
+ options;
72
+ out = [];
73
+ section = null;
74
+ described = 0;
75
+ constructor(markdown, math, options) {
76
+ this.markdown = markdown;
77
+ this.math = math;
78
+ this.options = options;
79
+ }
80
+ blocks(nodes) {
81
+ for (let at = 0; at < nodes.length; at++) {
82
+ const node = nodes[at];
83
+ if (node === undefined)
84
+ continue;
85
+ switch (node.type) {
86
+ case "heading": {
87
+ const text = this.spoken(node.children).replace(placeholder, (_all, n) => {
88
+ const tex = this.math[Number(n)]?.tex ?? "";
89
+ return readable(tex) ?? tex;
90
+ });
91
+ this.section = text.trim() || this.section;
92
+ this.text(text);
93
+ break;
94
+ }
95
+ case "paragraph": {
96
+ if (node.children.some((child) => child.type === "image")) {
97
+ // A caption or legend written under the picture belongs to it.
98
+ const captions = [];
99
+ while (captions.length < 2) {
100
+ const next = nodes[at + captions.length + 1];
101
+ if (next?.type !== "paragraph" || !isCaption(next, this.markdown))
102
+ break;
103
+ captions.push(next);
104
+ }
105
+ at += captions.length;
106
+ this.figure(node, captions);
107
+ break;
108
+ }
109
+ this.paragraph(node);
110
+ break;
111
+ }
112
+ case "list":
113
+ this.list(node);
114
+ break;
115
+ case "blockquote":
116
+ this.quote(node);
117
+ break;
118
+ case "table":
119
+ this.describe("table", this.source(node));
120
+ break;
121
+ case "code":
122
+ this.code(node);
123
+ break;
124
+ case "html":
125
+ this.html(node);
126
+ break;
127
+ // A footnote's text, a link definition, a rule and front matter are never spoken.
128
+ default:
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ text(text) {
134
+ const clean = tidy(text);
135
+ if (clean !== "")
136
+ this.out.push({ kind: "text", text: clean });
137
+ }
138
+ describe(kind, source, extra = {}) {
139
+ this.described += 1;
140
+ this.out.push({
141
+ kind,
142
+ index: this.described,
143
+ source: source.trim(),
144
+ section: this.section,
145
+ ...(extra.image === undefined ? {} : { image: extra.image }),
146
+ ...(extra.lang === undefined ? {} : { lang: extra.lang }),
147
+ ...(extra.inline === undefined ? {} : { inline: extra.inline }),
148
+ });
149
+ }
150
+ paragraph(node) {
151
+ const raw = this.spoken(node.children);
152
+ const only = /^\s*\uE000(\d+)\uE001\s*$/.exec(raw);
153
+ const alone = only === null ? undefined : this.math[Number(only[1])];
154
+ if (alone !== undefined) {
155
+ this.describe("math", alone.written);
156
+ return;
157
+ }
158
+ const spans = [...raw.matchAll(placeholder)].map((match) => this.math[Number(match[1])]);
159
+ if (spans.some((span) => span === undefined || span.display || readable(span.tex) === null)) {
160
+ // Said in words by the model, the rest of the paragraph with it.
161
+ this.describe("math", this.source(node), { inline: true });
162
+ return;
163
+ }
164
+ this.text(raw.replace(placeholder, (_all, n) => readable(this.math[Number(n)]?.tex ?? "") ?? ""));
165
+ }
166
+ figure(node, captions) {
167
+ const image = node.children.find((child) => child.type === "image");
168
+ const lines = [];
169
+ for (const child of node.children)
170
+ if (child.type === "image" && child.alt?.trim())
171
+ lines.push(`Alt text: ${child.alt.trim()}`);
172
+ const beside = tidy(this.restore(this.spoken(node.children.filter((child) => child.type !== "image"))));
173
+ if (beside !== "")
174
+ lines.push(`Caption: ${beside}`);
175
+ for (const caption of captions) {
176
+ const text = tidy(this.restore(this.spoken(caption.children)));
177
+ if (text !== "")
178
+ lines.push(`Caption: ${text}`);
179
+ }
180
+ this.describe("figure", lines.join("\n") || "A picture with no caption.", {
181
+ ...(image?.url ? { image: image.url } : {}),
182
+ });
183
+ }
184
+ html(node) {
185
+ // An article pasted from the web may carry its figures as HTML.
186
+ const img = /<img\b[^>]*>/i.exec(node.value);
187
+ if (img === null)
188
+ return;
189
+ const attribute = (name) => new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)')`, "i")
190
+ .exec(img[0])
191
+ ?.slice(2)
192
+ .find(Boolean);
193
+ const caption = /<figcaption[^>]*>([\s\S]*?)<\/figcaption>/i.exec(node.value)?.[1];
194
+ const lines = [
195
+ ...(attribute("alt")?.trim() ? [`Alt text: ${attribute("alt")?.trim() ?? ""}`] : []),
196
+ ...(caption?.trim() ? [`Caption: ${tidy(caption.replace(/<[^>]+>/g, " "))}`] : []),
197
+ ];
198
+ const src = attribute("src");
199
+ this.describe("figure", lines.join("\n") || "A picture with no caption.", {
200
+ ...(src ? { image: src } : {}),
201
+ });
202
+ }
203
+ code(node) {
204
+ const lang = node.lang?.trim().toLowerCase() ?? "";
205
+ const source = this.restore(this.source(node));
206
+ const named = lang === "" ? {} : { lang };
207
+ if (lang === "mermaid" ||
208
+ (["", "text", "txt", "ascii", "plain"].includes(lang) && isDrawing(node.value)))
209
+ this.describe("diagram", source, named);
210
+ else if (["math", "latex", "tex", "katex"].includes(lang))
211
+ this.describe("math", source);
212
+ else if (this.options.code !== "skip")
213
+ this.describe("code", source, named);
214
+ }
215
+ quote(node) {
216
+ const start = this.out.length;
217
+ this.blocks(node.children);
218
+ // The quoted words are spoken in quotation marks, which a voice reads as a quote.
219
+ for (let at = start; at < this.out.length; at++) {
220
+ const block = this.out[at];
221
+ if (block?.kind === "text")
222
+ this.out[at] = { kind: "text", text: `“${block.text}”` };
223
+ }
224
+ }
225
+ list(node) {
226
+ const items = [];
227
+ const flush = () => {
228
+ if (items.length > 0)
229
+ this.text(this.sentences(items.splice(0), node.ordered === true));
230
+ };
231
+ for (const item of node.children) {
232
+ const words = [];
233
+ for (const child of item.children) {
234
+ if (child.type === "paragraph" && !child.children.some((one) => one.type === "image")) {
235
+ const raw = this.spoken(child.children);
236
+ if (!raw.includes(open)) {
237
+ words.push(raw);
238
+ continue;
239
+ }
240
+ }
241
+ // A table, a picture or a nested list inside an item is its own block, after the
242
+ // item's words.
243
+ if (words.length > 0)
244
+ items.push(tidy(words.splice(0).join(" ")));
245
+ flush();
246
+ this.blocks([child]);
247
+ }
248
+ if (words.length > 0)
249
+ items.push(tidy(words.join(" ")));
250
+ }
251
+ flush();
252
+ }
253
+ sentences(items, ordered) {
254
+ const english = this.options.language === undefined || this.options.language === "en";
255
+ const kept = items.filter((item) => item !== "");
256
+ if (!english || kept.length < 2)
257
+ return kept.map(sentence).join(" ");
258
+ if (ordered)
259
+ return kept
260
+ .map((item, at) => sentence(`${at === 0 ? "First" : at === kept.length - 1 ? "Finally" : "Then"}, ${item}`))
261
+ .join(" ");
262
+ // Short items read as one sentence, "a, b and c"; longer ones one sentence each.
263
+ if (kept.every((item) => item.split(/\s+/).length <= 5 && !/[.!?;:,—–]/.test(item))) {
264
+ const joined = `${kept.slice(0, -1).join(", ")} and ${kept.at(-1) ?? ""}`;
265
+ return sentence(joined.charAt(0).toUpperCase() + joined.slice(1));
266
+ }
267
+ return kept.map(sentence).join(" ");
268
+ }
269
+ // The words of inline Markdown: link text without its address, no footnote markers, no
270
+ // bare URLs. Equations stay as placeholders for the caller to decide on.
271
+ spoken(nodes) {
272
+ return nodes.map((node) => this.inline(node)).join("");
273
+ }
274
+ inline(node) {
275
+ switch (node.type) {
276
+ case "text":
277
+ case "inlineCode":
278
+ return node.value;
279
+ case "emphasis":
280
+ case "strong":
281
+ case "delete":
282
+ return this.spoken(node.children);
283
+ case "link": {
284
+ const text = this.spoken(node.children);
285
+ return isAddress(text, node.url) ? "" : text;
286
+ }
287
+ case "linkReference": {
288
+ const text = this.spoken(node.children);
289
+ return /^\s*\^?\d+\s*$/.test(text) ? "" : text;
290
+ }
291
+ case "image":
292
+ case "imageReference":
293
+ return node.alt ?? "";
294
+ case "break":
295
+ return "\n";
296
+ default:
297
+ return "";
298
+ }
299
+ }
300
+ source(node) {
301
+ const from = node.position?.start.offset;
302
+ const to = node.position?.end.offset;
303
+ return from === undefined || to === undefined
304
+ ? ""
305
+ : this.restore(this.markdown.slice(from, to));
306
+ }
307
+ restore(text) {
308
+ return text.replace(placeholder, (_all, n) => this.math[Number(n)]?.written ?? "");
309
+ }
310
+ }
311
+ function isCaption(node, markdown) {
312
+ if (node.children.length === 1 && node.children[0]?.type === "emphasis")
313
+ return true;
314
+ const from = node.position?.start.offset ?? 0;
315
+ const head = markdown.slice(from, from + 40).replace(/^[*_\s]+/, "");
316
+ return /^(figure|fig\.|chart|graph|diagram|image|legend|caption|source)\b/i.test(head);
317
+ }
318
+ // An ASCII or box-drawing chart in a plain fence: box characters, or lines that are mostly
319
+ // rules and bars.
320
+ function isDrawing(value) {
321
+ if (/[─│┌┐└┘├┤┬┴┼═║╔╗╚╝▲▼►◄█░▒▓]/.test(value))
322
+ return true;
323
+ const lines = value.split("\n").filter((line) => line.trim() !== "");
324
+ return (lines.filter((line) => /[-+|*]{3,}|[|+].*[|+]/.test(line)).length >=
325
+ Math.max(3, lines.length / 2));
326
+ }
327
+ // A link whose text is its own address, or any bare URL: nothing a listener can use.
328
+ function isAddress(text, url) {
329
+ const clean = text.trim();
330
+ return clean === "" || clean === url || /^(https?:\/\/|www\.|mailto:)/i.test(clean);
331
+ }
332
+ const greek = /\\(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Phi|Psi|Omega)\b/g;
333
+ // A short inline symbol read as it is written ("$T$" is "T", "$\alpha$" is "alpha"), or null
334
+ // when it is a formula to be said in words.
335
+ function readable(tex) {
336
+ const plain = tex.replace(greek, (_all, name) => name.toLowerCase()).trim();
337
+ return plain.length <= 20 && /^[\p{L}\p{N}\s.,%]*$/u.test(plain) ? plain : null;
338
+ }
339
+ function sentence(text) {
340
+ const clean = text.trim();
341
+ return clean === "" || /[.!?…:;]["”’)]?$/.test(clean) ? clean : `${clean}.`;
342
+ }
343
+ // Footnote markers and bare addresses out, then the spacing they leave behind.
344
+ function tidy(text) {
345
+ return text
346
+ .replace(/\s?\[(?:\^?[\w-]+)(?:\s*[,–-]\s*\^?\d+)*\](?!\()/g, (marker) => /\[\^|\[\d/.test(marker) ? "" : marker)
347
+ .replace(/\s?\b(?:https?:\/\/|www\.)[^\s)]+/g, "")
348
+ .replace(/[ \t]+([.,;:!?])/g, "$1")
349
+ .replace(/[ \t]{2,}/g, " ")
350
+ .replace(/\(\s*\)/g, "")
351
+ .trim();
352
+ }
@@ -20,9 +20,20 @@ export function chunkNarration(text, chunking) {
20
20
  }
21
21
  }
22
22
  // What "N words" counts: runs of non-space. Exported because it is half of the rule -
23
- // a test that asserts a chunk fits in N has to count the same way the cut did.
23
+ // a test that asserts a chunk fits in N has to count the same way the cut did. Chinese,
24
+ // Japanese, Thai and the other scripts written without spaces are counted by the platform's
25
+ // own word breaker instead, or a whole paragraph would be one "word"; text without them
26
+ // (every English narration) is counted exactly as before.
27
+ const unspaced = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
24
28
  export function wordsIn(text) {
25
- return text.split(/\s+/).filter((word) => word !== "").length;
29
+ if (!unspaced.test(text))
30
+ return text.split(/\s+/).filter((word) => word !== "").length;
31
+ const segmenter = new Intl.Segmenter("en", { granularity: "word" });
32
+ let count = 0;
33
+ for (const part of segmenter.segment(text))
34
+ if (part.isWordLike === true)
35
+ count += 1;
36
+ return count;
26
37
  }
27
38
  export function sameChunking(left, right) {
28
39
  const a = left ?? defaultChunking;
@@ -52,11 +63,12 @@ function sentenceRuns(text, budget, measure) {
52
63
  // The provider-specific planning layer applies any hard request limit afterwards.
53
64
  return nonEmpty(chunks);
54
65
  }
55
- // ceiling: segmented as English. `Intl.Segmenter` is the platform's own sentence breaker
56
- // (ICU ships with Node), so no dependency and no regex full of abbreviations; the locale
57
- // is fixed because a run carries no language and the default one varies by machine. The
58
- // upgrade is a language on the run configuration, passed through to here.
59
- function* sentences(text) {
66
+ // `Intl.Segmenter` is the platform's own sentence breaker (ICU ships with Node), so no
67
+ // dependency and no regex full of abbreviations. Its sentence rules are Unicode's (UAX #29)
68
+ // for every language: "。", "!", "।", "؟" and "¿…?" end or open sentences whatever the
69
+ // locale, so the project language needs no locale here, and "en" keeps it the same on every
70
+ // machine (the default locale varies).
71
+ export function* sentences(text) {
60
72
  // Built per call rather than held at module scope: the standards forbid a module-level
61
73
  // singleton, and constructing one costs microseconds against a call that just read an
62
74
  // article off disk.
@@ -46,10 +46,10 @@ export async function joinNarration(deps, input) {
46
46
  const first = input.files[0];
47
47
  if (first === undefined) {
48
48
  // The stage refuses an empty narration before it gets here, so this is a bug.
49
- throw new Error("Slopify hit an internal error (there is no narration audio to join). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
49
+ throw new Error("Slopify hit an internal error (there is no narration audio to join). Try again; if it happens again, use Download diagnostics in Settings and report it.");
50
50
  }
51
51
  mkdirSync(dirname(input.output), { recursive: true, mode: 0o700 });
52
- if (input.files.length === 1) {
52
+ if (input.files.length === 1 && input.reencode !== true) {
53
53
  // Whole-text mode, and any run whose article is one paragraph: there is nothing to
54
54
  // join, and re-encoding the only chunk would lose quality for no reason.
55
55
  input.signal.throwIfAborted();
@@ -0,0 +1,45 @@
1
+ import { withLanguage } from "../../kernel/ports/languages.js";
2
+ const instructions = {
3
+ table: "Describe this table for a listener in 1–4 sentences in the narration's language and tone; give the pattern and the notable values, not every cell; never read it row by row.",
4
+ figure: "Describe this figure for a listener in 1–3 sentences in the narration's language and tone, from its caption, alt text and legend (and the attached picture, when there is one): say what it shows and what to take from it. Never say that a figure shows something without saying what; explain colours or symbols only through what they stand for.",
5
+ diagram: "Describe this diagram or chart for a listener in 1–3 sentences in the narration's language and tone: what it connects or compares and what it tells us. Never read labels, arrows or axis ticks one by one.",
6
+ math: "Say what this equation means for a listener in 1–3 sentences in the narration's language and tone: how the quantities depend on each other (for example, “evaporation grows with temperature and falls with humidity”), not symbol by symbol. A single short symbol may be read as it is.",
7
+ code: "Summarise what this code does for a listener in one or two sentences in the narration's language and tone, naming the language when it helps (for example, “a Rust function that carves river channels by lowering the terrain along the flow”). Never read code, symbols or names of variables aloud.",
8
+ };
9
+ const inlineMath = "Rewrite this paragraph so it can be read aloud, in the narration's language and tone: keep its words, but say each formula's meaning in words rather than symbol by symbol. A single short symbol may be read as it is.";
10
+ const answerRules = "Answer with the spoken passage only: plain sentences a narrator reads out, no Markdown, no lists, no headings, no quotation marks around the whole answer, no preamble such as “Here is”.";
11
+ export function describeInstruction(block) {
12
+ return block.kind === "math" && block.inline === true ? inlineMath : instructions[block.kind];
13
+ }
14
+ export function describeMessages(block, style, language) {
15
+ const guidance = style.trim();
16
+ const system = [
17
+ describeInstruction(block),
18
+ answerRules,
19
+ ...(guidance === ""
20
+ ? []
21
+ : [
22
+ `Style guidance for the narration (follow its tone; ignore anything about formats or cues):\n${guidance}`,
23
+ ]),
24
+ ].join("\n\n");
25
+ const user = [
26
+ ...(block.section === null ? [] : [`Section: ${block.section}`]),
27
+ ...(block.lang === undefined ? [] : [`Language of the code: ${block.lang}`]),
28
+ block.source,
29
+ ].join("\n\n");
30
+ return withLanguage([
31
+ { role: "system", content: system },
32
+ { role: "user", content: user },
33
+ ], language);
34
+ }
35
+ // The passage as the narration says it: one paragraph, no Markdown a voice would read out.
36
+ export function spokenPassage(answer) {
37
+ return answer
38
+ .replace(/```[\s\S]*?```/g, " ")
39
+ .replace(/^\s*(?:[-*+]|\d+[.)]|#{1,6})\s+/gm, "")
40
+ .replace(/[*_`#|]/g, "")
41
+ .replace(/\s+/g, " ")
42
+ .trim()
43
+ .replace(/^["“](.*)["”]$/s, "$1")
44
+ .trim();
45
+ }
@@ -0,0 +1,63 @@
1
+ // Pauses between sentences. Some voices (Inworld's TTS-2 among them) run one sentence into the
2
+ // next with barely a breath, so a narration sounds rushed. With a minimum set, the join finds
3
+ // the quiet after each sentence in every narration piece and lengthens it to at least that
4
+ // long by adding silence in the middle of it (`pauses.ts`): an existing pause is never
5
+ // shortened and a word is never cut. It happens in the join, before the word timing reads the
6
+ // narration, so the captions, the word-by-word highlights, the cuts, the shorts' clips, the
7
+ // chapters and the M4B's marks are timed on the paced narration and stay in step with it.
8
+ //
9
+ // A paragraph's minimum is separate and 0 (the sentence minimum only) unless set: the pause
10
+ // between paragraphs is mostly the narration prompt's and the voice's to make.
11
+ //
12
+ // Browser-safe: Play and Edit project read these limits. A run without either has neither on
13
+ // its config, which is what every project saved before them was, and the joins keep their
14
+ // fingerprints then.
15
+ // ceiling: a quarter second reads brisk, 0.35 to 0.45 natural or documentary, 0.5 to 0.8 an
16
+ // audiobook's relaxed pace, and above 0.8 sleep content; beyond 2 s it stops sounding like a
17
+ // pause between sentences.
18
+ export const defaultSentencePauseSeconds = 0.4;
19
+ export const pauseSecondsMax = 2;
20
+ export const pauseSecondsStep = 0.05;
21
+ // What a run's config asks for; undefined when neither minimum is set.
22
+ export function pausesOf(config) {
23
+ const sentenceSeconds = config.sentencePauseSeconds ?? 0;
24
+ const paragraphSeconds = config.paragraphPauseSeconds ?? 0;
25
+ return sentenceSeconds > 0 || paragraphSeconds > 0
26
+ ? { sentenceSeconds, paragraphSeconds }
27
+ : undefined;
28
+ }
29
+ export function pauseProblem(value, name) {
30
+ const steps = value / pauseSecondsStep;
31
+ return Number.isFinite(value) &&
32
+ value >= 0 &&
33
+ value <= pauseSecondsMax &&
34
+ Math.abs(steps - Math.round(steps)) < 1e-6
35
+ ? undefined
36
+ : `The pause between ${name} must be between 0 and ${String(pauseSecondsMax)} seconds, in steps of ${String(pauseSecondsStep)}. Change it under Play → Video and style, or Edit project → Pauses and volume.`;
37
+ }
38
+ export function pauseFields(config) {
39
+ const fields = [];
40
+ if (config.sentencePauseSeconds !== undefined) {
41
+ const problem = pauseProblem(config.sentencePauseSeconds, "sentences");
42
+ if (problem !== undefined)
43
+ fields.push({ field: "sentencePauseSeconds", message: problem });
44
+ }
45
+ if (config.paragraphPauseSeconds !== undefined) {
46
+ const problem = pauseProblem(config.paragraphPauseSeconds, "paragraphs");
47
+ if (problem !== undefined)
48
+ fields.push({ field: "paragraphPauseSeconds", message: problem });
49
+ }
50
+ return fields;
51
+ }
52
+ // Play's typed pauses as a run's config holds them: absent text is the default (the sentence
53
+ // default, no paragraph minimum), a value of 0 is left off, and text that is not a number is
54
+ // NaN, which the rule refuses in place.
55
+ export function pausesOfForm(form) {
56
+ const typed = (raw, fallback) => raw === undefined || raw.trim() === "" ? fallback : Number(raw.trim().replace(",", "."));
57
+ const sentence = typed(form.sentencePause, defaultSentencePauseSeconds);
58
+ const paragraph = typed(form.paragraphPause, 0);
59
+ return {
60
+ ...(sentence === 0 ? {} : { sentencePauseSeconds: sentence }),
61
+ ...(paragraph === 0 ? {} : { paragraphPauseSeconds: paragraph }),
62
+ };
63
+ }