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,43 @@
1
+ import { z } from "zod";
2
+ import { rhythmSettingsSchema } from "../rhythm/model.js";
3
+ import { readSequenceRhythm, updateSequenceRhythm } from "../rhythm/sequence-rhythm.js";
4
+ function json(value) {
5
+ return { content: [{ type: "text", text: JSON.stringify(value) }] };
6
+ }
7
+ export function sequenceRhythmDefs(engine) {
8
+ const schema = { book_id: z.string().uuid(), sequence_id: z.string().uuid() };
9
+ return [
10
+ {
11
+ name: "get_sequence_rhythm", description: "Read the selected master soundtrack and editable beat map with its revision and undo state",
12
+ scope: "book", schema,
13
+ handler: async (args) => {
14
+ try {
15
+ return json(await readSequenceRhythm(engine, String(args.sequence_id)));
16
+ }
17
+ catch (error) {
18
+ return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
19
+ }
20
+ },
21
+ },
22
+ {
23
+ name: "update_sequence_rhythm", description: "Save or undo a soundtrack choice or corrected source-timed beat map without changing the audio",
24
+ scope: "book", schema: {
25
+ ...schema, expected_revision: z.string().min(1),
26
+ operation: z.enum(["set", "undo", "redo"]), settings: rhythmSettingsSchema.optional(),
27
+ },
28
+ handler: async (args) => {
29
+ try {
30
+ const update = args.operation === "undo" || args.operation === "redo" ? args.operation : rhythmSettingsSchema.parse(args.settings);
31
+ const state = await updateSequenceRhythm(engine, String(args.sequence_id), String(args.expected_revision), update);
32
+ const sequence = engine.sequences.get(String(args.sequence_id));
33
+ if (!sequence.ok)
34
+ throw new Error(sequence.error.message);
35
+ return json({ state, sequence: sequence.value });
36
+ }
37
+ catch (error) {
38
+ return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
39
+ }
40
+ },
41
+ },
42
+ ];
43
+ }
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import { requireArtifactKind } from "./_shared.js";
3
+ /**
4
+ * Normalize the metadata arg into a plain object.
5
+ * - Objects pass through (agents often send structured JSON, not a string).
6
+ * - Valid JSON object strings parse as expected.
7
+ * - Free-form notes (no leading `{`/`[`) are stored as `{ notes: "..." }`.
8
+ */
9
+ function parseCharacterMetadata(input) {
10
+ if (input !== null && typeof input === "object" && !Array.isArray(input)) {
11
+ return { ok: true, value: input };
12
+ }
13
+ if (typeof input !== "string") {
14
+ return { ok: false, error: "Metadata must be a JSON object or string" };
15
+ }
16
+ const trimmed = input.trim();
17
+ if (!trimmed) {
18
+ return { ok: true, value: {} };
19
+ }
20
+ // Plain notes / prose — not JSON. Keep them without forcing users to quote keys.
21
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
22
+ return { ok: true, value: { notes: trimmed } };
23
+ }
24
+ try {
25
+ const parsed = JSON.parse(trimmed);
26
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
27
+ return { ok: false, error: "Metadata must be a JSON object" };
28
+ }
29
+ return { ok: true, value: parsed };
30
+ }
31
+ catch (err) {
32
+ const message = err instanceof Error ? err.message : String(err);
33
+ return {
34
+ ok: false,
35
+ error: `Invalid JSON metadata: ${message}. ` +
36
+ `Use double-quoted keys (e.g. {"display_name":"Ada"}) or free-form notes without braces.`,
37
+ };
38
+ }
39
+ }
40
+ export function setCharacterMetadataDef(engine) {
41
+ return {
42
+ name: "set_character_metadata",
43
+ description: "Write free-form native metadata for a character. " +
44
+ "Accepts a JSON object, a JSON-stringified object, or plain-text notes " +
45
+ "(stored as { notes: \"...\" }).",
46
+ schema: {
47
+ book_id: z.string().describe("Book id"),
48
+ char_asset_id: z.string().describe("Character artifact id"),
49
+ metadata: z
50
+ .union([z.string(), z.record(z.string(), z.unknown())])
51
+ .describe("JSON object (or stringified object) of character metadata, or free-form notes"),
52
+ },
53
+ handler: async (args) => {
54
+ const { book_id: _book_id, char_asset_id, metadata } = args;
55
+ const parsed = parseCharacterMetadata(metadata);
56
+ if (!parsed.ok) {
57
+ return {
58
+ isError: true,
59
+ content: [{ type: "text", text: parsed.error }],
60
+ };
61
+ }
62
+ const id = char_asset_id.replace(/^@/, "").trim();
63
+ const kindGate = await requireArtifactKind(engine, id, ["character"], "set_character_metadata");
64
+ if (kindGate)
65
+ return kindGate;
66
+ const writeResult = await engine.metadata.artifacts.write(id, "character", parsed.value);
67
+ if (!writeResult.ok) {
68
+ return {
69
+ isError: true,
70
+ content: [{ type: "text", text: `Failed to write character metadata: ${writeResult.error.message}` }],
71
+ };
72
+ }
73
+ return {
74
+ content: [{
75
+ type: "text",
76
+ text: JSON.stringify({
77
+ asset_id: id,
78
+ message: "Metadata successfully written",
79
+ }),
80
+ }],
81
+ };
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import { MANAGED_ENV_KEYS, getDefaultSecretStore, isSecretEnvKey, maskEnvValue, } from "../env-secrets.js";
3
+ /**
4
+ * Writes a managed key into the repo-root .env file. Runs inline (no job
5
+ * queue): it is sub-millisecond local file I/O, like rename_project.
6
+ *
7
+ * This tool intentionally has no book_id argument, so withLogging never
8
+ * persists its args into git-versioned project activity logs — the secret
9
+ * value must not leak there, and success/error results never echo it either.
10
+ */
11
+ export function setEnvSecretDef(store = getDefaultSecretStore()) {
12
+ return {
13
+ name: "set_env_secret",
14
+ description: "Write a secret into the server's .env file and live environment. Rejects keys managed by 1Password (edit those in the vault). Never returns the secret value.",
15
+ schema: {
16
+ key: z.enum(MANAGED_ENV_KEYS).describe("Environment variable name to set"),
17
+ value: z.string().min(1).describe("New value; stored in .env, never echoed back"),
18
+ },
19
+ handler: async (args) => {
20
+ const { key, value } = args;
21
+ try {
22
+ await store.setEnvSecret(key, value);
23
+ }
24
+ catch (err) {
25
+ const message = err instanceof Error ? err.message : String(err);
26
+ return {
27
+ isError: true,
28
+ content: [{ type: "text", text: message }],
29
+ };
30
+ }
31
+ const stored = store.getValue(key) ?? value;
32
+ const maskedKey = isSecretEnvKey(key) ? maskEnvValue(stored) : stored;
33
+ return {
34
+ content: [
35
+ { type: "text", text: JSON.stringify({ key, source: "env", maskedKey }) },
36
+ ],
37
+ };
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { writeOsgSettings } from "../osg-settings.js";
3
+ import { resolveOsgImagePath } from "../osg-assets.js";
4
+ import { requireArtifactKind, toolError } from "./_shared.js";
5
+ const OSG_POSITIONS = [
6
+ "top-left", "top-center", "top-right",
7
+ "middle-left", "middle-center", "middle-right",
8
+ "bottom-left", "bottom-center", "bottom-right",
9
+ ];
10
+ const positionEnum = z.enum(OSG_POSITIONS);
11
+ /**
12
+ * Persist OSG (on-screen graphic) overlay settings for a video asset WITHOUT
13
+ * triggering a render. The render path later reads these fields from the
14
+ * asset's `.render` metadata. We merge into every orientation's metadata so
15
+ * the badge + render stay consistent regardless of which orientation is
16
+ * rendered; `osg_positions` lets frame-aware callers store a different anchor
17
+ * per orientation.
18
+ */
19
+ export function setVideoOverlayDef(engine) {
20
+ return {
21
+ name: "set_video_overlay",
22
+ description: "Set or clear the on-screen graphic overlay on a video asset. Persists to the asset's render metadata for all orientations without rendering. Pass osg_asset_id=null to clear.",
23
+ schema: {
24
+ asset_id: z.string().describe("Video asset ID (e.g. vid-my-clip)"),
25
+ book_id: z.string().describe("Book id"),
26
+ osg_asset_id: z
27
+ .string()
28
+ .nullable()
29
+ .describe("Image asset ID (img- prefix) for the overlay, or null to clear the OSG"),
30
+ osg_position: positionEnum.describe("Anchor position (default top-right)"),
31
+ osg_positions: z
32
+ .object({
33
+ landscape: positionEnum.optional(),
34
+ portrait: positionEnum.optional(),
35
+ square: positionEnum.optional(),
36
+ })
37
+ .optional()
38
+ .describe("Per-orientation anchor overrides; unlisted orientations use osg_position"),
39
+ osg_size: z.enum(["small", "medium", "large"]).describe("Size preset"),
40
+ osg_scale: z
41
+ .number()
42
+ .min(0.1)
43
+ .max(2)
44
+ .optional()
45
+ .describe("Size multiplier applied on top of osg_size (default 1)"),
46
+ osg_opacity: z
47
+ .number()
48
+ .min(0.05)
49
+ .max(1)
50
+ .optional()
51
+ .describe("Overlay opacity 0.05-1; the floating watermark uses 0.5 (default 1)"),
52
+ osg_start_seconds: z
53
+ .number()
54
+ .min(0)
55
+ .nullable()
56
+ .optional()
57
+ .describe("Absolute window start in seconds; null ⇒ from clip start. Omit to leave unchanged"),
58
+ osg_end_seconds: z
59
+ .number()
60
+ .min(0)
61
+ .nullable()
62
+ .optional()
63
+ .describe("Absolute window end in seconds; null ⇒ to clip end. Omit to leave unchanged"),
64
+ },
65
+ handler: async (args) => {
66
+ const { asset_id, book_id: _book_id, osg_asset_id, osg_position, osg_positions, osg_size, osg_scale, osg_opacity, osg_start_seconds, osg_end_seconds, } = args;
67
+ const clearing = osg_asset_id == null;
68
+ const targetGate = await requireArtifactKind(engine, asset_id, ["video"], "set_video_overlay");
69
+ if (targetGate)
70
+ return targetGate;
71
+ if (!clearing) {
72
+ const imageGate = await requireArtifactKind(engine, osg_asset_id, ["image"], "osg_asset_id");
73
+ if (imageGate)
74
+ return imageGate;
75
+ const image = await resolveOsgImagePath(engine, osg_asset_id);
76
+ if (!image.ok)
77
+ return toolError(image.error);
78
+ }
79
+ const written = await writeOsgSettings(engine, asset_id, (orientation, base) => {
80
+ const merged = {
81
+ // osgAssetId is canonical; null out the legacy osgImagePath so a stale
82
+ // path can't override a cleared/changed asset id at render time.
83
+ osgImagePath: null,
84
+ osgAssetId: clearing ? null : osg_asset_id,
85
+ osgPosition: osg_positions?.[orientation] ?? osg_position,
86
+ osgSize: osg_size,
87
+ osgScale: osg_scale ?? (typeof base.osgScale === "number" ? base.osgScale : 1),
88
+ osgOpacity: clearing
89
+ ? 1
90
+ : osg_opacity ?? (typeof base.osgOpacity === "number" ? base.osgOpacity : 1),
91
+ };
92
+ if (osg_start_seconds !== undefined) {
93
+ merged.osgStartSeconds = clearing ? null : osg_start_seconds;
94
+ }
95
+ else if (clearing) {
96
+ merged.osgStartSeconds = null;
97
+ }
98
+ if (osg_end_seconds !== undefined) {
99
+ merged.osgEndSeconds = clearing ? null : osg_end_seconds;
100
+ }
101
+ else if (clearing) {
102
+ merged.osgEndSeconds = null;
103
+ }
104
+ // An absolute window supersedes the legacy duration-relative tail.
105
+ const windowProvided = osg_start_seconds !== undefined || osg_end_seconds !== undefined;
106
+ if (clearing || windowProvided)
107
+ merged.osgTailSeconds = null;
108
+ const resolved = { ...base, ...merged };
109
+ const start = typeof resolved.osgStartSeconds === "number" ? resolved.osgStartSeconds : 0;
110
+ if (!clearing && typeof resolved.osgEndSeconds === "number" && resolved.osgEndSeconds <= start) {
111
+ throw new Error(`osg_end_seconds must be greater than osg_start_seconds for ${orientation}`);
112
+ }
113
+ return merged;
114
+ });
115
+ if (!written.ok) {
116
+ return {
117
+ isError: true,
118
+ content: [{ type: "text", text: written.error }],
119
+ };
120
+ }
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text",
125
+ text: JSON.stringify({
126
+ asset_id,
127
+ osg: clearing
128
+ ? null
129
+ : {
130
+ assetId: osg_asset_id,
131
+ position: osg_position,
132
+ positions: osg_positions ?? null,
133
+ size: osg_size,
134
+ scale: written.settings.landscape.osgScale,
135
+ opacity: written.settings.landscape.osgOpacity,
136
+ },
137
+ }),
138
+ },
139
+ ],
140
+ };
141
+ },
142
+ };
143
+ }
@@ -0,0 +1,37 @@
1
+ import { execFile } from "node:child_process";
2
+ import { z } from "zod";
3
+ export function showFolderDef(engine) {
4
+ return {
5
+ name: "show_folder",
6
+ description: "Reveal an asset's folder in Finder",
7
+ schema: {
8
+ asset_id: z.string().describe("Asset ID (e.g. img-cute-cat)"),
9
+ book_id: z.string().describe("Book id"),
10
+ },
11
+ handler: async (args) => {
12
+ const { asset_id, book_id: _book_id } = args;
13
+ const result = await engine.workspaces.resolveArtifact(asset_id);
14
+ if (!result.ok) {
15
+ return {
16
+ isError: true,
17
+ content: [{ type: "text", text: result.error.message }],
18
+ };
19
+ }
20
+ return new Promise((resolve) => {
21
+ execFile("open", ["-R", result.value], (error) => {
22
+ if (error) {
23
+ resolve({
24
+ isError: true,
25
+ content: [{ type: "text", text: `Failed to open folder: ${error.message}` }],
26
+ });
27
+ }
28
+ else {
29
+ resolve({
30
+ content: [{ type: "text", text: JSON.stringify({ status: "opened", path: result.value }) }],
31
+ });
32
+ }
33
+ });
34
+ });
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,122 @@
1
+ import { join } from "node:path";
2
+ import { z } from "zod";
3
+ import { findVideoFile } from "videobook-engine";
4
+ import { probeVideo } from "../ffmpeg.js";
5
+ import { queue } from "../queue.js";
6
+ import { derivedLabel } from "../artifact-label.js";
7
+ import { fixedTransitionSeconds } from "../scene-transition.js";
8
+ function error(text) {
9
+ return {
10
+ isError: true,
11
+ content: [{ type: "text", text }],
12
+ };
13
+ }
14
+ function sourceName(artifact) {
15
+ return artifact.label?.trim() || artifact.artifactId;
16
+ }
17
+ function concatName(first, second) {
18
+ return derivedLabel("scene concat", `${sourceName(first)} + ${sourceName(second)}`);
19
+ }
20
+ function validVideoProbe(probe) {
21
+ return probe.width > 0 && probe.height > 0 && probe.fps > 0 && probe.videoDuration > 0;
22
+ }
23
+ export function smoothSceneConcatDef(engine) {
24
+ return {
25
+ name: "smooth_scene_concat",
26
+ description: "Join two video scenes into a new video, aligning overlapping boundary audio to avoid repeated speech and dissolving across it, otherwise crossfading briefly.",
27
+ schema: {
28
+ first_asset_id: z.string().describe("First video artifact id in playback order"),
29
+ second_asset_id: z.string().describe("Second video artifact id in playback order"),
30
+ transition_seconds: z.number().min(0.05).max(5).optional().describe("Optional fixed crossfade duration in seconds. Omit to auto-align shared boundary audio, falling back to 0.35 seconds."),
31
+ book_id: z.string().describe("Book UUID"),
32
+ },
33
+ handler: async (args) => {
34
+ const { first_asset_id, second_asset_id, transition_seconds, book_id, } = args;
35
+ const firstArtifact = engine.artifacts.get(first_asset_id);
36
+ if (!firstArtifact.ok)
37
+ return error(`First asset: ${firstArtifact.error.message}`);
38
+ const secondArtifact = engine.artifacts.get(second_asset_id);
39
+ if (!secondArtifact.ok)
40
+ return error(`Second asset: ${secondArtifact.error.message}`);
41
+ if (firstArtifact.value.kind !== "video" || secondArtifact.value.kind !== "video") {
42
+ return error("smooth_scene_concat requires two video artifacts");
43
+ }
44
+ if (first_asset_id === second_asset_id) {
45
+ return error("first_asset_id and second_asset_id must differ");
46
+ }
47
+ const [firstManifestResult, secondManifestResult] = await Promise.all([
48
+ engine.files.manifest(first_asset_id),
49
+ engine.files.manifest(second_asset_id),
50
+ ]);
51
+ if (!firstManifestResult.ok) {
52
+ return error(`First asset: ${firstManifestResult.error.message}`);
53
+ }
54
+ if (!secondManifestResult.ok) {
55
+ return error(`Second asset: ${secondManifestResult.error.message}`);
56
+ }
57
+ const firstVideo = findVideoFile(firstManifestResult.value.files);
58
+ const secondVideo = findVideoFile(secondManifestResult.value.files);
59
+ if (!firstVideo)
60
+ return error(`No video file in asset ${first_asset_id}`);
61
+ if (!secondVideo)
62
+ return error(`No video file in asset ${second_asset_id}`);
63
+ const firstPath = join(firstManifestResult.value.path, firstVideo.name);
64
+ const secondPath = join(secondManifestResult.value.path, secondVideo.name);
65
+ let firstProbe;
66
+ let secondProbe;
67
+ try {
68
+ [firstProbe, secondProbe] = await Promise.all([
69
+ probeVideo(firstPath),
70
+ probeVideo(secondPath),
71
+ ]);
72
+ }
73
+ catch (cause) {
74
+ const message = cause instanceof Error ? cause.message : String(cause);
75
+ return error(`Failed to probe input videos: ${message}`);
76
+ }
77
+ if (!validVideoProbe(firstProbe))
78
+ return error(`Invalid video in asset ${first_asset_id}`);
79
+ if (!validVideoProbe(secondProbe))
80
+ return error(`Invalid video in asset ${second_asset_id}`);
81
+ try {
82
+ fixedTransitionSeconds(firstProbe.videoDuration, secondProbe.videoDuration, transition_seconds);
83
+ }
84
+ catch (cause) {
85
+ return error(cause instanceof Error ? cause.message : String(cause));
86
+ }
87
+ const assetResult = await engine.artifacts.create("video", concatName(firstArtifact.value, secondArtifact.value));
88
+ if (!assetResult.ok) {
89
+ return error(`Failed to create scene concat asset: ${assetResult.error.message}`);
90
+ }
91
+ const { artifactId: assetId, path: assetDir } = assetResult.value;
92
+ const outputPath = join(assetDir, "original.mp4");
93
+ const jobId = `scene-concat-${assetId}-${Date.now()}`;
94
+ await queue.enqueue(jobId, {
95
+ type: "smooth_scene_concat",
96
+ firstPath,
97
+ secondPath,
98
+ outputPath,
99
+ firstAssetId: first_asset_id,
100
+ secondAssetId: second_asset_id,
101
+ assetId,
102
+ assetDir,
103
+ bookId: book_id,
104
+ transitionSeconds: transition_seconds,
105
+ });
106
+ return {
107
+ content: [{
108
+ type: "text",
109
+ text: JSON.stringify({
110
+ asset_id: assetId,
111
+ first_asset_id,
112
+ second_asset_id,
113
+ transition_seconds: transition_seconds ?? null,
114
+ alignment_mode: transition_seconds === undefined ? "auto" : "fixed",
115
+ job_id: jobId,
116
+ status: "enqueued",
117
+ }),
118
+ }],
119
+ };
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,149 @@
1
+ import { z } from "zod";
2
+ import { join } from "node:path";
3
+ import { queue } from "../queue.js";
4
+ import { probeVideo } from "../ffmpeg.js";
5
+ import { findVideoFile } from "videobook-engine";
6
+ function deriveSpliceName(assetIds) {
7
+ if (assetIds.length <= 2)
8
+ return `splice-${assetIds[0]}-${assetIds[1]}`;
9
+ return `splice-${assetIds[0]}-x${assetIds.length - 1}`;
10
+ }
11
+ function error(text) {
12
+ return {
13
+ isError: true,
14
+ content: [{ type: "text", text }],
15
+ };
16
+ }
17
+ function uniqueAssetIds(ids) {
18
+ const seen = new Set();
19
+ const unique = [];
20
+ for (const id of ids) {
21
+ const trimmed = id.trim();
22
+ if (!trimmed)
23
+ return { error: "splice asset ids must be non-empty" };
24
+ if (seen.has(trimmed)) {
25
+ return { error: "splice asset ids must be unique" };
26
+ }
27
+ seen.add(trimmed);
28
+ unique.push(trimmed);
29
+ }
30
+ return unique;
31
+ }
32
+ async function resolveSpliceClip(engine, assetId) {
33
+ const artifact = engine.artifacts.get(assetId);
34
+ if (!artifact.ok)
35
+ return { ok: false, error: artifact.error.message };
36
+ if (artifact.value.kind !== "video") {
37
+ return { ok: false, error: `Asset ${assetId} is not a video` };
38
+ }
39
+ const manifest = await engine.files.manifest(assetId);
40
+ if (!manifest.ok) {
41
+ return { ok: false, error: `${assetId}: ${manifest.error.message}` };
42
+ }
43
+ const video = findVideoFile(manifest.value.files);
44
+ if (!video)
45
+ return { ok: false, error: `No video file in asset ${assetId}` };
46
+ const path = join(manifest.value.path, video.name);
47
+ try {
48
+ const probe = await probeVideo(path);
49
+ return {
50
+ ok: true,
51
+ clip: { assetId, filename: video.name, path, probe },
52
+ };
53
+ }
54
+ catch (cause) {
55
+ const message = cause instanceof Error ? cause.message : String(cause);
56
+ return { ok: false, error: `Failed to probe ${assetId}: ${message}` };
57
+ }
58
+ }
59
+ export function spliceVideoDef(engine) {
60
+ return {
61
+ name: "splice_video",
62
+ description: "Join two or more videos in playback order into a new video. overlap 0 concatenates whole clips; a positive overlap cuts each boundary at the best-matching frame within that many seconds.",
63
+ schema: {
64
+ first_asset_id: z.string().describe("First video artifact id in playback order. The match point is searched within this video."),
65
+ second_asset_id: z.string().describe("Second video artifact id in playback order. Its first frame is the search target against the preceding clip."),
66
+ additional_asset_ids: z.array(z.string()).max(18).optional()
67
+ .describe("Further video artifact ids in playback order after the second clip."),
68
+ overlap: z.number().min(0).default(0)
69
+ .describe("Possible overlap in seconds at each consecutive boundary. Use 0 to concatenate complete clips without trimming."),
70
+ book_id: z.string().describe("Book UUID"),
71
+ },
72
+ handler: async (args) => {
73
+ const { first_asset_id, second_asset_id, additional_asset_ids, overlap, book_id, } = args;
74
+ const additional = Array.isArray(additional_asset_ids)
75
+ ? additional_asset_ids
76
+ : [];
77
+ const ids = uniqueAssetIds([
78
+ first_asset_id,
79
+ second_asset_id,
80
+ ...additional,
81
+ ]);
82
+ if (!Array.isArray(ids))
83
+ return error(ids.error);
84
+ const resolved = await Promise.all(ids.map((assetId) => resolveSpliceClip(engine, assetId)));
85
+ const failed = resolved.find((result) => !result.ok);
86
+ if (failed && !failed.ok)
87
+ return error(failed.error);
88
+ const clips = resolved.flatMap((result) => result.ok ? [result.clip] : []);
89
+ const first = clips[0];
90
+ if (!first)
91
+ return error("splice_video requires at least two videos");
92
+ const resolvedOverlap = typeof overlap === "number" ? overlap : 0;
93
+ const spliceName = deriveSpliceName(ids);
94
+ const assetResult = await engine.artifacts.create("video", spliceName);
95
+ if (!assetResult.ok) {
96
+ return error(`Failed to create splice asset: ${assetResult.error.message}`);
97
+ }
98
+ const { artifactId: assetId, path: assetDir } = assetResult.value;
99
+ const outputPath = join(assetDir, "original.mp4");
100
+ const jobId = `splice-${assetId}-${Date.now()}`;
101
+ const second = clips[1];
102
+ await queue.enqueue(jobId, {
103
+ type: "splice_video",
104
+ firstPath: first.path,
105
+ secondPath: second.path,
106
+ firstFilename: first.filename,
107
+ secondFilename: second.filename,
108
+ outputPath,
109
+ firstAssetId: first.assetId,
110
+ secondAssetId: second.assetId,
111
+ assetId,
112
+ assetDir,
113
+ bookId: book_id,
114
+ targetWidth: first.probe.width,
115
+ targetHeight: first.probe.height,
116
+ firstFps: first.probe.fps,
117
+ firstDuration: first.probe.videoDuration,
118
+ secondDuration: second.probe.videoDuration,
119
+ firstHasAudio: first.probe.hasAudio,
120
+ secondHasAudio: second.probe.hasAudio,
121
+ overlap: resolvedOverlap,
122
+ clips: clips.map((clip) => ({
123
+ assetId: clip.assetId,
124
+ filename: clip.filename,
125
+ duration: clip.probe.videoDuration,
126
+ hasAudio: clip.probe.hasAudio,
127
+ })),
128
+ });
129
+ return {
130
+ content: [{
131
+ type: "text",
132
+ text: JSON.stringify({
133
+ asset_id: assetId,
134
+ first: first.assetId,
135
+ second: second.assetId,
136
+ first_asset_id: first.assetId,
137
+ second_asset_id: second.assetId,
138
+ ...(clips.length > 2
139
+ ? { additional_asset_ids: clips.slice(2).map((clip) => clip.assetId) }
140
+ : {}),
141
+ overlap: resolvedOverlap,
142
+ job_id: jobId,
143
+ status: "enqueued",
144
+ }),
145
+ }],
146
+ };
147
+ },
148
+ };
149
+ }