videobook 0.0.0-stage → 0.2.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 (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { registerJobHandler } from "../queue.js";
3
+ import { requireApiKey } from "../api-key.js";
4
+ import { buildIntelligenceResponses, firstResponseText, intelligenceRequest } from "../openai-intelligence.js";
5
+ import { chatIntelligenceRequestOptions } from "../chat-models.js";
6
+ import { goalBriefConstraints, goalPlannerResponseSchema, goalPromptBudgetIssues, compileFrameSteps, compileStoryboardSteps, compileVideoSteps } from "../goal-plan.js";
7
+ import { goalPlannerBrief } from "../goal-characters.js";
8
+ import { goalBriefStyle, goalStyle } from "../goal-styles.js";
9
+ import { constrainGoalPlannerPlan, goalPlannerInstructions } from "../goal-planner-prompt.js";
10
+ import { readAssetTagSummaries } from "../asset-tags.js";
11
+ import { readGoal, requireGoalContent, saveGoal, writeGoalContent } from "../goal-store.js";
12
+ import { withNotebookMutation } from "../notebook-mutations.js";
13
+ import { assessToolCallEstimate } from "./cost-estimate-assessment.js";
14
+ import { goalPlannerImages } from "../goal-reference-images.js";
15
+ function sumEstimates(estimates) {
16
+ return {
17
+ usd: estimates.every(estimate => estimate.usd !== null)
18
+ ? estimates.reduce((sum, estimate) => sum + (estimate.usd ?? 0), 0) : null,
19
+ warnings: [...new Set(estimates.flatMap((estimate) => estimate.warnings))],
20
+ };
21
+ }
22
+ export async function estimateGoal(engine, plan, characters) {
23
+ const reference = characters[0]?.artifactId ?? "";
24
+ const pins = characters.map(character => ({ ...character, address: character.artifactId }));
25
+ const groups = {
26
+ frame: compileFrameSteps(plan, pins),
27
+ storyboards: compileStoryboardSteps(plan, pins, reference),
28
+ videos: compileVideoSteps("estimate", plan, plan.shots.map(() => reference), plan.frameContinuity ? plan.shots.map(() => reference) : []).filter(step => step.id !== "assemble"),
29
+ };
30
+ const estimated = await Promise.all(Object.values(groups).map(async (steps) => sumEstimates(await Promise.all(steps.map(async (step) => {
31
+ const estimate = await assessToolCallEstimate(engine, step.tool, { book_id: engine.book.get().bookId, ...step.params });
32
+ return { usd: estimate.available ? estimate.usd : null, warnings: estimate.warnings };
33
+ })))));
34
+ return { frame: estimated[0], storyboards: estimated[1], videos: estimated[2], total: sumEstimates(estimated) };
35
+ }
36
+ export function registerPlanGoalJob(engine) {
37
+ registerJobHandler("plan_goal", async (payload, context) => {
38
+ const goalId = String(payload.goalId);
39
+ const goal = readGoal(engine, goalId);
40
+ if (goal.planVersion !== 2 || goal.status !== "planning")
41
+ return { goalId };
42
+ try {
43
+ const { brief, characters, requestedStyle } = requireGoalContent(engine, goal);
44
+ const style = requestedStyle ?? goalBriefStyle(brief);
45
+ const constraints = goalBriefConstraints(brief);
46
+ const tags = readAssetTagSummaries(engine, characters.map(character => character.artifactId));
47
+ const notebook = engine.notebooks.read(goal.notebookId);
48
+ if (!notebook.ok)
49
+ throw new Error(notebook.error.message);
50
+ const responses = buildIntelligenceResponses(requireApiKey("OPENAI_API_KEY"), chatIntelligenceRequestOptions());
51
+ const request = {
52
+ instructions: goalPlannerInstructions(style, constraints),
53
+ input: [{ role: "user", content: [
54
+ { type: "input_text", text: JSON.stringify({ brief: goalPlannerBrief(brief, characters, notebook.value).replace(/Character (\d+)/gu, "Reference $1"), style: { ...style, label: goalStyle(style.id).label }, references: characters.map((character, index) => ({ name: `Reference ${index + 1}`, label: character.label, tags: tags.get(character.artifactId)?.effective ?? [] })), ...constraints }) },
55
+ ...await goalPlannerImages(engine, characters),
56
+ ] }],
57
+ text: { format: { type: "json_schema", name: "video_goal_plan", strict: true, schema: z.toJSONSchema(goalPlannerResponseSchema.omit({ style: true })) } },
58
+ max_output_tokens: 2500,
59
+ };
60
+ const response = await responses(intelligenceRequest(request, chatIntelligenceRequestOptions()), context.signal);
61
+ let plan = goalPlannerResponseSchema.parse(JSON.parse(firstResponseText(response)));
62
+ const budgetIssues = goalPromptBudgetIssues(plan);
63
+ if (budgetIssues.length > 0) {
64
+ const revised = await responses(intelligenceRequest({
65
+ ...request,
66
+ input: [...request.input, { role: "user", content: `Shorten this plan while preserving its meaning: ${JSON.stringify(plan)}\nFix these word limits: ${budgetIssues.join(" ")}` }],
67
+ }, chatIntelligenceRequestOptions()), context.signal);
68
+ const parsed = goalPlannerResponseSchema.safeParse(JSON.parse(firstResponseText(revised)));
69
+ if (!parsed.success || goalPromptBudgetIssues(parsed.data).length > 0)
70
+ throw new Error("The planner wrote prompts that are too long. Try planning again.");
71
+ plan = parsed.data;
72
+ }
73
+ plan = constrainGoalPlannerPlan(plan, brief, style);
74
+ plan.referenceRoles = characters.map((_, index) => plan.referenceRoles?.[index] ?? "reference");
75
+ const estimate = await estimateGoal(engine, plan, characters);
76
+ await withNotebookMutation(`goal:${goalId}`, async () => {
77
+ const current = readGoal(engine, goalId);
78
+ if (current.status !== "planning")
79
+ return;
80
+ const content = requireGoalContent(engine, current);
81
+ await writeGoalContent(engine, current, { ...content, plan, estimate, revision: content.revision + 1 });
82
+ saveGoal(engine, { ...current, stage: "frame", status: "review", error: null });
83
+ });
84
+ return { goalId };
85
+ }
86
+ catch (error) {
87
+ await withNotebookMutation(`goal:${goalId}`, async () => {
88
+ const current = readGoal(engine, goalId);
89
+ if (current.status === "planning")
90
+ saveGoal(engine, {
91
+ ...current, status: "failed", error: error instanceof Error ? error.message : String(error),
92
+ });
93
+ });
94
+ throw error;
95
+ }
96
+ });
97
+ }
@@ -0,0 +1,21 @@
1
+ import { createPost, getPost, linkPost, updatePost } from "../posts/post-store.js";
2
+ import { registerJobHandler } from "../queue.js";
3
+ import { withNotebookMutation } from "../notebook-mutations.js";
4
+ import { uploadPostToDropbox } from "../posts/dropbox-upload.js";
5
+ import { savePostToICloud } from "../posts/icloud-drive.js";
6
+ import { refreshPostMetrics } from "../posts/post-metrics.js";
7
+ import { draftPostCopy, postCopyResponses, readPostCopyContext } from "../posts/post-copy.js";
8
+ import { getApiKey } from "../api-key.js";
9
+ export function registerPostRecordJobs(engine) {
10
+ registerJobHandler("post_create", async (payload) => createPost(engine, payload.args));
11
+ registerJobHandler("post_update", async (payload) => withNotebookMutation(`post:${payload.postCellId}`, () => updatePost(engine, String(payload.postCellId), payload.patch)));
12
+ registerJobHandler("post_link", async (payload) => withNotebookMutation(`post:${payload.postCellId}`, () => linkPost(engine, String(payload.postCellId), String(payload.url), String(payload.postedAt))));
13
+ registerJobHandler("post_dropbox", async (payload, context) => withNotebookMutation(`post:${payload.postCellId}`, () => uploadPostToDropbox(engine, String(payload.postCellId), { folder: typeof payload.folder === "string" ? payload.folder : undefined, caption: typeof payload.caption === "string" ? payload.caption : undefined, signal: context.signal })));
14
+ registerJobHandler("post_icloud", async (payload) => withNotebookMutation(`post:${payload.postCellId}`, () => savePostToICloud(engine, String(payload.postCellId), { folder: typeof payload.folder === "string" ? payload.folder : undefined, caption: typeof payload.caption === "string" ? payload.caption : undefined })));
15
+ registerJobHandler("post_copy", async (payload, context) => {
16
+ const post = getPost(engine, String(payload.postCellId));
17
+ const copyContext = await readPostCopyContext(engine, post);
18
+ return draftPostCopy(postCopyResponses(getApiKey("OPENAI_API_KEY") ?? undefined), copyContext, payload.request, context.signal);
19
+ });
20
+ registerJobHandler("post_metrics", async (payload, context) => withNotebookMutation(`post-metrics:${payload.postCellId}`, () => refreshPostMetrics(engine, String(payload.postCellId), { signal: context.signal })));
21
+ }
@@ -0,0 +1,21 @@
1
+ import { registerJobHandler } from "../queue.js";
2
+ import { postToX, prepareXPublication } from "../x/post-to-x.js";
3
+ export function registerPostToXJob(engine) {
4
+ registerJobHandler("post_prepare_x", async (payload, context) => prepareXPublication({ engine, publicationKey: `job:${context.jobId}`, ...payload }));
5
+ registerJobHandler("post_to_x", async (payload, context) => {
6
+ const { assetId, postCellId, bookId, text, orientation, scheduleId, scheduledFor, followups, } = payload;
7
+ return await postToX({
8
+ engine,
9
+ publicationKey: scheduleId ? `schedule:${scheduleId}` : `job:${context.jobId}`,
10
+ signal: context.signal,
11
+ assetId,
12
+ postCellId,
13
+ bookId,
14
+ text,
15
+ orientation,
16
+ scheduleId,
17
+ scheduledFor,
18
+ followups,
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,190 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { basename, join } from "node:path";
6
+ import sharp from "sharp";
7
+ import { findPrimaryMediaFile, findVideoFile, LOCAL_CLIP_MANIFEST, } from "videobook-engine";
8
+ import { probeVideo, runFfmpeg } from "../ffmpeg.js";
9
+ import { decodeHeicToPng } from "../image.js";
10
+ import { registerLocalClipSearchProvider } from "../local-search-models.js";
11
+ import { searchReferenceStore, } from "../search-reference-store.js";
12
+ import { registerJobHandler } from "../queue.js";
13
+ async function runPrepareSearchReference(engine, payload, context) {
14
+ try {
15
+ const uploadPath = payload.uploadPath;
16
+ const source = uploadPath
17
+ ? { path: uploadPath, objectHash: undefined, readBytes: () => readFile(uploadPath) }
18
+ : await existingAssetSource(engine, payload.assetId, payload.mediaKind);
19
+ const prepared = await prepareSearchReferenceFile(engine, source.path, payload.mediaKind, {
20
+ name: payload.name,
21
+ readBytes: source.readBytes,
22
+ ...(source.objectHash
23
+ ? { sourceObjectHash: source.objectHash }
24
+ : {}),
25
+ sourceStartMs: payload.sourceStartMs,
26
+ sourceDurationMs: payload.sourceDurationMs,
27
+ signal: context.signal,
28
+ });
29
+ return searchReferenceStore.put(prepared);
30
+ }
31
+ finally {
32
+ if (payload.cleanupRoot) {
33
+ await rm(payload.cleanupRoot, { recursive: true, force: true });
34
+ }
35
+ }
36
+ }
37
+ async function prepareSearchReferenceFile(engine, sourcePath, mediaKind, options) {
38
+ const provider = registerLocalClipSearchProvider(engine);
39
+ const registered = engine.temporalSearch.manifests.register(LOCAL_CLIP_MANIFEST);
40
+ if (!registered.ok)
41
+ throw new Error(registered.error.message);
42
+ await provider.prepare({ signal: options.signal });
43
+ options.signal?.throwIfAborted();
44
+ const name = options.name?.trim() || basename(sourcePath);
45
+ if (mediaKind === "image") {
46
+ let normalizedRoot;
47
+ try {
48
+ let imagePath = sourcePath;
49
+ let preview;
50
+ if (/\.hei[cf]$/iu.test(sourcePath)) {
51
+ const decoded = await decodeHeicToPng(await options.readBytes());
52
+ options.signal?.throwIfAborted();
53
+ normalizedRoot = await mkdtemp(join(tmpdir(), "videobook-search-image-"));
54
+ imagePath = join(normalizedRoot, "reference.png");
55
+ await writeFile(imagePath, decoded);
56
+ preview = await sharp(decoded).resize(512, 512, { fit: "inside", withoutEnlargement: true }).png().toBuffer();
57
+ }
58
+ const vector = await provider.embedImage(imagePath, { signal: options.signal });
59
+ options.signal?.throwIfAborted();
60
+ return {
61
+ kind: "image",
62
+ name,
63
+ ...(preview ? { preview } : {}),
64
+ reference: {
65
+ kind: "image",
66
+ embeddingSpace: provider.embeddingSpace,
67
+ vector: [...vector],
68
+ fingerprints: [{
69
+ kind: "sha256",
70
+ value: options.sourceObjectHash ?? await fileHash(sourcePath),
71
+ }],
72
+ },
73
+ };
74
+ }
75
+ finally {
76
+ if (normalizedRoot)
77
+ await rm(normalizedRoot, { recursive: true, force: true });
78
+ }
79
+ }
80
+ const probe = await probeVideo(sourcePath, { signal: options.signal });
81
+ if (!probe.videoStream || probe.videoDuration <= 0) {
82
+ throw new Error("The selected reference has no usable video stream");
83
+ }
84
+ const fullDurationMs = Math.max(1, Math.round(probe.videoDuration * 1_000));
85
+ const sourceStartMs = boundedStart(options.sourceStartMs, fullDurationMs);
86
+ const availableMs = fullDurationMs - sourceStartMs;
87
+ const durationMs = boundedDuration(options.sourceDurationMs, availableMs);
88
+ const offsets = sampleOffsets(durationMs);
89
+ const frameRoot = await mkdtemp(join(tmpdir(), "videobook-search-frames-"));
90
+ try {
91
+ const samples = [];
92
+ for (const [index, offsetMs] of offsets.entries()) {
93
+ options.signal?.throwIfAborted();
94
+ const framePath = join(frameRoot, `${String(index).padStart(2, "0")}.jpg`);
95
+ await runFfmpeg([
96
+ "-ss",
97
+ ((sourceStartMs + offsetMs) / 1_000).toFixed(3),
98
+ "-i",
99
+ sourcePath,
100
+ "-frames:v",
101
+ "1",
102
+ "-q:v",
103
+ "2",
104
+ "-y",
105
+ framePath,
106
+ ], { signal: options.signal });
107
+ const vector = await provider.embedImage(framePath, { signal: options.signal });
108
+ samples.push({ offsetMs, vector: [...vector] });
109
+ }
110
+ return {
111
+ kind: "video",
112
+ name,
113
+ durationMs,
114
+ reference: {
115
+ kind: "video",
116
+ embeddingSpace: provider.embeddingSpace,
117
+ durationMs,
118
+ samples,
119
+ },
120
+ };
121
+ }
122
+ finally {
123
+ await rm(frameRoot, { recursive: true, force: true });
124
+ }
125
+ }
126
+ export function registerPrepareSearchReferenceJob(engine) {
127
+ registerJobHandler("prepare_search_reference", async (payload, context) => engine.withLocalMedia(() => runPrepareSearchReference(engine, payload, context)));
128
+ }
129
+ async function existingAssetSource(engine, assetId, mediaKind) {
130
+ if (!assetId)
131
+ throw new Error("Search reference asset ID is required");
132
+ const artifact = engine.artifacts.get(assetId);
133
+ if (!artifact.ok)
134
+ throw new Error(artifact.error.message);
135
+ if (mediaKind === "image"
136
+ ? artifact.value.kind !== "image"
137
+ : artifact.value.kind !== "video" && artifact.value.kind !== "final") {
138
+ throw new Error(`The selected asset is not a ${mediaKind}`);
139
+ }
140
+ const manifest = await engine.files.manifest(artifact.value.artifactId);
141
+ if (!manifest.ok)
142
+ throw Object.assign(new Error(manifest.error.message), { name: manifest.error.code, error: manifest.error });
143
+ const file = mediaKind === "video"
144
+ ? findVideoFile(manifest.value.files)
145
+ ?? findPrimaryMediaFile(manifest.value.files, artifact.value.kind)
146
+ : findPrimaryMediaFile(manifest.value.files, artifact.value.kind)
147
+ ?? manifest.value.files.find((candidate) => /\.hei[cf]$/iu.test(candidate.name));
148
+ if (!file)
149
+ throw new Error(`No ${mediaKind} file is available for this asset`);
150
+ return {
151
+ path: join(manifest.value.path, file.name),
152
+ objectHash: file.objectHash,
153
+ readBytes: async () => {
154
+ const read = await engine.files.read(artifact.value.artifactId, file.name);
155
+ if (!read.ok)
156
+ throw Object.assign(new Error(read.error.message), { name: read.error.code, error: read.error });
157
+ return read.value;
158
+ },
159
+ };
160
+ }
161
+ function boundedStart(value, durationMs) {
162
+ if (value === undefined)
163
+ return 0;
164
+ if (!Number.isFinite(value) || value < 0 || value >= durationMs) {
165
+ throw new Error("Reference start must fall within the video");
166
+ }
167
+ return Math.round(value);
168
+ }
169
+ function boundedDuration(value, availableMs) {
170
+ if (value === undefined)
171
+ return availableMs;
172
+ if (!Number.isFinite(value) || value <= 0) {
173
+ throw new Error("Reference duration must be positive");
174
+ }
175
+ return Math.max(1, Math.min(Math.round(value), availableMs));
176
+ }
177
+ function sampleOffsets(durationMs) {
178
+ if (durationMs <= 100)
179
+ return [0];
180
+ const count = Math.min(16, Math.max(2, Math.ceil(durationMs / 1_000)));
181
+ const finalOffset = Math.max(1, durationMs - Math.min(50, durationMs / 10));
182
+ return Array.from({ length: count }, (_, index) => Math.round(index * finalOffset / (count - 1)));
183
+ }
184
+ async function fileHash(filePath) {
185
+ const hash = createHash("sha256");
186
+ for await (const chunk of createReadStream(filePath)) {
187
+ hash.update(chunk);
188
+ }
189
+ return hash.digest("hex");
190
+ }
@@ -0,0 +1,16 @@
1
+ import { queue, registerJobHandler } from "../queue.js";
2
+ export async function enqueueTemporalSearchPreparation(bookId) {
3
+ const job = await queue.enqueue(`prepare-temporal-search:${bookId}`, {
4
+ type: "prepare_temporal_search",
5
+ bookId,
6
+ });
7
+ return { bookId, jobId: job.id };
8
+ }
9
+ export function registerPrepareTemporalSearchJob(engine) {
10
+ registerJobHandler("prepare_temporal_search", async (_payload, context) => {
11
+ const result = await engine.temporalSearch.prepare({ signal: context.signal });
12
+ if (!result.ok)
13
+ throw new Error(result.error.message);
14
+ return result.value;
15
+ });
16
+ }
@@ -0,0 +1,67 @@
1
+ import { extractWaveform, probeVideo } from "../ffmpeg.js";
2
+ import { enqueueAssetTagging } from "./tag-asset.js";
3
+ import { queue, registerJobHandler } from "../queue.js";
4
+ import { getApiKey } from "../api-key.js";
5
+ import { withLocalLease } from "./_lease.js";
6
+ import { registerMediaStreams } from "./process-video.js";
7
+ import { withTransientStorageRetry } from "./_storage-retry.js";
8
+ export async function processAudioFile(engine, payload, signal) {
9
+ signal?.throwIfAborted();
10
+ const probe = await probeVideo(payload.audioPath, { signal });
11
+ if (!probe.audioStream) {
12
+ throw new Error(`process_audio requires an audio stream: ${payload.assetId}`);
13
+ }
14
+ await registerMediaStreams(engine, payload.assetId, payload.sourceFilename, probe);
15
+ const duration = probe.audioDuration ?? probe.duration;
16
+ await engine.metadata.artifacts.write(payload.assetId, "original", {
17
+ ...payload.provenance,
18
+ duration,
19
+ sampleRateHz: probe.audioStream.audio?.sampleRateHz,
20
+ channels: probe.audioStream.audio?.channels,
21
+ });
22
+ const waveform = await extractWaveform(payload.audioPath);
23
+ if (waveform.length > 0) {
24
+ const written = await engine.metadata.waveforms.write(payload.assetId, waveform);
25
+ if (!written.ok)
26
+ throw new Error(written.error.message);
27
+ }
28
+ if (process.env.NODE_ENV !== "test") {
29
+ await queue.enqueue(`semantic-index-${payload.assetId}-${Date.now()}`, {
30
+ type: "semantic_index",
31
+ assetId: payload.assetId,
32
+ bookId: payload.bookId,
33
+ });
34
+ if (getApiKey("ELEVENLABS_API_KEY")) {
35
+ await queue.enqueue(`transcribe-audio-${payload.assetId}-${Date.now()}`, {
36
+ type: "transcribe",
37
+ audioPath: payload.audioPath,
38
+ audioFilename: payload.sourceFilename,
39
+ tagAudioEvents: true,
40
+ diarize: true,
41
+ assetId: payload.assetId,
42
+ assetDir: payload.assetDir,
43
+ bookId: payload.bookId,
44
+ });
45
+ }
46
+ else if (getApiKey("OPENAI_API_KEY")) {
47
+ await queue.enqueue(`transcribe-audio-${payload.assetId}-${Date.now()}`, {
48
+ type: "transcribe_video_audio",
49
+ videoPath: payload.audioPath,
50
+ sourceFilename: payload.sourceFilename,
51
+ assetId: payload.assetId,
52
+ assetDir: payload.assetDir,
53
+ bookId: payload.bookId,
54
+ });
55
+ }
56
+ }
57
+ if (process.env.NODE_ENV !== "test") {
58
+ await enqueueAssetTagging(engine, payload.assetId);
59
+ }
60
+ return { success: true, duration };
61
+ }
62
+ export function registerProcessAudioJob(engine) {
63
+ registerJobHandler("process_audio", async (payload, ctx) => {
64
+ const input = payload;
65
+ return withLocalLease(engine, input.assetId, ctx, "process", async () => withTransientStorageRetry(() => processAudioFile(engine, input, ctx.signal), ctx.signal));
66
+ });
67
+ }
@@ -0,0 +1,58 @@
1
+ import { enqueueAssetTagging } from "./tag-asset.js";
2
+ import { queue, registerJobHandler } from "../queue.js";
3
+ import { withLocalLease } from "./_lease.js";
4
+ import { probeImage, cropToFill, decodeHeicToPng } from "../image.js";
5
+ import { withTransientStorageRetry } from "./_storage-retry.js";
6
+ export const SKIP_CROP_EXTENSIONS = new Set([".svg", ".gif"]);
7
+ export function registerProcessImageJob(engine) {
8
+ registerJobHandler("process_image", async (payload, ctx) => {
9
+ const { assetId, bookId, extension, provenance, } = payload;
10
+ return withLocalLease(engine, assetId, ctx, "process", async () => withTransientStorageRetry(async () => {
11
+ const readResult = await engine.files.read(assetId, `original${extension}`);
12
+ if (!readResult.ok)
13
+ throw new Error(`Failed to read original: ${readResult.error.message}`);
14
+ let buffer = Buffer.isBuffer(readResult.value) ? readResult.value : Buffer.from(readResult.value);
15
+ if (extension === ".heic" || extension === ".heif") {
16
+ buffer = await decodeHeicToPng(buffer);
17
+ const converted = await engine.files.write(assetId, "original.png", buffer);
18
+ if (!converted.ok)
19
+ throw new Error(converted.error.message);
20
+ }
21
+ const { width, height } = await probeImage(buffer);
22
+ const metadata = await engine.metadata.artifacts.write(assetId, "original", { ...provenance, width, height });
23
+ if (!metadata.ok)
24
+ throw new Error(metadata.error.message);
25
+ if (!SKIP_CROP_EXTENSIONS.has(extension)) {
26
+ const [landscape, portrait, square] = await Promise.all([
27
+ cropToFill(buffer, 1920, 1080),
28
+ cropToFill(buffer, 1080, 1920),
29
+ cropToFill(buffer, 1080, 1080),
30
+ ]);
31
+ const landscapeWrite = await engine.files.write(assetId, "landscape.jpg", landscape);
32
+ if (!landscapeWrite.ok)
33
+ throw new Error(landscapeWrite.error.message);
34
+ const portraitWrite = await engine.files.write(assetId, "portrait.jpg", portrait);
35
+ if (!portraitWrite.ok)
36
+ throw new Error(portraitWrite.error.message);
37
+ const squareWrite = await engine.files.write(assetId, "square.jpg", square);
38
+ if (!squareWrite.ok)
39
+ throw new Error(squareWrite.error.message);
40
+ }
41
+ if (process.env.NODE_ENV !== "test") {
42
+ await queue.enqueue(`semantic-index-${assetId}-${Date.now()}`, {
43
+ type: "semantic_index",
44
+ assetId,
45
+ bookId,
46
+ });
47
+ }
48
+ // Vision analysis for the reproduction prompt is still gated to slot
49
+ // drop in tools/assign-slot.ts. Tagging is not: an image that never
50
+ // reaches a timeline still has to be findable in the Library, and the
51
+ // tag job asks for the analysis it needs once per content hash.
52
+ if (process.env.NODE_ENV !== "test") {
53
+ await enqueueAssetTagging(engine, assetId);
54
+ }
55
+ return { success: true, width, height };
56
+ }, ctx.signal));
57
+ });
58
+ }
@@ -0,0 +1,166 @@
1
+ import * as path from "node:path";
2
+ import { queue } from "../queue.js";
3
+ const VIDEO_EXTENSIONS = new Set([".mp4", ".mov", ".webm", ".avi", ".mkv"]);
4
+ const AUDIO_EXTENSIONS = new Set([
5
+ ".mp3",
6
+ ".wav",
7
+ ".aac",
8
+ ".flac",
9
+ ".ogg",
10
+ ".m4a",
11
+ ".weba",
12
+ ]);
13
+ const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".heic", ".heif"]);
14
+ const SCRIPT_EXTENSIONS = new Set([".md"]);
15
+ // process_image writes these alongside an original image. Their presence is
16
+ // proof that processing already ran, so the upload sweep must never promote
17
+ // one of them to `original.{ext}` — that's how a portrait PNG gets cropped
18
+ // into a 1920x1080 landscape JPG when the sweep races a missing
19
+ // native processing metadata.
20
+ const IMAGE_VARIANT_FILENAMES = new Set(["landscape.jpg", "portrait.jpg", "square.jpg"]);
21
+ // The artifact kind is authoritative for type — a video asset with a
22
+ // promoted OG thumbnail (original.jpg) must not be classified as an image
23
+ // upload just because the .jpg sorts before the .mp4. Returns null for asset
24
+ // kinds that aren't candidates for the upload sweep (character/final/plan).
25
+ function uploadShapeFor(type) {
26
+ switch (type) {
27
+ case "video": return { mediaType: "video", extensions: VIDEO_EXTENSIONS };
28
+ case "audio": return { mediaType: "audio", extensions: AUDIO_EXTENSIONS };
29
+ case "image": return { mediaType: "image", extensions: IMAGE_EXTENSIONS };
30
+ case "script": return { mediaType: "script", extensions: SCRIPT_EXTENSIONS };
31
+ default: return null;
32
+ }
33
+ }
34
+ export async function findUnprocessedUploads(engine, assets) {
35
+ const results = await Promise.all(assets.map(async (asset) => {
36
+ if (asset.kind === "final")
37
+ return null;
38
+ const shape = uploadShapeFor(asset.kind);
39
+ if (!shape)
40
+ return null;
41
+ // Parallelize the independent disk/db reads — each was ~5-20ms serially,
42
+ // multiplied by every asset on every sync.
43
+ const [manifest, originResult, locked, work] = await Promise.all([
44
+ engine.files.manifest(asset.artifactId),
45
+ engine.metadata.artifacts.read(asset.artifactId, "original"),
46
+ engine.jobs.locks.isLocked(asset.path),
47
+ engine.jobs.artifactWork.read(asset.artifactId),
48
+ ]);
49
+ if (!manifest.ok)
50
+ return null;
51
+ if (originResult.ok)
52
+ return null;
53
+ // Skip assets currently held by another job (splice/trim/render mid-write).
54
+ // Otherwise the sweep races the in-flight ffmpeg writing original.mp4 and
55
+ // hands a half-written file to process_video, producing a spurious
56
+ // runtime failure before the real owner finishes.
57
+ if (locked)
58
+ return null;
59
+ // Skip assets that already have an active job or a terminal failure.
60
+ // 'pending'/'working' WITH meta.kind = the upload's own process_video is
61
+ // already enqueued or in-flight — racing it produces duplicate jobs that
62
+ // re-run the full transcode pipeline (or, with the legacy delete-source
63
+ // flow, fail with "ffprobe missing original.mkv"). 'error' = a prior run
64
+ // failed and nothing has cleared the row; re-enqueuing without recovery
65
+ // just spins (the engine's lease guard rejects new work until the row
66
+ // is cleared via /clear-failed-tasks or equivalent).
67
+ //
68
+ // A 'pending' row with no meta.kind is the placeholder createAsset
69
+ // writes as a 5-minute cleanup marker — that's the exact case the
70
+ // sweep needs to pick up, not skip.
71
+ if (work.ok && work.value) {
72
+ const { status, meta } = work.value;
73
+ if (status === "error")
74
+ return null;
75
+ if ((status === "pending" || status === "working") && meta.kind != null)
76
+ return null;
77
+ }
78
+ // A queued job owns the asset before it acquires a lease or lock. This
79
+ // also covers registered job types without an artifact-work classification.
80
+ if (engine.jobs.queue.list({
81
+ artifactId: asset.artifactId,
82
+ states: ["queued", "running", "completing"],
83
+ limit: 1,
84
+ }).length > 0)
85
+ return null;
86
+ // Prefer a literal `original.{ext}` first — that's the canonical shape
87
+ // produced by upload-file and processExternalUpload itself. Only fall
88
+ // back to an arbitrary-name file (user dragged "photo.png" straight
89
+ // into the asset folder) when no `original.*` exists AND no
90
+ // process_image variant exists. Without that variant guard, missing
91
+ // processing metadata would cause the sweep to pick the alphabetically
92
+ // first variant (landscape.jpg) and rename it to original.jpg.
93
+ const originalFiles = manifest.value.files.filter((f) => {
94
+ const ext = path.extname(f.name).toLowerCase();
95
+ return shape.extensions.has(ext) && f.name === `original${ext}`;
96
+ });
97
+ let mediaFile = originalFiles.find((f) => !/\.(?:heic|heif)$/i.test(f.name)) ?? originalFiles.at(0);
98
+ if (!mediaFile) {
99
+ const hasImageVariant = shape.mediaType === "image"
100
+ && manifest.value.files.some((f) => IMAGE_VARIANT_FILENAMES.has(f.name));
101
+ if (hasImageVariant)
102
+ return null;
103
+ mediaFile = manifest.value.files.find((f) => {
104
+ const ext = path.extname(f.name).toLowerCase();
105
+ return shape.extensions.has(ext);
106
+ });
107
+ }
108
+ if (!mediaFile)
109
+ return null;
110
+ const ext = path.extname(mediaFile.name).toLowerCase();
111
+ return {
112
+ assetId: asset.artifactId,
113
+ assetDir: asset.path,
114
+ mediaFilename: mediaFile.name,
115
+ extension: ext,
116
+ mediaType: shape.mediaType,
117
+ };
118
+ }));
119
+ return results.filter((u) => u !== null);
120
+ }
121
+ export async function processExternalUpload(engine, upload, bookId) {
122
+ const targetFilename = `original${upload.extension}`;
123
+ if (upload.mediaFilename !== targetFilename) {
124
+ await engine.files.rename(upload.assetId, upload.mediaFilename, targetFilename);
125
+ }
126
+ await engine.jobs.failures.clear(upload.assetId);
127
+ const provenance = { origin: "external", processed_at: new Date().toISOString() };
128
+ if (upload.mediaType === "video") {
129
+ const videoPath = path.join(upload.assetDir, targetFilename);
130
+ const jobId = `process-video-${upload.assetId}-${Date.now()}`;
131
+ await queue.enqueue(jobId, {
132
+ type: "process_video",
133
+ videoPath,
134
+ assetId: upload.assetId,
135
+ assetDir: upload.assetDir,
136
+ bookId,
137
+ provenance,
138
+ });
139
+ }
140
+ else if (upload.mediaType === "image") {
141
+ const jobId = `process-image-${upload.assetId}-${Date.now()}`;
142
+ await queue.enqueue(jobId, {
143
+ type: "process_image",
144
+ assetId: upload.assetId,
145
+ bookId,
146
+ extension: upload.extension,
147
+ provenance,
148
+ });
149
+ }
150
+ else if (upload.mediaType === "audio") {
151
+ const audioPath = path.join(upload.assetDir, targetFilename);
152
+ const jobId = `process-audio-${upload.assetId}-${Date.now()}`;
153
+ await queue.enqueue(jobId, {
154
+ type: "process_audio",
155
+ audioPath,
156
+ sourceFilename: targetFilename,
157
+ assetId: upload.assetId,
158
+ assetDir: upload.assetDir,
159
+ bookId,
160
+ provenance,
161
+ });
162
+ }
163
+ else {
164
+ await engine.metadata.artifacts.write(upload.assetId, "original", provenance);
165
+ }
166
+ }