@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
@@ -3,16 +3,22 @@ import { transact } from "../../kernel/db/tx.js";
3
3
  import { sourceOf } from "../admission/model.js";
4
4
  import { projectById } from "../admission/repo.js";
5
5
  import { detectSlots } from "../admission/substitute.js";
6
+ import { projectChannelId } from "../channels/repo.js";
6
7
  import { listCheckpoints } from "../checkpoints/repo.js";
7
8
  import { documentThemeOf } from "../document/model.js";
9
+ import { imageScaleForm } from "../images/scale.js";
8
10
  import { requestHash } from "../play-drafts/repo.js";
11
+ import { reviewSettingsForm } from "../reviews/model.js";
9
12
  import { currentRevisionId, revisionById } from "../revisions/repo.js";
10
13
  import { shortsExtrasForm } from "../shorts/model.js";
14
+ import { ambientBedFormOf } from "../video/ambient-bed.js";
15
+ import { usesScriptPrompt } from "../voices/model.js";
16
+ import { templateNameMax } from "./schema.js";
11
17
  import { createTemplate, readTemplate } from "./service.js";
12
18
  export const projectTemplateCreateSchema = z
13
19
  .object({
14
20
  id: z.uuid(),
15
- name: z.string().trim().min(1).max(200),
21
+ name: z.string().trim().min(1).max(templateNameMax),
16
22
  projectId: z.string().min(1).max(64),
17
23
  revisionId: z.string().min(1).max(64),
18
24
  })
@@ -47,7 +53,11 @@ export function createTemplateFromProject(deps, input) {
47
53
  const result = createTemplate(deps, {
48
54
  id: parsed.data.id,
49
55
  name: parsed.data.name,
50
- document: documentFromProject(deps, revision),
56
+ // The project's channel, so the template lands where the video came from.
57
+ document: {
58
+ ...documentFromProject(deps, revision),
59
+ channelId: projectChannelId(deps.db, project.id),
60
+ },
51
61
  });
52
62
  if (result.ok)
53
63
  deps.db
@@ -56,7 +66,7 @@ export function createTemplateFromProject(deps, input) {
56
66
  return result;
57
67
  });
58
68
  }
59
- function documentFromProject(deps, revision) {
69
+ export function documentFromProject(deps, revision) {
60
70
  const config = revision.config;
61
71
  const prompts = [];
62
72
  const entries = [];
@@ -75,14 +85,21 @@ function documentFromProject(deps, revision) {
75
85
  updatedAt: revision.createdAt,
76
86
  });
77
87
  };
78
- addPrompt("article", config.articlePrompt, "article");
88
+ addPrompt(usesScriptPrompt(config) ? "script" : "article", config.articlePrompt, "article");
79
89
  addPrompt("narration", config.narrationPrompt, "narration");
80
90
  addPrompt("description", config.descriptionPrompt, "description");
91
+ for (const [stage, picked] of Object.entries(config.reviews?.stages ?? {}))
92
+ addPrompt("review", picked.prompt, `review.${stage}`);
81
93
  const definitions = revision.content.imageOrder.flatMap((key) => {
82
94
  const definition = revision.content.imageDefinitions[key];
83
95
  return definition === undefined ? [] : [definition];
84
96
  });
85
- const imagePrompts = config.sources.images === "generate" && definitions.length > 0
97
+ const imagePrompts =
98
+ // A project that scales its images with the narration's length keeps its ticked prompts
99
+ // and the setting, rather than one prompt per image of the count it planned.
100
+ config.sources.images === "generate" &&
101
+ definitions.length > 0 &&
102
+ config.imageScale === undefined
86
103
  ? definitions.map((definition, index) => {
87
104
  const key = definition.templateKey ?? "";
88
105
  const original = revision.content.promptTemplates[key];
@@ -139,6 +156,9 @@ function documentFromProject(deps, revision) {
139
156
  ...(config.shorts?.enabled === true && revision.content.shortsMusic !== undefined
140
157
  ? { shortsMusic: attachment("Music from project") }
141
158
  : {}),
159
+ ...(config.ambientBed?.source === "upload" && revision.content.ambientBed !== undefined
160
+ ? { ambientBed: attachment("Ambient sound from project") }
161
+ : {}),
142
162
  };
143
163
  return {
144
164
  schemaVersion: 1,
@@ -168,8 +188,10 @@ function documentFromProject(deps, revision) {
168
188
  },
169
189
  }),
170
190
  articlePrompt: config.articlePrompt ?? "",
191
+ ...(config.thumbnailCount === 3 ? { thumbnailCount: 3 } : {}),
171
192
  ...(config.narrationPrompt === undefined ? {} : { narrationPrompt: config.narrationPrompt }),
172
193
  ...(config.youtubeDescription === true ? { youtubeDescription: true } : {}),
194
+ ...(config.language === undefined ? {} : { language: config.language }),
173
195
  ...(config.descriptionPrompt === undefined
174
196
  ? {}
175
197
  : { descriptionPrompt: config.descriptionPrompt }),
@@ -189,7 +211,33 @@ function documentFromProject(deps, revision) {
189
211
  // A project without edit settings makes a template without them, so a video made from
190
212
  // it cuts every N seconds like the project did.
191
213
  ...(config.videoEdit === undefined ? {} : { videoEdit: config.videoEdit }),
214
+ ...(config.reviews === undefined ? {} : { reviews: reviewSettingsForm(config.reviews) }),
215
+ // Without the book: every run of a template would otherwise be the same chapter. "Make the
216
+ // next chapter" (`next-chapter.ts`) sets it itself.
217
+ ...(config.voices === undefined ? {} : { voices: withoutBook(config.voices) }),
218
+ ...(config.ambientBed === undefined
219
+ ? {}
220
+ : { ambientBed: ambientBedFormOf(config.ambientBed) }),
221
+ // Level the volume at the project's targets while it is on; off leaves it to Settings'
222
+ // default, which is how a template made before the setting starts a run.
223
+ ...(config.loudness === undefined
224
+ ? {}
225
+ : {
226
+ loudness: {
227
+ enabled: true,
228
+ videoLufs: config.loudness.videoLufs,
229
+ audioFilesLufs: config.loudness.audioFilesLufs,
230
+ },
231
+ }),
232
+ // The project's pauses; a project without them leaves the template to the defaults.
233
+ ...(config.sentencePauseSeconds === undefined
234
+ ? {}
235
+ : { sentencePause: String(config.sentencePauseSeconds) }),
236
+ ...(config.paragraphPauseSeconds === undefined
237
+ ? {}
238
+ : { paragraphPause: String(config.paragraphPauseSeconds) }),
192
239
  imagePrompts,
240
+ ...(config.imageScale === undefined ? {} : { imageScale: imageScaleForm(config.imageScale) }),
193
241
  thumbnailPrompt: config.thumbnailPrompt ?? "",
194
242
  intro: config.intro?.name ?? "",
195
243
  outro: config.outro?.name ?? "",
@@ -214,8 +262,13 @@ function documentFromProject(deps, revision) {
214
262
  },
215
263
  section: "content",
216
264
  variants: [],
217
- expectedWords: "1500",
265
+ // The length a project that scales its images planned for, so the template plans the same.
266
+ expectedWords: String(config.imageScale?.words ?? 1500),
218
267
  previewText: "Every story begins with a word.",
219
268
  fontUpload: null,
220
269
  };
221
270
  }
271
+ function withoutBook(voices) {
272
+ const { book: _book, ...rest } = voices;
273
+ return rest;
274
+ }
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ import { transact } from "../../kernel/db/tx.js";
3
+ import { projectById } from "../admission/repo.js";
4
+ import { projectChannelId } from "../channels/repo.js";
5
+ import { draftRow } from "../play-drafts/repo.js";
6
+ import { createDraft, readDraft } from "../play-drafts/service.js";
7
+ import { currentRevisionId, revisionById } from "../revisions/repo.js";
8
+ import { bookChapterMax, bookLabel, bookTitleMax } from "../voices/model.js";
9
+ import { documentFromProject } from "./from-project.js";
10
+ // Books: an audiobook is written chapter by chapter, one project each. "Make the next chapter"
11
+ // on a chapter opens a new Play draft with everything the book shares - format, speakers and
12
+ // their voices, channel (and so its cast), prompts and settings - and the chapter number one
13
+ // higher. The chapter's own words are left empty: the new chapter is written or pasted fresh,
14
+ // and files the last one was given are listed to attach again, as a template's are.
15
+ export const nextChapterSchema = z
16
+ .object({ id: z.uuid(), projectId: z.string().min(1).max(64) })
17
+ .strict()
18
+ .readonly();
19
+ // The book a project's next chapter belongs to: its own book one chapter on, or, for an
20
+ // audiobook made on its own, a book named after it whose second chapter comes next.
21
+ export function nextBook(title, book) {
22
+ return book === undefined
23
+ ? { title: title.trim().slice(0, bookTitleMax) || "Untitled book", chapter: 2 }
24
+ : { title: book.title, chapter: Math.min(bookChapterMax, book.chapter + 1) };
25
+ }
26
+ export function makeNextChapter(deps, input) {
27
+ const parsed = nextChapterSchema.safeParse(input);
28
+ if (!parsed.success)
29
+ return {
30
+ ok: false,
31
+ reason: "not-found",
32
+ message: "That project could not be found. Open it again from Projects, then try again.",
33
+ };
34
+ const { id, projectId } = parsed.data;
35
+ return transact(deps.db, () => {
36
+ // Pressed twice (a retry after a dropped answer): the draft it made the first time.
37
+ if (draftRow(deps.db, id) !== undefined) {
38
+ const made = readDraft(deps, id);
39
+ if (made.ok)
40
+ return { ok: true, value: made.value };
41
+ }
42
+ const project = projectById(deps.db, projectId);
43
+ const current = project === undefined ? undefined : currentRevisionId(deps.db, project.id);
44
+ const revision = project === undefined || current === undefined
45
+ ? undefined
46
+ : revisionById(deps.db, project.id, current);
47
+ if (project === undefined || revision === undefined)
48
+ return {
49
+ ok: false,
50
+ reason: "not-found",
51
+ message: "That project could not be found; it may have been deleted. Open Projects and pick another chapter.",
52
+ };
53
+ const voices = revision.config.voices;
54
+ if (voices?.format !== "audiobook")
55
+ return {
56
+ ok: false,
57
+ reason: "not-an-audiobook",
58
+ message: "Only an audiobook has chapters. Pick Audiobook under Speakers (Play → Narration) to start a book.",
59
+ };
60
+ const book = nextBook(revision.config.title, voices.book);
61
+ const document = documentFromProject(deps, revision);
62
+ const form = document.form;
63
+ const result = createDraft(deps, {
64
+ id,
65
+ document: {
66
+ ...document,
67
+ channelId: projectChannelId(deps.db, project.id),
68
+ form: {
69
+ ...form,
70
+ title: bookLabel(book),
71
+ voices: { ...voices, book },
72
+ provided: {
73
+ ...form.provided,
74
+ // The new chapter's own words, written or pasted fresh.
75
+ research: "",
76
+ article: "",
77
+ },
78
+ },
79
+ },
80
+ });
81
+ if (!result.ok)
82
+ return {
83
+ ok: false,
84
+ reason: "conflict",
85
+ message: "The next chapter's draft could not be made. Press Make the next chapter again; if it keeps failing, use Download diagnostics in Settings and report it.",
86
+ };
87
+ // Files the last chapter was given are attached again on Play, as with a template.
88
+ deps.db.prepare("UPDATE play_draft_attachments SET status='reattach' WHERE draft_id=?").run(id);
89
+ const made = readDraft(deps, id);
90
+ if (!made.ok)
91
+ throw new Error("The next chapter's draft could not be read back");
92
+ return { ok: true, value: made.value };
93
+ });
94
+ }
@@ -0,0 +1,26 @@
1
+ import { detectSlots } from "../admission/substitute.js";
2
+ // A template keeps the settings, never what was typed for one video. A keyword the project
3
+ // title names ({{Topic}} in "History: {{Topic}}") is the video's topic: it changes with every
4
+ // video, so a template stores it empty. Every other keyword ({{minWords}}, {{style}}) is a
5
+ // setting and keeps its value.
6
+ export function topicKeywords(title) {
7
+ return detectSlots(title).names;
8
+ }
9
+ // The values a template stores: the topic keywords emptied, the rest as they were.
10
+ export function templateValues(title, values) {
11
+ const topics = topicKeywords(title);
12
+ // Prototype-free, as `slices/admission/rules.ts` explains: a keyword can be "__proto__".
13
+ const kept = Object.create(null);
14
+ for (const [name, value] of Object.entries(values))
15
+ kept[name] = topics.includes(name) ? "" : value;
16
+ return kept;
17
+ }
18
+ // The Play document a template is saved from, without the one-off values: the topic keywords
19
+ // emptied and the queued extra videos dropped (each is a topic of its own).
20
+ export function templateDocument(document) {
21
+ return {
22
+ ...document,
23
+ form: { ...document.form, values: templateValues(document.form.title, document.form.values) },
24
+ variants: [],
25
+ };
26
+ }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { projectTemplateSchema } from "./schema.js";
3
- const select = `SELECT t.id,r.version,r.name,t.created_at AS createdAt,r.created_at AS updatedAt,r.document_json AS document FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id`;
3
+ // A template in the trash (`deleted_at` set, Settings → Trash) is invisible to every read here.
4
+ const select = `SELECT t.id,r.version,r.name,t.created_at AS createdAt,r.created_at AS updatedAt,r.document_json AS document FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id AND t.deleted_at IS NULL`;
4
5
  const templateSummarySchema = z
5
6
  .object({
6
7
  id: z.uuid(),
@@ -1,9 +1,14 @@
1
1
  import { z } from "zod";
2
2
  import { playDraftDocumentSchema } from "../play-drafts/schema.js";
3
+ // The longest template name, in characters. Play's Save as template and Library → Templates
4
+ // (save and rename) cap their name fields at it, and a new template is refused past it.
5
+ export const templateNameMax = 120;
6
+ // A template named before the cap (Play once allowed 200) can still be saved under its name.
7
+ const templateNameStoredMax = 200;
3
8
  export const templateCreateSchema = z
4
9
  .object({
5
10
  id: z.uuid(),
6
- name: z.string().trim().min(1).max(200),
11
+ name: z.string().trim().min(1).max(templateNameMax),
7
12
  document: playDraftDocumentSchema,
8
13
  })
9
14
  .strict()
@@ -11,6 +16,7 @@ export const templateCreateSchema = z
11
16
  export const templateUpdateSchema = templateCreateSchema
12
17
  .unwrap()
13
18
  .extend({
19
+ name: z.string().trim().min(1).max(templateNameStoredMax),
14
20
  baseVersion: z.number().int().positive(),
15
21
  mutationId: z.uuid(),
16
22
  })
@@ -1,12 +1,17 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
+ import { resolveChannelId, templateChannels } from "../channels/repo.js";
3
4
  import { requestHash } from "../play-drafts/repo.js";
4
5
  import { createDraft, readDraft } from "../play-drafts/service.js";
5
6
  import { insertTemplateRevision, templateById, templateSummaries } from "./repo.js";
6
7
  import { templateCreateSchema, templateDeleteSchema, templateInstantiateSchema, templateUpdateSchema, } from "./schema.js";
7
8
  import { freshTemplateDraft, templateSetup } from "./setup.js";
8
9
  export function listTemplates(deps) {
9
- return templateSummaries(deps.db);
10
+ const channels = templateChannels(deps.db);
11
+ return templateSummaries(deps.db).map((template) => ({
12
+ ...template,
13
+ channelId: channels.get(template.id) ?? resolveChannelId(deps.db, undefined),
14
+ }));
10
15
  }
11
16
  export function readTemplate(deps, id, version) {
12
17
  if (!z.uuid().safeParse(id).success ||
@@ -39,9 +44,11 @@ export function createTemplate(deps, input) {
39
44
  createdAt: at,
40
45
  updatedAt: at,
41
46
  };
47
+ // The channel is the template's, not part of its setup: moving it (Channels)
48
+ // makes no new version, and a draft made from it asks the template (`draftChannel`).
42
49
  deps.db
43
- .prepare("INSERT INTO project_templates(id,head_version,creation_hash,created_at) VALUES (?,1,?,?)")
44
- .run(id, hash, at);
50
+ .prepare("INSERT INTO project_templates(id,head_version,creation_hash,created_at,channel_id) VALUES (?,1,?,?,?)")
51
+ .run(id, hash, at, resolveChannelId(deps.db, document.channelId));
45
52
  insertTemplateRevision(deps.db, value);
46
53
  return { ok: true, value };
47
54
  });
@@ -96,7 +103,11 @@ export function deleteTemplate(deps, input) {
96
103
  .prepare("SELECT 1 FROM schedules WHERE template_id=? AND deleted_at IS NULL LIMIT 1")
97
104
  .get(parsed.data.id))
98
105
  return { ok: false, reason: "referenced-by-schedule" };
99
- deps.db.prepare("DELETE FROM project_templates WHERE id=?").run(parsed.data.id);
106
+ // Into the trash (Settings → Trash), not gone: `slices/trash` restores it or removes it
107
+ // for good. A schedule that is itself in the trash may still name it.
108
+ deps.db
109
+ .prepare("UPDATE project_templates SET deleted_at=? WHERE id=? AND deleted_at IS NULL")
110
+ .run(deps.clock.now().toISOString(), parsed.data.id);
100
111
  return { ok: true, value: { deleted: true } };
101
112
  });
102
113
  }
@@ -1,15 +1,26 @@
1
+ import { templateChannelId } from "../channels/repo.js";
1
2
  import { snapshotEntry, snapshotPrompt } from "../library/snapshot.js";
3
+ import { templateDocument } from "./one-off.js";
2
4
  export function templateSetup(deps, input) {
3
5
  const prompts = [];
4
6
  const entries = [];
5
7
  const choices = [
6
- { kind: "article", name: input.form.articlePrompt },
8
+ {
9
+ kind: input.form.voices?.source === "script" && input.form.sources.audio === "generate"
10
+ ? "script"
11
+ : "article",
12
+ name: input.form.articlePrompt,
13
+ },
7
14
  { kind: "narration", name: input.form.narrationPrompt ?? "" },
8
15
  { kind: "description", name: input.form.descriptionPrompt ?? "" },
9
16
  ...input.form.imagePrompts.map((row) => ({ kind: "image", name: row.name })),
10
17
  { kind: "thumbnail", name: input.form.thumbnailPrompt },
11
18
  { kind: "shorts", name: input.form.shorts?.prompt ?? "" },
12
19
  { kind: "image", name: input.form.shorts?.imagePrompt ?? "" },
20
+ ...Object.values(input.form.reviews?.stages ?? {}).map((stage) => ({
21
+ kind: "review",
22
+ name: stage.mode === "off" ? "" : stage.prompt,
23
+ })),
13
24
  {
14
25
  kind: "image",
15
26
  name: input.form.reference?.source === "prompt" ? input.form.reference.prompt : "",
@@ -32,7 +43,8 @@ export function templateSetup(deps, input) {
32
43
  return { ok: false, reason: "missing-prompt" };
33
44
  entries.push(row);
34
45
  }
35
- const { templateSource: _source, ...document } = input;
46
+ // Only the settings are kept: the topic typed for one video is left empty (`one-off.ts`).
47
+ const { templateSource: _source, channelId: _channel, ...document } = templateDocument(input);
36
48
  return {
37
49
  ok: true,
38
50
  value: { ...document, fontUpload: null, librarySnapshot: { prompts, entries } },
@@ -49,6 +61,7 @@ export function freshTemplateDraft(deps, document, source) {
49
61
  section: "content",
50
62
  fontUpload: null,
51
63
  templateSource: source,
64
+ channelId: templateChannelId(deps.db, source.id),
52
65
  variants: document.variants.map((variant) => ({ ...variant, id: deps.uuid() })),
53
66
  form: {
54
67
  ...document.form,
@@ -64,6 +77,9 @@ export function freshTemplateDraft(deps, document, source) {
64
77
  ...(provided.shortsMusic === undefined
65
78
  ? {}
66
79
  : { shortsMusic: provided.shortsMusic === null ? null : fresh(provided.shortsMusic) }),
80
+ ...(provided.ambientBed === undefined
81
+ ? {}
82
+ : { ambientBed: provided.ambientBed === null ? null : fresh(provided.ambientBed) }),
67
83
  },
68
84
  },
69
85
  };
@@ -72,7 +72,7 @@ export function admitPreview(deps, input) {
72
72
  workIds.push(String(existing.id));
73
73
  if (existing.state !== "running") {
74
74
  deps.db
75
- .prepare("UPDATE revision_work SET state='pending',dispatch_state='allowed',failure_reason=NULL WHERE id=?")
75
+ .prepare("UPDATE revision_work SET state='pending',dispatch_state='allowed',failure_reason=NULL,failure_kind=NULL,retry_at=NULL,auto_retries=0 WHERE id=?")
76
76
  .run(String(existing.id));
77
77
  deps.db
78
78
  .prepare(`UPDATE revision_work_pieces SET state='pending',dispatch_state='allowed'
@@ -30,6 +30,9 @@ const costRowSchema = z.object({
30
30
  low: z.number().finite().nonnegative().nullable(),
31
31
  high: z.number().finite().nonnegative().nullable(),
32
32
  detail: z.string(),
33
+ onPlan: z.boolean().optional(),
34
+ apiLow: z.number().finite().nonnegative().nullable().optional(),
35
+ apiHigh: z.number().finite().nonnegative().nullable().optional(),
33
36
  });
34
37
  const costEstimateSchema = z.object({
35
38
  currency: z.literal("USD"),
@@ -37,6 +40,9 @@ const costEstimateSchema = z.object({
37
40
  low: z.number().finite().nonnegative(),
38
41
  high: z.number().finite().nonnegative(),
39
42
  unknown: z.number().int().nonnegative(),
43
+ apiLow: z.number().finite().nonnegative().optional(),
44
+ apiHigh: z.number().finite().nonnegative().optional(),
45
+ apiUnknown: z.number().int().nonnegative().optional(),
40
46
  expectedWords: z.number().finite().nonnegative(),
41
47
  catalogueDate: z.string().nullable(),
42
48
  assumptions: z.array(z.string()),
@@ -1,9 +1,13 @@
1
1
  import { stageKinds } from "../../kernel/pipeline.js";
2
+ import { languageInfo } from "../../kernel/ports/languages.js";
2
3
  import { sourceOf } from "../admission/model.js";
3
4
  import { motionStyleLabels } from "../admission/rules.js";
4
5
  import { documentThemeLabel } from "../document/model.js";
6
+ import { loudnessLabel } from "../loudness/model.js";
7
+ import { reviewModeLabels, reviewModeOf, reviewStageLabels, reviewStages, } from "../reviews/model.js";
5
8
  import { getRevisionView } from "../revisions/view.js";
6
9
  import { musicVolumeOf, shortsSpeedOf } from "../shorts/model.js";
10
+ import { ambientBedLabels } from "../video/ambient-bed.js";
7
11
  import { videoEditOf, videoEditRows } from "../video/edit-settings.js";
8
12
  export function previewDetails(deps, view, allRecipes, selected) {
9
13
  const parent = view.revision.parentId === null
@@ -48,9 +52,11 @@ export function previewDetails(deps, view, allRecipes, selected) {
48
52
  key: row.key,
49
53
  label: row.key === "reference:image"
50
54
  ? "Establishing image"
51
- : index < 0
52
- ? "Thumbnail image"
53
- : `Image ${index + 1}`,
55
+ : row.key.startsWith("thumbnail:image:")
56
+ ? `Thumbnail image ${row.key.slice("thumbnail:image:".length)}`
57
+ : index < 0
58
+ ? "Thumbnail image"
59
+ : `Image ${index + 1}`,
54
60
  text: input.prompt,
55
61
  settings: [
56
62
  input.provider,
@@ -95,6 +101,9 @@ export function previewDetails(deps, view, allRecipes, selected) {
95
101
  });
96
102
  return { inputChanges: parent === undefined ? [] : inputChanges(parent, view), requests };
97
103
  }
104
+ function loudnessSummary(value) {
105
+ return `Video ${loudnessLabel(value.videoLufs, "video")}; audio files ${loudnessLabel(value.audioFilesLufs, "audioFiles")}`;
106
+ }
98
107
  function narrationIdentity(row) {
99
108
  if (row.input.kind === "tts")
100
109
  return { logicalKey: row.input.logicalKey, segment: row.input.segment };
@@ -138,9 +147,17 @@ function inputChanges(parent, view) {
138
147
  add(`${name} model`, a?.model, b?.model);
139
148
  add(`${name} thinking`, a?.thinking, b?.thinking);
140
149
  }
150
+ add("Language", languageInfo(before.language).name, languageInfo(after.language).name);
141
151
  add("Narration voice", before.audio?.voice, after.audio?.voice);
142
152
  add("Narration Preparation", before.narrationPrompt, after.narrationPrompt);
143
153
  add("Narration chunking", before.chunking, after.chunking);
154
+ add("Narration aliases", before.audio?.useNarrationAliases === true ? aliasLines(before.narrationAliases) : "Off", after.audio?.useNarrationAliases === true ? aliasLines(after.narrationAliases) : "Off");
155
+ const described = (audio) => audio?.describeFigures !== true ? "Off" : audio.skipCode === true ? "On, code left out" : "On";
156
+ add("Describe tables and figures", described(before.audio), described(after.audio));
157
+ add("Show tables and figures on screen", before.showFigures === true ? "On" : "Off", after.showFigures === true ? "On" : "Off");
158
+ add("Pause between sentences (seconds)", before.sentencePauseSeconds ?? 0, after.sentencePauseSeconds ?? 0);
159
+ add("Pause between paragraphs (seconds)", before.paragraphPauseSeconds ?? 0, after.paragraphPauseSeconds ?? 0);
160
+ add("Level the volume", before.loudness === undefined ? "Off" : loudnessSummary(before.loudness), after.loudness === undefined ? "Off" : loudnessSummary(after.loudness));
144
161
  add("YouTube description", before.youtubeDescription === true ? "On" : "Off", after.youtubeDescription === true ? "On" : "Off");
145
162
  add("Description prompt", before.descriptionPrompt, after.descriptionPrompt);
146
163
  add("Shorts", before.shorts?.enabled === true ? "On" : "Off", after.shorts?.enabled === true ? "On" : "Off");
@@ -150,8 +167,16 @@ function inputChanges(parent, view) {
150
167
  : `${String(before.shorts.minSeconds)}-${String(before.shorts.maxSeconds)}`, after.shorts === undefined
151
168
  ? undefined
152
169
  : `${String(after.shorts.minSeconds)}-${String(after.shorts.maxSeconds)}`);
170
+ add("Thumbnails", before.thumbnailCount === 3 ? "3" : "1", after.thumbnailCount === 3 ? "3" : "1");
153
171
  add("Shorts prompt", before.shorts?.prompt, after.shorts?.prompt);
154
172
  add("Shorts image prompt", before.shorts?.imagePrompt, after.shorts?.imagePrompt);
173
+ add("Reviewer provider", before.reviews?.provider, after.reviews?.provider);
174
+ add("Reviewer model", before.reviews?.model, after.reviews?.model);
175
+ add("Reviewer redos", before.reviews?.retries, after.reviews?.retries);
176
+ for (const stage of reviewStages) {
177
+ add(`${reviewStageLabels[stage]} review`, reviewModeLabels[reviewModeOf(before.reviews, stage)], reviewModeLabels[reviewModeOf(after.reviews, stage)]);
178
+ add(`${reviewStageLabels[stage]} review prompt`, before.reviews?.stages[stage]?.prompt, after.reviews?.stages[stage]?.prompt);
179
+ }
155
180
  add("Establishing image", referenceLabel(before), referenceLabel(after));
156
181
  add("Thumbnail drawn from the establishing image", before.reference === undefined
157
182
  ? undefined
@@ -170,6 +195,7 @@ function inputChanges(parent, view) {
170
195
  add("Short ranges set by hand", old.shortsRanges, next.shortsRanges);
171
196
  add("Silence gap (seconds)", before.silenceGapSeconds, after.silenceGapSeconds);
172
197
  add("Silence at start and end (seconds)", before.edgeSilenceSeconds, after.edgeSilenceSeconds);
198
+ add("Ambient sound", ambientBedLabel(before, old), ambientBedLabel(after, next));
173
199
  add("Seconds per image", before.imageSeconds, after.imageSeconds);
174
200
  add("Zoom (%)", before.zoomPercent, after.zoomPercent);
175
201
  add("Motion", motionStyleLabels[before.motionStyle], motionStyleLabels[after.motionStyle]);
@@ -207,9 +233,26 @@ function inputChanges(parent, view) {
207
233
  add("Manual captions", old.subtitleCues?.cues, next.subtitleCues?.cues);
208
234
  return changes;
209
235
  }
236
+ // The ambient bed in one line; undefined without one, so a project that never had one lists
237
+ // no change.
238
+ function ambientBedLabel(config, content) {
239
+ const bed = config.ambientBed;
240
+ if (bed === undefined)
241
+ return undefined;
242
+ const file = bed.source === "upload" && content.ambientBed !== undefined ? " (uploaded file)" : "";
243
+ return `${ambientBedLabels[bed.source]}${file}, ${String(bed.levelDb)} dB, fade in ${String(bed.fadeInSeconds)} s, tail ${String(bed.tailSeconds)} s`;
244
+ }
210
245
  function referenceLabel(config) {
211
246
  const reference = config.reference;
212
247
  if (reference === undefined)
213
248
  return "Off";
214
249
  return reference.source === "provide" ? "Uploaded image" : `Prompt: ${reference.prompt ?? ""}`;
215
250
  }
251
+ // One line per alias, as the Library shows it, so a changed alias reads as a changed line.
252
+ function aliasLines(aliases) {
253
+ if (aliases === undefined || aliases.length === 0)
254
+ return "On, no aliases copied";
255
+ return aliases
256
+ .map((alias) => `${alias.written} → ${alias.spoken}${alias.wholeWord ? "" : " (inside words too)"}${alias.caseSensitive ? " (match case)" : ""}`)
257
+ .join("\n");
258
+ }
@@ -168,13 +168,20 @@ export function planPreview(deps, view, catalogue, selection, id) {
168
168
  providedReuseRequired: execution.reviews.map((row) => row.key),
169
169
  costs: estimateRequests(work.flatMap((row) => priceRecipes(row.inflight || !requiresNewSubmission(deps, view.revision.id, row.key, row.fingerprint)
170
170
  ? { ...row, disposition: "reuse" }
171
- : row, recipes.find((recipe) => recipe.key === row.key))), snapshot),
171
+ : row, recipes.find((recipe) => recipe.key === row.key), view.revision.config)),
172
+ // The whole catalogue, as Play's review prices with: a CLI row's API figure is read
173
+ // from the API models, which the execution snapshot leaves out.
174
+ catalogue),
172
175
  wholeRequestNotice: plan.wholeRequestNotice,
173
- warnings: submissions.some((row) => row.uncertain)
174
- ? [
175
- "A previous request was submitted without a saved result or resumable job. Retrying may charge you again.",
176
- ]
177
- : [],
176
+ warnings: [
177
+ ...(submissions.some((row) => row.uncertain)
178
+ ? [
179
+ "A previous request was submitted without a saved result or resumable job. Retrying may charge you again.",
180
+ ]
181
+ : []),
182
+ ...(plan.glossaryNotice ? [plan.glossaryNotice] : []),
183
+ ...(plan.speakerPronunciationNotice ? [plan.speakerPronunciationNotice] : []),
184
+ ],
178
185
  };
179
186
  return { ok: true, value: { preview, execution } };
180
187
  }
@@ -1,4 +1,6 @@
1
- import { usesNarrationPreparation, usesPronunciationGlossary } from "../admission/rules.js";
1
+ import { aliasMatches } from "../../kernel/ports/narration-aliases.js";
2
+ import { narrationAliasesOf, usesNarrationPreparation, usesPronunciationGlossary, } from "../admission/rules.js";
3
+ import { aliasSpans, withAliasSpans } from "../narration/aliases.js";
2
4
  import { narrationRegenerationToken, normalizeNarrationText, planNarration, } from "../narration/plan.js";
3
5
  import { pronunciationSpans } from "../narration/pronunciation.js";
4
6
  import { prepareRequests } from "../narration/steering.js";
@@ -20,18 +22,20 @@ export function narrationParts(context, logicalKey, originalText, segment, depen
20
22
  if (!glossary.ok)
21
23
  return [refusedPart(context, logicalKey, dependsOn, glossary.reason)];
22
24
  const logicalText = normalizeNarrationText(override?.kind === "text" ? override.text : originalText);
23
- const spans = pronunciationSpans(logicalText, glossary.entries);
25
+ // Aliases first: where one applies, the glossary does not.
26
+ const aliases = narrationAliasesOf(context.config);
27
+ const matches = aliasMatches(logicalText, aliases);
28
+ const spans = withAliasSpans(pronunciationSpans(logicalText, glossary.entries), aliasSpans(logicalText, aliases));
24
29
  const wholeRequest = segment !== "body" || (context.config.chunking?.mode ?? "whole") === "whole";
25
30
  const choice = context.config.audio;
26
- const model = context.catalogue?.tts.find((row) => row.provider === choice?.provider &&
27
- row.id === choice.model &&
28
- row.enabled &&
29
- !row.deprecated);
31
+ // A retired model's character limit is still its limit: the automatic catalogue check marks
32
+ // models deprecated, and that alone must not re-split narration and outdate a project.
33
+ const model = context.catalogue?.tts.find((row) => row.provider === choice?.provider && row.id === choice.model && row.enabled);
30
34
  const logicalLimit = Math.max(2, logicalText.length +
31
35
  spans.reduce((sum, span) => sum + Math.max(0, span.text.length - (span.end - span.start)), 0));
32
36
  const maxCharacters = model?.tts.maxCharacters ?? logicalLimit;
33
37
  if (usesNarrationPreparation(context.config)) {
34
- const prepared = preparationForGroup(context, logicalKey, logicalText, segment, dependsOn, maxCharacters, spans);
38
+ const prepared = preparationForGroup(context, logicalKey, logicalText, segment, dependsOn, maxCharacters, spans, matches);
35
39
  preparations.push(prepared.preparation);
36
40
  if (prepared.refusal !== null)
37
41
  return [refusedPart(context, logicalKey, [prepared.preparation.key], prepared.refusal)];
@@ -135,6 +139,35 @@ export function pronunciationFutureValues(context, glossary, article, logicalKey
135
139
  ],
136
140
  ];
137
141
  }
142
+ // What aliases add to a narration recipe that can't be worked out yet. With none copied into
143
+ // the project (every project from before aliases), nothing: its fingerprints stay as they were.
144
+ export function aliasFutureValues(context, logicalKey, source) {
145
+ const aliases = narrationAliasesOf(context.config);
146
+ if (aliases.length === 0)
147
+ return [];
148
+ const override = context.content.narrationOverrides[logicalKey];
149
+ if (override?.kind === "asset")
150
+ return [];
151
+ // Unknown generated text may contain any alias.
152
+ if (source === null)
153
+ return [
154
+ [
155
+ "narration-aliases-v1",
156
+ aliases.map((alias) => [alias.written, alias.spoken, alias.wholeWord, alias.caseSensitive]),
157
+ ],
158
+ ];
159
+ const clean = normalizeNarrationText(override?.kind === "text" ? override.text : source);
160
+ const matches = aliasMatches(clean, aliases);
161
+ return matches.length === 0
162
+ ? []
163
+ : [
164
+ [
165
+ "narration-aliases-v1",
166
+ logicalKey,
167
+ matches.map((match) => [match.start, match.end, match.spoken]),
168
+ ],
169
+ ];
170
+ }
138
171
  export function voiceValues(context) {
139
172
  return [
140
173
  context.config.audio?.provider ?? null,