@gentbajko/slopify 2.5.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,26 +1,35 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
- import { join } from "node:path";
3
+ import { isAbsolute, join, relative, sep } from "node:path";
4
4
  import { crc32 } from "node:zlib";
5
5
  import { strFromU8, strToU8, Unzip, UnzipInflate, zipSync } from "fflate";
6
6
  import { z } from "zod";
7
7
  import { transact } from "../../kernel/db/tx.js";
8
+ import { liveProject } from "../admission/repo.js";
8
9
  import { silenceGapSecondsMax } from "../admission/rules.js";
9
10
  import { detectSlots } from "../admission/substitute.js";
10
11
  import { fontMaxBytes } from "../fonts/model.js";
11
12
  import { readFontMetadata } from "../fonts/sfnt.js";
12
13
  import { lintEntry, lintPrompt } from "../library/lint.js";
13
14
  import { entryCategories, entryModes, promptKinds } from "../library/model.js";
15
+ import { photorealisticPromptsKey } from "../library/photorealistic.js";
14
16
  import { listEntries, listPrompts } from "../library/repo.js";
15
17
  import { notificationUrlKey } from "../notifications/settings.js";
18
+ import { patchNotesSeenKey } from "../patch-notes/seen.js";
16
19
  import { projectTemplateSchema } from "../project-templates/schema.js";
17
20
  import { cliPathMaxLength } from "../settings/cli-paths.js";
21
+ import { providerDefaultsKey, providerDefaultsSchema } from "../settings/first-run.js";
18
22
  import { appearances, providerById, providerIds } from "../settings/model.js";
19
23
  import { listVoices } from "../settings/repo.js";
20
- import { voiceIdMax, voiceNameMax } from "../settings/voices.js";
24
+ import { realPersonVoicesKey, voiceIdMax, voiceNameMax } from "../settings/voices.js";
25
+ import { whatsNewSeenKey } from "../settings/whats-new.js";
26
+ import { studioPlaylistMax } from "../studio/model.js";
27
+ import { studioChannelPlaylistPrefix, studioFillQueuePrefix, studioPairingKey, studioPlaylistKey, studioRealFootageKey, } from "../studio/settings.js";
28
+ import { channelLinksKey } from "../youtube/edits-repo.js";
21
29
  import { defaultBackupsDir, projectDir, stagingPath } from "./layout.js";
22
30
  import { stageKinds } from "./model.js";
23
31
  import { insertStagedFile, stagedFiles } from "./repo.js";
32
+ import { projectStorage } from "./trim.js";
24
33
  const record = z.record(z.string(), z.string());
25
34
  export const portableMaxArchiveBytes = 100 * 1024 * 1024;
26
35
  export const portableMaxExpandedBytes = 128 * 1024 * 1024;
@@ -49,7 +58,18 @@ const libraryRow = z
49
58
  })
50
59
  .strict();
51
60
  const voiceRow = z
52
- .object({ id: z.string(), provider: z.enum(providerIds), name: z.string(), voice_id: z.string() })
61
+ .object({
62
+ id: z.string(),
63
+ provider: z.enum(providerIds),
64
+ name: z.string(),
65
+ voice_id: z.string(),
66
+ // Absent from backups made before voice languages, and on a voice whose languages are
67
+ // unknown.
68
+ languages: z
69
+ .array(z.string().regex(/^[a-z]{2,3}$/))
70
+ .max(60)
71
+ .optional(),
72
+ })
53
73
  .strict();
54
74
  const stagedMeta = z
55
75
  .object({
@@ -86,6 +106,11 @@ const manifestSchema = z
86
106
  staged: z.array(stagedMeta).max(portableMaxStagedFiles),
87
107
  })
88
108
  .strict();
109
+ // The settings-only .zip. Nothing in the app writes it any more: Export everything (the tar in
110
+ // `backup-export.ts`) replaced it in 2.5.0, and this stays so tests can make the .zip files
111
+ // older versions wrote. Channels and their cast are deliberately not in it: they arrived after
112
+ // the last version that could write a .zip, so no such file can hold one, and the full backup
113
+ // already carries them (with cast pictures) through `backup-format.ts`.
89
114
  export function exportPortable(deps) {
90
115
  const entries = {};
91
116
  const stagedPlan = planStagedExport(deps);
@@ -127,9 +152,10 @@ export function exportPortable(deps) {
127
152
  provider: voice.provider,
128
153
  name: voice.name,
129
154
  voice_id: voice.voiceId,
155
+ ...(voice.languages === undefined ? {} : { languages: voice.languages }),
130
156
  }));
131
157
  const templates = deps.db
132
- .prepare("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 r.version=t.head_version ORDER BY t.id")
158
+ .prepare("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 r.version=t.head_version WHERE t.deleted_at IS NULL ORDER BY t.id")
133
159
  .all()
134
160
  .map((row) => projectTemplateSchema.parse({ ...row, document: JSON.parse(String(row.document)) }));
135
161
  const fonts = exportUploadedFonts(deps.paths, entries);
@@ -209,8 +235,8 @@ export function importPortable(deps, bytes) {
209
235
  let voices = 0;
210
236
  for (const row of manifest.voices) {
211
237
  deps.db
212
- .prepare("INSERT INTO voices(id,provider,name,voice_id) VALUES(?,?,?,?) ON CONFLICT(id) DO UPDATE SET provider=excluded.provider,name=excluded.name,voice_id=excluded.voice_id")
213
- .run(row.id, row.provider, row.name, row.voice_id);
238
+ .prepare("INSERT INTO voices(id,provider,name,voice_id,languages_json) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET provider=excluded.provider,name=excluded.name,voice_id=excluded.voice_id,languages_json=excluded.languages_json")
239
+ .run(row.id, row.provider, row.name, row.voice_id, row.languages === undefined ? null : JSON.stringify(row.languages));
214
240
  voices += 1;
215
241
  }
216
242
  let templateCount = 0;
@@ -324,17 +350,56 @@ const slotsColumn = z.array(z.string());
324
350
  const storedCliPath = z.string().max(cliPathMaxLength).nullable();
325
351
  const storedSilenceGap = z.number().int().min(0).max(silenceGapSecondsMax);
326
352
  const storedAppearance = z.enum(appearances);
353
+ // Settings → General's Level the volume for new runs.
354
+ const storedLoudness = z
355
+ .object({ enabled: z.boolean(), videoLufs: z.number(), audioFilesLufs: z.number() })
356
+ .strict();
357
+ const storedPlaylist = z.string().max(studioPlaylistMax);
358
+ // Settings → Channel links, and the providers a fresh Play form starts with.
359
+ // The AI use marks: ticked voices, Image prompts and projects, by id.
360
+ const storedIds = z.array(z.string().max(200)).max(100_000);
361
+ // The channel page's "Keep only titles containing…" text, one per channel.
362
+ const importFilterPrefix = "channels.importFilter.";
363
+ const storedImportFilter = z.string().max(200);
364
+ const storedChannelLinks = z
365
+ .array(z.object({ name: z.string().max(200), url: z.string().max(2000) }).strict())
366
+ .max(200);
327
367
  function portableSettings(settings) {
328
368
  const portable = {};
329
369
  for (const [key, value] of Object.entries(settings)) {
330
370
  // The Notification URL stays on this machine: an ntfy topic in it is as good as a password.
331
- if (key === "tutorial.session" || key === notificationUrlKey)
371
+ // So does the Studio extension's pairing: its token reads every project's upload pack.
372
+ // Which "What's new" tour and which patch notes this install has closed are about this
373
+ // install, not the data.
374
+ if (key === "tutorial.session" ||
375
+ // Whether this install showed its first-run welcome belongs to this install.
376
+ key === "first-run.done" ||
377
+ key === notificationUrlKey ||
378
+ key === studioPairingKey ||
379
+ // What waits to be filled in Studio belongs to this install's pairing.
380
+ key.startsWith(studioFillQueuePrefix) ||
381
+ key === whatsNewSeenKey ||
382
+ key === patchNotesSeenKey)
332
383
  continue;
333
384
  const parsed = storedJson(value);
334
385
  if (key === "silenceGapSeconds")
335
386
  storedSilenceGap.parse(parsed);
336
387
  else if (key === "appearance")
337
388
  storedAppearance.parse(parsed);
389
+ else if (key === "loudness")
390
+ storedLoudness.parse(parsed);
391
+ else if (key === studioPlaylistKey || key.startsWith(studioChannelPlaylistPrefix))
392
+ storedPlaylist.parse(parsed);
393
+ else if (key === channelLinksKey)
394
+ storedChannelLinks.parse(parsed);
395
+ else if (key === realPersonVoicesKey ||
396
+ key === photorealisticPromptsKey ||
397
+ key === studioRealFootageKey)
398
+ storedIds.parse(parsed);
399
+ else if (key.startsWith(importFilterPrefix))
400
+ storedImportFilter.parse(parsed);
401
+ else if (key === providerDefaultsKey)
402
+ providerDefaultsSchema.parse(parsed);
338
403
  else if (key.startsWith("cli.path.")) {
339
404
  const provider = providerById(z.enum(providerIds).parse(key.slice("cli.path.".length)));
340
405
  if (provider.auth !== "cli")
@@ -863,16 +928,24 @@ function planStagedImport(deps, archive, files) {
863
928
  };
864
929
  });
865
930
  }
931
+ function inside(root, path) {
932
+ const r = relative(root, path);
933
+ return r === "" || (r !== ".." && !r.startsWith(`..${sep}`) && !isAbsolute(r));
934
+ }
866
935
  export function storageBytes(paths) {
867
- // Scheduled backups default to a folder inside the projects root; they are not project files.
868
- const projects = directoryBytes(paths.projects) - directoryBytes(defaultBackupsDir(paths));
936
+ // Scheduled backups default to a folder inside the projects root on installs from before
937
+ // 3.0; they are not project files. Newer installs keep projects outside the data dir.
938
+ const backups = defaultBackupsDir(paths);
939
+ const projects = directoryBytes(paths.projects) -
940
+ (inside(paths.projects, backups) ? directoryBytes(backups) : 0);
869
941
  const staging = directoryBytes(paths.staging);
870
- return { data: directoryBytes(paths.dataDir), projects, staging };
942
+ const data = directoryBytes(paths.dataDir) + (inside(paths.dataDir, paths.projects) ? 0 : projects);
943
+ return { data, projects, staging };
871
944
  }
872
945
  export function storageUsage(deps) {
873
946
  const totals = storageBytes(deps.paths);
874
947
  const byProject = deps.db
875
- .prepare("SELECT id,title FROM projects ORDER BY created_at DESC, id DESC")
948
+ .prepare(`SELECT id,title FROM projects WHERE ${liveProject()} ORDER BY created_at DESC, id DESC`)
876
949
  .all()
877
950
  .flatMap((row) => {
878
951
  if (typeof row.id !== "string" || typeof row.title !== "string")
@@ -882,10 +955,19 @@ export function storageUsage(deps) {
882
955
  id: row.id,
883
956
  title: row.title,
884
957
  bytes: directoryBytes(projectDir(deps.paths, row.id)),
958
+ ...projectStorage(deps, row.id),
885
959
  },
886
960
  ];
887
961
  });
888
- return { ...totals, byProject };
962
+ const trashed = deps.db
963
+ .prepare("SELECT project_id FROM project_trash ORDER BY project_id")
964
+ .all()
965
+ .flatMap((row) => (typeof row.project_id === "string" ? [row.project_id] : []));
966
+ const trash = {
967
+ projects: trashed.length,
968
+ bytes: trashed.reduce((sum, id) => sum + directoryBytes(projectDir(deps.paths, id)), 0),
969
+ };
970
+ return { ...totals, trash, byProject };
889
971
  }
890
972
  function directoryBytes(root) {
891
973
  if (!existsSync(root))
@@ -1,5 +1,5 @@
1
1
  import { readdirSync, rmSync, statSync, unlinkSync } from "node:fs";
2
- import { join, relative, sep } from "node:path";
2
+ import { isAbsolute, join, relative, sep } from "node:path";
3
3
  import { backupsFolderName, outputPath, stagingPath } from "./layout.js";
4
4
  import { stagedFiles as stagedRows } from "./repo.js";
5
5
  import { stagedFileReferenced } from "./staging-refs.js";
@@ -31,12 +31,20 @@ export function reconcileStorage(db, paths) {
31
31
  }
32
32
  }
33
33
  let orphanFiles = 0;
34
+ // A Projects folder outside the data dir (in Documents, from 3.0) is one the user browses,
35
+ // and the system drops its own files there (.DS_Store, desktop.ini). Only folders are
36
+ // Slopify's in it, so a loose file stays; hidden entries stay everywhere.
37
+ const userVisible = !within(paths.dataDir, paths.projects);
34
38
  for (const entry of readdirSync(paths.projects, { withFileTypes: true })) {
35
39
  const path = join(paths.projects, entry.name);
36
40
  // The scheduled backups' default folder: its archives belong to slices/backups, which
37
41
  // prunes them by its own rules.
38
42
  if (entry.name === backupsFolderName && entry.isDirectory())
39
43
  continue;
44
+ if (entry.name.startsWith(".") || osFiles.has(entry.name.toLowerCase()))
45
+ continue;
46
+ if (!entry.isDirectory() && userVisible)
47
+ continue;
40
48
  if (!entry.isDirectory()) {
41
49
  unlinkSync(path);
42
50
  orphanFiles += 1;
@@ -119,6 +127,11 @@ function filesUnder(root) {
119
127
  }
120
128
  return files;
121
129
  }
130
+ const osFiles = new Set(["desktop.ini", "thumbs.db"]);
131
+ function within(root, path) {
132
+ const inside = relative(root, path);
133
+ return (inside === "" || (inside !== ".." && !inside.startsWith(`..${sep}`) && !isAbsolute(inside)));
134
+ }
122
135
  // outputs.path is stored with forward slashes; the filesystem may hand back backslashes.
123
136
  function slashed(path) {
124
137
  return path.split(sep).join("/");
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { liveProject } from "../admission/repo.js";
2
3
  import { outputRoles, stagedFileStates, stageKinds } from "./model.js";
3
4
  import { metaSchema } from "./schema.js";
4
5
  const outputRow = z.object({
@@ -58,7 +59,9 @@ export function deleteStagedFile(db, id) {
58
59
  db.prepare("DELETE FROM staged_files WHERE id = ? AND NOT EXISTS (SELECT 1 FROM play_draft_attachments WHERE staged_file_id = ?)").run(id, id);
59
60
  }
60
61
  export function projectTitle(db, projectId) {
61
- const row = db.prepare("SELECT title FROM projects WHERE id = ?").get(projectId);
62
+ const row = db
63
+ .prepare(`SELECT title FROM projects WHERE id = ? AND ${liveProject()}`)
64
+ .get(projectId);
62
65
  if (row === undefined) {
63
66
  return undefined;
64
67
  }
@@ -10,11 +10,22 @@ export const metaSchema = z.object({
10
10
  prompt: z.string().optional(),
11
11
  index: z.number().optional(),
12
12
  short: z.number().optional(),
13
+ format: z.enum(["16:9", "9:16"]).optional(),
13
14
  sentences: z.tuple([z.number(), z.number()]).optional(),
14
15
  warnings: z.array(z.string()).optional(),
15
16
  provider: z.string().optional(),
16
17
  model: z.string().optional(),
17
18
  voice: z.string().optional(),
19
+ loudness: z
20
+ .object({
21
+ pieces: z.number(),
22
+ skipped: z.number(),
23
+ spreadBefore: z.number(),
24
+ spreadAfter: z.number(),
25
+ target: z.number(),
26
+ })
27
+ .optional(),
28
+ master: z.object({ target: z.number(), integrated: z.number(), truePeak: z.number() }).optional(),
18
29
  });
19
30
  export const outputSchema = z.object({
20
31
  id: z.string(),
@@ -0,0 +1,146 @@
1
+ import { existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
2
+ import { join, relative, sep } from "node:path";
3
+ import { derive } from "../../kernel/runner/graph.js";
4
+ import { projectExists, projectPaused, stagesOf } from "../admission/repo.js";
5
+ import { backupsFolderName, projectDir } from "./layout.js";
6
+ import { pieceFile } from "./reconcile.js";
7
+ // Keep outputs only: a finished project drops the working files it was made from (images,
8
+ // narration parts, subtitle timing, render settings) and keeps what gets published. Two
9
+ // rules keep it safe. A file the user supplied is never removed, because nothing could make
10
+ // it again. And only files a project record names are removed: anything else in the folder
11
+ // is reconcile's business (`reconcile.ts`), which leaves the Backups folder alone.
12
+ // What a finished project is for: the files that get uploaded or read, from every revision,
13
+ // so an older video in History stays too.
14
+ export const outputRoles = [
15
+ "video",
16
+ "short_video",
17
+ "shorts",
18
+ "thumbnail",
19
+ "article_md",
20
+ "article_txt",
21
+ "sources",
22
+ "youtube_description",
23
+ "youtube_tags",
24
+ "document_pdf",
25
+ "audio_export",
26
+ "subtitles_srt",
27
+ "subtitles_vtt",
28
+ ];
29
+ export function projectStorage(deps, projectId) {
30
+ const files = filesOf(deps.paths, projectId);
31
+ const kept = keptPaths(deps.db, projectId);
32
+ const outputs = outputPaths(deps.db, projectId);
33
+ const removable = removablePaths(deps.db, projectId, kept);
34
+ let outputsBytes = 0;
35
+ let workingBytes = 0;
36
+ let removableFiles = 0;
37
+ let removableBytes = 0;
38
+ for (const [path, bytes] of files) {
39
+ if (outputs.has(path))
40
+ outputsBytes += bytes;
41
+ else
42
+ workingBytes += bytes;
43
+ if (removable.has(path)) {
44
+ removableFiles += 1;
45
+ removableBytes += bytes;
46
+ }
47
+ }
48
+ return {
49
+ outputsBytes,
50
+ workingBytes,
51
+ removableFiles,
52
+ removableBytes,
53
+ finished: finished(deps, projectId),
54
+ };
55
+ }
56
+ export function keepOutputsOnly(deps, projectId) {
57
+ if (!projectExists(deps.db, projectId))
58
+ return { ok: false, reason: "no-project" };
59
+ if (!finished(deps, projectId))
60
+ return { ok: false, reason: "not-finished" };
61
+ const files = filesOf(deps.paths, projectId);
62
+ const removable = removablePaths(deps.db, projectId, keptPaths(deps.db, projectId));
63
+ let count = 0;
64
+ let bytesFreed = 0;
65
+ for (const path of removable) {
66
+ const bytes = files.get(path);
67
+ if (bytes === undefined)
68
+ continue;
69
+ unlinkSync(join(projectDir(deps.paths, projectId), path));
70
+ count += 1;
71
+ bytesFreed += bytes;
72
+ }
73
+ return { ok: true, files: count, bytesFreed };
74
+ }
75
+ function finished(deps, projectId) {
76
+ if (deps.hasInflight?.(projectId) === true)
77
+ return false;
78
+ const state = derive(stagesOf(deps.db, projectId), projectPaused(deps.db, projectId));
79
+ if (state !== "done" && state !== "partial")
80
+ return false;
81
+ // Nothing admitted that could still start: a rebuild waiting to be picked up needs its inputs.
82
+ return (deps.db
83
+ .prepare("SELECT 1 FROM revision_work WHERE project_id=? AND (state='running' OR (state='pending' AND dispatch_state='allowed')) LIMIT 1")
84
+ .get(projectId) === undefined);
85
+ }
86
+ // Every file under the project folder, by project-relative path with forward slashes.
87
+ function filesOf(paths, projectId) {
88
+ const files = new Map();
89
+ if (projectId === backupsFolderName)
90
+ return files;
91
+ const root = projectDir(paths, projectId);
92
+ if (!existsSync(root))
93
+ return files;
94
+ for (const entry of readdirSync(root, { withFileTypes: true, recursive: true })) {
95
+ if (!entry.isFile())
96
+ continue;
97
+ const full = join(entry.parentPath, entry.name);
98
+ files.set(relative(root, full).split(sep).join("/"), statSync(full).size);
99
+ }
100
+ return files;
101
+ }
102
+ function outputPaths(db, projectId) {
103
+ const roles = outputRoles.map(() => "?").join(",");
104
+ return pathsOf(db
105
+ .prepare(`SELECT a.path FROM revision_outputs o JOIN project_assets a ON a.id=o.asset_id AND a.project_id=o.project_id
106
+ WHERE o.project_id=? AND json_extract(o.descriptor,'$.role') IN (${roles})
107
+ UNION SELECT path FROM outputs WHERE project_id=? AND role IN (${roles})`)
108
+ .all(projectId, ...outputRoles, projectId, ...outputRoles));
109
+ }
110
+ // The outputs, and every file the user supplied: an asset any saved revision or the project's
111
+ // settings name, or a legacy output row that came from an upload.
112
+ function keptPaths(db, projectId) {
113
+ const kept = new Set(outputPaths(db, projectId));
114
+ for (const path of pathsOf(db
115
+ .prepare(`SELECT a.path FROM project_assets a WHERE a.project_id=? AND (
116
+ EXISTS(SELECT 1 FROM project_revisions r WHERE r.project_id=a.project_id
117
+ AND (instr(r.content,a.id)>0 OR instr(r.config,a.id)>0))
118
+ OR EXISTS(SELECT 1 FROM projects p WHERE p.id=a.project_id AND instr(p.config,a.id)>0))
119
+ UNION SELECT path FROM outputs WHERE project_id=? AND original_filename IS NOT NULL`)
120
+ .all(projectId, projectId)))
121
+ kept.add(path);
122
+ return kept;
123
+ }
124
+ // The files a record names that are not kept: made by a step, so a step can make them again.
125
+ function removablePaths(db, projectId, kept) {
126
+ const named = new Set([
127
+ ...pathsOf(db
128
+ .prepare("SELECT path FROM project_assets WHERE project_id=? UNION SELECT path FROM outputs WHERE project_id=?")
129
+ .all(projectId, projectId)),
130
+ ]);
131
+ for (const row of db
132
+ .prepare("SELECT p.payload FROM stage_pieces p JOIN stages s ON s.id=p.stage_id WHERE s.project_id=? AND p.payload IS NOT NULL")
133
+ .all(projectId)) {
134
+ const file = pieceFile(row.payload);
135
+ if (file !== undefined)
136
+ named.add(file.split(sep).join("/"));
137
+ }
138
+ return new Set([...named].filter((path) => !kept.has(path)));
139
+ }
140
+ function pathsOf(rows) {
141
+ const paths = new Set();
142
+ for (const row of rows)
143
+ if (typeof row.path === "string")
144
+ paths.add(row.path.split("\\").join("/"));
145
+ return paths;
146
+ }
@@ -0,0 +1,74 @@
1
+ // YouTube's "AI use" question (it was "Altered or synthetic content"), answered for every
2
+ // video and short of a project. Browser-safe: the upload pack carries the answer, the channel
3
+ // page names the setting.
4
+ //
5
+ // The question as Studio asks it (read on the live Details page, 2026-09-27), behind Show more:
6
+ // AI use — "Was AI used to generate or edit your content in any of the following ways?"
7
+ // 1. "Makes a real person appear to say or do something they didn't say or do"
8
+ // 2. "Alters footage of a real event or place"
9
+ // 3. "Generates a realistic-looking scene that didn't actually occur"
10
+ // Radios "Yes, AI was used" / "No, AI wasn't used"; "Selecting 'yes' adds a label to your
11
+ // content."
12
+ // YouTube's help page (https://support.google.com/youtube/answer/14328491) says the same:
13
+ // clearly unrealistic content (fantasy, animation), minor edits (colour, filters) and
14
+ // production help (AI for the script, thumbnail, title or captions) need no label.
15
+ //
16
+ // So the automatic answer is Yes only when one of the three cases applies, and No otherwise:
17
+ // 1. a saved voice marked "Imitates a real person" (Settings → Voices) narrates it: an AI
18
+ // voice cloned from, or made to sound like, a real person, so that person seems to say it;
19
+ // 2. uploaded clips the person marked as real footage (the project's Prepare upload) get the
20
+ // Look's atmosphere overlay (embers, dust or fog), which adds to a real scene what wasn't
21
+ // there. A colour grade, vignette or grain alone is one of YouTube's minor edits;
22
+ // 3. an AI image model draws its pictures from an Image prompt marked "Draws photorealistic
23
+ // pictures" (Library → Prompts). Stylised, painterly or illustrated images are not
24
+ // realistic-looking, and animating them doesn't make them so.
25
+ // An AI narrator that doesn't pose as a real person, AI-written text and an AI thumbnail are
26
+ // none of the three. Every mark is off until the person sets it. A channel can still set
27
+ // Always Yes or Always No (Channels → the channel → Brand → YouTube AI disclosure).
28
+ export const aiDisclosureSettings = ["auto", "yes", "no"];
29
+ export const aiDisclosureLabels = {
30
+ auto: "Automatic",
31
+ yes: "Always Yes",
32
+ no: "Always No",
33
+ };
34
+ // Studio's three cases, as it words them.
35
+ export const aiUseCases = [
36
+ "Makes a real person appear to say or do something they didn't say or do",
37
+ "Alters footage of a real event or place",
38
+ "Generates a realistic-looking scene that didn't actually occur",
39
+ ];
40
+ const where = "Channels → the channel → Brand → YouTube AI disclosure";
41
+ export function aiDisclosureOf(input) {
42
+ if (input.setting === "yes")
43
+ return { altered: true, why: `This channel is set to Always Yes (${where}).` };
44
+ if (input.setting === "no")
45
+ return { altered: false, why: `This channel is set to Always No (${where}).` };
46
+ const reasons = [];
47
+ if (input.realPersonVoices.length > 0)
48
+ reasons.push(`the narration uses ${quotedList(input.realPersonVoices)}, marked in Settings → Voices as imitating a real person (YouTube's case 1: "${aiUseCases[0]}")`);
49
+ if (input.realFootage && input.footageOverlay !== undefined)
50
+ reasons.push(`the Look's ${input.footageOverlay} overlay is laid over uploaded clips marked as real footage (YouTube's case 2: "${aiUseCases[1]}")`);
51
+ if (input.photorealistic)
52
+ reasons.push(`an AI image model draws its pictures from an Image prompt marked photorealistic in Library → Prompts (YouTube's case 3: "${aiUseCases[2]}")`);
53
+ if (reasons.length > 0)
54
+ return {
55
+ altered: true,
56
+ why: `Yes because ${list(reasons)}. Selecting Yes adds YouTube's AI label.`,
57
+ };
58
+ const footage = input.realFootage && input.kind === "video"
59
+ ? " The uploaded real footage only gets colour, vignette or grain, which YouTube counts as minor edits."
60
+ : "";
61
+ return {
62
+ altered: false,
63
+ why: `No because none of YouTube's three AI use cases applies: no narrating voice is marked as imitating a real person (Settings → Voices), no real footage is altered, and no AI pictures come from an Image prompt marked photorealistic (Library → Prompts). Stylised AI pictures and an AI narrator that doesn't pose as a real person don't need the label.${footage}`,
64
+ };
65
+ }
66
+ function quotedList(names) {
67
+ const quoted = names.map((name) => `"${name}"`);
68
+ return `the voice${names.length === 1 ? "" : "s"} ${list(quoted)}`;
69
+ }
70
+ function list(parts) {
71
+ if (parts.length <= 1)
72
+ return parts.join("");
73
+ return `${parts.slice(0, -1).join(", ")} and ${parts.at(-1) ?? ""}`;
74
+ }
@@ -0,0 +1,30 @@
1
+ // The upload pack: everything YouTube Studio asks for when a finished video (or one of its
2
+ // shorts) is uploaded by hand, in the order Studio asks for it. Slopify never uploads or
3
+ // publishes; the pack is copied into Studio by the person, or filled in by the Slopify Studio
4
+ // extension (`packages/extension`), and the person presses Publish.
5
+ // Browser-safe: the project page and the extension read these names too.
6
+ // YouTube's own limits on the upload dialog's fields.
7
+ export const studioTitleMax = 100;
8
+ export const studioPlaylistMax = 150;
9
+ // The one audience a Slopify video is uploaded with. Slopify's narrated videos are not made
10
+ // for children, and Studio refuses to continue until the question is answered.
11
+ export const studioAudience = "not_made_for_kids";
12
+ // Studio's upload page. It opens the upload dialog on the signed-in channel.
13
+ export const studioUploadUrl = "https://www.youtube.com/upload";
14
+ // The steps in Studio's order, as the Prepare upload view lists them.
15
+ export const studioSteps = [
16
+ "video",
17
+ "title",
18
+ "description",
19
+ "thumbnails",
20
+ "playlist",
21
+ "audience",
22
+ "altered",
23
+ "tags",
24
+ ];
25
+ // Tags as Studio's field takes them: comma-separated.
26
+ export function tagsLine(tags) {
27
+ return tags.join(", ");
28
+ }
29
+ // The browsers the extension is built for, as its download names them.
30
+ export const studioExtensionBrowsers = ["chrome", "firefox"];