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,535 @@
1
+ import { join } from "node:path";
2
+ import { registerLocalClipSearchProvider } from "./local-search-models.js";
3
+ import { enqueueTemporalSearchPreparation } from "./jobs/prepare-temporal-search.js";
4
+ import { enqueueSearchIndex } from "./jobs/enqueue-search-index.js";
5
+ import { missingVisualIndexes } from "./search-index-coverage.js";
6
+ import { markDescendantsStale } from "./notebook-action-catalog.js";
7
+ import { mutateNotebook } from "./notebook-mutations.js";
8
+ import { searchReferenceStore } from "./search-reference-store.js";
9
+ import { comparableHitScore } from "./search-scoring.js";
10
+ import { buildAssetList } from "./tools/reconcile-project-work.js";
11
+ import { assetRecency, readAssetUsage, recordAssetUse } from "./asset-usage.js";
12
+ import { matchesTagFilter, tagFacetCounts, transferAssetTags, } from "./asset-tags.js";
13
+ import { addStatusCounts, countBrowseStatuses, emptyStatusCounts, matchesBrowseStatus, pageLibraryAssets, } from "./library-browse.js";
14
+ function isMediaCellType(value) {
15
+ return value === "audio" || value === "image" || value === "video";
16
+ }
17
+ function isRecord(value) {
18
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+ function compatibleArtifact(cell, artifact) {
21
+ if (cell.type === "video") {
22
+ return artifact.kind === "video" || artifact.kind === "final";
23
+ }
24
+ return cell.type === artifact.kind;
25
+ }
26
+ function assertMediaCell(document, cellId) {
27
+ const cell = document.cells.find((candidate) => candidate.id === cellId);
28
+ if (!cell)
29
+ throw new Error(`Cell not found: ${cellId}`);
30
+ if (!isMediaCellType(cell.type)) {
31
+ throw new Error("Only audio, image, and video cells can hold media slots");
32
+ }
33
+ return cell;
34
+ }
35
+ function withCellExecution(document, cellId, status) {
36
+ const currentExecution = document.execution ?? {};
37
+ const currentCell = isRecord(currentExecution[cellId])
38
+ ? currentExecution[cellId]
39
+ : {};
40
+ return {
41
+ ...document,
42
+ execution: {
43
+ ...currentExecution,
44
+ [cellId]: {
45
+ ...currentCell,
46
+ status,
47
+ stale: false,
48
+ error: undefined,
49
+ },
50
+ },
51
+ };
52
+ }
53
+ function bindMediaSlot(document, cellId, artifact) {
54
+ const cell = assertMediaCell(document, cellId);
55
+ if (artifact && !compatibleArtifact(cell, artifact)) {
56
+ throw new Error(`A ${cell.type} cell cannot use a ${artifact.kind} artifact`);
57
+ }
58
+ if (artifact
59
+ && document.edges.some((edge) => edge.target === cellId)) {
60
+ throw new Error("Clear connected inputs before attaching Library media");
61
+ }
62
+ const cells = document.cells.map((candidate) => {
63
+ if (candidate.id !== cellId)
64
+ return candidate;
65
+ return {
66
+ ...candidate,
67
+ outputArtifactId: artifact?.artifactId,
68
+ inputs: {
69
+ ...(candidate.inputs ?? {}),
70
+ state: artifact ? "completed" : "ready",
71
+ },
72
+ };
73
+ });
74
+ const edges = artifact
75
+ ? document.edges
76
+ : document.edges.filter((edge) => edge.target !== cellId);
77
+ return withCellExecution(markDescendantsStale({ ...document, cells, edges }, cellId), cellId, artifact ? "completed" : "ready");
78
+ }
79
+ export async function setNotebookMediaSlot(engine, input) {
80
+ if (input.expectedRevision
81
+ && input.expectedRevision !== engine.head) {
82
+ throw new Error(`Stale notebook revision: expected ${input.expectedRevision}`);
83
+ }
84
+ const artifact = input.artifactRef
85
+ ? engine.artifacts.get(input.artifactRef)
86
+ : null;
87
+ if (artifact && !artifact.ok)
88
+ throw new Error(artifact.error.message);
89
+ const resolved = artifact?.ok ? artifact.value : null;
90
+ const result = await mutateNotebook(engine, input.notebookId, resolved ? "set_notebook_media_slot" : "clear_notebook_media_slot", (notebook) => bindMediaSlot(notebook, input.cellId, resolved), {
91
+ cellId: input.cellId,
92
+ outputArtifactId: resolved?.artifactId ?? null,
93
+ });
94
+ if (!result.ok)
95
+ throw new Error(result.error.message);
96
+ if (resolved)
97
+ recordAssetUse(engine, resolved.artifactId);
98
+ return {
99
+ cellId: input.cellId,
100
+ outputArtifactId: resolved?.artifactId ?? null,
101
+ revision: result.value.hash,
102
+ };
103
+ }
104
+ function kindMatches(requested, kind) {
105
+ return requested.has(kind)
106
+ || (kind === "final" && requested.has("video"));
107
+ }
108
+ function searchRank(asset, query) {
109
+ if (!query)
110
+ return 3;
111
+ const label = asset.label?.toLowerCase() ?? "";
112
+ const book = asset.owning_book_name.toLowerCase();
113
+ const prompt = asset.prompt?.toLowerCase() ?? "";
114
+ if ((label && label === query) || book === query)
115
+ return 0;
116
+ if ((label && label.startsWith(query)) || book.startsWith(query))
117
+ return 1;
118
+ return prompt.startsWith(query) ? 2 : 3;
119
+ }
120
+ function matchesQuery(asset, query) {
121
+ if (!query)
122
+ return true;
123
+ return [
124
+ asset.id,
125
+ asset.label ?? "",
126
+ asset.owning_book_name,
127
+ asset.prompt ?? "",
128
+ ].some((value) => value.toLowerCase().includes(query));
129
+ }
130
+ function lastUsedAt(usedAt) {
131
+ return usedAt === undefined ? null : new Date(usedAt).toISOString();
132
+ }
133
+ function libraryAssetRecency(asset) {
134
+ const used = asset.last_used_at ? Date.parse(asset.last_used_at) : undefined;
135
+ return assetRecency(asset.created_at, used);
136
+ }
137
+ function compareRecentlyUsed(left, right) {
138
+ const recency = libraryAssetRecency(right) - libraryAssetRecency(left);
139
+ if (recency !== 0)
140
+ return recency;
141
+ const created = Date.parse(right.created_at) - Date.parse(left.created_at);
142
+ return created !== 0
143
+ ? created
144
+ : left.artifact_id.localeCompare(right.artifact_id);
145
+ }
146
+ export async function searchLibraryAssets(books, input) {
147
+ await books.ready();
148
+ const query = input.query?.trim().toLowerCase() ?? "";
149
+ const requested = new Set(input.kinds);
150
+ const prepared = input.referenceId
151
+ ? searchReferenceStore.get(input.referenceId).reference
152
+ : undefined;
153
+ const searching = Boolean(query || prepared);
154
+ const browseStatus = searching
155
+ ? "ready"
156
+ : (input.browseStatus ?? "ready");
157
+ const artifactKinds = [
158
+ ...(requested.has("audio") ? ["audio"] : []),
159
+ ...(requested.has("image") ? ["image"] : []),
160
+ ...(requested.has("video") ? ["video", "final"] : []),
161
+ ];
162
+ const summaries = books.list({ sort: "newest" });
163
+ const results = await Promise.allSettled(summaries.map(async (summary) => {
164
+ const book = books.get(summary.bookId);
165
+ if (!book.ok)
166
+ throw new Error(book.error.message);
167
+ const engine = book.value.engine;
168
+ const builtAssets = await engine.withLocalMedia(() => buildAssetList(engine, summary.bookId, "newest"));
169
+ const usage = readAssetUsage(engine);
170
+ const eligible = builtAssets
171
+ .filter((asset) => kindMatches(requested, asset.type)
172
+ && matchesTagFilter(asset.tags ?? undefined, input.tags))
173
+ .map((asset) => ({
174
+ ...asset,
175
+ owning_book_id: summary.bookId,
176
+ owning_book_name: summary.name,
177
+ last_used_at: lastUsedAt(usage.get(asset.artifact_id)),
178
+ }));
179
+ const statusCounts = countBrowseStatuses(eligible);
180
+ const readyAssets = eligible.filter((asset) => asset.status === "ready");
181
+ const assets = searching
182
+ ? readyAssets
183
+ : eligible.filter((asset) => matchesBrowseStatus(asset.status, browseStatus));
184
+ let hits = [];
185
+ let searchError;
186
+ let preparationJob;
187
+ if (searching) {
188
+ registerLocalClipSearchProvider(engine);
189
+ const searchQuery = {
190
+ ...(query ? { text: query } : {}),
191
+ modalities: query
192
+ ? ["visual", "metadata"]
193
+ : ["visual"],
194
+ artifactKinds,
195
+ sourceArtifactIds: readyAssets.map((asset) => asset.artifact_id),
196
+ limit: 100,
197
+ };
198
+ const result = prepared
199
+ ? await engine.temporalSearch.queryPrepared(searchQuery, prepared, input.referenceRange ? { range: input.referenceRange } : {})
200
+ : await engine.temporalSearch.query(searchQuery);
201
+ if (result.ok) {
202
+ hits = result.value.hits;
203
+ }
204
+ else if (result.error.code === "NOT_READY" && result.error.details?.requiresIndexPreparation) {
205
+ preparationJob = await enqueueTemporalSearchPreparation(summary.bookId);
206
+ }
207
+ else {
208
+ searchError = result.error.message;
209
+ }
210
+ }
211
+ return {
212
+ assets,
213
+ statusCounts,
214
+ hits,
215
+ searchError,
216
+ preparationJob,
217
+ unindexed: missingVisualIndexes(engine, readyAssets).length,
218
+ };
219
+ }));
220
+ const errors = [];
221
+ const assets = [];
222
+ let statusCounts = emptyStatusCounts();
223
+ const semanticHits = [];
224
+ results.forEach((result, index) => {
225
+ if (result.status === "fulfilled") {
226
+ assets.push(...result.value.assets);
227
+ statusCounts = addStatusCounts(statusCounts, result.value.statusCounts);
228
+ semanticHits.push(...result.value.hits.map((hit) => ({
229
+ key: `${summaries[index]?.bookId ?? "unknown"}:${hit.artifactId}`,
230
+ hit,
231
+ })));
232
+ if (result.value.searchError) {
233
+ errors.push({
234
+ book_id: summaries[index]?.bookId ?? "unknown",
235
+ message: result.value.searchError,
236
+ });
237
+ }
238
+ return;
239
+ }
240
+ const book = summaries[index];
241
+ errors.push({
242
+ book_id: book?.bookId ?? "unknown",
243
+ message: result.reason instanceof Error
244
+ ? result.reason.message
245
+ : String(result.reason),
246
+ });
247
+ });
248
+ const metadataRank = new Map(assets
249
+ .filter((asset) => !prepared && matchesQuery(asset, query))
250
+ .sort((left, right) => {
251
+ const rank = searchRank(left, query) - searchRank(right, query);
252
+ if (rank !== 0)
253
+ return rank;
254
+ const created = Date.parse(right.created_at) - Date.parse(left.created_at);
255
+ return created !== 0
256
+ ? created
257
+ : left.artifact_id.localeCompare(right.artifact_id);
258
+ })
259
+ .map((asset, index) => [
260
+ `${asset.owning_book_id}:${asset.artifact_id}`,
261
+ index + 1,
262
+ ]));
263
+ semanticHits.sort((left, right) => comparableHitScore(right.hit) - comparableHitScore(left.hit)
264
+ || left.key.localeCompare(right.key));
265
+ const semanticRank = new Map();
266
+ const semanticMatch = new Map();
267
+ for (const item of semanticHits) {
268
+ if (semanticRank.has(item.key))
269
+ continue;
270
+ semanticRank.set(item.key, semanticRank.size + 1);
271
+ semanticMatch.set(item.key, item.hit);
272
+ }
273
+ const ranked = assets
274
+ .flatMap((asset) => {
275
+ const key = `${asset.owning_book_id}:${asset.artifact_id}`;
276
+ const metadata = metadataRank.get(key);
277
+ const semantic = semanticRank.get(key);
278
+ if ((query || prepared) && metadata === undefined && semantic === undefined) {
279
+ return [];
280
+ }
281
+ const hit = semanticMatch.get(key);
282
+ return [{
283
+ asset: hit
284
+ ? {
285
+ ...asset,
286
+ search_match: {
287
+ score: comparableHitScore(hit),
288
+ location: hit.location,
289
+ },
290
+ }
291
+ : asset,
292
+ score: (metadata === undefined ? 0 : 1 / (60 + metadata))
293
+ + (semantic === undefined ? 0 : 1 / (60 + semantic)),
294
+ }];
295
+ })
296
+ .sort((left, right) => {
297
+ const rank = right.score - left.score;
298
+ if (rank !== 0)
299
+ return rank;
300
+ const created = Date.parse(right.asset.created_at) - Date.parse(left.asset.created_at);
301
+ return created !== 0
302
+ ? created
303
+ : left.asset.artifact_id.localeCompare(right.asset.artifact_id);
304
+ })
305
+ .map((item) => item.asset);
306
+ if (!query && !prepared)
307
+ ranked.sort(compareRecentlyUsed);
308
+ const paged = pageLibraryAssets(ranked, {
309
+ offset: input.offset ?? 0,
310
+ limit: input.limit ?? 50,
311
+ });
312
+ return {
313
+ assets: paged.pageAssets,
314
+ tag_facets: tagFacetCounts(ranked),
315
+ unindexed_visual_assets: results.reduce((count, result) => count + (result.status === "fulfilled" ? result.value.unindexed : 0), 0),
316
+ preparationJobs: results.flatMap((result) => result.status === "fulfilled" && result.value.preparationJob
317
+ ? [result.value.preparationJob]
318
+ : []),
319
+ page: paged.page,
320
+ status_counts: statusCounts,
321
+ books_with_errors: errors,
322
+ };
323
+ }
324
+ export async function resolveBookReference(books, reference) {
325
+ await books.ready();
326
+ const byId = books.get(reference);
327
+ if (byId.ok)
328
+ return byId.value;
329
+ const byName = books.getByName(reference);
330
+ if (byName.ok)
331
+ return byName.value;
332
+ throw new Error(`Book not found: ${reference}`);
333
+ }
334
+ function copyableFile(name) {
335
+ return !name.endsWith(".part")
336
+ && !name.endsWith(".tmp")
337
+ && name !== "error.json"
338
+ && !name.endsWith("/error.json");
339
+ }
340
+ async function copyArtifactFiles(source, target, sourceArtifact, targetArtifact) {
341
+ const sourceManifest = await source.files.manifest(sourceArtifact.artifactId, { includeDotfiles: true });
342
+ if (!sourceManifest.ok)
343
+ throw new Error(sourceManifest.error.message);
344
+ const files = sourceManifest.value.files.filter((file) => copyableFile(file.name));
345
+ for (const file of files) {
346
+ const sourcePath = join(sourceManifest.value.path, ...file.name.split("/"));
347
+ const written = await target.files.writeFromPath(targetArtifact.artifactId, file.name, sourcePath);
348
+ if (!written.ok)
349
+ throw new Error(written.error.message);
350
+ }
351
+ const targetManifest = await target.files.manifest(targetArtifact.artifactId, { includeDotfiles: true });
352
+ if (!targetManifest.ok)
353
+ throw new Error(targetManifest.error.message);
354
+ const targetHashes = new Map(targetManifest.value.files.map((file) => [file.name, file.objectHash]));
355
+ for (const file of files) {
356
+ if (targetHashes.get(file.name) !== file.objectHash) {
357
+ throw new Error(`Copied file hash mismatch: ${file.name}`);
358
+ }
359
+ }
360
+ }
361
+ async function copyArtifactMetadata(source, target, sourceArtifact, targetArtifact, sourceBookId) {
362
+ const original = await source.metadata.artifacts.read(sourceArtifact.artifactId, "original");
363
+ const metadata = {
364
+ ...(original.ok ? original.value : {}),
365
+ origin: "library-copy",
366
+ source_book_id: sourceBookId,
367
+ source_artifact_id: sourceArtifact.artifactId,
368
+ };
369
+ const written = await target.metadata.artifacts.write(targetArtifact.artifactId, "original", metadata);
370
+ if (!written.ok)
371
+ throw new Error(written.error.message);
372
+ const waveform = await source.metadata.waveforms.read(sourceArtifact.artifactId);
373
+ if (waveform.ok) {
374
+ const waveformWrite = await target.metadata.waveforms.write(targetArtifact.artifactId, waveform.value.peaks);
375
+ if (!waveformWrite.ok)
376
+ throw new Error(waveformWrite.error.message);
377
+ }
378
+ const streams = source.streams.list(sourceArtifact.artifactId);
379
+ if (!streams.ok)
380
+ throw new Error(streams.error.message);
381
+ const manifest = await target.files.manifest(targetArtifact.artifactId);
382
+ if (!manifest.ok)
383
+ throw new Error(manifest.error.message);
384
+ const copiedHashes = new Map(manifest.value.files.map((file) => [file.name, file.objectHash]));
385
+ for (const stream of streams.value) {
386
+ if (copiedHashes.get(stream.sourcePath) !== stream.objectHash)
387
+ continue;
388
+ const registered = await target.streams.register({
389
+ artifactId: targetArtifact.artifactId,
390
+ sourcePath: stream.sourcePath,
391
+ objectHash: stream.objectHash,
392
+ streamIndex: stream.streamIndex,
393
+ kind: stream.kind,
394
+ timeBase: stream.timeBase,
395
+ durationTicks: stream.durationTicks,
396
+ codec: stream.codec,
397
+ ...(stream.video ? { video: stream.video } : {}),
398
+ ...(stream.audio ? { audio: stream.audio } : {}),
399
+ });
400
+ if (!registered.ok)
401
+ throw new Error(registered.error.message);
402
+ }
403
+ }
404
+ async function deleteCopiedArtifact(engine, artifactId) {
405
+ const deleted = await engine.artifacts.delete(artifactId, { deleteOwnedMedia: true });
406
+ return deleted.ok ? null : deleted.error.message;
407
+ }
408
+ export async function copyLibraryAsset(books, input) {
409
+ if (input.sourceBookId === input.targetBookId) {
410
+ throw new Error("Source and target books must be different");
411
+ }
412
+ const sourceBook = await resolveBookReference(books, input.sourceBookId);
413
+ const targetBook = await resolveBookReference(books, input.targetBookId);
414
+ if (sourceBook.bookId === targetBook.bookId) {
415
+ throw new Error("Source and target books must be different");
416
+ }
417
+ const sourceArtifactResult = sourceBook.engine.artifacts.get(input.sourceArtifactId);
418
+ if (!sourceArtifactResult.ok) {
419
+ throw new Error(sourceArtifactResult.error.message);
420
+ }
421
+ const sourceArtifact = sourceArtifactResult.value;
422
+ if (sourceArtifact.kind !== "audio"
423
+ && sourceArtifact.kind !== "image"
424
+ && sourceArtifact.kind !== "video"
425
+ && sourceArtifact.kind !== "final") {
426
+ throw new Error(`Artifact kind is not reusable media: ${sourceArtifact.kind}`);
427
+ }
428
+ const targetKind = sourceArtifact.kind === "final"
429
+ ? "video"
430
+ : sourceArtifact.kind;
431
+ const label = input.newName ?? sourceArtifact.label;
432
+ const created = await targetBook.engine.artifacts.create({
433
+ kind: targetKind,
434
+ ...(label ? { label } : {}),
435
+ });
436
+ if (!created.ok)
437
+ throw new Error(created.error.message);
438
+ const targetArtifact = created.value;
439
+ let lease = null;
440
+ try {
441
+ await copyArtifactFiles(sourceBook.engine, targetBook.engine, sourceArtifact, targetArtifact);
442
+ await copyArtifactMetadata(sourceBook.engine, targetBook.engine, sourceArtifact, targetArtifact, sourceBook.bookId);
443
+ // After the bytes, so the destination engine can validate the source's
444
+ // automatic evidence against the content the copy actually carries.
445
+ await transferAssetTags({ engine: sourceBook.engine, artifactId: sourceArtifact.artifactId }, { engine: targetBook.engine, artifactId: targetArtifact.artifactId });
446
+ lease = targetBook.engine.jobs.artifactWork.begin(targetArtifact.artifactId, {
447
+ kind: "upload",
448
+ ownerKind: "job",
449
+ durationMs: 15 * 60_000,
450
+ });
451
+ if (!lease) {
452
+ throw new Error("Could not claim the copied Library artifact");
453
+ }
454
+ if (!targetBook.engine.jobs.artifactWork.complete(targetArtifact.artifactId, lease.ownerId)) {
455
+ throw new Error("Could not complete the copied Library artifact");
456
+ }
457
+ await enqueueSearchIndex(targetBook.engine, targetArtifact.artifactId);
458
+ return {
459
+ sourceBookId: sourceBook.bookId,
460
+ sourceArtifactId: sourceArtifact.artifactId,
461
+ targetBookId: targetBook.bookId,
462
+ targetArtifactId: targetArtifact.artifactId,
463
+ };
464
+ }
465
+ catch (cause) {
466
+ if (lease) {
467
+ await targetBook.engine.jobs.artifactWork.fail(targetArtifact.artifactId, lease.ownerId, {
468
+ message: cause instanceof Error ? cause.message : String(cause),
469
+ });
470
+ }
471
+ const cleanupError = await deleteCopiedArtifact(targetBook.engine, targetArtifact.artifactId);
472
+ const message = cause instanceof Error ? cause.message : String(cause);
473
+ throw new Error(cleanupError
474
+ ? `${message}; orphan ${targetArtifact.artifactId}: ${cleanupError}`
475
+ : message);
476
+ }
477
+ }
478
+ function assertAttachableCell(document, input, artifact) {
479
+ const cell = assertMediaCell(document, input.cellId);
480
+ const currentOutput = cell.outputArtifactId ?? null;
481
+ if (currentOutput !== input.expectedOutputArtifactId) {
482
+ throw new Error("The media slot changed while the Library asset was copying");
483
+ }
484
+ if (document.edges.some((edge) => edge.target === input.cellId)) {
485
+ throw new Error("The media slot was connected while the Library asset was copying");
486
+ }
487
+ if (!compatibleArtifact(cell, artifact)) {
488
+ throw new Error(`A ${cell.type} cell cannot use a ${artifact.kind} artifact`);
489
+ }
490
+ }
491
+ export async function copyAndAttachLibraryAsset(books, input) {
492
+ const sourceBook = await resolveBookReference(books, input.sourceBookId);
493
+ const targetBook = await resolveBookReference(books, input.targetBookId);
494
+ const sourceArtifact = sourceBook.engine.artifacts.get(input.sourceArtifactId);
495
+ if (!sourceArtifact.ok)
496
+ throw new Error(sourceArtifact.error.message);
497
+ const before = targetBook.engine.notebooks.read(input.notebookId);
498
+ if (!before.ok)
499
+ throw new Error(before.error.message);
500
+ assertAttachableCell(before.value, input, sourceArtifact.value);
501
+ const copied = await copyLibraryAsset(books, input);
502
+ const targetArtifact = targetBook.engine.artifacts.get(copied.targetArtifactId);
503
+ if (!targetArtifact.ok) {
504
+ await deleteCopiedArtifact(targetBook.engine, copied.targetArtifactId);
505
+ throw new Error(targetArtifact.error.message);
506
+ }
507
+ try {
508
+ const result = await mutateNotebook(targetBook.engine, input.notebookId, "attach_library_asset", (notebook) => {
509
+ assertAttachableCell(notebook, input, targetArtifact.value);
510
+ return bindMediaSlot(notebook, input.cellId, targetArtifact.value);
511
+ }, {
512
+ sourceBookId: copied.sourceBookId,
513
+ sourceArtifactId: copied.sourceArtifactId,
514
+ targetArtifactId: copied.targetArtifactId,
515
+ cellId: input.cellId,
516
+ });
517
+ if (!result.ok)
518
+ throw new Error(result.error.message);
519
+ recordAssetUse(sourceBook.engine, copied.sourceArtifactId);
520
+ recordAssetUse(targetBook.engine, copied.targetArtifactId);
521
+ return {
522
+ ...copied,
523
+ notebookId: input.notebookId,
524
+ cellId: input.cellId,
525
+ revision: result.value.hash,
526
+ };
527
+ }
528
+ catch (cause) {
529
+ const cleanupError = await deleteCopiedArtifact(targetBook.engine, copied.targetArtifactId);
530
+ const message = cause instanceof Error ? cause.message : String(cause);
531
+ throw new Error(cleanupError
532
+ ? `${message}; orphan ${copied.targetArtifactId}: ${cleanupError}`
533
+ : message);
534
+ }
535
+ }
@@ -0,0 +1,33 @@
1
+ import { extname } from "node:path";
2
+ import { normalizeFilePath } from "videobook-engine";
3
+ export async function withMediaOutputLock(engine, assetDir, signal, fn) {
4
+ signal.throwIfAborted();
5
+ const lock = await engine.jobs.locks.acquire(assetDir, { durationMs: 120_000 });
6
+ if (!lock.ok)
7
+ throw new Error(`Asset is locked: ${lock.error.message}`);
8
+ try {
9
+ signal.throwIfAborted();
10
+ return await fn();
11
+ }
12
+ finally {
13
+ engine.jobs.locks.release(assetDir, lock.value.ownerId);
14
+ }
15
+ }
16
+ export async function validateMediaOutputPaths(engine, assetId, filenames, extension) {
17
+ const paths = filenames.map(normalizeFilePath);
18
+ const manifest = await engine.files.manifest(assetId, { includeDotfiles: true });
19
+ if (!manifest.ok)
20
+ throw new Error(`Destination asset ${assetId} not found: ${manifest.error.message}`);
21
+ const occupied = new Set(manifest.value.files.map((file) => file.name.normalize("NFC").toLowerCase()));
22
+ for (const filename of paths) {
23
+ const key = filename.normalize("NFC").toLowerCase();
24
+ if ([...occupied].some((existing) => existing === key || existing.startsWith(`${key}/`) || key.startsWith(`${existing}/`))) {
25
+ throw new Error(`Destination already contains ${filename} or a conflicting path. Refusing silent overwrite.`);
26
+ }
27
+ if (extname(filename).toLowerCase() !== extension) {
28
+ throw new Error(`Destination filename must end in ${extension}: ${filename}`);
29
+ }
30
+ occupied.add(key);
31
+ }
32
+ return paths;
33
+ }