videobook 0.0.0-stage → 0.1.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 (558) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -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 +192 -0
  45. package/dist/cli/doctor-command.js +255 -0
  46. package/dist/cli/entry.js +2 -0
  47. package/dist/cli/main.js +91 -0
  48. package/dist/cli/mcp-command.js +82 -0
  49. package/dist/cli/mcp-relay.js +51 -0
  50. package/dist/cli/open-browser.js +14 -0
  51. package/dist/cli/run-command.js +187 -0
  52. package/dist/cli/serve-command.js +57 -0
  53. package/dist/cli/stdio-guard.js +8 -0
  54. package/dist/cold-open-model.js +7 -0
  55. package/dist/cold-open.js +79 -0
  56. package/dist/command-reference.js +95 -0
  57. package/dist/content-store.js +74 -0
  58. package/dist/cost.js +8 -0
  59. package/dist/crop-geometry.js +23 -0
  60. package/dist/dancing-workflow.js +446 -0
  61. package/dist/data-home.js +44 -0
  62. package/dist/diagnostics.js +109 -0
  63. package/dist/dialog-quality.js +34 -0
  64. package/dist/downloads-dest.js +56 -0
  65. package/dist/editing-moments.js +194 -0
  66. package/dist/elevenlabs-audio-extension.js +66 -0
  67. package/dist/engine.js +54 -0
  68. package/dist/env-secrets.js +184 -0
  69. package/dist/ffmpeg-binary.js +12 -0
  70. package/dist/ffmpeg.js +425 -0
  71. package/dist/ffprobe-binary.js +55 -0
  72. package/dist/forget-asset.js +216 -0
  73. package/dist/frame-utils.js +27 -0
  74. package/dist/generation-credit-error.js +11 -0
  75. package/dist/generation-input-mentions.js +117 -0
  76. package/dist/generation-input-policy.js +68 -0
  77. package/dist/generation-reference-mentions.js +97 -0
  78. package/dist/goal-characters.js +49 -0
  79. package/dist/goal-plan-v3.js +273 -0
  80. package/dist/goal-plan.js +220 -0
  81. package/dist/goal-planner-prompt.js +28 -0
  82. package/dist/goal-prompt-budget.js +4 -0
  83. package/dist/goal-reference-images.js +25 -0
  84. package/dist/goal-state.js +261 -0
  85. package/dist/goal-stills.js +153 -0
  86. package/dist/goal-store.js +144 -0
  87. package/dist/goal-styles.js +144 -0
  88. package/dist/goal-v3-runtime.js +157 -0
  89. package/dist/guides/character_schema_guide.md +70 -0
  90. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  91. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  92. package/dist/guides/image_reproduction_guide.md +84 -0
  93. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  94. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  95. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  96. package/dist/http-access.js +49 -0
  97. package/dist/http-byte-range.js +31 -0
  98. package/dist/image-compress.js +77 -0
  99. package/dist/image-edit-default.js +1 -0
  100. package/dist/image-edit-models.js +41 -0
  101. package/dist/image-edit-settings.js +11 -0
  102. package/dist/image-region-edit.js +32 -0
  103. package/dist/image-subject-map.js +523 -0
  104. package/dist/image.js +46 -0
  105. package/dist/index.js +12 -0
  106. package/dist/job-deadline.js +60 -0
  107. package/dist/jobs/_alibaba-upload.js +30 -0
  108. package/dist/jobs/_audio-chunks.js +31 -0
  109. package/dist/jobs/_download-into-asset.js +15 -0
  110. package/dist/jobs/_fal-shared.js +22 -0
  111. package/dist/jobs/_fal-upload.js +35 -0
  112. package/dist/jobs/_image-edit-adapter.js +213 -0
  113. package/dist/jobs/_kie-poll.js +119 -0
  114. package/dist/jobs/_kie-upload.js +32 -0
  115. package/dist/jobs/_lease.js +163 -0
  116. package/dist/jobs/_persist-output.js +19 -0
  117. package/dist/jobs/_storage-retry.js +21 -0
  118. package/dist/jobs/_transcribe.js +245 -0
  119. package/dist/jobs/_translate-transcript.js +270 -0
  120. package/dist/jobs/analyze-alive-captions.js +10 -0
  121. package/dist/jobs/analyze-editing-moments.js +104 -0
  122. package/dist/jobs/analyze-jokes.js +143 -0
  123. package/dist/jobs/analyze-keyword-placement.js +155 -0
  124. package/dist/jobs/analyze-music.js +14 -0
  125. package/dist/jobs/analyze-video.js +282 -0
  126. package/dist/jobs/apply-cuts.js +83 -0
  127. package/dist/jobs/apply-sfx.js +107 -0
  128. package/dist/jobs/archive-clip.js +457 -0
  129. package/dist/jobs/assemble-goal.js +98 -0
  130. package/dist/jobs/audio-boost.js +71 -0
  131. package/dist/jobs/audio-mix.js +161 -0
  132. package/dist/jobs/backfill-asset-tags.js +144 -0
  133. package/dist/jobs/burn-watermark.js +316 -0
  134. package/dist/jobs/change-video-speed.js +176 -0
  135. package/dist/jobs/clean-video-audio.js +207 -0
  136. package/dist/jobs/cold-open.js +166 -0
  137. package/dist/jobs/combine-goal.js +108 -0
  138. package/dist/jobs/complete-provider-task.js +185 -0
  139. package/dist/jobs/copy-library-asset.js +33 -0
  140. package/dist/jobs/cost-estimate-assessment.js +113 -0
  141. package/dist/jobs/crop-image.js +47 -0
  142. package/dist/jobs/crop-video.js +71 -0
  143. package/dist/jobs/describe-image.js +181 -0
  144. package/dist/jobs/detect-scenes.js +33 -0
  145. package/dist/jobs/doctor-fix.js +100 -0
  146. package/dist/jobs/download-video.js +426 -0
  147. package/dist/jobs/enqueue-search-index.js +24 -0
  148. package/dist/jobs/even-split.js +122 -0
  149. package/dist/jobs/export-sequence.js +365 -0
  150. package/dist/jobs/extend-audio.js +229 -0
  151. package/dist/jobs/extract-audio.js +72 -0
  152. package/dist/jobs/extract-frame.js +58 -0
  153. package/dist/jobs/extract-notebook-audio.js +82 -0
  154. package/dist/jobs/extract-notebook-frame.js +93 -0
  155. package/dist/jobs/extract-video-clip.js +8 -0
  156. package/dist/jobs/extract-video-poster.js +55 -0
  157. package/dist/jobs/extract-waveform.js +25 -0
  158. package/dist/jobs/finish-goal.js +255 -0
  159. package/dist/jobs/forget-asset.js +11 -0
  160. package/dist/jobs/generate-acestep-cover.js +219 -0
  161. package/dist/jobs/generate-media.js +70 -0
  162. package/dist/jobs/image-subjects.js +1059 -0
  163. package/dist/jobs/index.js +18 -0
  164. package/dist/jobs/inject-first-frame.js +175 -0
  165. package/dist/jobs/isolate-vocals.js +69 -0
  166. package/dist/jobs/kie-paygate.js +177 -0
  167. package/dist/jobs/label-music-sections.js +5 -0
  168. package/dist/jobs/osg-placement.js +135 -0
  169. package/dist/jobs/pad-video.js +72 -0
  170. package/dist/jobs/plan-goal.js +97 -0
  171. package/dist/jobs/post-record.js +21 -0
  172. package/dist/jobs/post-to-x.js +21 -0
  173. package/dist/jobs/prepare-search-reference.js +190 -0
  174. package/dist/jobs/prepare-temporal-search.js +16 -0
  175. package/dist/jobs/process-audio.js +67 -0
  176. package/dist/jobs/process-image.js +58 -0
  177. package/dist/jobs/process-upload.js +166 -0
  178. package/dist/jobs/process-video.js +438 -0
  179. package/dist/jobs/register-all.js +161 -0
  180. package/dist/jobs/relayout-notebook.js +78 -0
  181. package/dist/jobs/remove-sound.js +299 -0
  182. package/dist/jobs/render-project.js +380 -0
  183. package/dist/jobs/render-qr-outro.js +138 -0
  184. package/dist/jobs/render-timeline.js +283 -0
  185. package/dist/jobs/repair-search-indexes.js +42 -0
  186. package/dist/jobs/replace-video-audio.js +64 -0
  187. package/dist/jobs/restore-archive-clip.js +53 -0
  188. package/dist/jobs/reverse-video.js +53 -0
  189. package/dist/jobs/rewrite-script.js +173 -0
  190. package/dist/jobs/run-notebook.js +3335 -0
  191. package/dist/jobs/semantic-index.js +797 -0
  192. package/dist/jobs/smooth-scene-concat.js +257 -0
  193. package/dist/jobs/splice-video.js +280 -0
  194. package/dist/jobs/split-video.js +127 -0
  195. package/dist/jobs/suggest-cuts.js +149 -0
  196. package/dist/jobs/suggest-sfx.js +135 -0
  197. package/dist/jobs/sync-video-to-audio.js +329 -0
  198. package/dist/jobs/tag-asset.js +266 -0
  199. package/dist/jobs/task-failure.js +16 -0
  200. package/dist/jobs/timewarp-video.js +152 -0
  201. package/dist/jobs/transcribe-video-audio.js +117 -0
  202. package/dist/jobs/transcribe.js +35 -0
  203. package/dist/jobs/translate-transcript.js +55 -0
  204. package/dist/jobs/trim-audio.js +133 -0
  205. package/dist/jobs/trim-video.js +291 -0
  206. package/dist/jobs/xai-paygate.js +26 -0
  207. package/dist/keyword-placement-cache.js +74 -0
  208. package/dist/lazy-render.js +142 -0
  209. package/dist/library-browse.js +78 -0
  210. package/dist/lightning-defaults.js +206 -0
  211. package/dist/load-env.js +9 -0
  212. package/dist/local-search-models.js +206 -0
  213. package/dist/local-upload.js +318 -0
  214. package/dist/logger.js +69 -0
  215. package/dist/media/adapters/alibaba.js +20 -0
  216. package/dist/media/adapters/elevenlabs.js +58 -0
  217. package/dist/media/adapters/fal.js +45 -0
  218. package/dist/media/adapters/index.js +26 -0
  219. package/dist/media/adapters/kie.js +120 -0
  220. package/dist/media/adapters/pending.js +21 -0
  221. package/dist/media/adapters/types.js +5 -0
  222. package/dist/media/adapters/xai.js +18 -0
  223. package/dist/media/catalog.js +236 -0
  224. package/dist/media/complete.js +191 -0
  225. package/dist/media/cost.js +45 -0
  226. package/dist/media/descriptor.js +157 -0
  227. package/dist/media/endpoints.js +11 -0
  228. package/dist/media/frame-options.js +18 -0
  229. package/dist/media/insert-video-timing.js +17 -0
  230. package/dist/media/json-schema.js +75 -0
  231. package/dist/media/legacy-task-types.js +129 -0
  232. package/dist/media/listing.js +24 -0
  233. package/dist/media/model-id.js +14 -0
  234. package/dist/media/preview.js +86 -0
  235. package/dist/media/prompt-dialects.js +65 -0
  236. package/dist/media/ref-fields.js +106 -0
  237. package/dist/media/refs.js +131 -0
  238. package/dist/media/request.js +188 -0
  239. package/dist/media/types.js +6 -0
  240. package/dist/media/wire.js +161 -0
  241. package/dist/media-library.js +535 -0
  242. package/dist/media-output-path.js +33 -0
  243. package/dist/multyculty-workflow.js +757 -0
  244. package/dist/notebook-action-catalog.js +608 -0
  245. package/dist/notebook-cell-outputs.js +726 -0
  246. package/dist/notebook-credit-retry.js +45 -0
  247. package/dist/notebook-fixtures.js +8 -0
  248. package/dist/notebook-generation-contract.js +77 -0
  249. package/dist/notebook-generation-controls.js +145 -0
  250. package/dist/notebook-goal-layout.js +40 -0
  251. package/dist/notebook-graph.js +37 -0
  252. package/dist/notebook-image-edit.js +84 -0
  253. package/dist/notebook-initial-generation-prompt.js +222 -0
  254. package/dist/notebook-live-analysis.js +941 -0
  255. package/dist/notebook-media-ops.js +579 -0
  256. package/dist/notebook-mutations.js +20 -0
  257. package/dist/notebook-placement.js +41 -0
  258. package/dist/notebook-provider-failure.js +87 -0
  259. package/dist/notebook-published-post.js +26 -0
  260. package/dist/notebook-reference-resolver.js +227 -0
  261. package/dist/notebook-relayout.js +270 -0
  262. package/dist/notebook-run-plan.js +519 -0
  263. package/dist/notebook-shots.js +162 -0
  264. package/dist/notebook-workflow.js +856 -0
  265. package/dist/openai-intelligence.js +99 -0
  266. package/dist/original-media-file.js +12 -0
  267. package/dist/osg-assets.js +36 -0
  268. package/dist/osg-placement-cache.js +64 -0
  269. package/dist/osg-settings.js +27 -0
  270. package/dist/paid-tools.js +22 -0
  271. package/dist/platform-guidance.js +26 -0
  272. package/dist/posts/dropbox-auth.js +91 -0
  273. package/dist/posts/dropbox-upload.js +132 -0
  274. package/dist/posts/icloud-drive.js +134 -0
  275. package/dist/posts/icloud-status.js +64 -0
  276. package/dist/posts/post-contract.js +2 -0
  277. package/dist/posts/post-copy-format.js +43 -0
  278. package/dist/posts/post-copy.js +193 -0
  279. package/dist/posts/post-folder.js +18 -0
  280. package/dist/posts/post-metrics.js +119 -0
  281. package/dist/posts/post-store.js +340 -0
  282. package/dist/posts/x-post-target.js +43 -0
  283. package/dist/preview-payload.js +90 -0
  284. package/dist/prompt-surfaces.js +22 -0
  285. package/dist/provider-prompt-transform.js +77 -0
  286. package/dist/provider-status-link.js +21 -0
  287. package/dist/provider-task-record.js +78 -0
  288. package/dist/provider-task-status.js +47 -0
  289. package/dist/provider-usage.js +294 -0
  290. package/dist/queue.js +806 -0
  291. package/dist/reference-video-duration.js +17 -0
  292. package/dist/remotion/BlurFillVideo.js +51 -0
  293. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  294. package/dist/remotion/OsgOverlay.js +30 -0
  295. package/dist/remotion/QrOutroComposition.js +47 -0
  296. package/dist/remotion/RenderComposition.js +11 -0
  297. package/dist/remotion/browser.js +59 -0
  298. package/dist/remotion/constants.js +35 -0
  299. package/dist/remotion/index.js +66 -0
  300. package/dist/remotion/keyword-graphics.js +151 -0
  301. package/dist/remotion/keyword-placement.js +182 -0
  302. package/dist/remotion/osg-spec.js +198 -0
  303. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  304. package/dist/remotion/qr-outro-audio.js +1 -0
  305. package/dist/remotion/qr-outro-spec.js +211 -0
  306. package/dist/remotion/shared-bundle.js +37 -0
  307. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  308. package/dist/remotion/subtitles/Highlight.js +58 -0
  309. package/dist/remotion/subtitles/Impact.js +67 -0
  310. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  311. package/dist/remotion/subtitles/alive-layout.js +321 -0
  312. package/dist/remotion/subtitles/alive-types.js +17 -0
  313. package/dist/remotion/subtitles/chunker.js +68 -0
  314. package/dist/remotion/subtitles/segments.js +42 -0
  315. package/dist/remotion/subtitles/styles.js +4 -0
  316. package/dist/remotion/subtitles/types.js +1 -0
  317. package/dist/remotion/util.js +18 -0
  318. package/dist/remotion-cancellation.js +15 -0
  319. package/dist/rename-helpers.js +204 -0
  320. package/dist/rhythm/analyze.js +128 -0
  321. package/dist/rhythm/contract.js +14 -0
  322. package/dist/rhythm/draft-model.js +19 -0
  323. package/dist/rhythm/energy.js +133 -0
  324. package/dist/rhythm/fft.js +44 -0
  325. package/dist/rhythm/model.js +71 -0
  326. package/dist/rhythm/music-analysis.js +124 -0
  327. package/dist/rhythm/music-draft.js +261 -0
  328. package/dist/rhythm/onset.js +101 -0
  329. package/dist/rhythm/run-worker.js +51 -0
  330. package/dist/rhythm/score.js +47 -0
  331. package/dist/rhythm/section-labels.js +168 -0
  332. package/dist/rhythm/sequence-rhythm.js +67 -0
  333. package/dist/rhythm/synthesize.js +103 -0
  334. package/dist/rhythm/timing.js +98 -0
  335. package/dist/rhythm/track.js +320 -0
  336. package/dist/rhythm/visual-fit.js +4 -0
  337. package/dist/rhythm/worker.js +25 -0
  338. package/dist/scene-detect.js +200 -0
  339. package/dist/scene-transition.js +12 -0
  340. package/dist/scheduled-posts.js +200 -0
  341. package/dist/search-index-coverage.js +13 -0
  342. package/dist/search-index-media.js +71 -0
  343. package/dist/search-reference-store.js +60 -0
  344. package/dist/search-scoring.js +17 -0
  345. package/dist/serve.js +150 -0
  346. package/dist/server-lock.js +143 -0
  347. package/dist/server.js +42 -0
  348. package/dist/srt.js +16 -0
  349. package/dist/tag-backfill-state.js +121 -0
  350. package/dist/tag-settings.js +30 -0
  351. package/dist/tag-vocabulary.js +237 -0
  352. package/dist/test-queue-cleanup.js +1 -0
  353. package/dist/time-parse.js +50 -0
  354. package/dist/timeline-helpers.js +554 -0
  355. package/dist/timewarp.js +87 -0
  356. package/dist/tools/_image-inputs.js +9 -0
  357. package/dist/tools/_media-refs.js +84 -0
  358. package/dist/tools/_shared.js +59 -0
  359. package/dist/tools/_suggest-shared.js +279 -0
  360. package/dist/tools/add-timeline-audio.js +73 -0
  361. package/dist/tools/alive-captions.js +157 -0
  362. package/dist/tools/analyze-jokes.js +56 -0
  363. package/dist/tools/analyze-video.js +66 -0
  364. package/dist/tools/animate-keywords.js +217 -0
  365. package/dist/tools/api-status.js +42 -0
  366. package/dist/tools/application-commands.js +34 -0
  367. package/dist/tools/apply-cuts.js +112 -0
  368. package/dist/tools/apply-sfx.js +152 -0
  369. package/dist/tools/apply-watermark.js +670 -0
  370. package/dist/tools/archive-clip.js +74 -0
  371. package/dist/tools/asset-history.js +61 -0
  372. package/dist/tools/asset-manifest.js +24 -0
  373. package/dist/tools/asset-tags.js +151 -0
  374. package/dist/tools/assign-slot.js +97 -0
  375. package/dist/tools/audio-boost.js +107 -0
  376. package/dist/tools/audio-mix.js +121 -0
  377. package/dist/tools/book-usage.js +16 -0
  378. package/dist/tools/chain.js +136 -0
  379. package/dist/tools/change-video-speed.js +105 -0
  380. package/dist/tools/chat-history.js +5 -0
  381. package/dist/tools/chat.js +848 -0
  382. package/dist/tools/clean-video-audio.js +97 -0
  383. package/dist/tools/clear-failed-tasks.js +66 -0
  384. package/dist/tools/cold-open.js +114 -0
  385. package/dist/tools/command-plan.js +1680 -0
  386. package/dist/tools/commands.js +1666 -0
  387. package/dist/tools/concat-images.js +236 -0
  388. package/dist/tools/connect-service.js +55 -0
  389. package/dist/tools/copy-file-to-clipboard.js +58 -0
  390. package/dist/tools/create-character.js +114 -0
  391. package/dist/tools/create-qart.js +253 -0
  392. package/dist/tools/create-qr-outro.js +149 -0
  393. package/dist/tools/create-qrbtf.js +215 -0
  394. package/dist/tools/create-watermark.js +97 -0
  395. package/dist/tools/crop-asset.js +18 -0
  396. package/dist/tools/crop-image.js +120 -0
  397. package/dist/tools/crop-video.js +97 -0
  398. package/dist/tools/delete-asset.js +104 -0
  399. package/dist/tools/describe-image.js +69 -0
  400. package/dist/tools/detect-scenes.js +44 -0
  401. package/dist/tools/doctor.js +126 -0
  402. package/dist/tools/download-video.js +114 -0
  403. package/dist/tools/duplicate-asset.js +184 -0
  404. package/dist/tools/edit-timeline.js +59 -0
  405. package/dist/tools/editing-moments.js +105 -0
  406. package/dist/tools/estimate-cost.js +171 -0
  407. package/dist/tools/even-split.js +295 -0
  408. package/dist/tools/export-asset-file.js +22 -0
  409. package/dist/tools/export-asset.js +140 -0
  410. package/dist/tools/export-sequence.js +250 -0
  411. package/dist/tools/export-to-downloads.js +54 -0
  412. package/dist/tools/export-transcript-srt.js +79 -0
  413. package/dist/tools/extend-audio.js +100 -0
  414. package/dist/tools/extract-audio.js +174 -0
  415. package/dist/tools/extract-frame.js +108 -0
  416. package/dist/tools/extract-notebook-audio.js +173 -0
  417. package/dist/tools/extract-notebook-frame.js +160 -0
  418. package/dist/tools/extract-video-clip.js +101 -0
  419. package/dist/tools/generate-media.js +157 -0
  420. package/dist/tools/generation-payload.js +48 -0
  421. package/dist/tools/get-timeline.js +12 -0
  422. package/dist/tools/get-watermark-config.js +38 -0
  423. package/dist/tools/goal.js +644 -0
  424. package/dist/tools/image-subjects.js +711 -0
  425. package/dist/tools/index.js +323 -0
  426. package/dist/tools/inject-first-frame.js +154 -0
  427. package/dist/tools/invert-image.js +70 -0
  428. package/dist/tools/isolate-vocals.js +66 -0
  429. package/dist/tools/list-assets.js +98 -0
  430. package/dist/tools/list-cell-generations.js +225 -0
  431. package/dist/tools/list-elevenlabs-voices.js +163 -0
  432. package/dist/tools/log-wrapper.js +29 -0
  433. package/dist/tools/make-cover-song.js +95 -0
  434. package/dist/tools/mark-asset-seen.js +23 -0
  435. package/dist/tools/media-library.js +245 -0
  436. package/dist/tools/move-timeline-audio.js +35 -0
  437. package/dist/tools/music-analysis.js +60 -0
  438. package/dist/tools/music-draft.js +29 -0
  439. package/dist/tools/pad-video.js +87 -0
  440. package/dist/tools/post-record.js +83 -0
  441. package/dist/tools/post.js +168 -0
  442. package/dist/tools/prompt-history.js +164 -0
  443. package/dist/tools/provider-key-status.js +248 -0
  444. package/dist/tools/qrbtf-render.js +849 -0
  445. package/dist/tools/read-asset-metadata.js +26 -0
  446. package/dist/tools/read-file-at-commit.js +29 -0
  447. package/dist/tools/read-transcript.js +41 -0
  448. package/dist/tools/reconcile-project-work.js +1452 -0
  449. package/dist/tools/recover-asset.js +28 -0
  450. package/dist/tools/remove-sound.js +72 -0
  451. package/dist/tools/remove-timeline-audio.js +34 -0
  452. package/dist/tools/remove-timeline-slot.js +40 -0
  453. package/dist/tools/rename-all.js +434 -0
  454. package/dist/tools/rename-asset.js +34 -0
  455. package/dist/tools/render-settings.js +47 -0
  456. package/dist/tools/render-timeline.js +120 -0
  457. package/dist/tools/render.js +384 -0
  458. package/dist/tools/reorder-character-images.js +45 -0
  459. package/dist/tools/replace-video-audio.js +133 -0
  460. package/dist/tools/restore-archive-clips.js +209 -0
  461. package/dist/tools/restore-asset-version.js +37 -0
  462. package/dist/tools/retry-generation.js +54 -0
  463. package/dist/tools/reverse-video.js +74 -0
  464. package/dist/tools/rewrite-prompt.js +388 -0
  465. package/dist/tools/rewrite-script.js +67 -0
  466. package/dist/tools/schema-from-apicity.js +66 -0
  467. package/dist/tools/search-archive.js +150 -0
  468. package/dist/tools/semantic-search.js +1214 -0
  469. package/dist/tools/sequence-rhythm.js +43 -0
  470. package/dist/tools/set-character-metadata.js +84 -0
  471. package/dist/tools/set-env-secret.js +40 -0
  472. package/dist/tools/set-osg.js +143 -0
  473. package/dist/tools/show-folder.js +37 -0
  474. package/dist/tools/smooth-scene-concat.js +122 -0
  475. package/dist/tools/splice-video.js +149 -0
  476. package/dist/tools/split-video.js +202 -0
  477. package/dist/tools/suggest-cuts.js +45 -0
  478. package/dist/tools/suggest-sfx.js +43 -0
  479. package/dist/tools/sync-video-to-audio.js +115 -0
  480. package/dist/tools/tag-backfill.js +177 -0
  481. package/dist/tools/timewarp-video.js +85 -0
  482. package/dist/tools/tool-def.js +1 -0
  483. package/dist/tools/tool-json-schema.js +116 -0
  484. package/dist/tools/transcribe.js +90 -0
  485. package/dist/tools/translate-subtitles.js +59 -0
  486. package/dist/tools/trim-audio.js +139 -0
  487. package/dist/tools/trim-video.js +168 -0
  488. package/dist/tools/update-script.js +25 -0
  489. package/dist/tools/update-timeline-audio.js +40 -0
  490. package/dist/tools/upload-file.js +41 -0
  491. package/dist/tools/verify-x-mentions.js +141 -0
  492. package/dist/tools/video-editing-command-contract.js +291 -0
  493. package/dist/tools/video-source-reference.js +30 -0
  494. package/dist/tools/video-time-input.js +18 -0
  495. package/dist/tools/videobook-domain.js +2006 -0
  496. package/dist/tools/wait-for-asset-ready.js +143 -0
  497. package/dist/tools/workspace-actions.js +54 -0
  498. package/dist/transcript-projection.js +202 -0
  499. package/dist/transcript-srt.js +33 -0
  500. package/dist/upload.js +76 -0
  501. package/dist/usage-providers.js +25 -0
  502. package/dist/video-analysis-projection.js +46 -0
  503. package/dist/video-poster.js +33 -0
  504. package/dist/video-proxy.js +99 -0
  505. package/dist/vision-regions.js +111 -0
  506. package/dist/voice-actions.js +90 -0
  507. package/dist/voice-audio.js +55 -0
  508. package/dist/voice-config.js +122 -0
  509. package/dist/voice-continuation.js +54 -0
  510. package/dist/voice-contract.js +1 -0
  511. package/dist/voice-http.js +145 -0
  512. package/dist/voice-reply-progress.js +55 -0
  513. package/dist/voice-session.js +992 -0
  514. package/dist/watermark-badge.js +259 -0
  515. package/dist/watermark-config.js +111 -0
  516. package/dist/watermark-handle-position.js +55 -0
  517. package/dist/watermark-platform.js +15 -0
  518. package/dist/watermark-timing.js +16 -0
  519. package/dist/workspace-action-catalog.js +315 -0
  520. package/dist/workspace-action-contract.js +21 -0
  521. package/dist/workspace-action-groups.js +98 -0
  522. package/dist/workspace-action-surfaces.js +7 -0
  523. package/dist/x/account.js +88 -0
  524. package/dist/x/authenticated-handle.js +30 -0
  525. package/dist/x/oauth-flow.js +191 -0
  526. package/dist/x/post-contract.js +1 -0
  527. package/dist/x/post-metadata.js +44 -0
  528. package/dist/x/post-to-x.js +456 -0
  529. package/dist/x/published-post-cell.js +90 -0
  530. package/dist/x/x-tokens.js +69 -0
  531. package/dist/zod-slots.js +334 -0
  532. package/package.json +92 -4
  533. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  534. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  535. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  536. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  537. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  538. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  539. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  540. package/ui/assets/index-DzPAnCUj.css +1 -0
  541. package/ui/assets/index-kS1905s5.js +37 -0
  542. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  543. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  544. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  545. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  546. package/ui/assets/ui-Oirr9VOf.css +1 -0
  547. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  548. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  549. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  550. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  551. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  552. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  553. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  554. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  555. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  556. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  557. package/ui/favicon.svg +8 -0
  558. package/ui/index.html +126 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `videobook` npm package are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and the package uses [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-19
10
+
11
+ ### Added
12
+
13
+ - `videobook` command-line interface: `serve` runs the MCP server with the
14
+ bundled web UI (or `--headless` for MCP and API only), `mcp` serves MCP over
15
+ stdio for agent hosts, `run` and `tools` call MCP tools from the shell,
16
+ `gc-books` reclaims catalog storage, and `doctor` checks the local
17
+ environment.
18
+ - Installed data defaults to `~/.videobook`, overridable with `VIDEOBOOK_HOME`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Tanner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,134 @@
1
- # Temporary Holding Version
1
+ # videobook
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Videobook is a local-first notebook studio for generative media: images,
4
+ videos, audio, prompts, characters and scenes live as cells in an executable
5
+ graph, and every change is recorded as a revision. This package installs one
6
+ `videobook` command that runs the Videobook MCP server three ways:
7
+
8
+ - with the web UI at `http://127.0.0.1:3001/`
9
+ - headless, serving MCP and the HTTP API only
10
+ - over stdio, for agent hosts such as Claude Code
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 22 or newer.
15
+ - macOS (Apple Silicon or Intel) or Linux (x64 or arm64). Windows x64 has
16
+ every native binary but is untested.
17
+ - About 1.2 GB of disk. Installing downloads the ffmpeg and yt-dlp binaries
18
+ from GitHub, and the first video render downloads Chrome Headless Shell
19
+ (about 150 MB) into `~/.cache/videobook/remotion`.
20
+ - `ffmpeg` and `ffprobe` on `PATH` are recommended. Apple Silicon and Linux
21
+ arm64 always use the system `ffprobe`, and every platform falls back to the
22
+ system binaries when the bundled ones are missing.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install -g videobook
28
+ videobook doctor
29
+ ```
30
+
31
+ npm 12 and newer skip dependency install scripts unless you approve them.
32
+ Approve the six that videobook needs, either per install or once for your
33
+ user:
34
+
35
+ ```bash
36
+ npm install -g videobook --allow-scripts=ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild
37
+ npm config set allow-scripts=ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild --location=user
38
+ ```
39
+
40
+ - `ffmpeg-static` downloads ffmpeg, which every media operation needs.
41
+ - `ytdlp-nodejs` downloads yt-dlp, which downloading videos from URLs needs.
42
+ - `@dolthub/doltlite`, `usearch` and `onnxruntime-node` use prebuilt native
43
+ binaries and only compile or fetch extras on unusual platforms.
44
+ - `esbuild` checks its platform binary for the Remotion renderer.
45
+
46
+ `videobook doctor` reports anything missing and prints the fix.
47
+
48
+ ## Run
49
+
50
+ ```bash
51
+ videobook # same as videobook serve
52
+ videobook serve --open # serve MCP and the web UI, open a browser
53
+ videobook serve --headless # MCP and HTTP API only
54
+ videobook mcp # MCP over stdio for agent hosts
55
+ videobook run list_books # call one MCP tool and print its result
56
+ videobook run create_book name=demo
57
+ videobook tools # list tools and their parameters
58
+ videobook gc-books # reclaim catalog storage in every book
59
+ videobook doctor # check Node, binaries, native modules and keys
60
+ videobook --version
61
+ ```
62
+
63
+ Every command accepts `--port <n>` (default 3001), `--library <dir>`,
64
+ `--home <dir>` and `--log-level <silent|error|warn|info|debug>`. Tool
65
+ arguments are `key=value`; values that parse as JSON are passed as JSON. Exit
66
+ codes are 0 for success, 1 for a failure and 2 for a usage error.
67
+
68
+ ## Agent hosts
69
+
70
+ ```bash
71
+ claude mcp add videobook -- videobook mcp
72
+ ```
73
+
74
+ Other hosts take the same command, for example
75
+ `{ "command": "videobook", "args": ["mcp"] }`. stdio accepts clients that
76
+ start with the 2025-era `initialize` and clients that use MCP 2026-07-28.
77
+ While `videobook serve` runs, HTTP clients can connect to
78
+ `http://127.0.0.1:3001/mcp`, which speaks MCP 2026-07-28 only.
79
+
80
+ ## One server per library
81
+
82
+ A library is owned by one videobook process at a time, recorded in
83
+ `<library>/.videobook-server.json`. A second `videobook serve` for the same
84
+ library exits and names the running URL. `videobook mcp` and `videobook run`
85
+ relay to the running server when there is one; otherwise they start one in
86
+ their own process, which `videobook mcp` keeps serving HTTP and the web UI
87
+ for as long as the agent host keeps it open. `videobook gc-books` refuses to
88
+ run while a server owns the library.
89
+
90
+ ## Data and configuration
91
+
92
+ Installed data lives in `~/.videobook`:
93
+
94
+ - `books/`, the library of books
95
+ - `app-state.json`, connected X and Dropbox accounts
96
+ - `.env`, provider keys, also editable from Settings in the web UI
97
+
98
+ `VIDEOBOOK_HOME` or `--home` moves all three. `VIDEOBOOK_LIBRARY_DIR`,
99
+ `VIDEOBOOK_APP_STATE_PATH` and `VIDEOBOOK_ENV_PATH` move one each; `--home`
100
+ overrides any of them inherited from your shell. Set `VIDEOBOOK_HOME` and
101
+ `VIDEOBOOK_ENV_PATH` in the environment, not in `.env`, because they decide
102
+ which `.env` is read.
103
+
104
+ Provider keys: `OPENAI_API_KEY`, `KIE_API_KEY`, `XAI_API_KEY`, `FAL_API_KEY`,
105
+ `DASHSCOPE_API_KEY`, `ELEVENLABS_API_KEY`, `KIMI_CODING_API_KEY`,
106
+ `X_CLIENT_ID` and `X_CLIENT_SECRET`, `DROPBOX_CLIENT_ID` and
107
+ `DROPBOX_CLIENT_SECRET`, and the `B2_*` storage settings. The server starts
108
+ without any of them; tools that need one report which.
109
+
110
+ Caches live in `~/.cache/videobook`. Other settings: `PORT`,
111
+ `VIDEOBOOK_FFMPEG_PATH`, `VIDEOBOOK_FFPROBE_PATH`,
112
+ `VIDEOBOOK_REMOTION_CACHE_DIR`, `VIDEOBOOK_MODEL_CACHE_DIR`,
113
+ `VIDEOBOOK_DOWNLOADS_DIR` and `VIDEOBOOK_LOG_LEVEL`.
114
+
115
+ ## Platform notes
116
+
117
+ - macOS on Apple Silicon is the primary platform. The bundled ffprobe is an
118
+ Intel build, so install `ffprobe` with `brew install ffmpeg`.
119
+ - macOS on Intel works, but ONNX Runtime has no Intel Mac build, so semantic
120
+ search is unavailable there.
121
+ - Linux x64 is supported. Linux arm64 needs `ffprobe` from the system package
122
+ manager.
123
+ - Windows x64 is untested.
124
+
125
+ ## Security
126
+
127
+ Videobook is a local, single-user service. It listens on `127.0.0.1` only and
128
+ accepts requests from loopback hosts and origins. It has no user
129
+ authentication, so do not expose it to other machines or share it between
130
+ users.
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ const major = Number(process.versions.node.split(".")[0]);
3
+ if (major < 22) {
4
+ process.stderr.write(`videobook needs Node.js 22 or newer; this is ${process.version}.\nInstall Node.js 22+ from https://nodejs.org or your version manager, then run videobook again.\n`);
5
+ process.exit(1);
6
+ }
7
+ await import(new URL("../dist/cli/entry.js", import.meta.url).href);
@@ -0,0 +1,203 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { AceStepError } from "./errors.js";
3
+ // The live ace-server job flow (tools/ace-server.cpp, verified against version
4
+ // 9761469): POST /synth returns {"id"} immediately, a FIFO worker thread runs
5
+ // the GPU job, GET /job?id reports running|done|failed|cancelled, GET
6
+ // /job?id&result=1 serves the multipart/mixed body (fixed boundary
7
+ // "ace-batch-boundary": one audio part plus one latent part per track), and
8
+ // POST /job?id&cancel=1 cancels a running job. Completed jobs are evicted FIFO
9
+ // past 32, so the result is fetched immediately after the poll reports done.
10
+ const DEFAULT_SYNTH_TIMEOUT_MS = 30 * 60_000;
11
+ const DEFAULT_POLL_INTERVAL_MS = 3_000;
12
+ const SUBMIT_TIMEOUT_MS = 60_000;
13
+ const POLL_TIMEOUT_MS = 5_000;
14
+ const RESULT_TIMEOUT_MS = 120_000;
15
+ const ERROR_BODY_PREVIEW_CHARS = 500;
16
+ export async function isHealthy(baseUrl, timeoutMs = 500) {
17
+ try {
18
+ const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
19
+ return response.ok;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ function wireRequest(request) {
26
+ return {
27
+ caption: request.caption,
28
+ lyrics: request.lyrics,
29
+ task_type: request.taskType,
30
+ output_format: request.outputFormat,
31
+ seed: request.seed,
32
+ inference_steps: request.inferenceSteps,
33
+ audio_cover_strength: request.audioCoverStrength,
34
+ synth_batch_size: request.synthBatchSize,
35
+ synth_model: request.synthModel,
36
+ duration: request.duration,
37
+ };
38
+ }
39
+ function headerRecord(headerBlock) {
40
+ const headers = {};
41
+ for (const line of headerBlock.split("\r\n")) {
42
+ const colon = line.indexOf(":");
43
+ if (colon === -1)
44
+ continue;
45
+ headers[line.slice(0, colon).trim().toLowerCase()] = line.slice(colon + 1).trim();
46
+ }
47
+ return headers;
48
+ }
49
+ export function parseMultipartMixed(body, contentType) {
50
+ const boundaryMatch = /boundary="?([^";]+)"?/u.exec(contentType);
51
+ if (boundaryMatch === null) {
52
+ throw new AceStepError("malformed_response", `ace-server response has no multipart boundary (content-type: "${contentType}")`);
53
+ }
54
+ const delimiter = Buffer.from(`--${boundaryMatch[1]}`);
55
+ const parts = [];
56
+ let offset = body.indexOf(delimiter);
57
+ while (offset !== -1) {
58
+ const afterDelimiter = offset + delimiter.length;
59
+ if (body.subarray(afterDelimiter, afterDelimiter + 2).toString("latin1") === "--")
60
+ break;
61
+ const boundaryLineEnd = body.indexOf("\r\n", afterDelimiter);
62
+ const headerEnd = body.indexOf("\r\n\r\n", afterDelimiter);
63
+ const next = headerEnd === -1 ? -1 : body.indexOf(delimiter, headerEnd + 4);
64
+ if (boundaryLineEnd === -1 || headerEnd === -1 || next === -1) {
65
+ throw new AceStepError("malformed_response", "ace-server multipart response is truncated");
66
+ }
67
+ const headerBlock = body.subarray(boundaryLineEnd + 2, headerEnd).toString("utf8");
68
+ let dataEnd = next;
69
+ if (body.subarray(dataEnd - 2, dataEnd).toString("latin1") === "\r\n")
70
+ dataEnd -= 2;
71
+ parts.push({ headers: headerRecord(headerBlock), data: body.subarray(headerEnd + 4, dataEnd) });
72
+ offset = next;
73
+ }
74
+ if (parts.length === 0) {
75
+ throw new AceStepError("malformed_response", "ace-server multipart response contained no parts");
76
+ }
77
+ return parts;
78
+ }
79
+ function describeFetchError(error) {
80
+ if (error instanceof Error) {
81
+ const cause = error.cause instanceof Error ? `: ${error.cause.message}` : "";
82
+ return `${error.message}${cause}`;
83
+ }
84
+ return String(error);
85
+ }
86
+ async function readErrorPreview(response) {
87
+ const bodyText = await response.text().catch(() => "");
88
+ return bodyText.slice(0, ERROR_BODY_PREVIEW_CHARS);
89
+ }
90
+ async function submitSynthJob(baseUrl, srcWavPath, request, signal) {
91
+ const sourceBytes = await readFile(srcWavPath);
92
+ const form = new FormData();
93
+ form.set("request", new Blob([JSON.stringify(wireRequest(request))], { type: "application/json" }), "request.json");
94
+ form.set("audio", new Blob([sourceBytes], { type: "audio/wav" }), "source.wav");
95
+ const response = await fetch(`${baseUrl}/synth`, { method: "POST", body: form, signal });
96
+ if (!response.ok) {
97
+ throw new AceStepError("synth_http_error", `ace-server /synth returned ${response.status}: ${await readErrorPreview(response)}`);
98
+ }
99
+ const body = (await response.json().catch(() => null));
100
+ if (typeof body?.id !== "string" || body.id.length === 0) {
101
+ throw new AceStepError("malformed_response", "ace-server /synth returned no job id");
102
+ }
103
+ return body.id;
104
+ }
105
+ async function readJobStatus(baseUrl, jobId) {
106
+ const response = await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}`, {
107
+ signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
108
+ });
109
+ if (!response.ok) {
110
+ throw new AceStepError("synth_http_error", `ace-server /job returned ${response.status}: ${await readErrorPreview(response)}`);
111
+ }
112
+ const body = (await response.json().catch(() => null));
113
+ if (typeof body?.status !== "string") {
114
+ throw new AceStepError("malformed_response", "ace-server /job returned no status");
115
+ }
116
+ return body.status;
117
+ }
118
+ async function cancelSynthJob(baseUrl, jobId) {
119
+ try {
120
+ await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}&cancel=1`, {
121
+ method: "POST",
122
+ signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
123
+ });
124
+ }
125
+ catch {
126
+ // Best-effort: the job also dies with the server, and an orphaned
127
+ // generation is discarded when its result is never fetched.
128
+ }
129
+ }
130
+ async function fetchSynthResult(baseUrl, jobId) {
131
+ const response = await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}&result=1`, {
132
+ signal: AbortSignal.timeout(RESULT_TIMEOUT_MS),
133
+ });
134
+ if (!response.ok) {
135
+ throw new AceStepError("synth_http_error", `ace-server /job result returned ${response.status}: ${await readErrorPreview(response)}`);
136
+ }
137
+ const contentType = response.headers.get("content-type") ?? "";
138
+ const body = Buffer.from(await response.arrayBuffer());
139
+ if (contentType.startsWith("audio/")) {
140
+ return { audio: body, audioContentType: contentType, latents: null };
141
+ }
142
+ const parts = parseMultipartMixed(body, contentType);
143
+ const audioPart = parts.find((part) => (part.headers["content-type"] ?? "").startsWith("audio/"));
144
+ if (audioPart === undefined) {
145
+ throw new AceStepError("malformed_response", "ace-server synth result contained no audio part");
146
+ }
147
+ const latentPart = parts.find((part) => (part.headers["content-type"] ?? "").startsWith("application/octet-stream"));
148
+ return {
149
+ audio: audioPart.data,
150
+ audioContentType: audioPart.headers["content-type"] ?? "audio/wav",
151
+ latents: latentPart?.data ?? null,
152
+ };
153
+ }
154
+ async function delay(ms, signal) {
155
+ await new Promise((resolve, reject) => {
156
+ const timer = setTimeout(() => {
157
+ signal.removeEventListener("abort", onAbort);
158
+ resolve();
159
+ }, ms);
160
+ const onAbort = () => {
161
+ clearTimeout(timer);
162
+ reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
163
+ };
164
+ signal.addEventListener("abort", onAbort, { once: true });
165
+ });
166
+ }
167
+ export async function synthCover(baseUrl, srcWavPath, request, options = {}) {
168
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SYNTH_TIMEOUT_MS;
169
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
170
+ const deadlineSignal = AbortSignal.timeout(timeoutMs);
171
+ const signal = options.signal
172
+ ? AbortSignal.any([options.signal, deadlineSignal])
173
+ : deadlineSignal;
174
+ let jobId = null;
175
+ try {
176
+ jobId = await submitSynthJob(baseUrl, srcWavPath, request, AbortSignal.any([signal, AbortSignal.timeout(SUBMIT_TIMEOUT_MS)]));
177
+ for (;;) {
178
+ const status = await readJobStatus(baseUrl, jobId);
179
+ if (status === "done")
180
+ return await fetchSynthResult(baseUrl, jobId);
181
+ if (status === "failed") {
182
+ throw new AceStepError("synth_failed", "ace-server reported the cover synthesis job as failed; the reason is in the [ace-step] logs above");
183
+ }
184
+ if (status === "cancelled") {
185
+ throw new AceStepError("synth_failed", "ace-server cancelled the cover synthesis job");
186
+ }
187
+ signal.throwIfAborted();
188
+ await delay(pollIntervalMs, signal);
189
+ }
190
+ }
191
+ catch (error) {
192
+ if (error instanceof AceStepError)
193
+ throw error;
194
+ if (jobId !== null && signal.aborted)
195
+ await cancelSynthJob(baseUrl, jobId);
196
+ if (options.signal?.aborted)
197
+ throw error;
198
+ if (deadlineSignal.aborted) {
199
+ throw new AceStepError("synth_timeout", `ace-server cover synthesis did not finish within ${timeoutMs}ms`);
200
+ }
201
+ throw new AceStepError("server_exited", `ace-server request failed: ${describeFetchError(error)}. The server may have crashed or been stopped; check [ace-step] logs.`);
202
+ }
203
+ }
@@ -0,0 +1,62 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { isHealthy } from "./client.js";
5
+ const DEFAULT_PORT = 8317;
6
+ const DEFAULT_MODEL = "acestep-v15-turbo-Q4_K_M.gguf";
7
+ const TEXT_ENCODER_MODEL = "Qwen3-Embedding-0.6B-Q8_0.gguf";
8
+ const VAE_MODEL = "vae-BF16.gguf";
9
+ const GATE_HEALTH_TIMEOUT_MS = 500;
10
+ function acestepRoot() {
11
+ return join(homedir(), ".cache", "videobook", "acestep");
12
+ }
13
+ export function aceStepConfig() {
14
+ const baseUrl = process.env.VIDEOBOOK_ACESTEP_BASE_URL?.trim();
15
+ const parsedPort = Number.parseInt(process.env.VIDEOBOOK_ACESTEP_PORT ?? "", 10);
16
+ return {
17
+ baseUrl: baseUrl ? baseUrl.replace(/\/+$/u, "") : null,
18
+ serverPath: process.env.VIDEOBOOK_ACESTEP_SERVER_PATH?.trim() || join(acestepRoot(), "bin", "ace-server"),
19
+ modelsDir: process.env.VIDEOBOOK_ACESTEP_MODELS_DIR?.trim() || join(acestepRoot(), "models"),
20
+ port: Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : DEFAULT_PORT,
21
+ model: process.env.VIDEOBOOK_ACESTEP_MODEL?.trim() || DEFAULT_MODEL,
22
+ keepLoaded: process.env.VIDEOBOOK_ACESTEP_KEEP_LOADED === "1",
23
+ };
24
+ }
25
+ function requiredModelFiles(config = aceStepConfig()) {
26
+ return [config.model, TEXT_ENCODER_MODEL, VAE_MODEL];
27
+ }
28
+ export function checkLocalAvailability(config = aceStepConfig()) {
29
+ if (!existsSync(config.serverPath)) {
30
+ return {
31
+ available: false,
32
+ kind: "binary_missing",
33
+ reason: `acestep.cpp runtime is not installed: ace-server not found at ${config.serverPath}. ` +
34
+ `Run "npm run install:acestep", or set VIDEOBOOK_ACESTEP_SERVER_PATH / VIDEOBOOK_ACESTEP_BASE_URL.`,
35
+ };
36
+ }
37
+ const missing = requiredModelFiles(config).filter((name) => !existsSync(join(config.modelsDir, name)));
38
+ if (missing.length > 0) {
39
+ return {
40
+ available: false,
41
+ kind: "models_missing",
42
+ reason: `ACE-Step models missing from ${config.modelsDir}: ${missing.join(", ")}. Run "npm run install:acestep".`,
43
+ };
44
+ }
45
+ return { available: true };
46
+ }
47
+ export async function checkAceStepAvailability() {
48
+ const config = aceStepConfig();
49
+ if (config.baseUrl !== null) {
50
+ const healthy = await isHealthy(config.baseUrl, GATE_HEALTH_TIMEOUT_MS);
51
+ if (!healthy) {
52
+ return {
53
+ available: false,
54
+ kind: "unreachable",
55
+ reason: `ace-server at ${config.baseUrl} did not answer /health within ${GATE_HEALTH_TIMEOUT_MS}ms ` +
56
+ `(VIDEOBOOK_ACESTEP_BASE_URL). Start it or unset the variable.`,
57
+ };
58
+ }
59
+ return { available: true };
60
+ }
61
+ return checkLocalAvailability(config);
62
+ }
@@ -0,0 +1,8 @@
1
+ export class AceStepError extends Error {
2
+ kind;
3
+ constructor(kind, message) {
4
+ super(message);
5
+ this.name = "AceStepError";
6
+ this.kind = kind;
7
+ }
8
+ }
@@ -0,0 +1,175 @@
1
+ import { spawn } from "node:child_process";
2
+ import { isHealthy } from "./client.js";
3
+ import { aceStepConfig, checkLocalAvailability } from "./config.js";
4
+ import { logger } from "../logger.js";
5
+ import { AceStepError } from "./errors.js";
6
+ const state = {
7
+ child: null,
8
+ handle: null,
9
+ starting: null,
10
+ spawnFailure: null,
11
+ lock: Promise.resolve(),
12
+ };
13
+ const HEALTH_POLL_INTERVAL_MS = 500;
14
+ const HEALTH_POLL_TIMEOUT_MS = 30_000;
15
+ const PROBE_TIMEOUT_MS = 700;
16
+ const EXTERNAL_HEALTH_ATTEMPTS = 3;
17
+ const STOP_GRACE_MS = 2_000;
18
+ async function delay(ms) {
19
+ await new Promise((resolve) => setTimeout(resolve, ms));
20
+ }
21
+ function drainOutput(stream) {
22
+ if (stream === null)
23
+ return;
24
+ stream.resume();
25
+ }
26
+ // Identity check before adopting a listener on our port: /health must return the
27
+ // documented {"status":"ok"} shape and /props must serve JSON, so we never treat
28
+ // an unrelated local service as ace-server.
29
+ async function probeAceServer(baseUrl) {
30
+ try {
31
+ const health = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
32
+ if (!health.ok)
33
+ return false;
34
+ const healthBody = (await health.json());
35
+ if (healthBody?.status !== "ok")
36
+ return false;
37
+ const props = await fetch(`${baseUrl}/props`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
38
+ if (!props.ok)
39
+ return false;
40
+ const propsBody = await props.json();
41
+ return typeof propsBody === "object" && propsBody !== null;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ async function startServer(config, signal) {
48
+ const baseUrl = `http://127.0.0.1:${config.port}`;
49
+ if (await probeAceServer(baseUrl)) {
50
+ const adopted = { baseUrl, owned: false };
51
+ state.handle = adopted;
52
+ return adopted;
53
+ }
54
+ const availability = checkLocalAvailability(config);
55
+ if (!availability.available) {
56
+ throw new AceStepError(availability.kind === "models_missing" ? "models_missing" : "binary_missing", availability.reason);
57
+ }
58
+ const serverArgs = [
59
+ "--models",
60
+ config.modelsDir,
61
+ "--host",
62
+ "127.0.0.1",
63
+ "--port",
64
+ String(config.port),
65
+ ];
66
+ if (config.keepLoaded)
67
+ serverArgs.push("--keep-loaded");
68
+ const child = spawn(config.serverPath, serverArgs, { stdio: ["ignore", "pipe", "pipe"] });
69
+ drainOutput(child.stdout);
70
+ drainOutput(child.stderr);
71
+ state.child = child;
72
+ state.spawnFailure = null;
73
+ child.on("error", (error) => {
74
+ state.spawnFailure = new AceStepError("spawn_failed", `Failed to start ace-server at ${config.serverPath}: ${error.message}`);
75
+ });
76
+ child.on("exit", (code, exitSignal) => {
77
+ logger.error("ace-server-exited", { exitCode: code, signal: exitSignal });
78
+ if (state.child === child) {
79
+ state.child = null;
80
+ state.handle = null;
81
+ }
82
+ });
83
+ const deadline = Date.now() + HEALTH_POLL_TIMEOUT_MS;
84
+ while (Date.now() < deadline) {
85
+ if (signal?.aborted) {
86
+ child.kill("SIGKILL");
87
+ state.child = null;
88
+ signal.throwIfAborted();
89
+ }
90
+ if (state.spawnFailure !== null)
91
+ throw state.spawnFailure;
92
+ if (child.exitCode !== null) {
93
+ throw new AceStepError("spawn_failed", `ace-server exited during startup (code ${child.exitCode}); run the configured server directly to inspect its output`);
94
+ }
95
+ if (await isHealthy(baseUrl, PROBE_TIMEOUT_MS)) {
96
+ const owned = { baseUrl, owned: true };
97
+ state.handle = owned;
98
+ return owned;
99
+ }
100
+ await delay(HEALTH_POLL_INTERVAL_MS);
101
+ }
102
+ child.kill("SIGKILL");
103
+ state.child = null;
104
+ throw new AceStepError("health_timeout", `ace-server did not become healthy on ${baseUrl} within ${HEALTH_POLL_TIMEOUT_MS}ms`);
105
+ }
106
+ export async function ensureAceServer(options = {}) {
107
+ const config = aceStepConfig();
108
+ if (config.baseUrl !== null) {
109
+ for (let attempt = 0; attempt < EXTERNAL_HEALTH_ATTEMPTS; attempt += 1) {
110
+ if (await probeAceServer(config.baseUrl))
111
+ return { baseUrl: config.baseUrl, owned: false };
112
+ if (attempt < EXTERNAL_HEALTH_ATTEMPTS - 1)
113
+ await delay(PROBE_TIMEOUT_MS);
114
+ }
115
+ throw new AceStepError("unreachable", `ace-server at ${config.baseUrl} is not responding (VIDEOBOOK_ACESTEP_BASE_URL). Start it or unset the variable.`);
116
+ }
117
+ if (state.handle !== null && (await isHealthy(state.handle.baseUrl, PROBE_TIMEOUT_MS))) {
118
+ return state.handle;
119
+ }
120
+ if (state.starting !== null)
121
+ return state.starting;
122
+ state.starting = startServer(config, options.signal);
123
+ try {
124
+ return await state.starting;
125
+ }
126
+ finally {
127
+ state.starting = null;
128
+ }
129
+ }
130
+ // ace-server runs one GPU job at a time on a FIFO worker thread and evicts
131
+ // completed jobs past a small cap while holding every result in memory, so we
132
+ // keep at most one videobook synthesis in flight: queued covers wait here
133
+ // instead of piling result buffers onto the server.
134
+ export async function withSynthLock(fn) {
135
+ const previous = state.lock;
136
+ let release;
137
+ state.lock = new Promise((resolve) => {
138
+ release = resolve;
139
+ });
140
+ await previous;
141
+ try {
142
+ return await fn();
143
+ }
144
+ finally {
145
+ release?.();
146
+ }
147
+ }
148
+ export async function stopAceServer() {
149
+ const child = state.child;
150
+ state.child = null;
151
+ state.handle = null;
152
+ state.starting = null;
153
+ if (child === null || child.exitCode !== null)
154
+ return;
155
+ const exited = new Promise((resolve) => {
156
+ child.once("exit", () => resolve());
157
+ });
158
+ child.kill("SIGTERM");
159
+ const outcome = await Promise.race([
160
+ exited.then(() => "exited"),
161
+ delay(STOP_GRACE_MS).then(() => "timeout"),
162
+ ]);
163
+ if (outcome === "timeout") {
164
+ child.kill("SIGKILL");
165
+ await exited;
166
+ }
167
+ }
168
+ export function resetAceStepForTests() {
169
+ state.child?.kill("SIGKILL");
170
+ state.child = null;
171
+ state.handle = null;
172
+ state.starting = null;
173
+ state.spawnFailure = null;
174
+ state.lock = Promise.resolve();
175
+ }