@gentbajko/slopify 2.4.0 → 3.0.0

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 (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  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 +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -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-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { isUniqueConstraint } from "../../kernel/db/index.js";
2
2
  import { providerById } from "./model.js";
3
- import { deleteVoice, insertVoice, listVoices } from "./repo.js";
3
+ import { deleteVoice, insertVoice, listVoices, readSetting, updateVoiceLanguages, writeSetting, } from "./repo.js";
4
4
  export const voiceNameMax = 200;
5
5
  export const voiceIdMax = 200;
6
6
  // A non-empty name and a non-empty voice ID, the ID unique within its provider, names free to
@@ -24,7 +24,16 @@ export function addVoice(deps, draft) {
24
24
  if (voiceId.length > voiceIdMax) {
25
25
  return { ok: false, reason: "voice-id-too-long" };
26
26
  }
27
- const voice = { id: deps.ids.next(), provider: draft.provider, name, voiceId };
27
+ const languages = voiceLanguagesOf(draft.languages);
28
+ if (languages === null)
29
+ return { ok: false, reason: "unknown-language" };
30
+ const voice = {
31
+ id: deps.ids.next(),
32
+ provider: draft.provider,
33
+ name,
34
+ voiceId,
35
+ ...(languages === undefined ? {} : { languages }),
36
+ };
28
37
  try {
29
38
  insertVoice(deps.db, voice);
30
39
  }
@@ -39,9 +48,60 @@ export function addVoice(deps, draft) {
39
48
  }
40
49
  return { ok: true, voice };
41
50
  }
51
+ // Lower-case primary codes, each once; an empty list is unknown. Null when one is not a
52
+ // language code at all.
53
+ export function voiceLanguagesOf(raw) {
54
+ if (raw === undefined)
55
+ return undefined;
56
+ const codes = raw.map((one) => one.trim().toLowerCase()).filter((one) => one !== "");
57
+ if (codes.some((one) => !/^[a-z]{2,3}$/.test(one)))
58
+ return null;
59
+ return codes.length === 0 ? undefined : [...new Set(codes)];
60
+ }
61
+ export function setVoiceLanguages(deps, id, raw) {
62
+ const languages = voiceLanguagesOf(raw);
63
+ if (languages === null)
64
+ return { ok: false, reason: "unknown-language" };
65
+ return updateVoiceLanguages(deps.db, id, languages)
66
+ ? { ok: true }
67
+ : { ok: false, reason: "missing" };
68
+ }
42
69
  export function removeVoice(deps, id) {
43
- return deleteVoice(deps.db, id) ? { ok: true } : { ok: false };
70
+ if (!deleteVoice(deps.db, id))
71
+ return { ok: false };
72
+ writeRealPersonVoices(deps.db, [...realPersonVoiceIds(deps.db)].filter((one) => one !== id));
73
+ return { ok: true };
44
74
  }
45
75
  export function voices(deps) {
46
- return listVoices(deps.db);
76
+ const flagged = realPersonVoiceIds(deps.db);
77
+ return listVoices(deps.db).map((voice) => flagged.has(voice.id) ? { ...voice, imitatesRealPerson: true } : voice);
78
+ }
79
+ // Which saved voices imitate a real person: one row of the key/value `settings` table holding
80
+ // their ids, so the flag needs no column of its own and travels with a backup.
81
+ export const realPersonVoicesKey = "voices.realPerson";
82
+ export function realPersonVoiceIds(db) {
83
+ const stored = readSetting(db, realPersonVoicesKey);
84
+ if (stored === undefined)
85
+ return new Set();
86
+ try {
87
+ const value = JSON.parse(stored);
88
+ return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
89
+ }
90
+ catch {
91
+ return new Set();
92
+ }
93
+ }
94
+ function writeRealPersonVoices(db, ids) {
95
+ if (ids.length === 0)
96
+ db.prepare("DELETE FROM settings WHERE key = ?").run(realPersonVoicesKey);
97
+ else
98
+ writeSetting(db, realPersonVoicesKey, JSON.stringify([...new Set(ids)].sort()));
99
+ }
100
+ // Settings → Voices' "Imitates a real person" tick.
101
+ export function setVoiceImitatesRealPerson(deps, id, on) {
102
+ if (!listVoices(deps.db).some((voice) => voice.id === id))
103
+ return { ok: false, reason: "missing" };
104
+ const others = [...realPersonVoiceIds(deps.db)].filter((one) => one !== id);
105
+ writeRealPersonVoices(deps.db, on ? [...others, id] : others);
106
+ return { ok: true };
47
107
  }
@@ -0,0 +1,64 @@
1
+ import { transact } from "../../kernel/db/tx.js";
2
+ import { machineOf } from "../telemetry/repo.js";
3
+ import { readSetting, writeSetting } from "./repo.js";
4
+ // "What's new" is a short tour shown once per major version, on the first launch after an
5
+ // update. The newest major this install has been shown (or has no need to be shown) lives in
6
+ // one settings key, so it is once per install rather than once per browser.
7
+ export const whatsNewSeenKey = "whats-new.seen-major";
8
+ export function majorOf(version) {
9
+ const match = /^(\d+)\.\d+\.\d+/.exec(version);
10
+ return match === null ? undefined : Number(match[1]);
11
+ }
12
+ // The newest major already shown; undefined when none was recorded, null when the stored
13
+ // value is damaged.
14
+ function seenMajor(db) {
15
+ const raw = readSetting(db, whatsNewSeenKey);
16
+ if (raw === undefined)
17
+ return undefined;
18
+ let value;
19
+ try {
20
+ value = JSON.parse(raw);
21
+ }
22
+ catch (error) {
23
+ if (!(error instanceof SyntaxError))
24
+ throw error;
25
+ return null;
26
+ }
27
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
28
+ }
29
+ // Telling an update from a fresh install: the machine row is written once, when the
30
+ // first-run notice is dismissed, and keeps the version that was running then. A fresh 3.x
31
+ // install either has no machine yet (the notice is still up) or one stamped 3.x, so it is not
32
+ // shown a tour of what changed since 2.x. An install that has been running since 2.x has a
33
+ // machine stamped 2.x. Once the tour is shown and closed, the key above takes over, so a
34
+ // later 4.0 compares against 3 rather than against the machine's first version.
35
+ export function readWhatsNew(db, version) {
36
+ const major = majorOf(version);
37
+ if (major === undefined)
38
+ return { show: false, major: null };
39
+ const seen = seenMajor(db);
40
+ // A damaged value is not a reason to show the tour on every launch.
41
+ if (seen === null)
42
+ return { show: false, major };
43
+ if (seen !== undefined)
44
+ return { show: seen < major, major };
45
+ const machine = machineOf(db);
46
+ if (machine === undefined)
47
+ return { show: false, major };
48
+ const first = majorOf(machine.appVersion);
49
+ return { show: first !== undefined && first < major, major };
50
+ }
51
+ // Closing the tour, finishing it or skipping it all record the running major. Idempotent, and
52
+ // never lowers the value, so running an older version for a while and updating again does not
53
+ // bring back a tour already closed.
54
+ export function dismissWhatsNew(db, version) {
55
+ const major = majorOf(version);
56
+ if (major === undefined)
57
+ return { show: false, major: null };
58
+ transact(db, () => {
59
+ const seen = seenMajor(db);
60
+ if (seen === undefined || seen === null || seen < major)
61
+ writeSetting(db, whatsNewSeenKey, JSON.stringify(major));
62
+ });
63
+ return { show: false, major };
64
+ }
@@ -173,6 +173,15 @@ export function shortCaptionsAss(words, style) {
173
173
  continue;
174
174
  const shown = next === undefined ? last.end + holdSeconds : Math.min(last.end + holdSeconds, next.start);
175
175
  const lines = lineBreaks(group.map((word) => word.text));
176
+ if (style.wordByWord === false) {
177
+ const text = group
178
+ .map((one, place) => `${assText(one.text)}${lines.has(place) ? "\\N" : place < group.length - 1 ? " " : ""}`)
179
+ .join("");
180
+ const from = group[0]?.start ?? last.start;
181
+ if (shown > from)
182
+ events.push(`Dialogue: 0,${assStamp(from)},${assStamp(shown)},Short,,0,0,0,,${position}${text}\n`);
183
+ continue;
184
+ }
176
185
  for (const [index, word] of group.entries()) {
177
186
  const from = word.start;
178
187
  const to = group[index + 1]?.start ?? Math.max(shown, word.end);
@@ -115,7 +115,7 @@ const answerSchema = z.array(z.object({
115
115
  hashtags: z.array(z.string()),
116
116
  why: z.string().optional(),
117
117
  }));
118
- const fix = "Retry stage, or choose another model in Edit project → Providers.";
118
+ const fix = "Try again, or choose another model in Edit project → Providers.";
119
119
  // Timing noise: the length rules are checked to the hundredth of a second.
120
120
  const tolerance = 0.01;
121
121
  // Reads the model's answer and keeps the clips that follow the rules: in range, the right
@@ -210,8 +210,8 @@ export function checkPicks(text, sentences, limits) {
210
210
  export function noPicksMessage(problems, limits) {
211
211
  const shortVideo = limits.durationSeconds < limits.minSeconds;
212
212
  if (shortVideo)
213
- return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest length in Edit project → Shorts, then Retry stage.`;
214
- return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Retry stage; if it keeps happening, widen the length range in Edit project → Shorts, or choose another model in Edit project → Providers.`;
213
+ return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest length in Edit project → Shorts, then Try again.`;
214
+ return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Try again; if it keeps happening, widen the length range in Edit project → Shorts, or choose another model in Edit project → Providers.`;
215
215
  }
216
216
  function round(seconds) {
217
217
  return Math.round(seconds * 1000) / 1000;
@@ -31,7 +31,7 @@ export function imagePromptMessages(brief) {
31
31
  },
32
32
  ];
33
33
  }
34
- const fix = "Retry stage, or choose another model in Edit project → Providers.";
34
+ const fix = "Try again, or choose another model in Edit project → Providers.";
35
35
  // The reason is the sentence the stage shows; the provider wrapper's `check` asks the model
36
36
  // again while attempts remain.
37
37
  export function checkImagePrompts(text, count) {
@@ -1,9 +1,9 @@
1
1
  import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { editListVersion } from "../video/edit-list.js";
3
+ import { editListVersion, } from "../video/edit-list.js";
4
4
  import { runFfmpeg } from "../video/ffmpeg.js";
5
5
  import { motionFor } from "../video/motion.js";
6
- import { fps } from "../video/plan.js";
6
+ import { figureFrames, fps } from "../video/plan.js";
7
7
  import { renderSlideshow } from "../video/slideshow.js";
8
8
  import { fasterWords, shortCaptionsAss } from "./captions.js";
9
9
  // A short is rendered in three steps. Its sound is cut out of the video's own timeline (the
@@ -46,7 +46,7 @@ export function shortAudioArgs(timeline, start, end, output, options = {}) {
46
46
  count += 1;
47
47
  }
48
48
  if (count === 0 || first === undefined)
49
- throw new Error("A short's clip falls outside the narration, so it has no sound to cut. Use Pick different moments in Edit project → Shorts, then Retry stage.");
49
+ throw new Error("A short's clip falls outside the narration, so it has no sound to cut. Use Pick different moments in Edit project → Shorts, then Try again.");
50
50
  const joined = Array.from({ length: count }, (_value, at) => `[a${String(at)}]`).join("");
51
51
  chains.push(`${joined}concat=n=${String(count)}:v=0:a=1[joined]`);
52
52
  const speed = options.speed ?? 1;
@@ -89,9 +89,46 @@ export function shortAudioArgs(timeline, start, end, output, options = {}) {
89
89
  // moving as the video's shots move by their place.
90
90
  export function shortEditList(input) {
91
91
  if (input.images.length === 0)
92
- throw new Error("A short has no images to show. Use Make this short again in Edit project → Shorts, then Retry stage.");
92
+ throw new Error("A short has no images to show. Use Make this short again in Edit project → Shorts, then Try again.");
93
93
  const total = Math.max(1, Math.round(input.seconds * fps));
94
94
  const each = Math.max(1, Math.round(input.imageSeconds * fps));
95
+ const figures = figureFrames(input.figures ?? [], total);
96
+ if (figures.length > 0) {
97
+ // The short's images take turns around the cards, in order, starting over if the cards
98
+ // leave room for more shots than there are images.
99
+ const shots = [];
100
+ let cursor = 0;
101
+ let slot = 0;
102
+ const stretch = (to) => {
103
+ for (let from = cursor; from < to; from += each) {
104
+ const frames = Math.min(each, to - from);
105
+ shots.push({
106
+ source: { kind: "image", path: input.images[slot % input.images.length] ?? "" },
107
+ frames,
108
+ motion: motionFor(input.motionStyle, slot, input.zoomPercent),
109
+ });
110
+ slot += 1;
111
+ }
112
+ cursor = Math.max(cursor, to);
113
+ };
114
+ for (const figure of figures) {
115
+ stretch(figure.startFrame);
116
+ shots.push({
117
+ source: { kind: "image", path: figure.path },
118
+ frames: figure.frames,
119
+ motion: { kind: "still" },
120
+ });
121
+ cursor = figure.startFrame + figure.frames;
122
+ }
123
+ stretch(total);
124
+ return {
125
+ version: editListVersion,
126
+ ...shortFrame,
127
+ fps,
128
+ audio: [{ kind: "body", path: input.audioPath, seconds: input.seconds }],
129
+ shots,
130
+ };
131
+ }
95
132
  const images = input.images.slice(0, Math.max(1, Math.ceil(total / each)));
96
133
  const shots = images.flatMap((path, at) => {
97
134
  const frames = at < images.length - 1 ? each : total - each * (images.length - 1);
@@ -113,6 +150,7 @@ export function shortEditList(input) {
113
150
  shots,
114
151
  };
115
152
  }
153
+ // What the master measured, when the run asked for one.
116
154
  export async function renderShort(run) {
117
155
  const speed = run.speed ?? 1;
118
156
  // The images keep their share of what is said, so at a higher speed each is on screen
@@ -134,12 +172,14 @@ export async function renderShort(run) {
134
172
  writeFileSync(join(workspace, "subtitles.ass"), shortCaptionsAss(fasterWords(run.words, speed), {
135
173
  ...shortFrame,
136
174
  fontName: run.font.assName,
175
+ ...(run.wordByWord === false ? { wordByWord: false } : {}),
137
176
  ...(run.title === undefined ? {} : { title: { text: run.title, seconds } }),
138
177
  }), { mode: 0o600 });
139
178
  mkdirSync(join(workspace, "fonts"), { mode: 0o700 });
140
179
  copyFileSync(run.font.path, join(workspace, "fonts", `selected${run.font.extension}`));
141
- await renderSlideshow({
180
+ return await renderSlideshow({
142
181
  bin: run.bin,
182
+ master: run.master,
143
183
  edit: shortEditList({
144
184
  audioPath: audio,
145
185
  seconds,
@@ -147,6 +187,15 @@ export async function renderShort(run) {
147
187
  imageSeconds: run.imageSeconds / speed,
148
188
  motionStyle: run.motionStyle,
149
189
  zoomPercent: run.zoomPercent,
190
+ ...(run.figures === undefined
191
+ ? {}
192
+ : {
193
+ figures: run.figures.map((figure) => ({
194
+ ...figure,
195
+ start: figure.start / speed,
196
+ end: figure.end / speed,
197
+ })),
198
+ }),
150
199
  }),
151
200
  output: run.output,
152
201
  burnSubtitles: true,
@@ -16,6 +16,9 @@ export function assetOf(output) {
16
16
  return `${asset}-${String(output.meta.short)}`;
17
17
  if (output.role === "short_image" && output.meta.short !== undefined)
18
18
  return `${asset}-${String(output.meta.short)}-${String(output.meta.index ?? 1)}`;
19
+ // A project can hold three thumbnails; the second and third carry their number.
20
+ if (output.role === "thumbnail" && output.meta.index !== undefined && output.meta.index > 1)
21
+ return `${asset}-${String(output.meta.index)}`;
19
22
  if (output.role === "instructions") {
20
23
  return `${output.stageKind}-${asset}`;
21
24
  }
@@ -3,7 +3,7 @@ import { constants, lstatSync, readdirSync } from "node:fs";
3
3
  import { open } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { transact } from "../../kernel/db/tx.js";
6
- import { backupFormat, backupSchemaVersion, checksumsMember, fontMember, libraryMember, manifestMember, projectFileMember, projectMember, projectTables, safeRelativePath, stagedMember, usageMember, } from "./backup-format.js";
6
+ import { backupFormat, backupSchemaVersion, checksumsMember, fontMember, imageMember, libraryMember, manifestMember, projectFileMember, projectMember, projectTables, safeRelativePath, stagedMember, usageMember, } from "./backup-format.js";
7
7
  import { projectDir, stagingPath } from "./layout.js";
8
8
  import { exportableSettings, installedUploadedFontFiles } from "./portable.js";
9
9
  import { tarEnd, tarHeader, tarMemberBytes, tarPadding } from "./tar.js";
@@ -25,6 +25,9 @@ export function busySentence(projects) {
25
25
  const more = projects.length > 3 ? ` and ${projects.length - 3} more` : "";
26
26
  return `Slopify can't export while projects are being made (${named}${more}): their files are still being written. Wait for them to finish, or pause them on their project page, then press Export everything again.`;
27
27
  }
28
+ function memberSize(member) {
29
+ return member.kind === "file" ? member.size : member.content.byteLength;
30
+ }
28
31
  export function busyProjects(deps) {
29
32
  return deps.db
30
33
  .prepare(`SELECT p.id AS id, p.title AS title,
@@ -84,6 +87,8 @@ export function planBackup(deps) {
84
87
  path: stagingPath(deps.paths, staged.id),
85
88
  size: staged.bytes,
86
89
  });
90
+ for (const image of snapshot.library.images)
91
+ files.push({ name: imageMember(image.sha256), kind: "data", content: image.content });
87
92
  const manifest = {
88
93
  format: backupFormat,
89
94
  schemaVersion: backupSchemaVersion,
@@ -97,7 +102,7 @@ export function planBackup(deps) {
97
102
  bytes: project.part.files.reduce((sum, file) => sum + file.bytes, 0),
98
103
  })),
99
104
  files: files.length,
100
- bytes: files.reduce((sum, file) => sum + (file.kind === "file" ? file.size : 0), 0),
105
+ bytes: files.reduce((sum, file) => sum + memberSize(file), 0),
101
106
  };
102
107
  const members = [
103
108
  json(manifestMember, manifest),
@@ -106,11 +111,7 @@ export function planBackup(deps) {
106
111
  ...snapshot.projects.map((project) => json(projectMember(project.part.id), project.part)),
107
112
  ...files,
108
113
  ];
109
- const archiveBytes = members.reduce((sum, member) => sum +
110
- tarMemberBytes({
111
- name: member.name,
112
- size: member.kind === "bytes" ? member.content.byteLength : member.size,
113
- }), 0) +
114
+ const archiveBytes = members.reduce((sum, member) => sum + tarMemberBytes({ name: member.name, size: memberSize(member) }), 0) +
114
115
  tarMemberBytes({
115
116
  name: checksumsMember,
116
117
  size: checksumsBytes(files, () => "0".repeat(64)).byteLength,
@@ -132,11 +133,16 @@ export async function* streamBackup(plan) {
132
133
  const digests = new Map();
133
134
  const files = [];
134
135
  for (const member of plan.members) {
135
- const size = member.kind === "bytes" ? member.content.byteLength : member.size;
136
+ const size = memberSize(member);
136
137
  yield tarHeader({ name: member.name, size });
137
138
  if (member.kind === "bytes") {
138
139
  yield member.content;
139
140
  }
141
+ else if (member.kind === "data") {
142
+ files.push(member);
143
+ digests.set(member.name, createHash("sha256").update(member.content).digest("hex"));
144
+ yield member.content;
145
+ }
140
146
  else {
141
147
  files.push(member);
142
148
  const hash = createHash("sha256");
@@ -255,10 +261,27 @@ function librarySnapshot(deps) {
255
261
  put("entries", rowsOf(db, "SELECT * FROM entries ORDER BY rowid"));
256
262
  put("voices", rowsOf(db, "SELECT * FROM voices ORDER BY rowid"));
257
263
  put("document_themes", rowsOf(db, "SELECT * FROM document_themes ORDER BY rowid"));
264
+ put("narration_aliases", rowsOf(db, "SELECT * FROM narration_aliases ORDER BY position,rowid"));
258
265
  put("project_templates", rowsOf(db, "SELECT * FROM project_templates ORDER BY rowid"));
259
266
  put("project_template_revisions", rowsOf(db, "SELECT * FROM project_template_revisions ORDER BY template_id,version"));
260
267
  put("schedules", rowsOf(db, "SELECT * FROM schedules ORDER BY rowid"));
261
268
  put("schedule_runs", rowsOf(db, "SELECT * FROM schedule_runs ORDER BY rowid"));
269
+ put("schedule_topics", rowsOf(db, "SELECT * FROM schedule_topics ORDER BY rowid"));
270
+ put("library_versions", rowsOf(db, "SELECT * FROM library_versions ORDER BY item_kind,item_id,version"));
271
+ put("channels", rowsOf(db, "SELECT * FROM channels ORDER BY rowid"));
272
+ put("cast_members", rowsOf(db, "SELECT * FROM cast_members ORDER BY rowid"));
273
+ put("cast_images", rowsOf(db, "SELECT * FROM cast_images ORDER BY rowid"));
274
+ put("episode_memories", rowsOf(db, "SELECT * FROM episode_memories ORDER BY rowid"));
275
+ put("channel_videos", rowsOf(db, "SELECT * FROM channel_videos ORDER BY rowid"));
276
+ put("standalone_usage", rowsOf(db, "SELECT * FROM standalone_usage ORDER BY rowid"));
277
+ // Every picture, not only the cast's: a project's config names the pictures it was started
278
+ // with by hash, and a Rebuild reads them from here.
279
+ const images = db
280
+ .prepare("SELECT sha256,mime,bytes FROM image_blobs ORDER BY sha256")
281
+ .all()
282
+ .flatMap((row) => row.bytes instanceof Uint8Array && row.bytes.byteLength > 0
283
+ ? [{ sha256: String(row.sha256), mime: String(row.mime), content: row.bytes }]
284
+ : []);
262
285
  // Play drafts are work the user typed and has not started yet, so they travel. A draft
263
286
  // that already became projects is not: its projects carry it. Its uploads travel with it
264
287
  // when they are complete on disk; one that is not comes back asking to be reattached,
@@ -293,7 +316,23 @@ function librarySnapshot(deps) {
293
316
  name: font.name,
294
317
  bytes: font.bytes,
295
318
  }));
296
- return { part: { tables, fonts, staged } };
319
+ return {
320
+ part: {
321
+ tables,
322
+ fonts,
323
+ staged,
324
+ ...(images.length === 0
325
+ ? {}
326
+ : {
327
+ images: images.map((image) => ({
328
+ sha256: image.sha256,
329
+ mime: image.mime,
330
+ bytes: image.content.byteLength,
331
+ })),
332
+ }),
333
+ },
334
+ images,
335
+ };
297
336
  }
298
337
  function fileBytes(path) {
299
338
  try {
@@ -7,7 +7,8 @@ import { fontMaxBytes } from "../fonts/model.js";
7
7
  // data/library.json settings, library, templates, schedules, drafts
8
8
  // data/usage.json the Usage screen's event log
9
9
  // data/projects/<id>.json one per project: every row it needs to open and keep going
10
- // files/... the bytes: project folders, uploaded fonts, draft uploads
10
+ // files/... the bytes: project folders, uploaded fonts, draft uploads and
11
+ // the pictures kept in the database (cast, end screen)
11
12
  // checksums.json a SHA-256 per file, written as the files streamed out
12
13
  //
13
14
  // Rows travel as the database holds them, table by table, with the schema version they fit;
@@ -24,6 +25,9 @@ export const projectMember = (id) => `data/projects/${id}.json`;
24
25
  export const projectFileMember = (id, path) => `files/projects/${id}/${path}`;
25
26
  export const fontMember = (name) => `files/fonts/${name}`;
26
27
  export const stagedMember = (id) => `files/staging/${id}`;
28
+ // A picture from image_blobs, named by its SHA-256: the database keeps the bytes (cast
29
+ // pictures, the end screen image), so they travel as files rather than inside a JSON row.
30
+ export const imageMember = (sha256) => `files/images/${sha256}`;
27
31
  // Every table a project's rows live in, parents before children. The queue is left out on
28
32
  // purpose: a backup never carries a project that is waiting to run.
29
33
  export const projectTables = [
@@ -49,6 +53,15 @@ export const projectTables = [
49
53
  "review_checkpoints",
50
54
  "review_checkpoint_approvals",
51
55
  "project_recovery_requests",
56
+ "youtube_description_edits",
57
+ "project_uploads",
58
+ // Since 2.5.0. Left out on purpose: plan_limit_waits and plan_limit_waiters (an account's
59
+ // current wait, a lease of this install) and prompt_softening (a pending one-off request).
60
+ "review_verdicts",
61
+ "provider_usage",
62
+ "plan_limit_readings",
63
+ "project_channels",
64
+ "project_trash",
52
65
  ];
53
66
  export const libraryTables = [
54
67
  "settings",
@@ -56,14 +69,27 @@ export const libraryTables = [
56
69
  "entries",
57
70
  "voices",
58
71
  "document_themes",
72
+ "narration_aliases",
59
73
  "project_templates",
60
74
  "project_template_revisions",
61
75
  "schedules",
62
76
  "schedule_runs",
77
+ "schedule_topics",
63
78
  "staged_files",
64
79
  "play_drafts",
65
80
  "play_draft_attachments",
66
81
  "project_template_instantiations",
82
+ // Since 2.5.0: prompt history, channels with their cast, episode memories and existing
83
+ // videos. image_blobs travel as files (`imageMember`), listed in the library part's `images`.
84
+ "library_versions",
85
+ "channels",
86
+ "cast_members",
87
+ "cast_images",
88
+ "episode_memories",
89
+ "channel_videos",
90
+ // Since 3.0.0: what topic generation, episode summaries and cast pictures cost, which
91
+ // belongs to a schedule or channel rather than a project.
92
+ "standalone_usage",
67
93
  ];
68
94
  export const usageTables = ["telemetry_events"];
69
95
  // Ceilings a well-formed backup of a real install stays far below. They bound what a hostile
@@ -118,6 +144,15 @@ export const manifestSchema = z
118
144
  bytes: z.number().int().min(0),
119
145
  })
120
146
  .strict();
147
+ // A picture in image_blobs is at most this large (cast uploads are capped well below it).
148
+ export const backupMaxImageBytes = 64 * 1024 * 1024;
149
+ export const backupImage = z
150
+ .object({
151
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
152
+ mime: z.enum(["image/png", "image/jpeg"]),
153
+ bytes: z.number().int().min(1).max(backupMaxImageBytes),
154
+ })
155
+ .strict();
121
156
  const fileEntry = z.object({ path: relativePath, bytes: z.number().int().min(0) }).strict();
122
157
  export const libraryPartSchema = z
123
158
  .object({
@@ -133,6 +168,8 @@ export const libraryPartSchema = z
133
168
  staged: z
134
169
  .array(z.object({ id: backupId, bytes: z.number().int().positive() }).strict())
135
170
  .max(10_000),
171
+ // Absent in backups made before channels.
172
+ images: z.array(backupImage).max(100_000).optional(),
136
173
  })
137
174
  .strict();
138
175
  export const usagePartSchema = z.object({ tables: tablesOf(usageTables) }).strict();