@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,12 +1,21 @@
1
+ import { thumbnailCountOf, thumbnailKey } from "../admission/model.js";
2
+ import { usesShortMode } from "../admission/short-mode.js";
1
3
  import { render } from "../admission/substitute.js";
4
+ import { usesAmbientBed } from "../video/ambient-bed.js";
5
+ import { castFor } from "./recipe-cast.js";
6
+ import { noMaster } from "./recipe-loudness.js";
2
7
  import { recipe, resourceIdentity, } from "./recipe-model.js";
3
- import { imageChoice } from "./recipe-reference.js";
8
+ import { castField, imageChoice } from "./recipe-reference.js";
4
9
  import { matchingText, renderedPrompt } from "./recipe-text.js";
5
10
  export function visualRecipes(config, content, audioFingerprint, captionFingerprint,
6
11
  // What the Video stage's edit settings add (`recipe-edit.ts`), from the images planned here.
7
12
  edit,
8
13
  // The establishing image the generated images are drawn from, when it is on.
9
- reference) {
14
+ reference,
15
+ // The word timing's identity, which a short's captions are drawn from.
16
+ timing = null,
17
+ // Level the volume: the levelled narration and the master (`recipe-loudness.ts`).
18
+ master = noMaster) {
10
19
  const recipes = [];
11
20
  const imageKeys = config.sources.images === "off" ? [] : content.imageOrder;
12
21
  for (const key of imageKeys) {
@@ -26,10 +35,19 @@ reference) {
26
35
  aspect: config.format,
27
36
  prompt: prompt ?? "",
28
37
  ...(reference === undefined ? {} : { reference: reference.input }),
38
+ ...castField(castFor(config, prompt)),
29
39
  }, image.source === "provide" || reference === undefined ? [] : [reference.key], { unresolved: image.source === "provide" ? image.assetId === null : !prompt?.trim() }));
30
40
  }
31
41
  if (config.sources.video !== "off") {
32
- const edited = edit?.([...recipes]) ?? { recipes: [], values: [], dependsOn: [] };
42
+ const short = usesShortMode(config);
43
+ // A short is rendered by the Shorts renderer, which has no cuts, transitions or Look.
44
+ const edited = (short ? undefined : edit?.([...recipes])) ?? {
45
+ recipes: [],
46
+ values: [],
47
+ dependsOn: [],
48
+ };
49
+ // A short (rendered by the Shorts renderer) never has the bed.
50
+ const bed = short ? undefined : ambientBedValues(config, content);
33
51
  const audioKeys = config.sources.audio === "off"
34
52
  ? []
35
53
  : [
@@ -64,18 +82,48 @@ reference) {
64
82
  // Only what the edit settings change; nothing at all for today's slideshow, so
65
83
  // its fingerprint is the one it always had.
66
84
  ...(edited.values.length === 0 ? [] : [["video-edit", ...edited.values]]),
85
+ // Only for a short, so every long video keeps its fingerprint: the word-by-word
86
+ // captions are drawn from the word timing in the caption font, under the title.
87
+ ...(short
88
+ ? [["short-v1", config.subtitles?.fontId ?? "default", config.title, timing]]
89
+ : []),
90
+ // Only while the video has an ambient bed, so every video without one keeps the
91
+ // fingerprint it always had.
92
+ ...(bed === undefined ? [] : [bed]),
93
+ // Only while the volume is levelled, so every video without it keeps its fingerprint.
94
+ ...master.values,
67
95
  ],
68
96
  }, [
69
97
  ...recipes.map((value) => value.key),
70
98
  ...audioKeys,
99
+ ...master.keys,
71
100
  ...(config.subtitles?.mode === "burn-in" ? ["subtitles:files"] : []),
101
+ ...(short ? ["subtitles:timing"] : []),
72
102
  ...edited.dependsOn,
73
- ], { unresolved: imageKeys.length === 0 }));
103
+ ], {
104
+ unresolved: imageKeys.length === 0 ||
105
+ (config.ambientBed?.source === "upload" && content.ambientBed === undefined),
106
+ }));
74
107
  // After the export, so the images' recipes above stay the only ones its first values list.
75
108
  recipes.splice(recipes.length - 1, 0, ...edited.recipes);
76
109
  }
77
110
  return recipes;
78
111
  }
112
+ // What the ambient bed adds to the render's fingerprint: its settings and, for the user's own
113
+ // file, the project asset it plays. Undefined without a bed.
114
+ export function ambientBedValues(config, content) {
115
+ const bed = config.ambientBed;
116
+ if (bed === undefined || !usesAmbientBed(config))
117
+ return undefined;
118
+ return [
119
+ "ambient-bed-v1",
120
+ bed.source,
121
+ bed.levelDb,
122
+ bed.fadeInSeconds,
123
+ bed.tailSeconds,
124
+ bed.source === "upload" ? (content.ambientBed ?? null) : null,
125
+ ];
126
+ }
79
127
  export function thumbnailRecipes(context, textRecipes,
80
128
  // The establishing image, when it is on and the thumbnail is drawn from it too.
81
129
  reference) {
@@ -95,35 +143,50 @@ reference) {
95
143
  const prompt = config.sources.thumbnail === "prompt_by_llm" && promptRecipe !== undefined
96
144
  ? matchingText(context, promptRecipe, "prompt")
97
145
  : renderedPrompt(context, "thumbnailPrompt");
98
- return [
99
- recipe(context, "thumbnail:image", "thumbnail", prompt === null
100
- ? {
101
- kind: "deferred",
102
- version: 1,
103
- operation: "thumbnail-image",
104
- template: [
105
- promptRecipe?.fingerprint ?? null,
106
- config.images?.provider ?? null,
107
- config.images?.model ?? null,
108
- config.format,
109
- // Only what is in use, so a thumbnail made before these existed keeps its
110
- // fingerprint.
111
- ...(config.images?.thinking === undefined ? [] : [config.images.thinking]),
112
- ...(reference === undefined ? [] : [reference.input.fingerprint]),
113
- ],
114
- }
115
- : {
116
- kind: "image",
117
- version: 1,
118
- ...imageChoice(config),
119
- aspect: config.format,
120
- prompt,
121
- ...(reference === undefined ? {} : { reference: reference.input }),
122
- }, [
123
- ...(promptRecipe === undefined ? [] : [promptRecipe.key]),
124
- ...(reference === undefined ? [] : [reference.key]),
125
- ]),
126
- ];
146
+ const variants = Array.from({ length: thumbnailCountOf(config) }, (_, index) => index + 1);
147
+ return variants.map((variant) => recipe(context, thumbnailKey(variant), "thumbnail", prompt === null
148
+ ? {
149
+ kind: "deferred",
150
+ version: 1,
151
+ operation: "thumbnail-image",
152
+ template: [
153
+ promptRecipe?.fingerprint ?? null,
154
+ config.images?.provider ?? null,
155
+ config.images?.model ?? null,
156
+ config.format,
157
+ // Only what is in use, so a thumbnail made before these existed keeps its
158
+ // fingerprint.
159
+ ...(config.images?.thinking === undefined ? [] : [config.images.thinking]),
160
+ ...(reference === undefined ? [] : [reference.input.fingerprint]),
161
+ ...(variant === 1 ? [] : [["thumbnail-variant", variant]]),
162
+ ],
163
+ }
164
+ : {
165
+ kind: "image",
166
+ version: 1,
167
+ ...imageChoice(config),
168
+ aspect: config.format,
169
+ prompt: thumbnailVariantPrompt(prompt, variant),
170
+ ...(reference === undefined ? {} : { reference: reference.input }),
171
+ // The thumbnail stands for the whole video, like the establishing image.
172
+ ...castField(castFor(config, config.title, prompt)),
173
+ }, [
174
+ ...(promptRecipe === undefined ? [] : [promptRecipe.key]),
175
+ ...(reference === undefined ? [] : [reference.key]),
176
+ ]));
177
+ }
178
+ // The second and third thumbnails are the same prompt asked for another composition, so the
179
+ // three test one idea against itself rather than three ideas. The first is the prompt as it
180
+ // is, which keeps the thumbnail every project already has.
181
+ const variantFraming = {
182
+ 2: "a tight close-up where the main subject fills most of the frame, seen from a different angle",
183
+ 3: "a wider shot that shows the main subject in its setting, placed off-centre with room around it",
184
+ };
185
+ export function thumbnailVariantPrompt(prompt, variant) {
186
+ const framing = variantFraming[variant];
187
+ if (framing === undefined)
188
+ return prompt;
189
+ return `${prompt}\n\nThis is thumbnail ${String(variant)} of 3 for a YouTube thumbnail test. Keep the subject, style, colours and any text asked for above, but use a clearly different composition: ${framing}.`;
127
190
  }
128
191
  export function visualAssets(context, recipes) {
129
192
  const images = recipes.filter((value) => value.stage === "images");
@@ -0,0 +1,286 @@
1
+ import { aliasMatches, applyAliases } from "../../kernel/ports/narration-aliases.js";
2
+ import { splitText } from "../../kernel/ports/text.js";
3
+ import { narrationAliasesOf, usesNarrationPreparation } from "../admission/rules.js";
4
+ import { aliasSpans, withAliasSpans } from "../narration/aliases.js";
5
+ import { normalizeNarrationText } from "../narration/plan.js";
6
+ import { parsePronunciationGlossary, pronunciationSpans, } from "../narration/pronunciation.js";
7
+ import { withSharedGlossary } from "../narration/shared-glossary.js";
8
+ import { prepareRequests } from "../narration/steering.js";
9
+ import { groupTurns } from "../voices/grouping.js";
10
+ import { speakerPace } from "../voices/model.js";
11
+ import { parseScript } from "../voices/script.js";
12
+ import { describedText, describeFuture, describing } from "./recipe-describe.js";
13
+ import { recipe, resourceIdentity, } from "./recipe-model.js";
14
+ import { pauseValues } from "./recipe-pauses.js";
15
+ import { preparationForGroup, preparationFuture, preparationTemplate, } from "./recipe-preparation.js";
16
+ export function voiceBodyRecipes(context, voices, script, glossary) {
17
+ const parts = [];
18
+ const preparations = [];
19
+ const layout = [];
20
+ // Which parts end a turn, for the pauses between sentences (a turn gap of 0 is still a turn).
21
+ const turnEnds = new Set();
22
+ let transcript = script.fingerprint ?? script.text ?? null;
23
+ const describe = describing(context);
24
+ const parsed = script.text === null ? undefined : parseScript(script.text, voices.speakers, describe);
25
+ const prepare = voices.speakers.some((speaker) => preparesTurns(context.config, speaker));
26
+ // The narration still to be worked out: the whole script's while it is being written, or
27
+ // every turn's while a turn's delivery cues are (as a single voice waits for all of its own).
28
+ const future = () => recipe(context, "audio:body:future", "audio", {
29
+ kind: "deferred",
30
+ version: 1,
31
+ operation: "body-narration",
32
+ template: [
33
+ script.fingerprint ?? script.text ?? null,
34
+ speakerValues(voices),
35
+ ...(prepare ? [preparationTemplate(context)] : []),
36
+ ...(described.length === 0
37
+ ? []
38
+ : [["described-v1", described.map((row) => row.fingerprint)]]),
39
+ ],
40
+ }, [
41
+ ...new Set([
42
+ ...script.dependsOn,
43
+ ...described.map((row) => row.key),
44
+ ...preparations.map((row) => row.key),
45
+ ]),
46
+ ]);
47
+ let pending = false;
48
+ // A table, figure, equation or code block inside a turn is described within that turn: the
49
+ // turn says the description where the block was, in the same voice.
50
+ const described = [];
51
+ const cards = [];
52
+ let turns = parsed?.ok === true ? parsed.script.turns : [];
53
+ if (describe && script.text === null) {
54
+ const from = script.dependsOn[0];
55
+ if (from !== undefined)
56
+ described.push(describeFuture(context, { key: from, fingerprint: script.fingerprint ?? from }));
57
+ }
58
+ else if (describe && parsed?.ok === true) {
59
+ let offset = 0;
60
+ turns = turns.map((turn) => {
61
+ if (turn.markdown === undefined)
62
+ return turn;
63
+ const said = describedText(context, turn.markdown, script.dependsOn, offset);
64
+ if (said.recipes.length === 0)
65
+ return turn;
66
+ offset += said.recipes.length;
67
+ described.push(...said.recipes);
68
+ cards.push(...said.cards);
69
+ if (said.spoken === null) {
70
+ pending = true;
71
+ return turn;
72
+ }
73
+ return { ...turn, text: said.spoken.trim().replace(/\s*\n\s*/g, " ") };
74
+ });
75
+ }
76
+ preparations.push(...described);
77
+ if (script.text === null) {
78
+ // Turns not yet known are prepared behind one future step, as a single voice's text is.
79
+ const from = script.dependsOn[0];
80
+ if (prepare && from !== undefined)
81
+ preparations.push(preparationFuture(context, "body", { key: from, fingerprint: script.fingerprint ?? from }));
82
+ parts.push(future());
83
+ }
84
+ else if (parsed !== undefined && !parsed.ok) {
85
+ parts.push(refused(context, "audio:body:turn:1", script.dependsOn, `The script can't be narrated: ${parsed.reason} Fix it in Edit project → Article, then Try again.`));
86
+ }
87
+ else if (pending) {
88
+ parts.push(future());
89
+ }
90
+ else if (parsed !== undefined) {
91
+ transcript = ["voices-transcript-v1", turns.map((turn) => [turn.speaker, turn.text])];
92
+ const aliases = narrationAliasesOf(context.config);
93
+ for (const group of groupTurns(turns, voices.speakers, voices.nativeDialogue, (text) => applyAliases(text, aliases).length)) {
94
+ const first = group.turns[0];
95
+ const speaker = voices.speakers.find((one) => one.id === first?.speaker);
96
+ if (first === undefined || speaker === undefined)
97
+ continue;
98
+ const made = groupParts(context, voices, group.native, group.turns, speaker, glossary, script, preparations);
99
+ if (made.length === 0)
100
+ pending = true;
101
+ made.forEach((part, index) => {
102
+ parts.push(part);
103
+ if (index === made.length - 1)
104
+ turnEnds.add(layout.length);
105
+ layout.push([speakerPace(speaker), index === made.length - 1 ? voices.turnGapSeconds : 0]);
106
+ });
107
+ }
108
+ // No gap after the last turn: the timeline's own gap follows it.
109
+ const last = layout.at(-1);
110
+ if (last !== undefined)
111
+ last[1] = 0;
112
+ if (pending) {
113
+ parts.length = 0;
114
+ layout.length = 0;
115
+ turnEnds.clear();
116
+ parts.push(future());
117
+ }
118
+ }
119
+ const body = recipe(context, "audio:body:concat", "audio", {
120
+ kind: "local",
121
+ version: 1,
122
+ operation: "concat-turns-v1",
123
+ values: [
124
+ parts.map((part) => resourceIdentity(context, part)),
125
+ layout,
126
+ // Pauses between sentences inside a turn; the turn gap stays the layout's.
127
+ ...pauseValues(context.config, layout.map(([, gap], at) => at === layout.length - 1 ? "end" : gap > 0 || turnEnds.has(at) ? "turn" : "sentence")),
128
+ ],
129
+ }, parts.map((part) => part.key), { unresolved: parsed?.ok !== true || pending });
130
+ return {
131
+ preparations,
132
+ cards,
133
+ parts,
134
+ body,
135
+ transcript,
136
+ ...(parsed?.ok === true ? { sections: parsed.script.sections } : {}),
137
+ };
138
+ }
139
+ function groupParts(context, voices, native, turns, speaker, glossary, script, preparations) {
140
+ const first = turns[0];
141
+ if (first === undefined)
142
+ return [];
143
+ const logicalKey = `audio:body:turn:${String(first.index)}`;
144
+ const override = context.content.narrationOverrides[logicalKey];
145
+ if (override?.kind === "asset")
146
+ return [
147
+ recipe(context, `${logicalKey}:1`, "audio", {
148
+ kind: "provided",
149
+ version: 1,
150
+ assetId: override.assetId,
151
+ semantic: [turns.map((turn) => turn.text).join("\n"), speaker.id],
152
+ }, script.dependsOn, { tokenKey: logicalKey }),
153
+ ];
154
+ if (native) {
155
+ const byId = new Map(voices.speakers.map((one) => [one.id, one]));
156
+ // Narration aliases reach every line of the one request, each turn on its own so an alias
157
+ // never joins the end of one turn to the start of the next. The clean turns stay the
158
+ // transcript, as they do for a voice that gets one request per turn.
159
+ const aliases = narrationAliasesOf(context.config);
160
+ const spoken = turns.map((turn) => applyAliases(turn.text, aliases));
161
+ const logicalText = turns.map((turn) => turn.text).join("\n");
162
+ const text = spoken.join("\n");
163
+ return [
164
+ recipe(context, `${logicalKey}:1`, "audio", {
165
+ kind: "tts",
166
+ version: 1,
167
+ provider: speaker.voice.provider,
168
+ model: speaker.voice.model,
169
+ voice: speaker.voice.voice,
170
+ text,
171
+ ...(text === logicalText ? {} : { spokenText: logicalText }),
172
+ logicalKey,
173
+ logicalText,
174
+ segment: "body",
175
+ pronunciation: null,
176
+ dialogue: turns.map((turn, index) => ({
177
+ speaker: turn.speaker,
178
+ turn: turn.index,
179
+ voice: byId.get(turn.speaker)?.voice.voice ?? "",
180
+ text: spoken[index] ?? turn.text,
181
+ })),
182
+ }, script.dependsOn, { tokenKey: logicalKey }),
183
+ ];
184
+ }
185
+ const logicalText = normalizeNarrationText(override?.kind === "text" ? override.text : first.text);
186
+ const entries = speakerGlossary(context, speaker, glossary);
187
+ if (!entries.ok)
188
+ return [refused(context, logicalKey, script.dependsOn, entries.reason)];
189
+ const maxCharacters = context.catalogue?.tts.find((row) => row.provider === speaker.voice.provider &&
190
+ row.id === speaker.voice.model &&
191
+ row.enabled &&
192
+ !row.deprecated)?.tts.maxCharacters ?? Math.max(2, logicalText.length * 4);
193
+ // Narration aliases apply to every voice, the speaker's pronunciations to IPA voices only.
194
+ const spans = withAliasSpans(pronunciationSpans(logicalText, entries.entries), aliasSpans(logicalText, narrationAliasesOf(context.config)));
195
+ if (preparesTurns(context.config, speaker)) {
196
+ const prepared = preparationForGroup(context, logicalKey, logicalText, "body", script.dependsOn, maxCharacters, spans, aliasMatches(logicalText, narrationAliasesOf(context.config)), { name: speaker.name.trim(), role: speaker.role });
197
+ preparations.push(prepared.preparation);
198
+ if (prepared.refusal !== null)
199
+ return [refused(context, logicalKey, [prepared.preparation.key], prepared.refusal)];
200
+ return (prepared.requests ?? []).map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
201
+ kind: "tts",
202
+ version: 1,
203
+ provider: speaker.voice.provider,
204
+ model: speaker.voice.model,
205
+ voice: speaker.voice.voice,
206
+ text: request.text,
207
+ spokenText: request.spokenText,
208
+ logicalKey,
209
+ logicalText,
210
+ segment: "body",
211
+ pronunciation: null,
212
+ speaker: speaker.id,
213
+ turn: first.index,
214
+ }, [prepared.preparation.key], { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
215
+ }
216
+ let requests;
217
+ if (spans.length > 0) {
218
+ const prepared = prepareRequests(logicalText, [], maxCharacters, spans);
219
+ if (!prepared.ok)
220
+ return [refused(context, logicalKey, script.dependsOn, prepared.reason)];
221
+ requests = prepared.requests;
222
+ }
223
+ else
224
+ requests = splitText(logicalText, maxCharacters).map((text) => ({ text }));
225
+ return requests.map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
226
+ kind: "tts",
227
+ version: 1,
228
+ provider: speaker.voice.provider,
229
+ model: speaker.voice.model,
230
+ voice: speaker.voice.voice,
231
+ text: request.text,
232
+ ...(request.spokenText === undefined ? {} : { spokenText: request.spokenText }),
233
+ logicalKey,
234
+ logicalText,
235
+ segment: "body",
236
+ pronunciation: null,
237
+ speaker: speaker.id,
238
+ turn: first.index,
239
+ }, script.dependsOn, { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
240
+ }
241
+ // IPA pronunciations reach only the voices that read IPA: the speaker's own first, then the
242
+ // run's glossary where Use Pronunciation Glossary is on.
243
+ function speakerGlossary(context, speaker, glossary) {
244
+ if (!readsIpa(speaker))
245
+ return { ok: true, entries: [] };
246
+ const own = speaker.pronunciations?.trim()
247
+ ? parsePronunciationGlossary(speaker.pronunciations, context.config.language)
248
+ : { ok: true, entries: [] };
249
+ if (!own.ok)
250
+ return {
251
+ ok: false,
252
+ reason: `${speaker.name.trim()}'s pronunciations can't be used: ${own.reason.replace("Edit the glossary or turn off Use Pronunciation Glossary.", "Fix them under Speakers (Play → Narration, or Edit project → Providers).")}`,
253
+ };
254
+ const run = context.config.audio?.usePronunciationGlossary === true && glossary?.ok === true
255
+ ? glossary.entries
256
+ : [];
257
+ return { ok: true, entries: withSharedGlossary(own.entries, run) };
258
+ }
259
+ // A turn is prepared when the run has a Narration Preparation prompt and its speaker's voice
260
+ // takes Inworld TTS-2's delivery tags; every other speaker's turns are spoken as written.
261
+ export function preparesTurns(config, speaker) {
262
+ return (usesNarrationPreparation(config) &&
263
+ speaker.voice.provider === "inworld" &&
264
+ speaker.voice.model === "inworld-tts-2");
265
+ }
266
+ export function readsIpa(speaker) {
267
+ return (speaker.voice.provider === "inworld" &&
268
+ (speaker.voice.model === "inworld-tts-2" || speaker.voice.model === "inworld-tts-2-flash"));
269
+ }
270
+ function refused(context, logicalKey, dependsOn, reason) {
271
+ return recipe(context, `${logicalKey}:1`, "audio", { kind: "deferred", version: 1, operation: "resolve-revision-recipe", template: reason }, dependsOn, { unresolved: true, refusal: reason });
272
+ }
273
+ // What a pending script will be spoken with, so a voice change before the script lands is
274
+ // still a change.
275
+ export function speakerValues(voices) {
276
+ return [
277
+ voices.nativeDialogue,
278
+ voices.speakers.map((speaker) => [
279
+ speaker.id,
280
+ speaker.voice.provider,
281
+ speaker.voice.model,
282
+ speaker.voice.voice,
283
+ speaker.pronunciations ?? null,
284
+ ]),
285
+ ];
286
+ }
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
2
  import { estimateRequests } from "../estimate/index.js";
3
+ import { skippedGlossaryNotice, skippedSpeakerPronunciations, skippedSpeakerPronunciationsNotice, } from "../narration/pronunciation.js";
4
+ import { isLocalCliProvider } from "../settings/model.js";
3
5
  import { defaultShortsPrompt, shortsImageUpperBound } from "../shorts/model.js";
6
+ import { usesVoices } from "../voices/model.js";
4
7
  import { planDependencies } from "./dependencies.js";
5
8
  import { matchingNarrationPiece } from "./narration-reuse.js";
6
9
  import { buildRecipes } from "./recipe-build.js";
7
10
  import { manualCuesNeedReview } from "./recipe-exports.js";
8
11
  import { selectedReference, } from "./recipe-model.js";
12
+ import { recipeProviderChoice } from "./recipe-provider-choice.js";
13
+ import { narrationGlossary } from "./recipe-text.js";
14
+ import { readsIpa } from "./recipe-voices.js";
9
15
  export function planRevisionWork(revision, manifest, catalogue, availableAssetIds, resolved, retainedNarration = []) {
10
16
  const selected = {
11
17
  outputs: manifest.outputs.filter(selectedReference),
@@ -27,6 +33,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
27
33
  resolved: { ...resolved, articleMarkdown: usableArticle ? resolved.articleMarkdown : null },
28
34
  };
29
35
  const logical = buildRecipes(logicalContext);
36
+ const glossary = narrationGlossary(logicalContext);
30
37
  const context = { ...logicalContext, catalogue };
31
38
  const recipes = buildRecipes(context).map((row) => ({
32
39
  ...row,
@@ -64,7 +71,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
64
71
  null,
65
72
  after: row.fingerprint,
66
73
  }));
67
- const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key)));
74
+ const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key), revision.config));
68
75
  return {
69
76
  recipes,
70
77
  work,
@@ -74,6 +81,13 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
74
81
  (revision.config.chunking?.mode ?? "whole") === "whole"
75
82
  ? "Whole-text narration is one logical request. Editing its text rebuilds every provider part of that request."
76
83
  : null,
84
+ glossaryNotice: glossary?.ok === true && glossary.skipped !== undefined
85
+ ? skippedGlossaryNotice(glossary.skipped)
86
+ : null,
87
+ // Only speakers whose voice reads IPA use their pronunciations at all.
88
+ speakerPronunciationNotice: usesVoices(revision.config) && revision.config.voices !== undefined
89
+ ? skippedSpeakerPronunciationsNotice(skippedSpeakerPronunciations(revision.config.voices.speakers.filter(readsIpa), revision.config.language))
90
+ : null,
77
91
  costs: estimateRequests(priced, catalogue),
78
92
  };
79
93
  }
@@ -187,14 +201,16 @@ const shortsTranscriptEstimate = 24000;
187
201
  // What a step is charged as. Most steps are one request; what stands in for the shorts
188
202
  // until they are picked is every clip's image prompts and, at the longest length allowed,
189
203
  // every image, which is the most the step can cost.
190
- export function priceRecipes(work, value) {
204
+ export function priceRecipes(work, value,
205
+ // The revision's settings, which name the provider of a request built only when it runs.
206
+ config) {
191
207
  const input = value?.input;
192
208
  const unfolds = value?.unfoldsImages;
193
209
  // A short's image prompts once the clips are picked: the call, and the images it leads to,
194
210
  // as many as the clip's length asks for.
195
211
  if (work.disposition === "generate" && unfolds !== undefined)
196
212
  return [
197
- priceRecipe(work, value),
213
+ priceRecipe(work, value, config),
198
214
  ...Array.from({ length: unfolds.count }, () => ({
199
215
  kind: "image",
200
216
  stage: work.key,
@@ -223,7 +239,7 @@ export function priceRecipes(work, value) {
223
239
  input?.kind !== "deferred" ||
224
240
  input.operation !== "shorts" ||
225
241
  !Array.isArray(input.template))
226
- return [priceRecipe(work, value)];
242
+ return [priceRecipe(work, value, config)];
227
243
  const [, style, provider, model, imageSeconds, count, maxSeconds, llmProvider, llmModel] = input.template;
228
244
  const clips = typeof count === "number" ? count : 0;
229
245
  const images = typeof imageSeconds === "number" && typeof maxSeconds === "number"
@@ -249,7 +265,7 @@ export function priceRecipes(work, value) {
249
265
  })),
250
266
  ];
251
267
  }
252
- export function priceRecipe(work, value) {
268
+ export function priceRecipe(work, value, config) {
253
269
  if (value === undefined || work.disposition !== "generate")
254
270
  return {
255
271
  kind: "local",
@@ -259,12 +275,21 @@ export function priceRecipe(work, value) {
259
275
  : "Local, provided, or blocked work; no admitted generation charge.",
260
276
  };
261
277
  const input = value.input;
278
+ const onPlan = config === undefined ? undefined : cliRequest(work, value, config);
279
+ if (onPlan !== undefined)
280
+ return onPlan;
262
281
  if (input.kind === "deferred" && input.operation === "narration-preparation")
263
282
  return {
264
283
  kind: "unknown",
265
284
  stage: work.key,
266
285
  detail: "Narration Preparation: one LLM call per future logical chunk or entry; source length is not yet known.",
267
286
  };
287
+ if (input.kind === "deferred" && input.operation === "narration-description")
288
+ return {
289
+ kind: "unknown",
290
+ stage: work.key,
291
+ detail: "Narration descriptions: one LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
292
+ };
268
293
  if (input.kind === "llm")
269
294
  return {
270
295
  kind: "llm",
@@ -273,7 +298,7 @@ export function priceRecipe(work, value) {
273
298
  model: input.model,
274
299
  inputCharacters: input.messages.reduce((sum, message) => sum + message.content.length, 0) +
275
300
  (input.documents ?? []).reduce((sum, document) => sum + document.content.length, 0),
276
- outputCharacters: work.key === "article:body" ? 9000 : 2400,
301
+ outputCharacters: work.key === "article:body" ? 9000 : input.describe !== undefined ? 600 : 2400,
277
302
  detail: "Actual saved request input; output length is estimated. Retries and tools are excluded.",
278
303
  };
279
304
  if (input.kind === "tts")
@@ -299,6 +324,21 @@ export function priceRecipe(work, value) {
299
324
  detail: "The numbered transcript is built when the step runs; its length is estimated. One more call is made when the first answer breaks the rules. Retries are excluded.",
300
325
  };
301
326
  }
327
+ // A review: the stage's prompt and the item (the article's text, or a short brief beside an
328
+ // image, which the provider prices as it does).
329
+ if (input.kind === "local" && input.operation === "review-v1") {
330
+ const [stage, , , provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
331
+ return {
332
+ kind: "llm",
333
+ stage: work.key,
334
+ provider: typeof provider === "string" ? provider : "",
335
+ model: typeof model === "string" ? model : "",
336
+ inputCharacters: (typeof prompt === "string" && prompt !== "" ? prompt.length : 600) +
337
+ (stage === "article" || stage === "narration" ? 20000 : 1500),
338
+ outputCharacters: 400,
339
+ detail: "The item is read when the step runs; its length is estimated. A failed item made again is reviewed again, which is not in this estimate.",
340
+ };
341
+ }
302
342
  if (input.kind === "local" && input.operation === "youtube-description-v1") {
303
343
  const [, provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
304
344
  return {
@@ -320,3 +360,34 @@ export function priceRecipe(work, value) {
320
360
  detail: "Future generated input is unknown. This group materializes under the same admitted revision; its final request count and charge are unknown.",
321
361
  };
322
362
  }
363
+ // A request built only when its step runs, on a command-line provider: it runs on the user's
364
+ // plan, so it is known at $0, as Play's review prices the same step. Its size is estimated
365
+ // for the API figure beside it; Narration Preparation's number of calls is not known yet, so
366
+ // it has none.
367
+ function cliRequest(work, value, config) {
368
+ if (value.input.kind !== "deferred")
369
+ return undefined;
370
+ const choice = recipeProviderChoice(value, config);
371
+ if (choice === undefined || !isLocalCliProvider(choice.provider))
372
+ return undefined;
373
+ if (choice.family === "image")
374
+ return { kind: "image", stage: work.key, provider: choice.provider, model: choice.model };
375
+ if (choice.family !== "llm")
376
+ return undefined;
377
+ const preparation = value.input.operation === "narration-preparation" ||
378
+ value.input.operation === "narration-description";
379
+ return {
380
+ kind: "llm",
381
+ stage: work.key,
382
+ provider: choice.provider,
383
+ model: choice.model,
384
+ inputCharacters: 6000,
385
+ outputCharacters: work.key === "article:body" ? 9000 : 2400,
386
+ ...(preparation ? { apiUnknown: true } : {}),
387
+ detail: preparation
388
+ ? value.input.operation === "narration-description"
389
+ ? "Runs on your CLI plan, so it adds no charge. Narration descriptions make one call per table, figure, equation or code block; how many is known once the article is written, so no API figure is given."
390
+ : "Runs on your CLI plan, so it adds no charge. Narration Preparation makes one call per logical chunk or entry; how many is known when the step runs, so no API figure is given."
391
+ : "Runs on your CLI plan, so it adds no charge. The request is built when the step runs, so its API figure uses an estimated length.",
392
+ };
393
+ }
@@ -25,6 +25,8 @@ export function youtubeRecipes(context, exports) {
25
25
  // ships as a new operation version rather than a silent change here.
26
26
  config.descriptionPrompt?.trim() ? renderedPrompt(context, "description") : "",
27
27
  config.title,
28
+ // The project language, only when it is not English, so English keeps its fingerprint.
29
+ ...(config.language === undefined || config.language === "en" ? [] : [config.language]),
28
30
  ],
29
31
  }, [timing.key],
30
32
  // A paid LLM call: readiness, the charge warning and the estimate treat it like one.