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,84 @@
1
+ import { findAudioFile, findVideoFile } from "videobook-engine";
2
+ import { extname } from "node:path";
3
+ const IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "webp", "bmp", "tif", "tiff", "gif"]);
4
+ function findImageFile(files) {
5
+ const images = files.filter((f) => {
6
+ const ext = (f.extension ?? extname(f.name)).replace(/^\./, "").toLowerCase();
7
+ return IMAGE_EXTENSIONS.has(ext);
8
+ });
9
+ return images.find((f) => f.name.startsWith("original")) ?? images[0] ?? null;
10
+ }
11
+ export async function resolveImageRef(engine, raw) {
12
+ const assetId = raw.replace(/^@/, "");
13
+ const artifact = engine.artifacts.get(assetId);
14
+ if (!artifact.ok)
15
+ return { ok: false, error: artifact.error.message };
16
+ if (artifact.value.kind !== "image") {
17
+ return { ok: false, error: `image ref must be an image artifact, got ${artifact.value.kind}: ${assetId}` };
18
+ }
19
+ const manifest = await engine.files.manifest(assetId);
20
+ if (!manifest.ok)
21
+ return { ok: false, error: manifest.error.message };
22
+ const file = findImageFile(manifest.value.files);
23
+ if (!file)
24
+ return { ok: false, error: `No image file found in asset ${assetId}` };
25
+ return { ok: true, value: { assetId, filename: file.name } };
26
+ }
27
+ // `@<assetId>/<filename>` addresses a specific sibling file inside an
28
+ // asset. Used by plan-driven render (e.g. `@plan/s1.mp4`); bypasses the
29
+ // artifact-kind gate since the destination asset may hold multiple media
30
+ // files of mixed kinds.
31
+ async function resolveSlashedRef(engine, raw) {
32
+ const stripped = raw.replace(/^@/, "");
33
+ const slashIdx = stripped.indexOf("/");
34
+ if (slashIdx < 0)
35
+ return null;
36
+ const assetId = stripped.slice(0, slashIdx);
37
+ const filename = stripped.slice(slashIdx + 1);
38
+ if (!assetId || !filename)
39
+ return { ok: false, error: `Invalid slashed ref: ${raw}` };
40
+ const manifest = await engine.files.manifest(assetId);
41
+ if (!manifest.ok)
42
+ return { ok: false, error: manifest.error.message };
43
+ const file = manifest.value.files.find((f) => f.name === filename);
44
+ if (!file)
45
+ return { ok: false, error: `File ${filename} not found in asset ${assetId}` };
46
+ return { ok: true, value: { assetId, filename } };
47
+ }
48
+ export async function resolveVideoRef(engine, raw) {
49
+ const slashed = await resolveSlashedRef(engine, raw);
50
+ if (slashed !== null)
51
+ return slashed;
52
+ const assetId = raw.replace(/^@/, "");
53
+ const artifact = engine.artifacts.get(assetId);
54
+ if (!artifact.ok)
55
+ return { ok: false, error: artifact.error.message };
56
+ if (artifact.value.kind !== "video") {
57
+ return { ok: false, error: `video ref must be a video artifact, got ${artifact.value.kind}: ${assetId}` };
58
+ }
59
+ const manifest = await engine.files.manifest(assetId);
60
+ if (!manifest.ok)
61
+ return { ok: false, error: manifest.error.message };
62
+ const file = findVideoFile(manifest.value.files);
63
+ if (!file)
64
+ return { ok: false, error: `No video file found in asset ${assetId}` };
65
+ return { ok: true, value: { assetId, filename: file.name } };
66
+ }
67
+ // Audio replacement always targets a first-class audio artifact. Notebook
68
+ // cells reference artifact identities; they do not expose nested file paths.
69
+ export async function resolveAudioRef(engine, raw) {
70
+ const assetId = raw.replace(/^@/, "");
71
+ const artifact = engine.artifacts.get(assetId);
72
+ if (!artifact.ok)
73
+ return { ok: false, error: artifact.error.message };
74
+ if (artifact.value.kind !== "audio") {
75
+ return { ok: false, error: `audio ref must be an audio artifact, got ${artifact.value.kind}: ${assetId}` };
76
+ }
77
+ const manifest = await engine.files.manifest(assetId);
78
+ if (!manifest.ok)
79
+ return { ok: false, error: manifest.error.message };
80
+ const file = findAudioFile(manifest.value.files, "audio");
81
+ if (!file)
82
+ return { ok: false, error: `No audio file found in asset ${assetId}` };
83
+ return { ok: true, value: { assetId, filename: file.name } };
84
+ }
@@ -0,0 +1,59 @@
1
+ // Stand-in https URL for pre-upload apicity safeParse. Asset fields are typed
2
+ // as z.string().url() on the wire, but tools pass local @img-*/@vid-*/@aud-*
3
+ // slugs until the job uploads and substitutes real CDN URLs. Use this only
4
+ // for schema validation — never as the outbound provider payload.
5
+ export function refAsPlaceholderUrl(slug) {
6
+ const bare = slug.replace(/^@/, "").trim();
7
+ return `https://placeholder.local/${bare || "asset"}`;
8
+ }
9
+ // Convert a ZodError from an @apicity provider safeParse into an MCP tool
10
+ // `isError` result. Strips the leading `input.` path segment so the surfaced
11
+ // message reads as "image_url: ..." rather than "input.image_url: ..." —
12
+ // MCP callers don't care about apicity's internal request envelope.
13
+ export function zodErrorToToolResult(err) {
14
+ const lines = err.issues.map((issue) => {
15
+ const path = issue.path.filter((seg) => seg !== "input").join(".");
16
+ return path ? `${path}: ${issue.message}` : issue.message;
17
+ });
18
+ return {
19
+ isError: true,
20
+ content: [{ type: "text", text: lines.join("\n") }],
21
+ };
22
+ }
23
+ // Standard MCP tool error from a plain message. Replaces the inline
24
+ // `{ isError: true as const, content: [{ type: "text" as const, text }] }`
25
+ // literals scattered through the tool handlers.
26
+ export function toolError(text) {
27
+ return { isError: true, content: [{ type: "text", text }] };
28
+ }
29
+ // "provided" = a non-blank string, or an array with at least one non-blank entry.
30
+ function isProvided(value) {
31
+ if (typeof value === "string")
32
+ return value.trim().length > 0;
33
+ if (Array.isArray(value))
34
+ return value.some(isProvided);
35
+ return value !== undefined && value !== null;
36
+ }
37
+ // Enforce a cross-field "at least one of these inputs must be provided" rule —
38
+ // the kind MCP's flat JSON-Schema input can't express. Returns a tool error
39
+ // naming every candidate when none are present, else null.
40
+ export function requireAtLeastOne(label, inputs) {
41
+ if (inputs.some((i) => isProvided(i.value)))
42
+ return null;
43
+ return toolError(`${label} requires at least one of: ${inputs.map((i) => i.name).join(", ")}`);
44
+ }
45
+ // Artifact kind gate. Media kind used to be readable from a slug prefix
46
+ // (`vid-`, `img-`, …); references are artifact ids now, so the kind has to
47
+ // come from the catalog row.
48
+ export async function requireArtifactKind(engine, assetId, allowed, label) {
49
+ const artifact = engine.artifacts.get(assetId.replace(/^@/u, ""));
50
+ if (!artifact.ok)
51
+ return toolError(`${label}: ${artifact.error.message}`);
52
+ return allowed.includes(artifact.value.kind)
53
+ ? null
54
+ : toolError(`${label} requires ${allowed.join(" or ")} media; ${assetId} is ${artifact.value.kind}`);
55
+ }
56
+ export function artifactKindOf(engine, assetId) {
57
+ const artifact = engine.artifacts.get(assetId.replace(/^@/u, ""));
58
+ return artifact.ok ? artifact.value.kind : null;
59
+ }
@@ -0,0 +1,279 @@
1
+ import { z } from "zod";
2
+ import { findVideoFile } from "videobook-engine";
3
+ const MAX_FRAMES = 15;
4
+ const SILENCE_MARKER_THRESHOLD_S = 0.5;
5
+ export const DEAD_AIR_THRESHOLD_S = 1.5;
6
+ export const TRIM_BUDGET_FRACTION = 0.4;
7
+ const CANDIDATE_TOLERANCE_S = 0.3;
8
+ export const TrimSchema = z.object({
9
+ cut_start: z.number().min(0),
10
+ cut_end: z.number().min(0),
11
+ reason: z.string(),
12
+ severity: z.enum(["low", "med", "high"]),
13
+ });
14
+ export const SfxSchema = z.object({
15
+ timestamp: z.number().min(0),
16
+ type: z.string(),
17
+ description: z.string(),
18
+ });
19
+ function decodeBuffer(buf) {
20
+ if (typeof buf === "string")
21
+ return buf;
22
+ return new TextDecoder().decode(buf);
23
+ }
24
+ export function sampleFrames(frames) {
25
+ if (frames.length <= MAX_FRAMES)
26
+ return frames;
27
+ const step = (frames.length - 1) / (MAX_FRAMES - 1);
28
+ return Array.from({ length: MAX_FRAMES }, (_, i) => frames[Math.round(i * step)]);
29
+ }
30
+ export function extractSilenceCandidates(words, minDurationS) {
31
+ const wordEntries = words.filter((w) => w.type === "word");
32
+ const out = [];
33
+ for (let i = 1; i < wordEntries.length; i++) {
34
+ const prev = wordEntries[i - 1];
35
+ const curr = wordEntries[i];
36
+ const gap = curr.start - prev.end;
37
+ if (gap >= minDurationS) {
38
+ out.push({ start: prev.end, end: curr.start, duration: gap });
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ function formatCandidatesBlock(candidates) {
44
+ if (candidates.length === 0)
45
+ return "Silence candidates: none (transcript has no gaps >= 1.5s)";
46
+ const lines = candidates.map((c, i) => ` ${i + 1}. ${c.start.toFixed(2)}s -> ${c.end.toFixed(2)}s (duration ${c.duration.toFixed(2)}s)`);
47
+ return `Silence candidates (the only valid sources for severity "high" trims):\n${lines.join("\n")}`;
48
+ }
49
+ function condenseElevenLabsWords(words) {
50
+ const wordEntries = words.filter((w) => w.type === "word");
51
+ if (wordEntries.length === 0)
52
+ return "(no words)";
53
+ const lines = [];
54
+ let currentSpeaker = undefined;
55
+ let currentTurn = [];
56
+ let turnStart = 0;
57
+ let prevEnd = null;
58
+ const flush = () => {
59
+ if (currentTurn.length === 0)
60
+ return;
61
+ const spk = currentSpeaker ?? "?";
62
+ lines.push(`[t=${turnStart.toFixed(2)} spk=${spk}] ${currentTurn.join(" ")}`);
63
+ currentTurn = [];
64
+ };
65
+ for (const w of wordEntries) {
66
+ if (prevEnd !== null) {
67
+ const gap = w.start - prevEnd;
68
+ if (gap > SILENCE_MARKER_THRESHOLD_S) {
69
+ flush();
70
+ lines.push(`[silence=${gap.toFixed(2)}s]`);
71
+ currentSpeaker = undefined;
72
+ }
73
+ }
74
+ if (w.speaker_id !== currentSpeaker) {
75
+ flush();
76
+ currentSpeaker = w.speaker_id;
77
+ turnStart = w.start;
78
+ }
79
+ if (currentTurn.length === 0)
80
+ turnStart = w.start;
81
+ currentTurn.push(w.text.trim());
82
+ prevEnd = w.end;
83
+ }
84
+ flush();
85
+ return lines.join("\n");
86
+ }
87
+ async function loadTranscript(engine, assetId) {
88
+ const elevenRead = await engine.files.read(assetId, "elevenlabs.json");
89
+ if (elevenRead.ok) {
90
+ try {
91
+ const parsed = JSON.parse(decodeBuffer(elevenRead.value));
92
+ if (Array.isArray(parsed.words) && parsed.words.length > 0) {
93
+ return { source: "elevenlabs", words: parsed.words, text: parsed.text ?? "" };
94
+ }
95
+ }
96
+ catch {
97
+ // fall through to gpt4otrans
98
+ }
99
+ }
100
+ const gptRead = await engine.files.read(assetId, ".analysis.gpt4otrans.json");
101
+ if (gptRead.ok) {
102
+ try {
103
+ const parsed = JSON.parse(decodeBuffer(gptRead.value));
104
+ if (parsed.text) {
105
+ return { source: "gpt4otrans", text: parsed.text };
106
+ }
107
+ }
108
+ catch {
109
+ // fall through
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ async function loadPriorAnalysis(engine, assetId) {
115
+ const read = await engine.files.read(assetId, ".original.analysis.json");
116
+ if (!read.ok)
117
+ return null;
118
+ try {
119
+ return JSON.parse(decodeBuffer(read.value));
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ function isWithinCandidate(cutStart, cutEnd, candidates) {
126
+ return candidates.some((c) => cutStart >= c.start - CANDIDATE_TOLERANCE_S
127
+ && cutEnd <= c.end + CANDIDATE_TOLERANCE_S);
128
+ }
129
+ export function filterValidTrims(trims, candidates, trimBudgetS, evidence) {
130
+ const dropped = [];
131
+ const sorted = [...trims].sort((a, b) => a.cut_start - b.cut_start);
132
+ const kept = [];
133
+ let totalDuration = 0;
134
+ let lastEnd = -Infinity;
135
+ for (const t of sorted) {
136
+ if (evidence.duration !== null && t.cut_end > evidence.duration) {
137
+ dropped.push({ trim: t, reason: "cut_end exceeds video duration" });
138
+ continue;
139
+ }
140
+ if (t.cut_end <= t.cut_start) {
141
+ dropped.push({ trim: t, reason: "cut_end <= cut_start" });
142
+ continue;
143
+ }
144
+ if (t.cut_start < lastEnd) {
145
+ dropped.push({ trim: t, reason: `overlaps previous kept trim ending at ${lastEnd.toFixed(2)}s` });
146
+ continue;
147
+ }
148
+ if (t.severity === "high" && !isWithinCandidate(t.cut_start, t.cut_end, candidates)) {
149
+ dropped.push({
150
+ trim: t,
151
+ reason: `severity:"high" trim ${t.cut_start.toFixed(2)}->${t.cut_end.toFixed(2)} is not contained in any silence candidate`,
152
+ });
153
+ continue;
154
+ }
155
+ if (t.severity === "med" && evidence.transcriptSource !== "elevenlabs") {
156
+ dropped.push({ trim: t, reason: "filler trims require a word-timed transcript" });
157
+ continue;
158
+ }
159
+ if (t.severity === "med" && t.cut_end - t.cut_start > 1.5) {
160
+ dropped.push({
161
+ trim: t,
162
+ reason: `severity:"med" trim is ${(t.cut_end - t.cut_start).toFixed(2)}s — fillers must be < 1.5s`,
163
+ });
164
+ continue;
165
+ }
166
+ const dur = t.cut_end - t.cut_start;
167
+ if (totalDuration + dur > trimBudgetS) {
168
+ dropped.push({
169
+ trim: t,
170
+ reason: `trim budget exceeded (${(totalDuration + dur).toFixed(2)}s > ${trimBudgetS.toFixed(2)}s)`,
171
+ });
172
+ continue;
173
+ }
174
+ kept.push(t);
175
+ totalDuration += dur;
176
+ lastEnd = t.cut_end;
177
+ }
178
+ return { kept, dropped };
179
+ }
180
+ function metaLine(meta) {
181
+ if (!meta)
182
+ return null;
183
+ const dims = meta.width && meta.height ? `${meta.width}x${meta.height}` : "unknown size";
184
+ const dur = typeof meta.duration === "number" ? `${meta.duration.toFixed(1)}s` : "unknown duration";
185
+ return `Video metadata: ${dims}, duration ${dur}, hasAudio=${meta.hasAudio ?? "unknown"}`;
186
+ }
187
+ function priorLine(prior) {
188
+ if (!prior)
189
+ return null;
190
+ const scenes = (prior.scenes ?? [])
191
+ .map((s) => `t=${(s.timestamp ?? 0).toFixed(1)}s: ${s.description ?? ""}`)
192
+ .join("; ");
193
+ return `Prior analysis — summary: ${prior.summary ?? "n/a"}; mood: ${prior.mood ?? "n/a"}; scenes: ${scenes || "n/a"}`;
194
+ }
195
+ function transcriptLine(transcript) {
196
+ if (transcript.source === "none") {
197
+ return "Transcript: unavailable. Assess visual pacing only; do not infer speech, silence, filler words, or audio events from missing transcription.";
198
+ }
199
+ if (transcript.source === "elevenlabs") {
200
+ return `Transcript (word-level, with silence markers):\n${condenseElevenLabsWords(transcript.words)}`;
201
+ }
202
+ return `Transcript (no word timings):\n${transcript.text}`;
203
+ }
204
+ export function buildCutsContext(meta, prior, transcript, frameCount, candidates, trimBudgetS, instructions) {
205
+ const parts = [];
206
+ if (instructions?.trim())
207
+ parts.push(`Editing request: ${instructions.trim()}`);
208
+ const m = metaLine(meta);
209
+ if (m)
210
+ parts.push(m);
211
+ parts.push(`Trim budget: total trim duration must be <= ${trimBudgetS.toFixed(1)}s.`);
212
+ if (transcript.source === "elevenlabs") {
213
+ parts.push(formatCandidatesBlock(candidates));
214
+ }
215
+ else if (transcript.source === "gpt4otrans") {
216
+ parts.push("Silence candidates: none (transcript has no word timings; only visually supported severity \"low\" trims are allowed).");
217
+ }
218
+ else {
219
+ parts.push("Silence candidates: none (no transcript; only visually supported severity \"low\" trims are allowed).");
220
+ }
221
+ const p = priorLine(prior);
222
+ if (p)
223
+ parts.push(p);
224
+ parts.push(transcriptLine(transcript));
225
+ parts.push(`${frameCount} timestamped frames follow.`);
226
+ return parts.join("\n\n");
227
+ }
228
+ export function buildSfxContext(meta, prior, transcript, frameCount) {
229
+ const parts = [];
230
+ const m = metaLine(meta);
231
+ if (m)
232
+ parts.push(m);
233
+ const p = priorLine(prior);
234
+ if (p)
235
+ parts.push(p);
236
+ parts.push(transcriptLine(transcript));
237
+ parts.push(`${frameCount} frames at low detail follow.`);
238
+ return parts.join("\n\n");
239
+ }
240
+ export async function loadAssetForAnalysis(engine, assetId, options = {}) {
241
+ const manifestResult = await engine.files.manifest(assetId);
242
+ if (!manifestResult.ok) {
243
+ return { ok: false, error: { kind: "manifest", message: manifestResult.error.message } };
244
+ }
245
+ const manifest = manifestResult.value;
246
+ const videoFile = findVideoFile(manifest.files);
247
+ if (!videoFile) {
248
+ return { ok: false, error: { kind: "no-video", message: "No video file found. Wait for the selected video's generation or upload to finish." } };
249
+ }
250
+ const frameNames = (manifest.directories?.["original_frames"] ?? [])
251
+ .filter((f) => f.endsWith(".jpg"))
252
+ .sort((a, b) => parseFloat(a) - parseFloat(b));
253
+ if (frameNames.length === 0) {
254
+ return {
255
+ ok: false,
256
+ error: { kind: "no-frames", message: "The selected video has no frames yet. Run /process-video for its notebook cell first." },
257
+ };
258
+ }
259
+ const transcript = await loadTranscript(engine, assetId);
260
+ if (!transcript && options.requireTranscript !== false) {
261
+ return {
262
+ ok: false,
263
+ error: { kind: "no-transcript", message: "No transcript found. Run /transcribe for the selected video's notebook cell first." },
264
+ };
265
+ }
266
+ const metaResult = await engine.metadata.artifacts.read(assetId, "original");
267
+ const meta = metaResult.ok ? metaResult.value : null;
268
+ const prior = await loadPriorAnalysis(engine, assetId);
269
+ return {
270
+ ok: true,
271
+ value: {
272
+ manifest,
273
+ frameNames,
274
+ transcript: transcript ?? { source: "none" },
275
+ meta,
276
+ prior,
277
+ },
278
+ };
279
+ }
@@ -0,0 +1,73 @@
1
+ import { join } from "node:path";
2
+ import { findAudioFile } from "videobook-engine";
3
+ import { v7 as uuidv7 } from "uuid";
4
+ import { z } from "zod";
5
+ import { probeVideo } from "../ffmpeg.js";
6
+ import { readTimeline, resolveArtifact, timelineFailure, timelineInput, timelinePayload, writeTimeline, } from "../timeline-helpers.js";
7
+ export function addTimelineAudioDef(engine) {
8
+ return {
9
+ name: "add_timeline_audio",
10
+ description: "Add a typed audio overlay to the selected book timeline",
11
+ schema: {
12
+ book_id: z.string().uuid(),
13
+ asset_id: z.string(),
14
+ start_frame: z.number().int().min(0),
15
+ duration_frames: z.number().int().positive().optional(),
16
+ volume: z.number().min(0).max(100).optional(),
17
+ fade_in: z.number().min(0).optional(),
18
+ fade_out: z.number().min(0).optional(),
19
+ },
20
+ handler: async (args) => {
21
+ const asset = resolveArtifact(engine, String(args.asset_id));
22
+ if (!asset || asset.kind !== "audio") {
23
+ return timelineFailure(`Audio artifact not found: ${String(args.asset_id)}`);
24
+ }
25
+ const manifest = await engine.files.manifest(asset.artifactId);
26
+ if (!manifest.ok)
27
+ return timelineFailure(manifest.error.message);
28
+ const audioFile = findAudioFile(manifest.value.files, asset.kind);
29
+ if (!audioFile)
30
+ return timelineFailure(`No audio file found in ${asset.label ?? asset.artifactId}`);
31
+ let durationFrames = args.duration_frames;
32
+ if (durationFrames === undefined) {
33
+ try {
34
+ const probe = await probeVideo(join(manifest.value.path, audioFile.name));
35
+ const { frameRate } = engine.sequences.getPrimary();
36
+ durationFrames = Math.max(1, Math.round((probe.audioDuration ?? probe.duration) * frameRate.numerator / frameRate.denominator));
37
+ }
38
+ catch (error) {
39
+ return timelineFailure(error instanceof Error ? error.message : String(error));
40
+ }
41
+ }
42
+ const timeline = readTimeline(engine);
43
+ const clipId = uuidv7();
44
+ const audio = [
45
+ ...timeline.audio.map((clip) => ({
46
+ id: clip.id,
47
+ artifactId: clip.artifactId,
48
+ startFrame: clip.startFrame,
49
+ durationFrames: clip.durationFrames,
50
+ ...(clip.volume === undefined ? {} : { volume: clip.volume }),
51
+ ...(clip.fadeIn === undefined ? {} : { fadeIn: clip.fadeIn }),
52
+ ...(clip.fadeOut === undefined ? {} : { fadeOut: clip.fadeOut }),
53
+ })),
54
+ {
55
+ id: clipId,
56
+ artifactId: asset.artifactId,
57
+ startFrame: args.start_frame,
58
+ durationFrames,
59
+ ...(args.volume === undefined ? {} : { volume: args.volume }),
60
+ ...(args.fade_in === undefined ? {} : { fadeIn: args.fade_in }),
61
+ ...(args.fade_out === undefined ? {} : { fadeOut: args.fade_out }),
62
+ },
63
+ ];
64
+ const result = await writeTimeline(engine, { ...timelineInput(timeline), audio });
65
+ if (!result.ok)
66
+ return timelineFailure(result.error.message);
67
+ const clip = result.value.audio.find((item) => item.id === clipId);
68
+ return {
69
+ content: [{ type: "text", text: JSON.stringify({ ...timelinePayload(result.value), clip }) }],
70
+ };
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ import { aliveSource, cachedAliveAnalysis } from "../alive-captions/analyze.js";
3
+ import { readAliveSettings, writeAliveSettings } from "../alive-captions/model.js";
4
+ import { queue } from "../queue.js";
5
+ import { isValidHex } from "../remotion/util.js";
6
+ import { ALIVE_MOTIONS, ALIVE_STYLES } from "../remotion/subtitles/alive-types.js";
7
+ import { artifactKindOf } from "./_shared.js";
8
+ import { RENDER_ORIENTATIONS } from "./reconcile-project-work.js";
9
+ const ANALYSIS_TIMEOUT_MS = 600_000;
10
+ function errorResult(status, message) {
11
+ return {
12
+ isError: true,
13
+ content: [{ type: "text", text: JSON.stringify({ status, message }) }],
14
+ };
15
+ }
16
+ function textResult(payload) {
17
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
18
+ }
19
+ function videoGate(engine, assetId, tool) {
20
+ const kind = artifactKindOf(engine, assetId);
21
+ if (kind === null)
22
+ return errorResult("asset_not_found", `Artifact ${assetId} was not found.`);
23
+ if (kind !== "video")
24
+ return errorResult("invalid_asset", `${tool} requires a video artifact; ${assetId} is ${kind}`);
25
+ return null;
26
+ }
27
+ async function runAnalysis(engine, bookId, assetId, force) {
28
+ const manifest = await engine.files.manifest(assetId);
29
+ if (!manifest.ok)
30
+ throw new Error(manifest.error.message);
31
+ const clear = await engine.jobs.failures.clear(assetId);
32
+ if (!clear.ok)
33
+ throw new Error(clear.error.message);
34
+ const result = await queue.enqueueAndWait(`alive-captions-${assetId}-${Date.now()}`, {
35
+ type: "analyze_alive_captions",
36
+ assetId,
37
+ assetDir: manifest.value.path,
38
+ bookId,
39
+ force,
40
+ }, ANALYSIS_TIMEOUT_MS);
41
+ return result.analysis;
42
+ }
43
+ export function aliveCaptionsDef(engine) {
44
+ return {
45
+ name: "alive_captions",
46
+ description: "Read a video's alive-caption analysis (phrases, vocal onsets, per-word energy, loudness, face and text regions) and its saved caption settings, analyzing first when it is missing or stale.",
47
+ schema: {
48
+ asset_id: z.string().describe("Video artifact id"),
49
+ book_id: z.string().describe("Book id"),
50
+ reanalyze: z.boolean().optional().describe("Re-run the analysis even when the cached one is current (default false)"),
51
+ },
52
+ handler: async (args) => {
53
+ const { asset_id, book_id, reanalyze = false } = args;
54
+ const gate = videoGate(engine, asset_id, "alive_captions");
55
+ if (gate)
56
+ return gate;
57
+ let fingerprint;
58
+ try {
59
+ fingerprint = (await aliveSource(engine, asset_id)).fingerprint;
60
+ }
61
+ catch (error) {
62
+ return errorResult("missing_transcript", error instanceof Error ? error.message : String(error));
63
+ }
64
+ const cached = reanalyze ? null : await cachedAliveAnalysis(engine, asset_id, fingerprint);
65
+ let analysis = cached;
66
+ if (!analysis) {
67
+ try {
68
+ analysis = await runAnalysis(engine, book_id, asset_id, reanalyze);
69
+ }
70
+ catch (error) {
71
+ return errorResult("analysis_failed", error instanceof Error ? error.message : String(error));
72
+ }
73
+ }
74
+ return textResult({
75
+ asset_id,
76
+ status: "ready",
77
+ cached: cached !== null,
78
+ regions_analyzed: analysis.regionsAnalyzed,
79
+ settings: await readAliveSettings(engine, asset_id),
80
+ analysis,
81
+ });
82
+ },
83
+ };
84
+ }
85
+ function mergeSettings(current, args) {
86
+ return {
87
+ style: args.style ?? current.style,
88
+ motion: args.motion ?? current.motion,
89
+ energySizing: args.energy_sizing ?? current.energySizing,
90
+ snapToOnsets: args.snap_to_onsets ?? current.snapToOnsets,
91
+ avoidFaces: args.avoid_faces ?? current.avoidFaces,
92
+ avoidText: args.avoid_text ?? current.avoidText,
93
+ textColor: args.text_color ?? current.textColor,
94
+ hitColor: args.hit_color ?? current.hitColor,
95
+ };
96
+ }
97
+ function invalidColor(args) {
98
+ if (args.text_color !== undefined && !isValidHex(args.text_color))
99
+ return `Invalid text_color hex: ${args.text_color}`;
100
+ if (args.hit_color !== undefined && !isValidHex(args.hit_color))
101
+ return `Invalid hit_color hex: ${args.hit_color}`;
102
+ return null;
103
+ }
104
+ /** Point every orientation's stored render settings at alive subtitles. */
105
+ async function applyToRenders(engine, assetId) {
106
+ for (const orientation of RENDER_ORIENTATIONS) {
107
+ const key = `${orientation}.render`;
108
+ const existing = await engine.metadata.artifacts.read(assetId, key);
109
+ const write = await engine.metadata.artifacts.write(assetId, key, {
110
+ ...(existing.ok ? existing.value : {}),
111
+ subtitlesEnabled: true,
112
+ highlightStyle: "alive",
113
+ });
114
+ if (!write.ok)
115
+ throw new Error(`Failed to persist ${orientation} render settings: ${write.error.message}`);
116
+ }
117
+ }
118
+ export function setAliveCaptionsDef(engine) {
119
+ return {
120
+ name: "set_alive_captions",
121
+ description: "Save a video's alive caption style (stack, punch or karaoke), word entrance (pop, slam or rise), energy sizing, onset snapping, face and text avoidance and colors; by default its renders switch to alive subtitles.",
122
+ schema: {
123
+ asset_id: z.string().describe("Video artifact id"),
124
+ book_id: z.string().describe("Book id"),
125
+ style: z.enum(ALIVE_STYLES).optional()
126
+ .describe("stack builds a stacked block word by word; punch shows one big word at a time; karaoke shows the phrase dimmed and lights words"),
127
+ motion: z.enum(ALIVE_MOTIONS).optional()
128
+ .describe("Word entrance animation"),
129
+ energy_sizing: z.boolean().optional().describe("Size lines by the vocal energy under each word"),
130
+ snap_to_onsets: z.boolean().optional().describe("Start words on detected vocal onsets instead of raw transcript times"),
131
+ avoid_faces: z.boolean().optional().describe("Keep captions off detected faces"),
132
+ avoid_text: z.boolean().optional().describe("Keep captions off detected on-screen text and graphics"),
133
+ text_color: z.string().optional().describe("Caption text color hex"),
134
+ hit_color: z.string().optional().describe("Color for the sung word and loud words, hex"),
135
+ apply_to_renders: z.boolean().optional().describe("Enable alive subtitles in stored render settings (default true)"),
136
+ },
137
+ handler: async (args) => {
138
+ const input = args;
139
+ const gate = videoGate(engine, input.asset_id, "set_alive_captions");
140
+ if (gate)
141
+ return gate;
142
+ const colorError = invalidColor(input);
143
+ if (colorError)
144
+ return errorResult("invalid_color", colorError);
145
+ const settings = mergeSettings(await readAliveSettings(engine, input.asset_id), input);
146
+ try {
147
+ await writeAliveSettings(engine, input.asset_id, settings);
148
+ if (input.apply_to_renders !== false)
149
+ await applyToRenders(engine, input.asset_id);
150
+ }
151
+ catch (error) {
152
+ return errorResult("settings_write_failed", error instanceof Error ? error.message : String(error));
153
+ }
154
+ return textResult({ asset_id: input.asset_id, status: "configured", settings, applied_to_renders: input.apply_to_renders !== false });
155
+ },
156
+ };
157
+ }