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,56 @@
1
+ import { copyFile, lstat } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ function defaultDownloadsDir() {
6
+ return process.env.VIDEOBOOK_DOWNLOADS_DIR || join(homedir(), "Downloads");
7
+ }
8
+ /** Flatten a display label into a single Downloads filename stem.
9
+ * Generation labels include model slugs like `kling-3.0/video — …`; those
10
+ * slashes (and `..` / backslashes) must not become path segments. */
11
+ function sanitizeDownloadBaseName(baseName) {
12
+ const parts = baseName.split(/[/\\]+/u).flatMap((part) => {
13
+ const trimmed = part.replaceAll("\0", "").trim();
14
+ if (!trimmed || trimmed === "." || trimmed === "..")
15
+ return [];
16
+ return [trimmed];
17
+ });
18
+ const joined = parts.join(" - ").replace(/\s+/gu, " ").trim();
19
+ return joined || "download";
20
+ }
21
+ export async function resolveDownloadDestination(baseName, ext, downloadsDir = defaultDownloadsDir()) {
22
+ const stem = sanitizeDownloadBaseName(baseName);
23
+ const suffix = ext.startsWith(".") ? ext : `.${ext}`;
24
+ let candidate = join(downloadsDir, `${stem}${suffix}`);
25
+ let n = 2;
26
+ while (await fileExists(candidate)) {
27
+ candidate = join(downloadsDir, `${stem} (${n})${suffix}`);
28
+ n += 1;
29
+ }
30
+ return candidate;
31
+ }
32
+ async function fileExists(path) {
33
+ try {
34
+ await lstat(path);
35
+ return true;
36
+ }
37
+ catch (error) {
38
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
39
+ return false;
40
+ throw error;
41
+ }
42
+ }
43
+ export async function copyToDownloads(sourcePath, baseName, ext, downloadsDir) {
44
+ while (true) {
45
+ const destination = await resolveDownloadDestination(baseName, ext, downloadsDir);
46
+ try {
47
+ await copyFile(sourcePath, destination, fsConstants.COPYFILE_EXCL);
48
+ return destination;
49
+ }
50
+ catch (error) {
51
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST")
52
+ continue;
53
+ throw error;
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,194 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { readVideoAnalysisSummary } from "./video-analysis-projection.js";
4
+ const MOMENTS_SCHEMA_VERSION = 1;
5
+ export const MOMENTS_EXTRACTOR_ID = "videobook-scene-scan";
6
+ export const MOMENTS_EXTRACTOR_VERSION = "1";
7
+ export const MOMENTS_SHOT_THRESHOLD = 0.3;
8
+ export const MOMENTS_CHANGE_THRESHOLD = 0.12;
9
+ export const MOMENTS_MIN_MOMENT_SECONDS = 0.5;
10
+ export const MOMENTS_MAX_SCAN_SECONDS = 1800;
11
+ const MOMENTS_MAX_SHOTS = 4000;
12
+ export const MOMENTS_MAX_RANKED = 12;
13
+ const rangeSchema = z.object({
14
+ startSeconds: z.number().finite().nonnegative(),
15
+ endSeconds: z.number().finite().positive(),
16
+ });
17
+ const shotSchema = z.object({
18
+ id: z.string().min(1).max(100),
19
+ startSeconds: z.number().finite().nonnegative(),
20
+ endSeconds: z.number().finite().positive(),
21
+ motion: z.number().min(0).max(1),
22
+ change: z.object({
23
+ timeSeconds: z.number().finite().nonnegative(),
24
+ strength: z.number().min(0).max(1),
25
+ quality: z.number().min(0).max(1),
26
+ }).nullable(),
27
+ });
28
+ export const editingMomentsSchema = z.object({
29
+ schemaVersion: z.literal(MOMENTS_SCHEMA_VERSION),
30
+ momentsId: z.string().min(1),
31
+ artifactId: z.string().uuid(),
32
+ objectHash: z.string().min(1),
33
+ streamId: z.string().uuid(),
34
+ durationSeconds: z.number().finite().positive(),
35
+ extractor: z.object({
36
+ id: z.string().min(1),
37
+ version: z.string().min(1),
38
+ params: z.object({
39
+ shotThreshold: z.number(),
40
+ changeThreshold: z.number(),
41
+ minMomentSeconds: z.number(),
42
+ }),
43
+ }),
44
+ analyzedRanges: z.array(rangeSchema).min(1),
45
+ createdAt: z.number().finite().positive(),
46
+ shots: z.array(shotSchema).max(MOMENTS_MAX_SHOTS),
47
+ usable: z.array(rangeSchema),
48
+ moments: z.array(z.object({
49
+ shotId: z.string().min(1),
50
+ startSeconds: z.number().finite().nonnegative(),
51
+ endSeconds: z.number().finite().positive(),
52
+ score: z.number().min(0).max(1),
53
+ quality: z.number().min(0).max(1),
54
+ })).max(MOMENTS_MAX_SHOTS),
55
+ reused: z.object({
56
+ visionAnalysis: z.object({
57
+ summary: z.string(),
58
+ tags: z.array(z.string()),
59
+ subjects: z.array(z.string()),
60
+ sceneCount: z.number().int().nonnegative(),
61
+ timestampsApproximate: z.literal(true),
62
+ }).nullable(),
63
+ indexCoverage: z.object({
64
+ state: z.string(),
65
+ generation: z.string(),
66
+ }).nullable(),
67
+ }),
68
+ });
69
+ export function editingMomentsId(input) {
70
+ return createHash("sha256").update(JSON.stringify([
71
+ input.objectHash, input.streamIndex, MOMENTS_EXTRACTOR_ID, MOMENTS_EXTRACTOR_VERSION,
72
+ input.startSeconds, input.endSeconds,
73
+ ])).digest("hex");
74
+ }
75
+ export function editingMomentsCacheKey(momentsId) {
76
+ return `editing.moments.${momentsId}`;
77
+ }
78
+ export async function readEditingMoments(engine, artifactId, momentsId) {
79
+ const read = await engine.metadata.artifacts.read(artifactId, editingMomentsCacheKey(momentsId));
80
+ if (!read.ok)
81
+ return null;
82
+ const parsed = editingMomentsSchema.safeParse(read.value);
83
+ if (!parsed.success || parsed.data.artifactId !== artifactId || parsed.data.momentsId !== momentsId)
84
+ return null;
85
+ return parsed.data;
86
+ }
87
+ export async function reuseContext(engine, artifactId) {
88
+ const summary = await readVideoAnalysisSummary(engine, artifactId);
89
+ let coverage = null;
90
+ try {
91
+ const state = engine.temporalSearch.coverage(artifactId);
92
+ coverage = state.ok ? { state: state.value.state, generation: state.value.generation } : null;
93
+ }
94
+ catch {
95
+ coverage = null;
96
+ }
97
+ return {
98
+ visionAnalysis: summary ? {
99
+ summary: summary.summary,
100
+ tags: summary.tags,
101
+ subjects: summary.subjects,
102
+ sceneCount: summary.scene_count,
103
+ timestampsApproximate: true,
104
+ } : null,
105
+ indexCoverage: coverage,
106
+ };
107
+ }
108
+ const round = (value) => Math.round(value * 10000) / 10000;
109
+ export function shotsFromScan(frames, durationSeconds) {
110
+ const boundaries = shotBoundaries(frames);
111
+ const edges = [0, ...boundaries, durationSeconds];
112
+ let cursor = 0;
113
+ return edges.slice(0, -1).map((start, index) => {
114
+ const end = edges[index + 1];
115
+ while (cursor < frames.length && frames[cursor].timeSeconds < start)
116
+ cursor += 1;
117
+ const samples = [];
118
+ for (let probe = cursor; probe < frames.length && frames[probe].timeSeconds < end; probe += 1) {
119
+ samples.push(frames[probe]);
120
+ }
121
+ return buildShot(`shot-${index + 1}`, start, end, samples);
122
+ }).filter((shot) => shot.endSeconds - shot.startSeconds > 0);
123
+ }
124
+ function shotBoundaries(frames) {
125
+ const peaks = [];
126
+ for (let index = 1; index < frames.length - 1; index += 1) {
127
+ const frame = frames[index];
128
+ if (frame.score < MOMENTS_SHOT_THRESHOLD)
129
+ continue;
130
+ if (frame.score < frames[index - 1].score || frame.score < frames[index + 1].score)
131
+ continue;
132
+ peaks.push({ timeSeconds: frame.timeSeconds, score: frame.score });
133
+ }
134
+ const kept = [];
135
+ for (const peak of [...peaks].sort((left, right) => right.score - left.score)) {
136
+ if (kept.every((time) => Math.abs(time - peak.timeSeconds) >= MOMENTS_MIN_MOMENT_SECONDS)) {
137
+ kept.push(peak.timeSeconds);
138
+ }
139
+ }
140
+ return kept.sort((left, right) => left - right).map(round);
141
+ }
142
+ function buildShot(id, startSeconds, endSeconds, samples) {
143
+ const motion = samples.length ? samples.reduce((sum, frame) => sum + frame.score, 0) / samples.length : 0;
144
+ return {
145
+ id,
146
+ startSeconds: round(startSeconds),
147
+ endSeconds: round(endSeconds),
148
+ motion: round(Math.min(1, motion)),
149
+ change: strongestChange(samples, startSeconds, endSeconds),
150
+ };
151
+ }
152
+ function strongestChange(samples, startSeconds, endSeconds) {
153
+ let best = null;
154
+ for (const frame of samples) {
155
+ if (frame.score < MOMENTS_CHANGE_THRESHOLD)
156
+ continue;
157
+ if (frame.timeSeconds - startSeconds < MOMENTS_MIN_MOMENT_SECONDS)
158
+ continue;
159
+ if (endSeconds - frame.timeSeconds < MOMENTS_MIN_MOMENT_SECONDS)
160
+ continue;
161
+ if (!best || frame.score > best.strength)
162
+ best = { timeSeconds: frame.timeSeconds, strength: frame.score };
163
+ }
164
+ return best ? {
165
+ timeSeconds: round(best.timeSeconds),
166
+ strength: round(best.strength),
167
+ quality: round(Math.min(1, best.strength / (2 * MOMENTS_SHOT_THRESHOLD))),
168
+ } : null;
169
+ }
170
+ export function usableRanges(ranges, shots) {
171
+ return ranges.map((range) => {
172
+ const inside = shots.filter((shot) => shot.startSeconds >= range.startSeconds && shot.endSeconds <= range.endSeconds + 0.001);
173
+ const start = inside.length && inside[0].startSeconds - range.startSeconds < MOMENTS_MIN_MOMENT_SECONDS
174
+ ? inside[0].endSeconds
175
+ : range.startSeconds;
176
+ const last = inside.at(-1);
177
+ const end = last && range.endSeconds - last.endSeconds < MOMENTS_MIN_MOMENT_SECONDS
178
+ ? last.startSeconds
179
+ : range.endSeconds;
180
+ return { startSeconds: round(start), endSeconds: round(end) };
181
+ }).filter((range) => range.endSeconds - range.startSeconds >= MOMENTS_MIN_MOMENT_SECONDS);
182
+ }
183
+ export function rankedMoments(shots) {
184
+ return shots
185
+ .filter((shot) => shot.endSeconds - shot.startSeconds >= MOMENTS_MIN_MOMENT_SECONDS)
186
+ .sort((left, right) => right.motion - left.motion || left.startSeconds - right.startSeconds)
187
+ .map((shot) => ({
188
+ shotId: shot.id,
189
+ startSeconds: shot.startSeconds,
190
+ endSeconds: shot.endSeconds,
191
+ score: shot.motion,
192
+ quality: round(Math.min(1, (shot.endSeconds - shot.startSeconds) / 2)),
193
+ }));
194
+ }
@@ -0,0 +1,66 @@
1
+ import { ElevenLabsSoundGenerationRequestSchema } from "@apicity/elevenlabs/zod";
2
+ export const ELEVENLABS_SCRIBE_MODEL = "scribe_v2";
3
+ export const ELEVENLABS_SOUND_EFFECT_MODEL = "eleven_text_to_sound_v2";
4
+ export const ELEVENLABS_SOUND_EFFECT_OUTPUT_FORMAT = "mp3_44100_128";
5
+ const AUDIO_CROSSFADE_MS = 500;
6
+ export const MIN_AUDIO_CONTINUATION_MS = 500;
7
+ export const MAX_AUDIO_CONTINUATION_MS = 29_500;
8
+ // Derived from @apicity/elevenlabs@0.8.0 Sound Generation `.max(450)`.
9
+ export const MAX_SOUND_GENERATION_PROMPT_CHARS = ElevenLabsSoundGenerationRequestSchema.shape.text.maxLength ?? 450;
10
+ export const MAX_CONTINUATION_DIRECTION_CHARS = 250;
11
+ const MAX_SOURCE_CONTEXT_CHARS = 100;
12
+ const MIN_SOURCE_CONTEXT_CHARS = 20;
13
+ export function normalizeContinuationPrompt(raw) {
14
+ const trimmed = raw?.trim();
15
+ if (!trimmed)
16
+ return undefined;
17
+ const wrapped = /^\(([^()]*)\)$/.exec(trimmed);
18
+ const direction = (wrapped ? wrapped[1] : trimmed).trim();
19
+ return direction || undefined;
20
+ }
21
+ function transcriptText(response) {
22
+ if ("text" in response && typeof response.text === "string") {
23
+ return response.text;
24
+ }
25
+ if ("transcripts" in response && Array.isArray(response.transcripts)) {
26
+ return response.transcripts.map((transcript) => transcript.text).join(" ");
27
+ }
28
+ return "";
29
+ }
30
+ export function describeSourceAudio(response) {
31
+ const description = transcriptText(response)
32
+ .replace(/\[([^\]]+)\]/g, "$1")
33
+ .replace(/\s+/g, " ")
34
+ .trim();
35
+ return description.slice(0, 1_000) || "the same sound and ambient texture as the source audio";
36
+ }
37
+ export function buildSoundContinuationPrompt(sourceDescription, generationDurationSeconds, continuationPrompt) {
38
+ const duration = generationDurationSeconds.toFixed(1);
39
+ const direction = normalizeContinuationPrompt(continuationPrompt)
40
+ ?.slice(0, MAX_CONTINUATION_DIRECTION_CHARS)
41
+ .replace(/[.!?\s]+$/, "");
42
+ if (!direction) {
43
+ return [
44
+ `Continue this audio seamlessly for ${duration}s: ${sourceDescription.slice(0, MAX_SOURCE_CONTEXT_CHARS)}.`,
45
+ "Start immediately mid-action and match its character, setting, intensity, tone, and perspective.",
46
+ "Repeat or vary the source event naturally so matching sound fills the duration.",
47
+ "Keep matching activity and ambience audible through the final second.",
48
+ "No intro, unrelated sound, fade-in, early ending, or silent tail.",
49
+ ].join(" ").slice(0, MAX_SOUND_GENERATION_PROMPT_CHARS);
50
+ }
51
+ // Direction leads so the 450-char cap can only ever trim source context or
52
+ // closing guidance, never the user's requested change.
53
+ const lead = `Continue this audio for ${duration}s: ${direction}.`;
54
+ const close = "Transition smoothly from the source, follow the direction for the full duration, and keep it audible through the final second with no silent tail.";
55
+ const sourceLabel = "Source audio: ";
56
+ const sourceBudget = MAX_SOUND_GENERATION_PROMPT_CHARS
57
+ - lead.length - close.length - sourceLabel.length - " .".length - 1;
58
+ const source = sourceBudget >= MIN_SOURCE_CONTEXT_CHARS
59
+ ? sourceDescription.slice(0, Math.min(MAX_SOURCE_CONTEXT_CHARS, sourceBudget))
60
+ : "";
61
+ const context = source ? ` ${sourceLabel}${source}.` : "";
62
+ return `${lead}${context} ${close}`.slice(0, MAX_SOUND_GENERATION_PROMPT_CHARS);
63
+ }
64
+ export function audioCrossfadeMs(sourceDurationMs, continuationDurationMs) {
65
+ return Math.max(1, Math.min(AUDIO_CROSSFADE_MS, Math.floor(sourceDurationMs / 2), Math.floor(continuationDurationMs / 2)));
66
+ }
package/dist/engine.js ADDED
@@ -0,0 +1,54 @@
1
+ import { resolve } from "node:path";
2
+ import { createEngine } from "videobook-engine";
3
+ import { BookManager } from "./book-manager.js";
4
+ import { scopedBookEngine } from "./book-context.js";
5
+ import { contentStoreFromEnvironment } from "./content-store.js";
6
+ import { dataLayout } from "./data-home.js";
7
+ export function environmentValue(value, fallback) {
8
+ const normalized = value?.trim();
9
+ return normalized ? normalized : fallback;
10
+ }
11
+ function catalogBackupTemplate() {
12
+ const legacy = process.env.VIDEOBOOK_CATALOG_BACKUP_URL?.trim();
13
+ const template = process.env.VIDEOBOOK_CATALOG_BACKUP_URL_TEMPLATE?.trim();
14
+ if (legacy && !template) {
15
+ throw new Error("VIDEOBOOK_CATALOG_BACKUP_URL is not supported for multi-book storage; use VIDEOBOOK_CATALOG_BACKUP_URL_TEMPLATE with {bookId}");
16
+ }
17
+ if (template && !template.includes("{bookId}")) {
18
+ throw new Error("VIDEOBOOK_CATALOG_BACKUP_URL_TEMPLATE must include {bookId}");
19
+ }
20
+ return template || undefined;
21
+ }
22
+ const BOOK_LIBRARY_DIR = environmentValue(process.env.VIDEOBOOK_LIBRARY_DIR, dataLayout().libraryDir);
23
+ const objectStore = contentStoreFromEnvironment();
24
+ export const bookManager = new BookManager({
25
+ libraryDir: BOOK_LIBRARY_DIR,
26
+ ...(objectStore ? { remoteObjects: objectStore } : {}),
27
+ objectPrefix: environmentValue(process.env.VIDEOBOOK_OBJECT_PREFIX, "superlzy-media/videobook/sha256"),
28
+ catalogBackupName: environmentValue(process.env.VIDEOBOOK_CATALOG_BACKUP_NAME, "backup"),
29
+ ...(catalogBackupTemplate()
30
+ ? { catalogBackupUrlTemplate: catalogBackupTemplate() }
31
+ : {}),
32
+ });
33
+ export const scopedEngine = scopedBookEngine;
34
+ const testEngines = new Map();
35
+ export function createEngineForTest(rootDir) {
36
+ const normalizedRoot = resolve(rootDir);
37
+ const existing = testEngines.get(normalizedRoot);
38
+ if (existing)
39
+ return existing;
40
+ const engine = createEngine({
41
+ durableWrites: false,
42
+ rootDir: normalizedRoot,
43
+ initialBookName: "test-book",
44
+ });
45
+ testEngines.set(normalizedRoot, engine);
46
+ return engine;
47
+ }
48
+ export function closeEngineForTest(rootDir, engine) {
49
+ const normalizedRoot = resolve(rootDir);
50
+ if (testEngines.get(normalizedRoot) === engine) {
51
+ testEngines.delete(normalizedRoot);
52
+ }
53
+ engine.close();
54
+ }
@@ -0,0 +1,91 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { chmod, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { parse } from "dotenv";
6
+ const KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
7
+ const NEW_FILE_MODE = 0o600;
8
+ const NEW_DIRECTORY_MODE = 0o700;
9
+ function assignmentPattern(key) {
10
+ return new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=`, "u");
11
+ }
12
+ export function hasControlCharacters(value) {
13
+ return [...value].some((character) => {
14
+ const code = character.codePointAt(0) ?? 0;
15
+ return code < 32 || code === 127;
16
+ });
17
+ }
18
+ export function formatEnvValue(value) {
19
+ if (!/[#'"\s]/u.test(value))
20
+ return value;
21
+ if (!value.includes("'"))
22
+ return `'${value}'`;
23
+ if (!value.includes("\""))
24
+ return `"${value}"`;
25
+ throw new Error("value cannot contain both single and double quotes");
26
+ }
27
+ function withoutAssignments(lines, key) {
28
+ const pattern = assignmentPattern(key);
29
+ return lines.filter((line) => !pattern.test(line));
30
+ }
31
+ function withAssignment(lines, key, value) {
32
+ const pattern = assignmentPattern(key);
33
+ const line = `${key}=${formatEnvValue(value)}`;
34
+ const last = lines.reduce((found, candidate, index) => pattern.test(candidate) ? index : found, -1);
35
+ if (last >= 0)
36
+ return lines.map((candidate, index) => index === last ? line : candidate);
37
+ return [...lines, line];
38
+ }
39
+ function splitLines(raw) {
40
+ if (raw === "")
41
+ return [];
42
+ const lines = raw.split("\n");
43
+ return lines.at(-1) === "" ? lines.slice(0, -1) : lines;
44
+ }
45
+ export function applyEnvUpdates(raw, updates) {
46
+ let lines = splitLines(raw);
47
+ for (const [key, value] of Object.entries(updates)) {
48
+ if (!KEY_PATTERN.test(key))
49
+ throw new Error(`${key} is not a valid environment variable name`);
50
+ lines = value === null ? withoutAssignments(lines, key) : withAssignment(lines, key, value);
51
+ }
52
+ return lines.length === 0 ? "" : `${lines.join("\n")}\n`;
53
+ }
54
+ export function readEnvValues(path) {
55
+ try {
56
+ return parse(readFileSync(path, "utf8"));
57
+ }
58
+ catch (error) {
59
+ if (error.code === "ENOENT")
60
+ return {};
61
+ throw error;
62
+ }
63
+ }
64
+ async function readIfPresent(path) {
65
+ try {
66
+ return await readFile(path, "utf8");
67
+ }
68
+ catch (error) {
69
+ if (error.code === "ENOENT")
70
+ return null;
71
+ throw error;
72
+ }
73
+ }
74
+ async function existingMode(path) {
75
+ try {
76
+ return (await stat(path)).mode & 0o777;
77
+ }
78
+ catch {
79
+ return NEW_FILE_MODE;
80
+ }
81
+ }
82
+ export async function updateEnvFile(path, updates) {
83
+ const raw = await readIfPresent(path);
84
+ const next = applyEnvUpdates(raw ?? "", updates);
85
+ await mkdir(dirname(path), { recursive: true, mode: NEW_DIRECTORY_MODE });
86
+ const mode = raw === null ? NEW_FILE_MODE : await existingMode(path);
87
+ const temporary = `${path}.tmp.${randomUUID()}`;
88
+ await writeFile(temporary, next, { encoding: "utf8", mode });
89
+ await chmod(temporary, mode);
90
+ await rename(temporary, path);
91
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Secret-source detection and .env editing.
3
+ *
4
+ * Secrets reach the server three ways:
5
+ * 1. `op run --env-file=.env.tpl --` resolves op:// references and injects
6
+ * them into the environment BEFORE Node starts (npm run dev:server).
7
+ * 2. A plain `.env` file, loaded here with dotenv semantics: never override
8
+ * a var that is already set, so op-injected values always win.
9
+ * 3. op:// references left in the environment or `.env`, plus the vault
10
+ * named by VIDEOBOOK_OP_VAULT, resolved through the 1Password CLI by
11
+ * resolveOnePassword() before a server starts.
12
+ *
13
+ * A managed key that was already set before `.env` loaded, or that 1Password
14
+ * resolved, is therefore "1password" — externally managed and read-only,
15
+ * because a `.env` write would be silently shadowed on the next start. A key
16
+ * whose literal value arrived via `.env` is "env" and editable. A
17
+ * shell-exported var is also classified "1password"; the label is
18
+ * approximate but the read-only behavior is right.
19
+ *
20
+ * `.env` lives in the data home (the repo root in a source checkout) and is
21
+ * server runtime configuration, not a project artifact, so direct node:fs is
22
+ * correct here — the videobook-engine rule covers managed artifacts and their
23
+ * materialized workspaces.
24
+ */
25
+ import { dataLayout } from "./data-home.js";
26
+ import { hasControlCharacters, readEnvValues, updateEnvFile } from "./env-file.js";
27
+ import { resolveOnePasswordEnv } from "./one-password.js";
28
+ export const MANAGED_ENV_KEYS = [
29
+ "OPENAI_API_KEY",
30
+ "KIE_API_KEY",
31
+ "XAI_API_KEY",
32
+ "X_CLIENT_ID",
33
+ "X_CLIENT_SECRET",
34
+ "DROPBOX_CLIENT_ID",
35
+ "DROPBOX_CLIENT_SECRET",
36
+ "KIMI_CODING_API_KEY",
37
+ "FAL_API_KEY",
38
+ "DASHSCOPE_API_KEY",
39
+ "ELEVENLABS_API_KEY",
40
+ "B2_ACCESS_KEY_ID",
41
+ "B2_SECRET_ACCESS_KEY",
42
+ "B2_REGION",
43
+ "B2_BUCKET",
44
+ "B2_ENDPOINT",
45
+ "B2_ARCHIVE_PREFIX",
46
+ ];
47
+ const NON_SECRET_ENV_KEYS = new Set([
48
+ "B2_REGION",
49
+ "B2_BUCKET",
50
+ "B2_ENDPOINT",
51
+ "B2_ARCHIVE_PREFIX",
52
+ ]);
53
+ function isManagedEnvKey(key) {
54
+ return MANAGED_ENV_KEYS.includes(key);
55
+ }
56
+ export function isSecretEnvKey(key) {
57
+ return !NON_SECRET_ENV_KEYS.has(key);
58
+ }
59
+ export function maskEnvValue(value) {
60
+ const prefixLength = Math.min(6, Math.max(2, value.length));
61
+ return `${value.slice(0, prefixLength)}${"*".repeat(8)}`;
62
+ }
63
+ /** Raised on rejected writes. Messages never include the secret value. */
64
+ export class SecretWriteError extends Error {
65
+ }
66
+ function defaultEnvPath() {
67
+ return process.env.VIDEOBOOK_ENV_PATH ?? dataLayout().envPath;
68
+ }
69
+ export function createSecretStore(options = {}) {
70
+ const envPath = options.envPath ?? defaultEnvPath();
71
+ const processEnv = options.processEnv ?? process.env;
72
+ const preSet = new Set();
73
+ const fromOnePassword = new Set();
74
+ let loaded = false;
75
+ let resolution = null;
76
+ function loadDotenv() {
77
+ if (loaded)
78
+ return;
79
+ loaded = true;
80
+ for (const key of MANAGED_ENV_KEYS) {
81
+ const value = processEnv[key];
82
+ if (value !== undefined && value !== "")
83
+ preSet.add(key);
84
+ }
85
+ for (const [key, value] of Object.entries(readEnvValues(envPath))) {
86
+ if (processEnv[key] === undefined)
87
+ processEnv[key] = value;
88
+ }
89
+ }
90
+ function getValue(key) {
91
+ loadDotenv();
92
+ return processEnv[key];
93
+ }
94
+ function getSource(key) {
95
+ loadDotenv();
96
+ const value = processEnv[key];
97
+ if (!value || value.startsWith("op://"))
98
+ return "missing";
99
+ if (preSet.has(key) || fromOnePassword.has(key))
100
+ return "1password";
101
+ return "env";
102
+ }
103
+ async function setEnvSecret(key, rawValue) {
104
+ loadDotenv();
105
+ if (!isManagedEnvKey(key)) {
106
+ throw new SecretWriteError(`${String(key)} is not a managed environment key`);
107
+ }
108
+ const value = rawValue.trim();
109
+ if (!value)
110
+ throw new SecretWriteError(`${key} value is empty`);
111
+ if (hasControlCharacters(value)) {
112
+ throw new SecretWriteError(`${key} value contains control characters`);
113
+ }
114
+ if (getSource(key) === "1password") {
115
+ throw new SecretWriteError(`${key} is managed by 1Password; edit it in the vault`);
116
+ }
117
+ if (value.includes("'") && value.includes("\"")) {
118
+ throw new SecretWriteError("value cannot contain both single and double quotes");
119
+ }
120
+ await updateEnvFile(envPath, { [key]: value });
121
+ processEnv[key] = value;
122
+ }
123
+ function resolveOnePassword() {
124
+ if (resolution)
125
+ return resolution;
126
+ loadDotenv();
127
+ const resolved = resolveOnePasswordEnv(processEnv, MANAGED_ENV_KEYS);
128
+ for (const key of [...resolved.fromReferences, ...resolved.fromVault])
129
+ fromOnePassword.add(key);
130
+ resolution = resolved;
131
+ return resolved;
132
+ }
133
+ return {
134
+ loadDotenv,
135
+ getValue,
136
+ getSource,
137
+ setEnvSecret,
138
+ resolveOnePassword,
139
+ onePasswordResolution: () => resolution,
140
+ envPath: () => envPath,
141
+ };
142
+ }
143
+ let defaultStore = null;
144
+ export function getDefaultSecretStore() {
145
+ if (!defaultStore)
146
+ defaultStore = createSecretStore();
147
+ return defaultStore;
148
+ }
@@ -0,0 +1,12 @@
1
+ import { createRequire } from "node:module";
2
+ import { binaryRunsOnHost, readBinaryHeader } from "./ffprobe-binary.js";
3
+ export function resolveFfmpegPath(bundledPath, override, hostArch) {
4
+ if (override)
5
+ return override;
6
+ if (!bundledPath)
7
+ return "ffmpeg";
8
+ const header = readBinaryHeader(bundledPath);
9
+ return header && binaryRunsOnHost(header, hostArch) ? bundledPath : "ffmpeg";
10
+ }
11
+ const require = createRequire(import.meta.url);
12
+ export const FFMPEG = resolveFfmpegPath(require("ffmpeg-static"), process.env.VIDEOBOOK_FFMPEG_PATH, process.arch);