@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
@@ -1,10 +1,12 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { z } from "zod";
3
+ import { imageBlob } from "../channels/repo.js";
3
4
  import { outputPath } from "../storage/layout.js";
4
5
  // The provider call an image step makes: its request, the effort when one is chosen and, while
5
6
  // the project's Establishing image is on, that image's bytes as the visual reference.
6
7
  export function imageCall(deps, projectId, input, previewLabel) {
7
8
  const reference = referenceImage(deps, projectId, input);
9
+ const cast = castImages(deps, input);
8
10
  return {
9
11
  provider: input.provider,
10
12
  model: input.model,
@@ -12,6 +14,7 @@ export function imageCall(deps, projectId, input, previewLabel) {
12
14
  aspect: input.aspect,
13
15
  ...(input.thinking === undefined ? {} : { thinking: input.thinking }),
14
16
  ...(reference === undefined ? {} : { reference }),
17
+ ...(cast === undefined ? {} : { cast }),
15
18
  ...(previewLabel === undefined ? {} : { previewLabel }),
16
19
  };
17
20
  }
@@ -19,7 +22,7 @@ function referenceImage(deps, projectId, input) {
19
22
  if (input.reference === undefined)
20
23
  return undefined;
21
24
  // The step waits for the establishing image, so a missing one went away since.
22
- const missing = "The establishing image this image is drawn from is missing. Use Regenerate on the establishing image in the Images section (or upload it again in Edit project → Images), then use Retry stage.";
25
+ const missing = "The establishing image this image is drawn from is missing. Use Regenerate on the establishing image in the Images section (or upload it again in Edit project → Images), then use Try again.";
23
26
  if (input.reference.assetId === null)
24
27
  throw new Error(missing);
25
28
  const row = z
@@ -39,9 +42,25 @@ function referenceImage(deps, projectId, input) {
39
42
  }
40
43
  const mime = imageMime(bytes);
41
44
  if (mime === undefined)
42
- throw new Error("The establishing image is not a PNG or JPEG file, so it can't be sent as a reference. Upload a PNG or JPEG in Edit project → Images → Establishing image, then use Retry stage.");
45
+ throw new Error("The establishing image is not a PNG or JPEG file, so it can't be sent as a reference. Upload a PNG or JPEG in Edit project → Images → Establishing image, then use Try again.");
43
46
  return { bytes, mime };
44
47
  }
48
+ // The cast pictures the request names, read by hash from the database. They are never removed
49
+ // while a project may name them, so a missing one came from a backup of another install.
50
+ function castImages(deps, input) {
51
+ if (input.cast === undefined)
52
+ return undefined;
53
+ return input.cast.map((member) => ({
54
+ name: member.name,
55
+ description: member.description,
56
+ images: member.images.map((sha256) => {
57
+ const blob = imageBlob(deps.db, sha256);
58
+ if (blob === undefined)
59
+ throw new Error(`A picture of ${member.name} that this project was started with is not in this Slopify's database, so it can't be sent as a reference. Add the picture again in Channels → Cast, then start the video again from Play.`);
60
+ return blob;
61
+ }),
62
+ }));
63
+ }
45
64
  function imageMime(bytes) {
46
65
  const png = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
47
66
  if (png.every((value, index) => bytes[index] === value))
@@ -1,12 +1,18 @@
1
- import { statSync } from "node:fs";
1
+ import { rmSync, statSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { referenceKey } from "../admission/model.js";
5
5
  import { plainText } from "../article/plain.js";
6
6
  import { splitEndMatter } from "../article/split.js";
7
+ import { levelPieces } from "../loudness/level-pieces.js";
7
8
  import { joinNarration } from "../narration/concat.js";
9
+ import { applyPauses, planPieces } from "../narration/pauses.js";
8
10
  import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
9
11
  import { outputPath } from "../storage/layout.js";
12
+ import { probeDurationMs, runFfmpeg } from "../video/ffmpeg.js";
13
+ import { turnJoinArgs } from "../voices/join.js";
14
+ import { levelOperation } from "./recipe-loudness.js";
15
+ import { executeFigureCard } from "./runtime-figure-card.js";
10
16
  import { publishNarrationText } from "./runtime-narration-text.js";
11
17
  import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
12
18
  import { preparedResult, preparedText, preparedTexts, publishResult, } from "./runtime-publication.js";
@@ -20,7 +26,7 @@ export async function executeLocalRecipe(deps, context, piece) {
20
26
  const input = piece.input;
21
27
  if (input.kind === "provided") {
22
28
  if (input.assetId === null)
23
- throw new Error("A file you uploaded for this project is missing. Upload it again in Edit project, then Retry stage.");
29
+ throw new Error("A file you uploaded for this project is missing. Upload it again in Edit project, then Try again.");
24
30
  const row = z
25
31
  .object({ id: z.string(), project_id: z.string(), path: z.string(), created_at: z.string() })
26
32
  .parse(deps.db
@@ -46,12 +52,16 @@ export async function executeLocalRecipe(deps, context, piece) {
46
52
  return "done";
47
53
  }
48
54
  if (input.kind !== "local")
49
- throw new Error("Slopify hit an internal error (a local step was set up wrongly). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
55
+ throw new Error("Slopify hit an internal error (a local step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
56
+ if (input.operation === "figure-card-v1")
57
+ return executeFigureCard(deps, context, piece);
50
58
  if (input.operation === "narration-files-v1") {
51
59
  await publishNarrationText(deps, context, piece);
52
60
  return "done";
53
61
  }
54
- if (input.operation === "concat-narration") {
62
+ if (input.operation === "concat-narration" ||
63
+ input.operation === "concat-turns-v1" ||
64
+ input.operation === levelOperation) {
55
65
  await concatenate(deps, context, piece);
56
66
  return "done";
57
67
  }
@@ -78,19 +88,19 @@ export async function executeLocalRecipe(deps, context, piece) {
78
88
  });
79
89
  return "done";
80
90
  }
81
- throw new Error(`Slopify hit an internal error (no handler for the step "${input.operation}"). Retry stage; if it happens again, use Download diagnostics in Settings and report it.`);
91
+ throw new Error(`Slopify hit an internal error (no handler for the step "${input.operation}"). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
82
92
  }
83
93
  async function concatenate(deps, context, piece) {
84
94
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
85
95
  if (view === undefined)
86
- throw new Error("Slopify hit an internal error (the project version being built is missing). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
96
+ throw new Error("Slopify hit an internal error (the project version being built is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
87
97
  const row = deps.db
88
98
  .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
89
99
  .get(context.work.workId);
90
100
  const plan = executionPlan(deps, view, savedCatalogue(row?.recipe_context));
91
101
  const recipe = plan.recipes.find((candidate) => candidate.key === piece.key && candidate.fingerprint === piece.fingerprint);
92
102
  if (recipe === undefined)
93
- throw new Error("Slopify hit an internal error (the narration chunks to join no longer match the project's saved inputs). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
103
+ throw new Error("Slopify hit an internal error (the narration chunks to join no longer match the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
94
104
  const files = recipe.dependsOn.map((key) => {
95
105
  const input = view.pieces.find((candidate) => candidate.key === key &&
96
106
  candidate.selected &&
@@ -98,36 +108,73 @@ async function concatenate(deps, context, piece) {
98
108
  candidate.piece.state === "done" &&
99
109
  candidate.fingerprint === plan.recipes.find((value) => value.key === key)?.fingerprint);
100
110
  if (input?.assetId === null || input === undefined)
101
- throw new Error("One narration chunk has no saved audio, so the narration can't be joined together. Regenerate the missing chunk in Edit project → Narration, then Retry stage.");
111
+ throw new Error("One narration chunk has no saved audio, so the narration can't be joined together. Regenerate the missing chunk in Edit project → Narration, then Try again.");
102
112
  const asset = deps.db
103
113
  .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
104
114
  .get(input.assetId, context.work.projectId);
105
115
  return outputPath(deps.paths, context.work.projectId, z.string().parse(asset?.path));
106
116
  });
117
+ // Level the volume repeats the join its recipe names, from the same pieces, each levelled
118
+ // first (`recipe-loudness.ts`).
119
+ const level = recipe.input.kind === "local" && recipe.input.operation === levelOperation;
120
+ const [operation, values] = recipe.input.kind !== "local"
121
+ ? ["", undefined]
122
+ : level
123
+ ? z.tuple([z.string(), z.unknown(), z.unknown()]).parse(recipe.input.values)
124
+ : [recipe.input.operation, recipe.input.values];
107
125
  const pending = allocateAsset(deps, context.work.projectId, "narration.mp3");
126
+ const piecesDirectory = join(dirname(pending.absolutePath), "levelled");
108
127
  try {
109
- const durationMs = await joinNarration({ bin: deps.ffmpeg, log: deps.log }, {
110
- files,
111
- output: pending.absolutePath,
112
- listPath: join(dirname(pending.absolutePath), "parts.txt"),
113
- signal: context.signal,
114
- });
128
+ const run = { bin: deps.ffmpeg, log: deps.log, signal: context.signal };
129
+ const levelled = level ? await levelPieces(run, files, piecesDirectory) : undefined;
130
+ // Pauses between sentences: planned from the pieces as spoken, made in what is joined.
131
+ const pauses = pausesIn(values);
132
+ const paced = pauses === undefined
133
+ ? undefined
134
+ : await applyPauses(run, levelled?.files ?? files, paceScaled(await planPieces(run, files.map((file, at) => ({
135
+ file,
136
+ text: pieceText(plan.recipes.find((one) => one.key === recipe.dependsOn[at])),
137
+ next: pauses.next[at] ?? "end",
138
+ })), pauses.settings), operation === "concat-turns-v1" ? values : undefined), join(piecesDirectory, "paced"));
139
+ const joined = paced?.files ?? levelled?.files ?? files;
140
+ const durationMs = operation === "concat-turns-v1"
141
+ ? await joinTurns(run, joined, values, pending.absolutePath)
142
+ : await joinNarration({ bin: deps.ffmpeg, log: deps.log }, {
143
+ files: joined,
144
+ output: pending.absolutePath,
145
+ listPath: join(dirname(pending.absolutePath), "parts.txt"),
146
+ signal: context.signal,
147
+ reencode: level || paced !== undefined,
148
+ });
149
+ rmSync(piecesDirectory, { recursive: true, force: true });
115
150
  context.signal.throwIfAborted();
116
151
  const asset = sealAsset(deps, pending);
117
- const role = piece.key === "audio:intro"
118
- ? "audio_intro"
119
- : piece.key === "audio:outro"
120
- ? "audio_outro"
121
- : "audio_body";
152
+ const segment = /^(?:audio|level):(intro|outro)$/.exec(piece.key)?.[1] ?? "body";
153
+ const role = level
154
+ ? "audio_levelled"
155
+ : piece.key === "audio:intro"
156
+ ? "audio_intro"
157
+ : piece.key === "audio:outro"
158
+ ? "audio_outro"
159
+ : "audio_body";
160
+ const voice = view.revision.config.audio === undefined
161
+ ? {}
162
+ : {
163
+ provider: view.revision.config.audio.provider,
164
+ model: view.revision.config.audio.model,
165
+ voice: view.revision.config.audio.voice,
166
+ };
122
167
  await publishResult(deps, context, piece, [
123
- preparedResult(deps, context, piece, role, asset, durationMs, view.revision.config.audio === undefined
124
- ? {}
168
+ preparedResult(deps, context, piece, role, asset, durationMs, levelled === undefined
169
+ ? voice
125
170
  : {
126
- provider: view.revision.config.audio.provider,
127
- model: view.revision.config.audio.model,
128
- voice: view.revision.config.audio.voice,
171
+ ...voice,
172
+ segment: segment,
173
+ loudness: levelled.report,
129
174
  }),
130
175
  ], { durationMs }, asset);
176
+ if (level)
177
+ return;
131
178
  for (const key of recipe.dependsOn) {
132
179
  const retained = view.pieces.find((candidate) => candidate.key === key && candidate.selected && candidate.available);
133
180
  if (retained?.publicationId === null || retained === undefined)
@@ -140,6 +187,83 @@ async function concatenate(deps, context, piece) {
140
187
  }
141
188
  }
142
189
  finally {
190
+ rmSync(piecesDirectory, { recursive: true, force: true });
143
191
  discardPreparedAssets(deps, [pending]);
144
192
  }
145
193
  }
194
+ // The pauses a join's values ask for (`recipe-pauses.ts`): its third value, when there is one.
195
+ function pausesIn(values) {
196
+ const value = Array.isArray(values) ? values[2] : undefined;
197
+ const parsed = z
198
+ .tuple([
199
+ z.literal("pauses-v1"),
200
+ z.number(),
201
+ z.number(),
202
+ z.array(z.enum(["end", "turn", "sentence", "paragraph"])),
203
+ ])
204
+ .safeParse(value);
205
+ if (!parsed.success)
206
+ return undefined;
207
+ const [, sentenceSeconds, paragraphSeconds, next] = parsed.data;
208
+ return { settings: { sentenceSeconds, paragraphSeconds }, next };
209
+ }
210
+ // A speaker's pace plays their turn faster or slower, pauses included, so a turn's added
211
+ // silence is scaled by it to come out at the minimum.
212
+ function paceScaled(plans, turnValues) {
213
+ const layout = Array.isArray(turnValues) ? turnValues[1] : undefined;
214
+ if (!Array.isArray(layout))
215
+ return plans;
216
+ return plans.map((plan, at) => {
217
+ const pace = z.tuple([z.number().positive(), z.number()]).safeParse(layout[at]);
218
+ const factor = pace.success ? pace.data[0] : 1;
219
+ if (factor === 1)
220
+ return plan;
221
+ return {
222
+ ...plan,
223
+ padEnd: plan.padEnd * factor,
224
+ pauses: {
225
+ ...plan.pauses,
226
+ inserts: plan.pauses.inserts.map((one) => ({ ...one, seconds: one.seconds * factor })),
227
+ },
228
+ };
229
+ });
230
+ }
231
+ // The words a narration piece speaks, where its sentences end: the clean words of a request, or
232
+ // of a piece supplied as a file.
233
+ function pieceText(recipe) {
234
+ if (recipe === undefined)
235
+ return "";
236
+ const input = recipe.input;
237
+ // The words as said: a prepared request's delivery tags take no time of their own.
238
+ if (input.kind === "tts")
239
+ return (input.spokenText ?? input.text).replace(/\[[^\]]*\]/g, "");
240
+ if (input.kind === "provided" &&
241
+ Array.isArray(input.semantic) &&
242
+ typeof input.semantic[0] === "string")
243
+ return input.semantic[0];
244
+ return "";
245
+ }
246
+ // A multi-voice narration: every turn at its speaker's pace with the gap between turns, as the
247
+ // recipe laid it out (`recipe-voices.ts`), measured off the file that was written. `values` is
248
+ // the concat-turns-v1 recipe's own values.
249
+ export async function joinTurns(run, files, values, output) {
250
+ const layout = z
251
+ .tuple([z.unknown(), z.array(z.tuple([z.number().positive(), z.number().nonnegative()]))])
252
+ // The pauses between sentences may follow (`recipe-pauses.ts`); they were made already.
253
+ .rest(z.unknown())
254
+ .parse(values)[1];
255
+ if (layout.length !== files.length || files.length === 0)
256
+ throw new Error("Slopify hit an internal error (the speaker turns to join don't match their audio). Try again; if it happens again, use Download diagnostics in Settings and report it.");
257
+ await runFfmpeg({
258
+ bin: run.bin,
259
+ args: turnJoinArgs(files.map((path, index) => ({
260
+ path,
261
+ pace: layout[index]?.[0] ?? 1,
262
+ gapAfter: layout[index]?.[1] ?? 0,
263
+ })), output),
264
+ signal: run.signal,
265
+ log: run.log,
266
+ onProgress: () => { },
267
+ });
268
+ return probeDurationMs(run.bin, output, run.signal, run.log);
269
+ }
@@ -136,6 +136,10 @@ function futureKey(recipe) {
136
136
  return `narration:prepare:${recipe.input.preparation.segment}:future`;
137
137
  if (recipe.input.kind === "tts")
138
138
  return `audio:${recipe.input.segment}:future`;
139
+ // The descriptions unfold from the one step that stood in for them while the text was
140
+ // unwritten.
141
+ if (recipe.key.startsWith("narration:describe:") || recipe.key.startsWith("figure:card:"))
142
+ return "narration:describe:future";
139
143
  if (recipe.key.startsWith("research:chapter:"))
140
144
  return "research:planner";
141
145
  if (recipe.key === "article:continuation")
@@ -143,6 +147,10 @@ function futureKey(recipe) {
143
147
  // A short's images and render unfold from its image prompts, admitted with them: under the
144
148
  // pick on a first run, or on their own when one short is made again and the pick is kept.
145
149
  // The prompts unfold from the pick's answer.
150
+ // A short's review unfolds with the short it reviews.
151
+ const reviewed = /^review:(shorts:\d+)$/.exec(recipe.key);
152
+ if (reviewed?.[1] !== undefined)
153
+ return `${reviewed[1]}:prompts`;
146
154
  const clip = /^(shorts:\d+):(?:image:\d+|render)$/.exec(recipe.key);
147
155
  if (clip?.[1] !== undefined)
148
156
  return `${clip[1]}:prompts`;
@@ -3,7 +3,8 @@ import { insertManifestOutput, insertManifestPiece, selectOutputRecord, selectPi
3
3
  import { retainedNarrationPieces } from "./narration-history.js";
4
4
  import { matchingNarrationPiece } from "./narration-reuse.js";
5
5
  export function bindNarrationReuse(deps, view, recipe, ordinal) {
6
- if (recipe.input.kind === "local" && recipe.input.operation === "concat-narration") {
6
+ if (recipe.input.kind === "local" &&
7
+ (recipe.input.operation === "concat-narration" || recipe.input.operation === "concat-turns-v1")) {
7
8
  const completed = view.pieces.find((one) => one.available &&
8
9
  one.piece.state === "done" &&
9
10
  one.key === recipe.key &&
@@ -9,7 +9,7 @@ export function joinedNarration(parts) {
9
9
  export function narrationTextParts(view, plan, segment) {
10
10
  const concat = plan.recipes.find((row) => row.key === (segment === "body" ? "audio:body:concat" : `audio:${segment}`));
11
11
  if (concat === undefined)
12
- throw new Error(`Slopify hit an internal error (the ${segment} narration is missing from the project plan). Retry stage; if it happens again, use Download diagnostics in Settings and report it.`);
12
+ throw new Error(`Slopify hit an internal error (the ${segment} narration is missing from the project plan). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
13
13
  return concat.dependsOn.map((key) => {
14
14
  const recipe = plan.recipes.find((row) => row.key === key);
15
15
  const selected = view.pieces.find((row) => row.key === key &&
@@ -18,7 +18,7 @@ export function narrationTextParts(view, plan, segment) {
18
18
  row.piece.state === "done" &&
19
19
  row.fingerprint === recipe?.fingerprint);
20
20
  if (selected === undefined || recipe === undefined)
21
- throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Retry stage on it), or regenerate the missing chunk in Edit project → Narration.");
21
+ throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Try again on it), or regenerate the missing chunk in Edit project → Narration.");
22
22
  const input = recipe.input;
23
23
  if (input.kind === "provided" &&
24
24
  Array.isArray(input.semantic) &&
@@ -29,13 +29,13 @@ export function narrationTextParts(view, plan, segment) {
29
29
  requestText: null,
30
30
  };
31
31
  if (input.kind !== "tts")
32
- throw new Error("Slopify hit an internal error (a narration chunk's text was never worked out). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
32
+ throw new Error("Slopify hit an internal error (a narration chunk's text was never worked out). Try again; if it happens again, use Download diagnostics in Settings and report it.");
33
33
  const payload = z
34
34
  .object({ text: z.string().optional(), spokenText: z.string().optional() })
35
35
  .parse(JSON.parse(selected.piece.payload ?? "{}"));
36
36
  if (input.spokenText !== undefined &&
37
37
  (payload.spokenText !== input.spokenText || payload.text !== input.text))
38
- throw new Error("A narration chunk's audio no longer matches its prepared text. Regenerate that chunk in Edit project → Narration, then Retry stage.");
38
+ throw new Error("A narration chunk's audio no longer matches its prepared text. Regenerate that chunk in Edit project → Narration, then Try again.");
39
39
  return {
40
40
  logicalKey: input.logicalKey,
41
41
  spokenText: input.spokenText === undefined ? (payload.text ?? input.text) : (payload.spokenText ?? ""),
@@ -45,7 +45,7 @@ export function narrationTextParts(view, plan, segment) {
45
45
  }
46
46
  export async function publishNarrationText(deps, context, piece) {
47
47
  if (piece.input.kind !== "local")
48
- throw new Error("Slopify hit an internal error (the narration text file step was set up wrongly). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
48
+ throw new Error("Slopify hit an internal error (the narration text file step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
49
49
  const { segment } = z
50
50
  .object({ segment: z.enum(["body", "intro", "outro"]) })
51
51
  .parse(piece.input.values);
@@ -54,10 +54,10 @@ export async function publishNarrationText(deps, context, piece) {
54
54
  .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
55
55
  .get(context.work.workId);
56
56
  if (view === undefined)
57
- throw new Error("Slopify hit an internal error (the project version being narrated is missing). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
57
+ throw new Error("Slopify hit an internal error (the project version being narrated is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
58
58
  const plan = executionPlan(deps, view, savedCatalogue(row?.recipe_context));
59
59
  if (!plan.recipes.some((recipe) => recipe.key === piece.key && recipe.fingerprint === piece.fingerprint))
60
- throw new Error("Slopify hit an internal error (the narration text no longer matches the project's saved inputs). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
60
+ throw new Error("Slopify hit an internal error (the narration text no longer matches the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
61
61
  const parts = narrationTextParts(view, plan, segment);
62
62
  const outputs = preparedTexts(deps, context, piece, [
63
63
  ["narration_txt", `${segment}-narration.txt`, joinedNarration(parts)],
@@ -0,0 +1,28 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+ import { outputPath } from "../storage/layout.js";
4
+ // The file behind a picture the article names (`![alt](maps/relief.png)`): an image uploaded
5
+ // to the project under that file name. An article's pictures are otherwise web addresses
6
+ // Slopify never fetches, so a figure without its upload is described and drawn from its
7
+ // caption alone.
8
+ export function articlePicture(deps, view, named) {
9
+ if (named === undefined)
10
+ return undefined;
11
+ const name = basename((named.split(/[?#]/)[0] ?? "").replace(/\\/g, "/")).toLowerCase();
12
+ if (name === "")
13
+ return undefined;
14
+ for (const image of Object.values(view.revision.content.imageDefinitions)) {
15
+ if (image.source !== "provide" || image.assetId === null)
16
+ continue;
17
+ const row = deps.db
18
+ .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
19
+ .get(image.assetId, view.revision.projectId);
20
+ const path = typeof row?.path === "string" ? row.path : undefined;
21
+ if (path === undefined || basename(path).toLowerCase() !== name)
22
+ continue;
23
+ const file = outputPath(deps.paths, view.revision.projectId, path);
24
+ if (existsSync(file))
25
+ return file;
26
+ }
27
+ return undefined;
28
+ }
@@ -4,6 +4,9 @@ import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js"
4
4
  // Undefined for steps that name their own place (captions, export) or have no single one.
5
5
  export function pieceLabel(deps, context, piece) {
6
6
  const { key } = piece;
7
+ const variant = /^thumbnail:image:([23])$/.exec(key);
8
+ if (variant !== null)
9
+ return `Thumbnail ${variant[1] ?? ""}`;
7
10
  if (key === "thumbnail:image" || key.startsWith("thumbnail:"))
8
11
  return "Thumbnail";
9
12
  if (key === "reference:image")
@@ -14,6 +17,30 @@ export function pieceLabel(deps, context, piece) {
14
17
  return "Document";
15
18
  if (key === "youtube:description")
16
19
  return "YouTube description";
20
+ if (key.startsWith("review:")) {
21
+ const item = key.slice("review:".length);
22
+ const itemLabel = item === "narration"
23
+ ? "Narration"
24
+ : /^shorts:\d+$/.test(item)
25
+ ? `Short ${item.slice("shorts:".length)}`
26
+ : pieceLabel(deps, context, { key: item });
27
+ return `Review of ${itemLabel ?? "an item"}`;
28
+ }
29
+ if (key === "voices:files")
30
+ return "MP3 and M4B files";
31
+ if (key === "script:attribute")
32
+ return "Speaker split";
33
+ if (key === "narration:describe:future")
34
+ return "Narration descriptions";
35
+ const card = /^figure:card:(\d+)$/.exec(key);
36
+ if (card !== null)
37
+ return `On-screen card ${card[1] ?? ""}`;
38
+ const described = /^narration:describe:(\d+)$/.exec(key);
39
+ if (described !== null)
40
+ return `Narration description ${described[1] ?? ""}`;
41
+ const turn = /^audio:body:turn:(\d+):\d+$/.exec(key);
42
+ if (turn !== null)
43
+ return `Turn ${turn[1] ?? ""}`;
17
44
  if (key === "shorts:pick" || key === "shorts:future")
18
45
  return "Shorts";
19
46
  if (key === "animate:future")