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,202 @@
1
+ import { findVideoFile } from "videobook-engine";
2
+ import { z } from "zod";
3
+ import { join } from "node:path";
4
+ import { queue } from "../queue.js";
5
+ import { probeVideo } from "../ffmpeg.js";
6
+ import { resolveVideoAssetReference } from "./video-source-reference.js";
7
+ import { parseVideoTimestamp, videoTimestampSchema } from "./video-time-input.js";
8
+ // Each chunk must be at least this long, otherwise downstream process_video
9
+ // can't extract frames / probe meaningfully and the asset gets stuck.
10
+ const MIN_CHUNK_SEC = 0.5;
11
+ function validateSplitParams(chunkAEnd, chunkBStart) {
12
+ if (!Number.isFinite(chunkAEnd) || !Number.isFinite(chunkBStart)) {
13
+ return { error: "chunk_a_end and chunk_b_start must be finite numbers." };
14
+ }
15
+ if (chunkAEnd <= 0) {
16
+ return { error: "chunk_a_end must be > 0." };
17
+ }
18
+ if (chunkBStart < 0) {
19
+ return { error: "chunk_b_start must be >= 0." };
20
+ }
21
+ // Overlap is allowed (chunkBStart <= chunkAEnd) but not fully reversed.
22
+ if (chunkBStart > chunkAEnd) {
23
+ return {
24
+ error: `chunk_b_start (${chunkBStart}) must be <= chunk_a_end (${chunkAEnd}). Gaps are not supported; use trim_video instead.`,
25
+ };
26
+ }
27
+ return { chunkAEnd, chunkBStart };
28
+ }
29
+ // Numeric suffixes 1/2 mark chunk order; trim the source label to its last
30
+ // 2 words so labels stay short. Mirrors even-split.ts.
31
+ function deriveChunkName(source, suffix) {
32
+ const base = source.label ?? source.artifactId;
33
+ const words = base.split(/[\s-]+/u).filter((w) => w.length > 0);
34
+ const tail = words.slice(-2).join("-") || base;
35
+ return `${tail}-${suffix}`;
36
+ }
37
+ export function splitVideoDef(engine) {
38
+ return {
39
+ name: "split_video",
40
+ description: "Create two new clips from one video while preserving the source. Use for requests such as 'trim @a1 15s and remaining'. chunk_a_end accepts seconds or timestamps such as 15s or 1:30; omit chunk_b_start for a clean split.",
41
+ annotations: {
42
+ title: "Split video into clips",
43
+ readOnlyHint: false,
44
+ destructiveHint: false,
45
+ idempotentHint: false,
46
+ openWorldHint: false,
47
+ },
48
+ schema: {
49
+ asset_id: z.string().describe("Source video artifact id or notebook reference such as @a1"),
50
+ book_id: z.string().describe("Book UUID"),
51
+ notebook_id: z.string().uuid().optional().describe("Notebook UUID when asset_id is a grid reference and the book has multiple matching notebooks"),
52
+ chunk_a_end: videoTimestampSchema.describe("End of the first clip in seconds or a timestamp such as 15s or 1:30"),
53
+ chunk_b_start: videoTimestampSchema.optional().describe("Start of the remaining clip; defaults to chunk_a_end for a clean split"),
54
+ },
55
+ handler: async (args) => {
56
+ const { asset_id, book_id, notebook_id, chunk_a_end, chunk_b_start } = args;
57
+ let assetId;
58
+ let chunkAEnd;
59
+ let chunkBStart;
60
+ try {
61
+ assetId = resolveVideoAssetReference(engine, asset_id, notebook_id);
62
+ chunkAEnd = parseVideoTimestamp(chunk_a_end, "chunk_a_end");
63
+ chunkBStart = parseVideoTimestamp(chunk_b_start, "chunk_b_start") ?? chunkAEnd;
64
+ }
65
+ catch (err) {
66
+ return {
67
+ isError: true,
68
+ content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
69
+ };
70
+ }
71
+ const validated = validateSplitParams(chunkAEnd, chunkBStart);
72
+ if ("error" in validated) {
73
+ return {
74
+ isError: true,
75
+ content: [{ type: "text", text: validated.error }],
76
+ };
77
+ }
78
+ const sourceArtifact = engine.artifacts.get(assetId);
79
+ if (!sourceArtifact.ok) {
80
+ return {
81
+ isError: true,
82
+ content: [{ type: "text", text: sourceArtifact.error.message }],
83
+ };
84
+ }
85
+ const manifestResult = await engine.files.manifest(assetId);
86
+ if (!manifestResult.ok) {
87
+ return {
88
+ isError: true,
89
+ content: [{ type: "text", text: manifestResult.error.message }],
90
+ };
91
+ }
92
+ const manifest = manifestResult.value;
93
+ const videoFile = findVideoFile(manifest.files);
94
+ if (!videoFile) {
95
+ return {
96
+ isError: true,
97
+ content: [{ type: "text", text: `No video file found in asset ${assetId}` }],
98
+ };
99
+ }
100
+ const sourcePath = join(manifest.path, videoFile.name);
101
+ let sourceDurationSec;
102
+ try {
103
+ const probe = await probeVideo(sourcePath);
104
+ sourceDurationSec = probe.videoDuration || probe.duration;
105
+ }
106
+ catch (err) {
107
+ return {
108
+ isError: true,
109
+ content: [{
110
+ type: "text",
111
+ text: `Failed to probe source duration: ${err instanceof Error ? err.message : String(err)}`,
112
+ }],
113
+ };
114
+ }
115
+ if (validated.chunkAEnd > sourceDurationSec + 0.05) {
116
+ return {
117
+ isError: true,
118
+ content: [{
119
+ type: "text",
120
+ text: `chunk_a_end (${validated.chunkAEnd.toFixed(2)}s) exceeds source duration (${sourceDurationSec.toFixed(2)}s).`,
121
+ }],
122
+ };
123
+ }
124
+ const chunkADur = validated.chunkAEnd;
125
+ const chunkBDur = sourceDurationSec - validated.chunkBStart;
126
+ if (chunkADur < MIN_CHUNK_SEC || chunkBDur < MIN_CHUNK_SEC) {
127
+ return {
128
+ isError: true,
129
+ content: [{
130
+ type: "text",
131
+ text: `Chunks would be too short: A=${chunkADur.toFixed(2)}s, B=${chunkBDur.toFixed(2)}s (min ${MIN_CHUNK_SEC}s each). Source is ${sourceDurationSec.toFixed(2)}s — pick a split point further from the ends.`,
132
+ }],
133
+ };
134
+ }
135
+ const nameA = deriveChunkName(sourceArtifact.value, "1");
136
+ const nameB = deriveChunkName(sourceArtifact.value, "2");
137
+ const createA = await engine.artifacts.create(sourceArtifact.value.kind, nameA);
138
+ if (!createA.ok) {
139
+ return {
140
+ isError: true,
141
+ content: [{ type: "text", text: `Failed to create chunk A asset: ${createA.error.message}` }],
142
+ };
143
+ }
144
+ const createB = await engine.artifacts.create(sourceArtifact.value.kind, nameB);
145
+ if (!createB.ok) {
146
+ return {
147
+ isError: true,
148
+ content: [{ type: "text", text: `Failed to create chunk B asset: ${createB.error.message}` }],
149
+ };
150
+ }
151
+ const assetIdA = createA.value.artifactId;
152
+ const assetIdB = createB.value.artifactId;
153
+ const assetDirA = createA.value.path;
154
+ const assetDirB = createB.value.path;
155
+ const outputPathA = join(assetDirA, "original.mp4");
156
+ const outputPathB = join(assetDirB, "original.mp4");
157
+ const jobId = `split-${assetId}-${Date.now()}`;
158
+ try {
159
+ await queue.enqueueAndWait(jobId, {
160
+ type: "split_video",
161
+ sourcePath,
162
+ sourceAssetId: assetId,
163
+ chunkAEnd: validated.chunkAEnd,
164
+ chunkBStart: validated.chunkBStart,
165
+ assetIdA,
166
+ assetDirA,
167
+ outputPathA,
168
+ assetIdB,
169
+ assetDirB,
170
+ outputPathB,
171
+ bookId: book_id,
172
+ });
173
+ }
174
+ catch (err) {
175
+ return {
176
+ isError: true,
177
+ content: [{
178
+ type: "text",
179
+ text: `Split failed: ${err instanceof Error ? err.message : String(err)}`,
180
+ }],
181
+ };
182
+ }
183
+ return {
184
+ content: [{
185
+ type: "text",
186
+ text: JSON.stringify({
187
+ source_asset_id: assetId,
188
+ chunk_a_asset_id: assetIdA,
189
+ chunk_a_artifact_id: createA.value.artifactId,
190
+ chunk_b_asset_id: assetIdB,
191
+ chunk_b_artifact_id: createB.value.artifactId,
192
+ chunk_a_end: validated.chunkAEnd,
193
+ chunk_b_start: validated.chunkBStart,
194
+ overlap_sec: Math.max(0, validated.chunkAEnd - validated.chunkBStart),
195
+ job_id: jobId,
196
+ status: "completed",
197
+ }),
198
+ }],
199
+ };
200
+ },
201
+ };
202
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { queue } from "../queue.js";
3
+ import { loadAssetForAnalysis } from "./_suggest-shared.js";
4
+ export function suggestCutsDef(engine) {
5
+ return {
6
+ name: "suggest_cuts",
7
+ description: "Suggest trims for visual pacing and awkward transitions from a video's frames in a background job. Uses a transcript when available for dialogue, dead air and fillers; no transcript is required. Pass the editing goal in instructions; review the result before apply_cuts.",
8
+ schema: {
9
+ asset_id: z.string().describe("Video artifact ID"),
10
+ book_id: z.string().describe("Book id"),
11
+ instructions: z.string().trim().min(1).optional().describe("Requested editing goal, such as smoother transitions and better timing"),
12
+ },
13
+ handler: async (args) => {
14
+ const { asset_id, book_id, instructions } = args;
15
+ const assetId = asset_id.replace(/^@/, "");
16
+ if (!process.env.OPENAI_API_KEY) {
17
+ return {
18
+ isError: true,
19
+ content: [{ type: "text", text: "OPENAI_API_KEY environment variable is not set" }],
20
+ };
21
+ }
22
+ const loaded = await loadAssetForAnalysis(engine, assetId, { requireTranscript: false });
23
+ if (!loaded.ok) {
24
+ return {
25
+ isError: true,
26
+ content: [{ type: "text", text: loaded.error.message }],
27
+ };
28
+ }
29
+ const jobId = `suggest-cuts-${assetId}-${Date.now()}`;
30
+ await queue.enqueue(jobId, {
31
+ type: "suggest_cuts",
32
+ assetId,
33
+ assetDir: loaded.value.manifest.path,
34
+ bookId: book_id,
35
+ instructions,
36
+ });
37
+ return {
38
+ content: [{
39
+ type: "text",
40
+ text: JSON.stringify({ asset_id: assetId, job_id: jobId, status: "enqueued" }),
41
+ }],
42
+ };
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { queue } from "../queue.js";
3
+ import { loadAssetForAnalysis } from "./_suggest-shared.js";
4
+ export function suggestSfxDef(engine) {
5
+ return {
6
+ name: "suggest_sfx",
7
+ description: "Suggest timed sound effects for a video from its frames and transcript in a background job. Needs a transcript; mix the result in with apply_sfx.",
8
+ schema: {
9
+ asset_id: z.string().describe("Video asset ID (vid-* or @vid-slug)"),
10
+ book_id: z.string().describe("Book id"),
11
+ },
12
+ handler: async (args) => {
13
+ const { asset_id, book_id } = args;
14
+ const assetId = asset_id.replace(/^@/, "");
15
+ if (!process.env.OPENAI_API_KEY) {
16
+ return {
17
+ isError: true,
18
+ content: [{ type: "text", text: "OPENAI_API_KEY environment variable is not set" }],
19
+ };
20
+ }
21
+ const loaded = await loadAssetForAnalysis(engine, assetId);
22
+ if (!loaded.ok) {
23
+ return {
24
+ isError: true,
25
+ content: [{ type: "text", text: loaded.error.message }],
26
+ };
27
+ }
28
+ const jobId = `suggest-sfx-${assetId}-${Date.now()}`;
29
+ await queue.enqueue(jobId, {
30
+ type: "suggest_sfx",
31
+ assetId,
32
+ assetDir: loaded.value.manifest.path,
33
+ bookId: book_id,
34
+ });
35
+ return {
36
+ content: [{
37
+ type: "text",
38
+ text: JSON.stringify({ asset_id: assetId, job_id: jobId, status: "enqueued" }),
39
+ }],
40
+ };
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,115 @@
1
+ import { findVideoFile } from "videobook-engine";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { createAudioSyncPlan } from "../audio-sync.js";
5
+ import { probeVideo } from "../ffmpeg.js";
6
+ import { queue } from "../queue.js";
7
+ function errorResult(message) {
8
+ return {
9
+ isError: true,
10
+ content: [{ type: "text", text: message }],
11
+ };
12
+ }
13
+ const videoAnchor = z.object({
14
+ video_seconds: z.number().nonnegative().describe("Timestamp of the visual action in the source video"),
15
+ audio_seconds: z.number().nonnegative().describe("Timestamp in the original audio where that action should land"),
16
+ }).strict();
17
+ const cueAnchor = z.object({
18
+ video_seconds: z.number().nonnegative().describe("Timestamp of the visual action in the source video"),
19
+ audio_cue: z.string().trim().min(1).describe("Unique contiguous phrase in the timed ElevenLabs transcript"),
20
+ }).strict();
21
+ function validateVideoAnchors(anchors, videoDuration, sourceFps) {
22
+ const frameDuration = 1 / sourceFps;
23
+ let previousVideoSeconds = 0;
24
+ for (const [index, anchor] of anchors.entries()) {
25
+ if (anchor.videoSeconds > videoDuration) {
26
+ throw new Error(`Video anchor ${index + 1} must be within the video duration`);
27
+ }
28
+ if (index > 0 && anchor.videoSeconds <= previousVideoSeconds) {
29
+ throw new Error("Audio-sync video anchors must be strictly increasing");
30
+ }
31
+ if (anchor.videoSeconds - previousVideoSeconds < frameDuration) {
32
+ throw new Error(`Audio-sync video anchor intervals must be at least one source frame (${frameDuration.toFixed(6)}s)`);
33
+ }
34
+ previousVideoSeconds = anchor.videoSeconds;
35
+ }
36
+ if (videoDuration - previousVideoSeconds < frameDuration) {
37
+ throw new Error(`Audio-sync video anchor intervals must be at least one source frame (${frameDuration.toFixed(6)}s)`);
38
+ }
39
+ }
40
+ export function syncVideoToAudioDef(engine) {
41
+ return {
42
+ name: "sync_video_to_audio",
43
+ description: "Smoothly retime a video's picture through multiple anchors while preserving its original audio unchanged.",
44
+ schema: {
45
+ asset_id: z.string().describe("Video asset ID"),
46
+ book_id: z.string().describe("Book id"),
47
+ anchors: z.array(z.union([cueAnchor, videoAnchor])).min(1).describe("Visual timestamps mapped to transcript cues or numeric audio timestamps"),
48
+ },
49
+ handler: async (args) => {
50
+ const { asset_id, book_id, anchors } = args;
51
+ const manifestResult = await engine.files.manifest(asset_id);
52
+ if (!manifestResult.ok)
53
+ return errorResult(manifestResult.error.message);
54
+ const videoFile = findVideoFile(manifestResult.value.files);
55
+ if (!videoFile)
56
+ return errorResult(`No video file found in asset ${asset_id}`);
57
+ const inputPath = join(manifestResult.value.path, videoFile.name);
58
+ let source;
59
+ try {
60
+ source = await probeVideo(inputPath);
61
+ }
62
+ catch (error) {
63
+ return errorResult(error instanceof Error ? error.message : String(error));
64
+ }
65
+ if (source.videoCodec === null) {
66
+ return errorResult(`No usable video stream found in asset ${asset_id}`);
67
+ }
68
+ if (!source.hasAudio || source.audioDuration === null) {
69
+ return errorResult(`sync_video_to_audio requires an audio stream in asset ${asset_id}`);
70
+ }
71
+ const requestedAnchors = anchors.map((anchor) => ("audio_cue" in anchor
72
+ ? { videoSeconds: anchor.video_seconds, audioCue: anchor.audio_cue }
73
+ : { videoSeconds: anchor.video_seconds, audioSeconds: anchor.audio_seconds }));
74
+ const numericAnchors = requestedAnchors.filter((anchor) => "audioSeconds" in anchor);
75
+ try {
76
+ if (numericAnchors.length === requestedAnchors.length) {
77
+ createAudioSyncPlan({
78
+ videoDuration: source.videoDuration,
79
+ audioDuration: source.audioDuration,
80
+ sourceFps: source.fps,
81
+ anchors: numericAnchors,
82
+ });
83
+ }
84
+ else {
85
+ validateVideoAnchors(requestedAnchors, source.videoDuration, source.fps);
86
+ }
87
+ }
88
+ catch (error) {
89
+ return errorResult(error instanceof Error ? error.message : String(error));
90
+ }
91
+ const now = Date.now();
92
+ const jobId = `sync-audio-${asset_id}-${now}`;
93
+ const outputPath = join(manifestResult.value.path, `.sync-audio-${now}.mp4`);
94
+ await queue.enqueue(jobId, {
95
+ type: "sync_video_to_audio",
96
+ inputPath,
97
+ outputPath,
98
+ anchors: requestedAnchors,
99
+ assetId: asset_id,
100
+ assetDir: manifestResult.value.path,
101
+ bookId: book_id,
102
+ });
103
+ return {
104
+ content: [{
105
+ type: "text",
106
+ text: JSON.stringify({
107
+ asset_id,
108
+ job_id: jobId,
109
+ status: "enqueued",
110
+ }),
111
+ }],
112
+ };
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Controls for tagging a library that already exists, and for how new assets
3
+ * are tagged from here on.
4
+ *
5
+ * The split these tools express: adopting cached analysis is free and happens
6
+ * by default, while asking a provider to analyze media that was never
7
+ * analyzed costs money and happens only when someone says so. A caller can
8
+ * therefore run a full pass, read the counts, and then decide whether the
9
+ * remaining assets are worth paying for.
10
+ */
11
+ import { z } from "zod";
12
+ import { queue } from "../queue.js";
13
+ import { backfillProgress, startedState, } from "../jobs/backfill-asset-tags.js";
14
+ import { runAssetTagging } from "../jobs/tag-asset.js";
15
+ import { readBackfillState, writeBackfillState, } from "../tag-backfill-state.js";
16
+ import { readTaggingSettings, writeTaggingSettings, } from "../tag-settings.js";
17
+ import { assetTagSummary } from "../asset-tags.js";
18
+ import { TAG_ANALYZER_VERSION } from "../tag-vocabulary.js";
19
+ function json(value) {
20
+ return { content: [{ type: "text", text: JSON.stringify(value) }] };
21
+ }
22
+ function failure(cause) {
23
+ return {
24
+ isError: true,
25
+ content: [{
26
+ type: "text",
27
+ text: cause instanceof Error ? cause.message : String(cause),
28
+ }],
29
+ };
30
+ }
31
+ function progressPayload(engine, state) {
32
+ const progress = backfillProgress(engine, state);
33
+ return {
34
+ status: progress.state.status,
35
+ analyzer_version: TAG_ANALYZER_VERSION,
36
+ allow_analysis: progress.state.allowAnalysis,
37
+ force: progress.state.force,
38
+ scanned: progress.state.scanned,
39
+ total: progress.total,
40
+ remaining: progress.remaining,
41
+ counts: {
42
+ tagged: progress.state.counts.tagged,
43
+ already_current: progress.state.counts.current,
44
+ no_useful_evidence: progress.state.counts.empty,
45
+ needs_analysis: progress.state.counts.needsAnalysis,
46
+ unsupported: progress.state.counts.unsupported,
47
+ skipped: progress.state.counts.skipped,
48
+ failed: progress.state.counts.failed,
49
+ },
50
+ started_at: progress.state.startedAt,
51
+ updated_at: progress.state.updatedAt,
52
+ finished_at: progress.state.finishedAt,
53
+ ...(progress.state.lastError ? { last_error: progress.state.lastError } : {}),
54
+ };
55
+ }
56
+ export function tagBackfillDefs(engine) {
57
+ return [
58
+ {
59
+ name: "backfill_asset_tags",
60
+ description: "Tag the assets already in a book. 'start' adopts cached analysis; allow_analysis permits provider calls. Pause and cancel stop new work; already queued analysis may finish. Resume continues saved progress.",
61
+ schema: {
62
+ book_id: z.string().describe("Book to backfill"),
63
+ action: z.enum(["start", "resume", "pause", "cancel", "status"])
64
+ .describe("What to do with this book's backfill pass"),
65
+ allow_analysis: z.boolean().optional().describe("Analyze assets with no usable cached analysis; this spends provider calls"),
66
+ force: z.boolean().optional().describe("Re-tag assets whose tags already cover their current content"),
67
+ },
68
+ handler: async (args) => {
69
+ const { book_id, action, allow_analysis, force } = args;
70
+ try {
71
+ const previous = readBackfillState(engine);
72
+ if (action === "status")
73
+ return json(progressPayload(engine, previous));
74
+ if (action === "pause" || action === "cancel") {
75
+ if (previous.status !== "running" && !(action === "cancel" && previous.status === "paused")) {
76
+ return json(progressPayload(engine, previous));
77
+ }
78
+ const stopped = writeBackfillState(engine, {
79
+ ...previous,
80
+ status: action === "pause" ? "paused" : "cancelled",
81
+ ...(action === "cancel" ? { finishedAt: Date.now() } : {}),
82
+ });
83
+ return json(progressPayload(engine, stopped));
84
+ }
85
+ if (previous.status === "running") {
86
+ return json(progressPayload(engine, previous));
87
+ }
88
+ const started = writeBackfillState(engine, startedState(previous, {
89
+ allowAnalysis: allow_analysis ?? (action === "resume" && previous.allowAnalysis),
90
+ force: force ?? (action === "resume" && previous.force),
91
+ resume: action === "resume",
92
+ }));
93
+ await queue.enqueue(`backfill-asset-tags-${book_id}-${started.runId}`, {
94
+ type: "backfill_asset_tags",
95
+ bookId: book_id,
96
+ runId: started.runId,
97
+ ...(allow_analysis ? { allowAnalysis: true } : {}),
98
+ ...(force ? { force: true } : {}),
99
+ });
100
+ return json(progressPayload(engine, started));
101
+ }
102
+ catch (cause) {
103
+ return failure(cause);
104
+ }
105
+ },
106
+ },
107
+ {
108
+ name: "retag_asset",
109
+ description: "Re-run tagging for one asset and return its tag state. Manual tags and dismissals are preserved; use it to retry after adding a provider key or to pick up a newer analysis.",
110
+ schema: {
111
+ book_id: z.string().describe("Book that owns the asset"),
112
+ asset_id: z.string().describe("Artifact id to re-tag"),
113
+ force: z.boolean().optional().describe("Re-run even when the current tags already cover this content"),
114
+ cached_only: z.boolean().optional().describe("Adopt cached analysis only; never request new provider analysis"),
115
+ },
116
+ handler: async (args) => {
117
+ const { book_id, asset_id, force, cached_only } = args;
118
+ try {
119
+ const result = await runAssetTagging(engine, {
120
+ type: "tag_asset",
121
+ bookId: book_id,
122
+ assetId: asset_id,
123
+ ...(force ? { force: true } : {}),
124
+ ...(cached_only ? { cacheOnly: true } : {}),
125
+ });
126
+ if (result.outcome === "missing_asset") {
127
+ return failure(new Error(`Asset not found: ${asset_id}`));
128
+ }
129
+ const state = engine.tags.read(asset_id);
130
+ return json({
131
+ asset_id,
132
+ outcome: result.outcome,
133
+ tag_count: result.tagCount,
134
+ analyzer_version: result.analyzerVersion,
135
+ ...(state.ok ? { tags: assetTagSummary(state.value) } : {}),
136
+ });
137
+ }
138
+ catch (cause) {
139
+ return failure(cause);
140
+ }
141
+ },
142
+ },
143
+ {
144
+ name: "configure_asset_tagging",
145
+ description: "Read or change how new assets are tagged in a book. Manual tagging, filtering and search keep working whatever this is set to.",
146
+ schema: {
147
+ book_id: z.string().describe("Book to configure"),
148
+ automatic_enabled: z.boolean().optional().describe("Tag new assets automatically as they become ready"),
149
+ analysis_on_new_assets: z.boolean().optional().describe("Let automatic tagging analyze new assets that have no cached analysis"),
150
+ },
151
+ handler: async (args) => {
152
+ const { automatic_enabled, analysis_on_new_assets } = args;
153
+ try {
154
+ const update = {
155
+ ...(automatic_enabled === undefined
156
+ ? {}
157
+ : { automaticEnabled: automatic_enabled }),
158
+ ...(analysis_on_new_assets === undefined
159
+ ? {}
160
+ : { analysisOnNewAssets: analysis_on_new_assets }),
161
+ };
162
+ const settings = Object.keys(update).length > 0
163
+ ? writeTaggingSettings(engine, update)
164
+ : readTaggingSettings(engine);
165
+ return json({
166
+ automatic_enabled: settings.automaticEnabled,
167
+ analysis_on_new_assets: settings.analysisOnNewAssets,
168
+ analyzer_version: TAG_ANALYZER_VERSION,
169
+ });
170
+ }
171
+ catch (cause) {
172
+ return failure(cause);
173
+ }
174
+ },
175
+ },
176
+ ];
177
+ }
@@ -0,0 +1,85 @@
1
+ import { findVideoFile } from "videobook-engine";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { probeVideo } from "../ffmpeg.js";
5
+ import { queue } from "../queue.js";
6
+ import { createTimewarpPlan } from "../timewarp.js";
7
+ function errorResult(message) {
8
+ return {
9
+ isError: true,
10
+ content: [{ type: "text", text: message }],
11
+ };
12
+ }
13
+ export function timewarpVideoDef(engine) {
14
+ return {
15
+ name: "timewarp_video",
16
+ description: "Retime one video range and compensate the remaining picture to preserve the original duration and audio.",
17
+ schema: {
18
+ asset_id: z.string().describe("Video asset ID"),
19
+ book_id: z.string().describe("Book id"),
20
+ start_seconds: z.number().describe("Selected range start in seconds"),
21
+ end_seconds: z.number().describe("Selected range end in seconds"),
22
+ stretch_factor: z.number().describe("Duration multiplier from 0.25 to 4; values above 1 lengthen the selection"),
23
+ },
24
+ handler: async (args) => {
25
+ const { asset_id, book_id, start_seconds, end_seconds, stretch_factor, } = args;
26
+ const manifestResult = await engine.files.manifest(asset_id);
27
+ if (!manifestResult.ok)
28
+ return errorResult(manifestResult.error.message);
29
+ const videoFile = findVideoFile(manifestResult.value.files);
30
+ if (!videoFile)
31
+ return errorResult(`No video file found in asset ${asset_id}`);
32
+ const inputPath = join(manifestResult.value.path, videoFile.name);
33
+ let source;
34
+ try {
35
+ source = await probeVideo(inputPath);
36
+ }
37
+ catch (error) {
38
+ return errorResult(error instanceof Error ? error.message : String(error));
39
+ }
40
+ if (source.videoCodec === null) {
41
+ return errorResult(`No usable video stream found in asset ${asset_id}`);
42
+ }
43
+ let plan;
44
+ try {
45
+ plan = createTimewarpPlan({
46
+ sourceDuration: source.duration,
47
+ sourceFps: source.fps,
48
+ startSeconds: start_seconds,
49
+ endSeconds: end_seconds,
50
+ stretchFactor: stretch_factor,
51
+ });
52
+ }
53
+ catch (error) {
54
+ return errorResult(error instanceof Error ? error.message : String(error));
55
+ }
56
+ const now = Date.now();
57
+ const jobId = `timewarp-${asset_id}-${now}`;
58
+ const outputPath = join(manifestResult.value.path, `.timewarp-${now}.mp4`);
59
+ await queue.enqueue(jobId, {
60
+ type: "timewarp_video",
61
+ inputPath,
62
+ outputPath,
63
+ startSeconds: start_seconds,
64
+ endSeconds: end_seconds,
65
+ stretchFactor: stretch_factor,
66
+ assetId: asset_id,
67
+ assetDir: manifestResult.value.path,
68
+ bookId: book_id,
69
+ });
70
+ return {
71
+ content: [{
72
+ type: "text",
73
+ text: JSON.stringify({
74
+ asset_id,
75
+ job_id: jobId,
76
+ status: "enqueued",
77
+ source_duration: plan.sourceDuration,
78
+ selected_output_duration: plan.selectedOutputDuration,
79
+ outside_stretch_factor: plan.outsideStretchFactor,
80
+ }),
81
+ }],
82
+ };
83
+ },
84
+ };
85
+ }
@@ -0,0 +1 @@
1
+ export {};