@gentbajko/slopify 3.25.9 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -0,0 +1,169 @@
1
+ import { constants, setPriority } from "node:os";
2
+ import { errorText } from "../../kernel/errors.js";
3
+ import { lookEncoding } from "./look.js";
4
+ // Settings → Production defaults → Rendering: how this computer encodes a video's picture.
5
+ // They change how a video is made, never what it is, so no recipe fingerprint carries them and
6
+ // a render reads them once as it starts (`renderMachine`).
7
+ export const videoEncoders = ["cpu", "nvidia"];
8
+ export const clipEncodeSpeeds = ["thorough", "fast"];
9
+ export const renderPriorities = ["normal", "background"];
10
+ // Today's render: x264 at its default preset, at normal priority.
11
+ export const defaultEncode = { kind: "x264", clipSpeed: "thorough" };
12
+ // Clips that are joined by copying get the final encode's settings. Clips that will be decoded
13
+ // again under burned-in captions are kept closer to the source, so the second encode is not a
14
+ // visible second generation.
15
+ const intermediateCrf = "16";
16
+ // measured on two minutes of a real project (changelog.d/2026-10-13-render-settings.md): 20
17
+ // matches CRF 16's quality for the clips under captions; 28.5 makes the finished video about the
18
+ // size CRF 23 does, where NVENC scores about 2.5 VMAF below x264 (no NVENC setting tried closed
19
+ // that gap at that size).
20
+ export const nvencQuality = { intermediate: 20, final: 28.5 };
21
+ // `final` is a picture someone watches (a clip joined by copying, a burned-in part, the join);
22
+ // `intermediate` is a clip decoded again under burned-in captions.
23
+ export function codecArgs(encode, role, look) {
24
+ if (encode.kind === "nvenc")
25
+ return [
26
+ "-c:v",
27
+ "h264_nvenc",
28
+ // The slowest, best preset, constant quality with no bitrate cap. No `-multipass`: measured,
29
+ // a second full-resolution pass gave no better picture for its size on these videos.
30
+ "-preset",
31
+ "p7",
32
+ "-tune",
33
+ "hq",
34
+ "-rc",
35
+ "vbr",
36
+ "-cq",
37
+ String(nvencQuality[role]),
38
+ "-b:v",
39
+ "0",
40
+ // x264's own keyframe interval, so a clip opens on its one keyframe as x264's do and the
41
+ // clips still join by copying.
42
+ "-g",
43
+ "250",
44
+ "-pix_fmt",
45
+ "yuv420p",
46
+ ];
47
+ return [
48
+ "-c:v",
49
+ "libx264",
50
+ "-pix_fmt",
51
+ "yuv420p",
52
+ ...lookEncoding(look),
53
+ ...(role === "intermediate"
54
+ ? [...(encode.clipSpeed === "fast" ? ["-preset", "veryfast"] : []), "-crf", intermediateCrf]
55
+ : []),
56
+ ];
57
+ }
58
+ // Said to a person who may have no NVIDIA card at all: stock ffmpeg builds list the NVIDIA
59
+ // encoder everywhere, so a failed test usually means there is no card, not a broken one. What
60
+ // the test printed goes under Details (`detail`), never into these sentences.
61
+ export const noNvenc = "No NVIDIA graphics card was found on this computer.";
62
+ export const failedNvenc = "No working NVIDIA graphics card was found on this computer.";
63
+ // The reason with the test's output, for the log and a run's notice.
64
+ export function nvencWhy(status) {
65
+ return status.detail === undefined ? status.reason : `${status.reason} (${status.detail})`;
66
+ }
67
+ // The renders' own settings on a tiny picture: a driver or card that refuses any of them fails
68
+ // here rather than in a render. NVENC refuses anything under about 145 pixels across.
69
+ const testArgs = [
70
+ "-hide_banner",
71
+ "-nostdin",
72
+ "-loglevel",
73
+ "error",
74
+ "-f",
75
+ "lavfi",
76
+ "-i",
77
+ "color=c=black:s=256x256:r=30:d=0.1",
78
+ "-frames:v",
79
+ "1",
80
+ ...codecArgs({ kind: "nvenc", bin: "", clipSpeed: "thorough" }, "final", undefined),
81
+ "-f",
82
+ "null",
83
+ "-",
84
+ ];
85
+ // The filters a render runs in the NVENC ffmpeg besides the encoder.
86
+ const neededFilters = ["zoompan", "xfade", "ass"];
87
+ // Which ffmpeg can encode on an NVIDIA card, asked once and remembered until Slopify restarts.
88
+ // The bundled ffmpeg first, then the computer's own: unlike everything else, NVENC is the
89
+ // computer's (its driver and its ffmpeg), and only used when chosen in Settings.
90
+ export function createNvencProbe(deps) {
91
+ let answer;
92
+ return () => {
93
+ answer ??= probe(deps).catch((error) => ({
94
+ available: false,
95
+ reason: noNvenc,
96
+ detail: errorText(error),
97
+ }));
98
+ return answer;
99
+ };
100
+ }
101
+ async function probe(deps) {
102
+ let failed;
103
+ for (const bin of new Set(deps.candidates)) {
104
+ const encoders = await deps.exec(bin, ["-hide_banner", "-encoders"]).catch(() => "");
105
+ if (!/^\s*V\S*\s+h264_nvenc\s/m.test(encoders))
106
+ continue;
107
+ const filters = await deps.exec(bin, ["-hide_banner", "-filters"]).catch(() => "");
108
+ const missing = neededFilters.filter((name) => !new RegExp(`^\\s*\\S+\\s+${name}\\s`, "m").test(filters));
109
+ if (missing.length > 0) {
110
+ failed = `the ffmpeg at ${bin} has NVENC but not the ${missing.join(", ")} filter a video needs`;
111
+ continue;
112
+ }
113
+ try {
114
+ await deps.exec(bin, testArgs);
115
+ deps.log.write("info", "video.encoder", { detail: `NVENC works through ${bin}` });
116
+ return { available: true, bin };
117
+ }
118
+ catch (error) {
119
+ failed = `the ffmpeg at ${bin} lists NVENC but its test encode failed: ${errorText(error)}`;
120
+ }
121
+ }
122
+ if (failed !== undefined) {
123
+ deps.log.write("info", "video.encoder", { detail: failed });
124
+ return { available: false, reason: failedNvenc, detail: failed };
125
+ }
126
+ return { available: false, reason: noNvenc };
127
+ }
128
+ // The render's machine settings, read as it starts. A card chosen but not working is a notice,
129
+ // never a failed render: the processor encodes it instead.
130
+ export async function renderMachine(deps) {
131
+ const { settings } = deps;
132
+ const cpu = { kind: "x264", clipSpeed: settings.clipEncodeSpeed };
133
+ if (settings.videoEncoder !== "nvidia")
134
+ return { machine: { encode: cpu, priority: settings.renderPriority } };
135
+ const status = await deps.nvenc();
136
+ if (status.available)
137
+ return {
138
+ machine: {
139
+ encode: { kind: "nvenc", bin: status.bin, clipSpeed: settings.clipEncodeSpeed },
140
+ priority: settings.renderPriority,
141
+ },
142
+ };
143
+ deps.log.write("warn", "video.encoder", {
144
+ projectId: deps.projectId,
145
+ detail: `NVIDIA encoder chosen but unavailable; encoding on the processor. ${nvencWhy(status)}`,
146
+ });
147
+ return {
148
+ machine: { encode: cpu, priority: settings.renderPriority },
149
+ notice: nvencFallbackNotice(nvencWhy(status)),
150
+ };
151
+ }
152
+ export function nvencFallbackNotice(reason) {
153
+ return `This video was encoded on the processor because the NVIDIA graphics card encoder didn't work (${reason.replace(/\.$/, "")}). Check the NVIDIA driver, or choose Processor (x264) in Settings → Production defaults → Video encoder.`;
154
+ }
155
+ // Background renders run below normal priority: nice 10 on Linux and macOS, the below-normal
156
+ // class on Windows. Threads ffmpeg starts afterwards inherit it. Advisory: a system that
157
+ // refuses is logged and the render carries on.
158
+ export function setRenderPriority(pid, priority, log, set = setPriority) {
159
+ if (priority !== "background")
160
+ return;
161
+ try {
162
+ set(pid, constants.priority.PRIORITY_BELOW_NORMAL);
163
+ }
164
+ catch (error) {
165
+ log.write("warn", "video.priority", {
166
+ detail: `could not lower the render's priority: ${errorText(error)}`,
167
+ });
168
+ }
169
+ }
@@ -1,7 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { isAbsolute, resolve as resolvePath } from "node:path";
3
+ import { errorText, internalError } from "../../kernel/errors.js";
3
4
  import { bedChains, bedInputs } from "./ambient-mix.js";
4
- import { gradeFilter, lookChain, lookEncoding, placed } from "./look.js";
5
+ import { codecArgs, defaultEncode, setRenderPriority, } from "./encoding.js";
6
+ import { gradeFilter, lookChain, placed } from "./look.js";
5
7
  import { decimal, zoomRange } from "./plan.js";
6
8
  import { segments, transitionHalves } from "./transitions.js";
7
9
  // Hand-rolled per the standards: the filtergraph is the load-bearing part of this slice
@@ -44,7 +46,7 @@ export function slideshowClips(edit) {
44
46
  named.set(key, name);
45
47
  const shot = edit.shots[segment.kind === "shot" ? segment.shot : segment.to];
46
48
  if (shot === undefined)
47
- throw new Error("Slopify hit an internal error (the video's edit list lost a shot). Use More → Render the video again in the Video section; if it happens again, use Download diagnostics in Settings and report it.");
49
+ throw new Error(internalError("the video's edit list lost a shot", "Press Make the video again in the project's Video section"));
48
50
  clips.push({ name, segment, shot, frames: segment.count });
49
51
  }
50
52
  return name;
@@ -77,10 +79,6 @@ function clipKey(edit, segment) {
77
79
  ? [shot?.source, shot?.motion, shot?.frames]
78
80
  : [shot?.source, shot?.motion, shot?.frames, segment.from, segment.count, at]);
79
81
  }
80
- // Clips that are joined by copying get the final encode's settings. Clips that will be
81
- // decoded again under burned-in captions are kept closer to the source, so the second
82
- // encode is not a visible second generation.
83
- const intermediateCrf = "16";
84
82
  // A whole shot as one clip.
85
83
  export function clipArgs(frame, shot, output, intermediate = false) {
86
84
  return segmentArgs({ ...frame, shots: [shot] }, { kind: "shot", shot: 0, from: 0, count: shot.frames, start: 0 }, output, intermediate);
@@ -88,12 +86,14 @@ export function clipArgs(frame, shot, output, intermediate = false) {
88
86
  // One clip of the slideshow: part of a shot, or a transition between two, with the Look.
89
87
  export function segmentArgs(edit, segment, output, intermediate = false,
90
88
  // The clip's own chapter-card script, beside it in the working folder (`cards.ts`).
91
- cardsFile = "cards.ass") {
89
+ cardsFile = "cards.ass",
90
+ // Settings → Production defaults → Rendering (`encoding.ts`).
91
+ encode = defaultEncode) {
92
92
  const inputs = [];
93
93
  const chains = [];
94
94
  const picture = (shot, from, label) => {
95
95
  if (shot === undefined)
96
- throw new Error("Slopify hit an internal error (the video's edit list lost a shot). Use More → Render the video again in the Video section; if it happens again, use Download diagnostics in Settings and report it.");
96
+ throw new Error(internalError("the video's edit list lost a shot", "Press Make the video again in the project's Video section"));
97
97
  const at = inputs.filter((one) => one === "-i").length;
98
98
  const built = pictureChain(edit, shot, from, segment.count, at, label, gradeFilter(edit.look));
99
99
  inputs.push(...built.inputs);
@@ -125,9 +125,7 @@ cardsFile = "cards.ass") {
125
125
  ...(segment.kind === "shot" && segment.from === 0 && look === undefined
126
126
  ? []
127
127
  : ["-frames:v", String(segment.count)]),
128
- ...videoCodec,
129
- ...lookEncoding(edit.look),
130
- ...(intermediate ? ["-crf", intermediateCrf] : []),
128
+ ...codecArgs(encode, intermediate ? "intermediate" : "final", edit.look),
131
129
  "-an",
132
130
  output,
133
131
  ];
@@ -257,7 +255,7 @@ export function audioMixArgs(edit, output, codec = "wav") {
257
255
  }
258
256
  // `sound`, when given, is the finished AAC (`audioMixArgs`), copied in; without it the join
259
257
  // mixes and encodes the edit list's audio itself.
260
- export function joinArgs(edit, output, list, burnSubtitles = false, portraits = [], sound) {
258
+ export function joinArgs(edit, output, list, burnSubtitles = false, portraits = [], sound, encode = defaultEncode) {
261
259
  const mix = sound === undefined ? audioMix(edit, 1) : { inputs: ["-i", sound], chains: [] };
262
260
  const inputs = ["-f", "concat", "-i", list, ...mix.inputs];
263
261
  const chains = burnSubtitles ? burnIn(inputs, portraits) : [];
@@ -270,7 +268,7 @@ export function joinArgs(edit, output, list, burnSubtitles = false, portraits =
270
268
  "-map",
271
269
  burnSubtitles ? "[v]" : "0:v",
272
270
  ...(audio ? ["-map", sound === undefined ? "[a]" : "1:a"] : []),
273
- ...(burnSubtitles ? [...videoCodec, ...lookEncoding(edit.look)] : ["-c:v", "copy"]),
271
+ ...(burnSubtitles ? codecArgs(encode, "final", edit.look) : ["-c:v", "copy"]),
274
272
  ...(audio ? ["-c:a", sound === undefined ? "aac" : "copy"] : ["-an"]),
275
273
  "-movflags",
276
274
  "+faststart",
@@ -280,7 +278,7 @@ export function joinArgs(edit, output, list, burnSubtitles = false, portraits =
280
278
  // One part of a long video's picture with its captions burned in, from frame `start` of the
281
279
  // video (`slideshow.ts` encodes the parts side by side, then joins them by copying). The part's
282
280
  // frames are moved to their place in the video for the captions and back to zero after them.
283
- export function burnPartArgs(edit, list, output, start, portraits = []) {
281
+ export function burnPartArgs(edit, list, output, start, portraits = [], encode = defaultEncode) {
284
282
  const inputs = ["-f", "concat", "-i", list];
285
283
  const shifted = start === 0
286
284
  ? { before: "", after: "" }
@@ -298,8 +296,7 @@ export function burnPartArgs(edit, list, output, start, portraits = []) {
298
296
  chains.join(";"),
299
297
  "-map",
300
298
  "[v]",
301
- ...videoCodec,
302
- ...lookEncoding(edit.look),
299
+ ...codecArgs(encode, "final", edit.look),
303
300
  "-an",
304
301
  output,
305
302
  ];
@@ -333,7 +330,6 @@ const progressArgs = [
333
330
  "-nostats",
334
331
  "-y",
335
332
  ];
336
- const videoCodec = ["-c:v", "libx264", "-pix_fmt", "yuv420p"];
337
333
  const centredX = "iw/2-(iw/zoom/2)";
338
334
  const centredY = "ih/2-(ih/zoom/2)";
339
335
  // zoompan's expressions for a motion, or undefined for one that does not move. `on` is
@@ -403,6 +399,9 @@ export function runFfmpeg(run) {
403
399
  windowsHide: true,
404
400
  ...(run.cwd === undefined ? {} : { cwd: run.cwd }),
405
401
  });
402
+ // Before ffmpeg starts its threads, which inherit the priority.
403
+ if (child.pid !== undefined && run.priority !== undefined)
404
+ setRenderPriority(child.pid, run.priority, run.log);
406
405
  const errors = [];
407
406
  let pending = "";
408
407
  // A throw out of a stream listener is an uncaughtException, not a rejection: it would
@@ -413,7 +412,7 @@ export function runFfmpeg(run) {
413
412
  work();
414
413
  }
415
414
  catch (error) {
416
- run.log.write("warn", what, { detail: messageOf(error) });
415
+ run.log.write("warn", what, { detail: errorText(error) });
417
416
  }
418
417
  };
419
418
  const report = (line) => {
@@ -508,9 +507,6 @@ export async function probeDurationMs(bin, file, signal, log) {
508
507
  });
509
508
  return last;
510
509
  }
511
- function messageOf(error) {
512
- return error instanceof Error ? error.message : String(error);
513
- }
514
510
  function failure(code, signal, errors) {
515
511
  const ended = code === null ? `killed by ${signal ?? "a signal"}` : `exited with code ${code}`;
516
512
  const said = errors.length === 0
@@ -518,3 +514,9 @@ function failure(code, signal, errors) {
518
514
  : `ffmpeg ${ended}: ${errors.join(" / ")}`;
519
515
  return `The audio/video export failed (${said}). Try again; if it fails again, make sure your disk has free space (see Settings → Backup & storage), then use Download diagnostics in Settings and report it.`;
520
516
  }
517
+ // `runFfmpeg` at Settings → Production defaults → Render priority, read as each call starts: for
518
+ // the work outside a video's render that still runs ffmpeg on this computer (a design's drawing,
519
+ // a post's picture, Template Studio's free samples). A priority the caller set itself is kept.
520
+ export function runFfmpegAt(priority, run = runFfmpeg) {
521
+ return (call) => run({ ...call, priority: call.priority ?? priority() });
522
+ }
@@ -1,5 +1,6 @@
1
1
  import { remark } from "remark";
2
2
  import remarkGfm from "remark-gfm";
3
+ import { assText, assColour as colour } from "./ass-text.js";
3
4
  import { runFfmpeg } from "./ffmpeg.js";
4
5
  // The 3.0 design tokens (`packages/web/src/styles/index.css`): the surface graphite, ink,
5
6
  // the second ink, Slopify lime and the line colour, in the bundled Barlow.
@@ -449,21 +450,6 @@ function wrap(textValue, width, most) {
449
450
  function clip(value, room) {
450
451
  return value.length <= room ? value : `${value.slice(0, Math.max(1, room - 1))}…`;
451
452
  }
452
- // Text as libass draws it literally: braces and backslashes escaped rather than read as
453
- // override tags; code keeps its spaces, prose has them collapsed.
454
- function assText(value, keepSpaces = false) {
455
- const escaped = value.replace(/\\/g, "\\\\").replace(/\{/g, "\\{").replace(/\}/g, "\\}");
456
- return keepSpaces
457
- ? escaped.replace(/ {2,}/g, (run) => "\\h".repeat(run.length))
458
- : escaped.replace(/\s+/g, " ");
459
- }
460
- function colour(hex) {
461
- const match = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/.exec(hex);
462
- if (match === null)
463
- return "&HFFFFFF&";
464
- const [, r, g, b] = match;
465
- return `&H${(b ?? "").toUpperCase()}${(g ?? "").toUpperCase()}${(r ?? "").toUpperCase()}&`;
466
- }
467
453
  function text(x, y, align, body) {
468
454
  return `Dialogue: 0,0:00:00.00,0:00:10.00,Card,,0,0,0,,{\\an${String(align)}\\pos(${String(Math.round(x))},${String(Math.round(y))})}${body}`;
469
455
  }
@@ -1,3 +1,4 @@
1
+ import { editSettings } from "../../kernel/ports/settings-places.js";
1
2
  import { chapterCuts, narrationShotFrames } from "./cuts.js";
2
3
  import { editListVersion, } from "./edit-list.js";
3
4
  import { chapterCardSeconds, endScreenSeconds } from "./edit-settings.js";
@@ -41,7 +42,7 @@ const figureGapSeconds = 1;
41
42
  export function planRender(input) {
42
43
  if (input.images.length === 0) {
43
44
  // Admission permits Video only with an image source, so an empty set is a bug upstream.
44
- throw new Error("The video needs at least one image, but there are none. Check the Images section in Edit project, then Try again.");
45
+ throw new Error(`The video needs at least one image, but there are none. Check ${editSettings.images}, then Try again.`);
45
46
  }
46
47
  const frame = frames[input.format];
47
48
  const bed = input.body === undefined ? undefined : input.bed;
@@ -1,8 +1,10 @@
1
1
  import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { extname, join } from "node:path";
3
+ import { errorText, internalError } from "../../kernel/errors.js";
3
4
  import { masterFile, masterReport } from "../loudness/loudnorm.js";
4
5
  import { cardsAss } from "./cards.js";
5
6
  import { cacheKey, fileIdentity, keepClip, pruneClips, reuseClip } from "./clip-cache.js";
7
+ import { defaultEncode, nvencFallbackNotice, } from "./encoding.js";
6
8
  import { audioMixArgs, burnPartArgs, concatList, joinArgs, runFfmpeg, segmentArgs, slideshowClips, } from "./ffmpeg.js";
7
9
  import { cardsIn } from "./look.js";
8
10
  import { inPool, renderJobs } from "./pool.js";
@@ -14,9 +16,31 @@ const copyWeight = 0.05;
14
16
  const leastPartFrames = 60 * 30;
15
17
  // The sound the join plays, as the join itself brings every segment to it.
16
18
  const masterFormat = { sampleRate: 44100, channels: 2 };
17
- // What the master measured, when the run asked for one.
19
+ // What the master measured, when the run asked for one. A render on the NVIDIA card that fails
20
+ // for any reason but a cancel is made again on the processor: the card never fails a render.
18
21
  export async function renderSlideshow(run) {
22
+ const encode = run.machine?.encode ?? defaultEncode;
23
+ if (encode.kind !== "nvenc")
24
+ return renderWith(run, encode);
25
+ try {
26
+ return await renderWith(run, encode);
27
+ }
28
+ catch (error) {
29
+ if (run.signal.aborted)
30
+ throw error;
31
+ const detail = errorText(error);
32
+ run.log.write("warn", "video.encoder", {
33
+ detail: `the NVENC render failed; rendering again on the processor: ${detail}`,
34
+ });
35
+ run.notice?.(nvencFallbackNotice(`it stopped the render: ${detail}`));
36
+ return renderWith(run, { kind: "x264", clipSpeed: encode.clipSpeed });
37
+ }
38
+ }
39
+ async function renderWith(run, encode) {
19
40
  const { edit } = run;
41
+ const priority = run.machine?.priority;
42
+ // The ffmpeg that encodes the picture; copying, sound and probing stay with the bundled one.
43
+ const encoder = encode.kind === "nvenc" ? encode.bin : run.bin;
20
44
  const { clips, order } = slideshowClips(edit);
21
45
  const msOf = (frames) => (frames / edit.fps) * 1000;
22
46
  const joinMs = msOf(edit.shots.reduce((sum, shot) => sum + shot.frames, 0));
@@ -38,7 +62,7 @@ export async function renderSlideshow(run) {
38
62
  const font = edit.cardFont;
39
63
  if (cards.length > 0) {
40
64
  if (font === undefined)
41
- throw new Error("Slopify hit an internal error (the chapter cards have no font). Use More → Render the video again in the Video section; if it happens again, use Download diagnostics in Settings and report it.");
65
+ throw new Error(internalError("the chapter cards have no font", "Press Make the video again in the project's Video section"));
42
66
  mkdirSync(join(workspace, "fonts"), { mode: 0o700 });
43
67
  copyFileSync(font.path, join(workspace, "fonts", `card${extname(font.path)}`));
44
68
  }
@@ -81,7 +105,7 @@ export async function renderSlideshow(run) {
81
105
  if (drawn !== undefined)
82
106
  writeFileSync(join(workspace, script), drawn, { mode: 0o600 });
83
107
  const output = join(workspace, clip.name);
84
- const args = segmentArgs(edit, clip.segment, output, run.burnSubtitles, script);
108
+ const args = segmentArgs(edit, clip.segment, output, run.burnSubtitles, script, encode);
85
109
  const weight = msOf(clip.frames) * clipWeight;
86
110
  const cache = run.cache;
87
111
  let key;
@@ -89,7 +113,7 @@ export async function renderSlideshow(run) {
89
113
  // let it go a moment ago) is encoded as if there were none.
90
114
  try {
91
115
  if (cache !== undefined) {
92
- key = cacheKey(run.bin, args.map((value) => drawn === undefined ? value : value.replaceAll(script, "\u0000cards")), output, drawn === undefined || font === undefined ? [] : [drawn, fileIdentity(font.path)]);
116
+ key = cacheKey(encoder, args.map((value) => drawn === undefined ? value : value.replaceAll(script, "\u0000cards")), output, drawn === undefined || font === undefined ? [] : [drawn, fileIdentity(font.path)]);
93
117
  keys.add(key);
94
118
  if (reuseClip(cache, key, output)) {
95
119
  rendered += weight;
@@ -99,15 +123,16 @@ export async function renderSlideshow(run) {
99
123
  }
100
124
  }
101
125
  catch (error) {
102
- run.log.write("warn", "video.cache", { detail: messageOf(error) });
126
+ run.log.write("warn", "video.cache", { detail: errorText(error) });
103
127
  rmSync(output, { force: true });
104
128
  }
105
129
  await runFfmpeg({
106
- bin: run.bin,
130
+ bin: encoder,
107
131
  ...(cards.length > 0 ? { cwd: workspace } : {}),
108
132
  args,
109
133
  signal,
110
134
  log: run.log,
135
+ priority,
111
136
  onProgress: (elapsedMs) => {
112
137
  running.set(clip.name, Math.min(elapsedMs * clipWeight, weight));
113
138
  tell();
@@ -120,7 +145,7 @@ export async function renderSlideshow(run) {
120
145
  keepClip(cache, key, output);
121
146
  }
122
147
  catch (error) {
123
- run.log.write("warn", "video.cache", { detail: messageOf(error) });
148
+ run.log.write("warn", "video.cache", { detail: errorText(error) });
124
149
  }
125
150
  }
126
151
  });
@@ -138,11 +163,12 @@ export async function renderSlideshow(run) {
138
163
  const partList = join(workspace, `${part.name}.ffconcat`);
139
164
  writeFileSync(partList, concatList(part.names), { mode: 0o600 });
140
165
  await runFfmpeg({
141
- bin: run.bin,
166
+ bin: encoder,
142
167
  cwd: run.cwd,
143
- args: burnPartArgs(edit, partList, join(workspace, `${part.name}.mp4`), part.start, run.portraits),
168
+ args: burnPartArgs(edit, partList, join(workspace, `${part.name}.mp4`), part.start, run.portraits, encode),
144
169
  signal,
145
170
  log: run.log,
171
+ priority,
146
172
  onProgress: (elapsedMs) => {
147
173
  done.set(part.name, Math.min(elapsedMs, msOf(part.frames)));
148
174
  burned();
@@ -167,11 +193,12 @@ export async function renderSlideshow(run) {
167
193
  throw new Error("the render was canceled");
168
194
  const burnsHere = run.burnSubtitles && parts.length <= 1;
169
195
  await runFfmpeg({
170
- bin: run.bin,
196
+ bin: burnsHere ? encoder : run.bin,
171
197
  cwd: run.cwd,
172
- args: joinArgs(edit, run.output, played, burnsHere, run.portraits, sound),
198
+ args: joinArgs(edit, run.output, played, burnsHere, run.portraits, sound, encode),
173
199
  signal: run.signal,
174
200
  log: run.log,
201
+ priority,
175
202
  onProgress: (elapsedMs) => report(rendered + elapsedMs * (burnsHere ? 1 : copyWeight)),
176
203
  });
177
204
  if (run.cache !== undefined) {
@@ -179,12 +206,12 @@ export async function renderSlideshow(run) {
179
206
  pruneClips(run.cache, keys);
180
207
  }
181
208
  catch (error) {
182
- run.log.write("warn", "video.cache", { detail: messageOf(error) });
209
+ run.log.write("warn", "video.cache", { detail: errorText(error) });
183
210
  }
184
211
  }
185
212
  // What the finished video measures, after its AAC encode.
186
213
  return run.master !== undefined && edit.audio.length > 0
187
- ? await masterReport(run, run.output, run.master)
214
+ ? await masterReport({ ...run, priority }, run.output, run.master)
188
215
  : undefined;
189
216
  }
190
217
  finally {
@@ -194,13 +221,14 @@ export async function renderSlideshow(run) {
194
221
  // The video's finished sound as AAC in the working folder: the narration, silences and bed
195
222
  // mixed, and with Level the volume mastered first, alone and then as one file.
196
223
  async function renderSound(run, edit, workspace, signal) {
197
- const quiet = { bin: run.bin, signal, log: run.log, onProgress: () => { } };
224
+ const priority = run.machine?.priority;
225
+ const quiet = { bin: run.bin, signal, log: run.log, priority, onProgress: () => { } };
198
226
  let played = edit;
199
227
  if (run.master !== undefined) {
200
228
  const mixed = join(workspace, "mix.wav");
201
229
  await runFfmpeg({ ...quiet, args: audioMixArgs(edit, mixed) });
202
230
  const mastered = join(workspace, "master.wav");
203
- await masterFile({ ...run, signal }, mixed, mastered, run.master, masterFormat);
231
+ await masterFile({ ...run, signal, priority }, mixed, mastered, run.master, masterFormat);
204
232
  played = {
205
233
  ...edit,
206
234
  audio: [
@@ -249,6 +277,3 @@ export function burnParts(order, clips, jobs) {
249
277
  parts.push({ name: `p${String(parts.length + 1)}`, names, start, frames: at - start });
250
278
  return parts;
251
279
  }
252
- function messageOf(error) {
253
- return error instanceof Error ? error.message : String(error);
254
- }
@@ -1,3 +1,4 @@
1
+ import { editSettings } from "../../kernel/ports/settings-places.js";
1
2
  import { parseScript } from "./script.js";
2
3
  // Audiobooks from an existing text: the text model hands each passage to a speaker - the
3
4
  // narrator's prose to the narrator, each quoted line to the character who says it - and
@@ -35,12 +36,12 @@ export function parseAttribution(answer, source, speakers) {
35
36
  if (expected.length > 0 && kept / expected.length < 0.9)
36
37
  return {
37
38
  ok: false,
38
- reason: `The speaker split left out ${String(expected.length - kept)} of the text's ${String(expected.length)} words. The text model must hand every word to a speaker; Try again, or choose another model in Edit project → Providers.`,
39
+ reason: `The speaker split left out ${String(expected.length - kept)} of the text's ${String(expected.length)} words. The AI model must hand every word to a speaker; Try again, or choose another model in ${editSettings.writer}.`,
39
40
  };
40
41
  if (got.length - kept > Math.max(3, expected.length * 0.1))
41
42
  return {
42
43
  ok: false,
43
- reason: `The speaker split added ${String(got.length - kept)} words that are not in the text. The text model must not write anything of its own; Try again, or choose another model in Edit project → Providers.`,
44
+ reason: `The speaker split added ${String(got.length - kept)} words that are not in the text. The AI model must not write anything of its own; Try again, or choose another model in ${editSettings.writer}.`,
44
45
  };
45
46
  return parsed;
46
47
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
2
3
  // Multiple voices: a run whose narration is a script of speaker turns rather than one voice
3
4
  // reading an article. Absent on a run is the Narration format, which is every project saved
4
5
  // before this existed, and nothing about such a run reads anything in this slice.
@@ -136,23 +137,23 @@ export function voicesProblems(settings) {
136
137
  if (speakers.length === 0)
137
138
  problems.push({
138
139
  field: "voices.speakers",
139
- message: "Add at least one speaker under Speakers (Play → Narration, or Edit project → Providers).",
140
+ message: `Add at least one speaker under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
140
141
  });
141
142
  if (settings.source === "attribute" && settings.format !== "audiobook")
142
143
  problems.push({
143
144
  field: "voices.source",
144
- message: "Only audiobooks can hand an existing text's dialogue to speakers. Choose Write a script under Speakers (Play → Narration, or Edit project → Providers).",
145
+ message: `Only audiobooks can hand an existing text's dialogue to speakers. Choose Write a script under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
145
146
  });
146
147
  if ((settings.format === "podcast" || settings.format === "interview") && speakers.length < 2)
147
148
  problems.push({
148
149
  field: "voices.speakers",
149
- message: `A ${voiceFormatLabels[settings.format].toLowerCase()} needs at least two speakers. Add one under Speakers (Play → Narration, or Edit project → Providers).`,
150
+ message: `A ${voiceFormatLabels[settings.format].toLowerCase()} needs at least two speakers. Add one under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
150
151
  });
151
152
  if ((settings.format === "audiobook" || settings.format === "drama") &&
152
153
  !speakers.some((speaker) => speaker.role === "narrator"))
153
154
  problems.push({
154
155
  field: "voices.speakers",
155
- message: `An ${settings.format === "drama" ? "radio drama" : "audiobook"} needs a narrator. Set one speaker's role to Narrator under Speakers (Play → Narration, or Edit project → Providers).`,
156
+ message: `An ${settings.format === "drama" ? "radio drama" : "audiobook"} needs a narrator. Set one speaker's role to Narrator under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
156
157
  });
157
158
  const names = new Set();
158
159
  const ids = new Set();
@@ -181,7 +182,7 @@ export function voicesProblems(settings) {
181
182
  if (speaker.voice.provider.trim() === "" || speaker.voice.model.trim() === "")
182
183
  problems.push({
183
184
  field: `${field}.voice`,
184
- message: `Choose a voice provider and model for ${name || "this speaker"}.`,
185
+ message: `Choose a voice service and model for ${name || "this speaker"}.`,
185
186
  });
186
187
  else if (speaker.voice.voice.trim() === "")
187
188
  problems.push({
@@ -198,20 +199,20 @@ export function voicesProblems(settings) {
198
199
  if (settings.book.title.trim() === "")
199
200
  problems.push({
200
201
  field: "voices.book.title",
201
- message: "Enter the book's title under Speakers (Play → Narration), or remove the book to make a project on its own.",
202
+ message: `Enter the book's title under Speakers (${createRows.narration}, or ${editSettings.narration}), or remove the book to make a project on its own.`,
202
203
  });
203
204
  if (!Number.isInteger(settings.book.chapter) ||
204
205
  settings.book.chapter < 1 ||
205
206
  settings.book.chapter > bookChapterMax)
206
207
  problems.push({
207
208
  field: "voices.book.chapter",
208
- message: `Enter a chapter number from 1 to ${String(bookChapterMax)} under Speakers (Play → Narration).`,
209
+ message: `Enter a chapter number from 1 to ${String(bookChapterMax)} under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
209
210
  });
210
211
  }
211
212
  if (!turnGapSteps.includes(settings.turnGapSeconds))
212
213
  problems.push({
213
214
  field: "voices.turnGapSeconds",
214
- message: "Pick a gap between turns from the list under Speakers (Play → Narration, or Edit project → Providers).",
215
+ message: `Pick a gap between turns from the list under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
215
216
  });
216
217
  return problems;
217
218
  }
@@ -14,9 +14,6 @@ export function speakerColour(index) {
14
14
  }
15
15
  // ASS writes colours blue first: &HAABBGGRR in a style (alpha 00 is opaque) and &HBBGGRR& in
16
16
  // an inline override.
17
- export function assColour(hex, alpha = 0) {
18
- return `&H${alpha.toString(16).padStart(2, "0")}${bgr(hex)}`.toUpperCase();
19
- }
20
17
  export function assInlineColour(hex) {
21
18
  return `&H${bgr(hex)}&`.toUpperCase();
22
19
  }
@@ -1,3 +1,4 @@
1
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
1
2
  import { voiceFormatLabels } from "./model.js";
2
3
  const heading = /^#{1,6}\s+(.+?)\s*#*\s*$/;
3
4
  const turnLine = /^\*{0,2}([^:*\n]{1,60}?)\*{0,2}\s*:\s*\*{0,2}\s*(.*)$/;
@@ -42,7 +43,7 @@ export function parseScript(markdown, speakers, keepMarkdown = false) {
42
43
  ok: false,
43
44
  reason: turn === null
44
45
  ? `Line ${String(number)} of the script is not a turn. Start every turn with a speaker's name and a colon (${names}), one turn per paragraph.`
45
- : `Line ${String(number)} of the script is spoken by "${(turn[1] ?? "").trim()}", who is not one of the speakers (${names}). Use only these names, or add the speaker under Speakers (Play → Narration, or Edit project → Providers).`,
46
+ : `Line ${String(number)} of the script is spoken by "${(turn[1] ?? "").trim()}", who is not one of the speakers (${names}). Use only these names, or add the speaker under Speakers (${createRows.narration}, or ${editSettings.narration}).`,
46
47
  };
47
48
  }
48
49
  const spoken = [];