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 { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, resolve } from "node:path";
4
+ import { dataLayout } from "./data-home.js";
5
+ export function resolveApplicationStatePath(envValue, cwd = process.cwd(), fallback = dataLayout(process.env, cwd).appStatePath) {
6
+ const normalized = envValue?.trim();
7
+ if (!normalized)
8
+ return fallback;
9
+ return isAbsolute(normalized) ? normalized : resolve(cwd, normalized);
10
+ }
11
+ const statePath = resolveApplicationStatePath(process.env.VIDEOBOOK_APP_STATE_PATH);
12
+ function parseStoredState(raw) {
13
+ const parsed = JSON.parse(raw);
14
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
15
+ return { values: {} };
16
+ const values = parsed.values;
17
+ return typeof values === "object" && values !== null && !Array.isArray(values)
18
+ ? { values: values }
19
+ : { values: {} };
20
+ }
21
+ /**
22
+ * A JSON file store that re-reads the file on every access. Other processes
23
+ * write it too — `scripts/mint-x-token.mjs` drops freshly minted X tokens in
24
+ * while the server runs — so nothing may be cached across calls.
25
+ */
26
+ export function createFileApplicationState(path) {
27
+ let writeChain = Promise.resolve();
28
+ async function read() {
29
+ try {
30
+ return parseStoredState(await readFile(path, "utf8"));
31
+ }
32
+ catch {
33
+ return { values: {} };
34
+ }
35
+ }
36
+ async function write(key, value) {
37
+ const state = await read();
38
+ state.values[key] = value;
39
+ await mkdir(dirname(path), { recursive: true });
40
+ const temporary = `${path}.${randomUUID()}.tmp`;
41
+ await writeFile(temporary, JSON.stringify(state), "utf8");
42
+ await rename(temporary, path);
43
+ }
44
+ return {
45
+ async get(key) {
46
+ const state = await read();
47
+ return key in state.values ? state.values[key] : null;
48
+ },
49
+ set(key, value) {
50
+ const next = writeChain.then(() => write(key, value), () => write(key, value));
51
+ writeChain = next.catch(() => undefined);
52
+ return next;
53
+ },
54
+ };
55
+ }
56
+ export const appState = createFileApplicationState(statePath);
package/dist/app.js ADDED
@@ -0,0 +1,257 @@
1
+ import cors from "cors";
2
+ import express from "express";
3
+ import { createHash } from "node:crypto";
4
+ import { createWriteStream, existsSync } from "node:fs";
5
+ import { mkdtemp, rm } from "node:fs/promises";
6
+ import { createRequire } from "node:module";
7
+ import { tmpdir } from "node:os";
8
+ import { extname, join, resolve } from "node:path";
9
+ import { Transform } from "node:stream";
10
+ import { pipeline } from "node:stream/promises";
11
+ import { fileURLToPath } from "node:url";
12
+ import { toNodeHandler } from "@modelcontextprotocol/node";
13
+ import { SCHEMA_VERSION } from "videobook-engine";
14
+ import { v7 as uuidv7 } from "uuid";
15
+ import { localArchiveDir } from "./archive-store.js";
16
+ import { bookManager, scopedEngine } from "./engine.js";
17
+ import { parseHttpByteRange } from "./http-byte-range.js";
18
+ import { restrictHttpAccess, restrictMediaContent } from "./http-access.js";
19
+ import { uploadLocalStream } from "./local-upload.js";
20
+ import { queue } from "./queue.js";
21
+ import { searchReferenceStore } from "./search-reference-store.js";
22
+ import { SERVER_VERSION, createHttpHandler } from "./server.js";
23
+ import { VoiceSessions } from "./voice-session.js";
24
+ import { voiceRouter } from "./voice-http.js";
25
+ const enginePackage = createRequire(import.meta.url)("videobook-engine/package.json");
26
+ const SEARCH_REFERENCE_LIMIT_BYTES = 500 * 1024 * 1024;
27
+ const RESERVED_ROOTS = new Set(["mcp", "api", "files", "archive-files", "healthz", "assets"]);
28
+ function defaultUiDir(env = process.env) {
29
+ const override = env.VIDEOBOOK_UI_DIR?.trim();
30
+ return override ? resolve(override) : fileURLToPath(new URL("../ui/", import.meta.url));
31
+ }
32
+ function limitUploadBytes(maxBytes) {
33
+ let received = 0;
34
+ return new Transform({
35
+ transform(chunk, _encoding, callback) {
36
+ received += chunk.byteLength;
37
+ if (received > maxBytes) {
38
+ callback(new Error("Search reference exceeds the 500 MB limit"));
39
+ return;
40
+ }
41
+ callback(null, chunk);
42
+ },
43
+ });
44
+ }
45
+ async function engineForBook(bookId) {
46
+ return bookManager.ensure(bookId);
47
+ }
48
+ function sendHealth(_req, res) {
49
+ res.json({
50
+ ok: true,
51
+ service: "videobook",
52
+ version: SERVER_VERSION,
53
+ engineVersion: enginePackage.version,
54
+ schemaVersion: SCHEMA_VERSION,
55
+ });
56
+ }
57
+ async function sendArtifactFile(req, res) {
58
+ const bookId = String(req.params[0]);
59
+ const artifactId = String(req.params[1]);
60
+ const filename = String(req.params[2]);
61
+ const book = await engineForBook(bookId);
62
+ if (!book.ok) {
63
+ res.status(404).json({ error: book.error.message });
64
+ return;
65
+ }
66
+ const result = await book.value.engine.files.read(artifactId, filename);
67
+ if (!result.ok) {
68
+ res.status(result.error.code === "NOT_FOUND" ? 404 : 400).json({ error: result.error.message });
69
+ return;
70
+ }
71
+ res.type(filename);
72
+ res.setHeader("Accept-Ranges", "bytes");
73
+ const etag = `"${createHash("sha256").update(result.value).digest("hex")}"`;
74
+ res.setHeader("ETag", etag);
75
+ if (req.fresh) {
76
+ res.status(304).end();
77
+ return;
78
+ }
79
+ const rangeHeader = req.headers.range;
80
+ const ifRange = req.get("If-Range");
81
+ if (req.method === "HEAD" || !rangeHeader || (ifRange !== undefined && ifRange !== etag)) {
82
+ res.send(result.value);
83
+ return;
84
+ }
85
+ const range = parseHttpByteRange(rangeHeader, result.value.byteLength);
86
+ if (!range.ok) {
87
+ res.setHeader("Content-Range", `bytes */${result.value.byteLength}`);
88
+ res.sendStatus(416);
89
+ return;
90
+ }
91
+ res.status(206);
92
+ res.setHeader("Content-Range", `bytes ${range.start}-${range.end}/${result.value.byteLength}`);
93
+ res.send(result.value.subarray(range.start, range.end + 1));
94
+ }
95
+ async function receiveUpload(req, res) {
96
+ const filename = typeof req.query.filename === "string" ? req.query.filename : "";
97
+ if (!filename) {
98
+ res.status(400).json({ error: "Missing filename" });
99
+ return;
100
+ }
101
+ const book = await engineForBook(String(req.params.bookId));
102
+ if (!book.ok) {
103
+ res.status(404).json({ error: book.error.message });
104
+ return;
105
+ }
106
+ const targetAssetId = typeof req.query.targetAssetId === "string" ? req.query.targetAssetId : undefined;
107
+ const result = await uploadLocalStream(book.value.engine, {
108
+ bookId: book.value.bookId,
109
+ filename,
110
+ stream: req,
111
+ targetAssetId,
112
+ });
113
+ if (!result.ok) {
114
+ res.status(result.statusCode).json({ error: result.message });
115
+ return;
116
+ }
117
+ res.json(result.value);
118
+ }
119
+ async function receiveSearchReference(req, res) {
120
+ const filename = typeof req.query.filename === "string" ? req.query.filename : "";
121
+ const bookId = typeof req.query.bookId === "string" ? req.query.bookId : "";
122
+ const kind = req.query.kind === "image" || req.query.kind === "video"
123
+ ? req.query.kind
124
+ : null;
125
+ if (!filename || !bookId || !kind) {
126
+ res.status(400).json({ error: "filename, bookId, and kind are required" });
127
+ return;
128
+ }
129
+ const book = await engineForBook(bookId);
130
+ if (!book.ok) {
131
+ res.status(404).json({ error: book.error.message });
132
+ return;
133
+ }
134
+ const declaredLength = Number(req.headers["content-length"] ?? 0);
135
+ if (declaredLength > SEARCH_REFERENCE_LIMIT_BYTES) {
136
+ res.status(413).json({ error: "Search reference exceeds the 500 MB limit" });
137
+ return;
138
+ }
139
+ const uploadRoot = await mkdtemp(join(tmpdir(), "videobook-search-upload-"));
140
+ const suffix = extname(filename).toLowerCase().replace(/[^a-z0-9.]/g, "");
141
+ const uploadPath = join(uploadRoot, `reference${suffix}`);
142
+ try {
143
+ await pipeline(req, limitUploadBytes(SEARCH_REFERENCE_LIMIT_BYTES), createWriteStream(uploadPath, { flags: "wx" }));
144
+ const result = await queue.enqueueAndWait(`prepare-search-reference-${uuidv7()}`, {
145
+ type: "prepare_search_reference",
146
+ bookId,
147
+ mediaKind: kind,
148
+ name: filename,
149
+ uploadPath,
150
+ cleanupRoot: uploadRoot,
151
+ }, 15 * 60_000);
152
+ res.json(result);
153
+ }
154
+ catch (cause) {
155
+ await rm(uploadRoot, { recursive: true, force: true });
156
+ const message = cause instanceof Error ? cause.message : String(cause);
157
+ res.status(message.includes("500 MB limit") ? 413 : 400).json({
158
+ error: message,
159
+ });
160
+ }
161
+ }
162
+ function sendSearchReferencePreview(req, res) {
163
+ try {
164
+ const preview = searchReferenceStore.get(String(req.params.referenceId)).preview;
165
+ if (!preview) {
166
+ res.status(404).json({ error: "Search reference preview is unavailable" });
167
+ return;
168
+ }
169
+ res.set("Cache-Control", "no-store").type("png").send(preview);
170
+ }
171
+ catch (cause) {
172
+ res.status(404).json({ error: cause instanceof Error ? cause.message : String(cause) });
173
+ }
174
+ }
175
+ function releaseSearchReference(req, res) {
176
+ res.json({
177
+ released: searchReferenceStore.release(String(req.params.referenceId)),
178
+ });
179
+ }
180
+ async function sendArtifactManifest(req, res) {
181
+ const book = await engineForBook(String(req.params.bookId));
182
+ if (!book.ok) {
183
+ res.status(404).json({ error: book.error.message });
184
+ return;
185
+ }
186
+ const result = await book.value.engine.files.manifest(String(req.params.artifactId), {
187
+ includeDotfiles: true,
188
+ });
189
+ if (!result.ok) {
190
+ res.status(result.error.code === "NOT_FOUND" ? 404 : 400).json({ error: result.error.message });
191
+ return;
192
+ }
193
+ res.json(result.value);
194
+ }
195
+ function registerAccessPolicy(app) {
196
+ app.use(restrictHttpAccess);
197
+ app.use(cors({ origin: true }));
198
+ app.use(["/files", "/archive-files"], restrictMediaContent);
199
+ }
200
+ function registerMediaRoutes(app) {
201
+ app.use("/archive-files", express.static(localArchiveDir(), { dotfiles: "allow" }));
202
+ app.get("/healthz", sendHealth);
203
+ app.get(/^\/files\/([^/]+)\/([^/]+)\/(.+)$/, sendArtifactFile);
204
+ }
205
+ function registerApiRoutes(app, voiceSessions) {
206
+ app.post("/api/books/:bookId/upload", receiveUpload);
207
+ app.post("/api/search-references", receiveSearchReference);
208
+ app.get("/api/search-references/:referenceId/preview", sendSearchReferencePreview);
209
+ app.delete("/api/search-references/:referenceId", releaseSearchReference);
210
+ app.use("/api/voice", voiceRouter(voiceSessions));
211
+ app.use(express.json({ limit: "500mb" }));
212
+ app.get("/api/books/:bookId/artifacts/:artifactId/manifest", sendArtifactManifest);
213
+ }
214
+ function registerMcpRoute(app, mcpHandler) {
215
+ const handleMcpRequest = toNodeHandler(mcpHandler);
216
+ app.all("/mcp", (req, res) => {
217
+ void handleMcpRequest(req, res, req.body);
218
+ });
219
+ }
220
+ function isAppRoute(path) {
221
+ return !RESERVED_ROOTS.has(path.split("/")[1] ?? "");
222
+ }
223
+ function spaFallback(indexPath) {
224
+ return (req, res, next) => {
225
+ if ((req.method !== "GET" && req.method !== "HEAD") || !isAppRoute(req.path) || !req.accepts("html")) {
226
+ next();
227
+ return;
228
+ }
229
+ res.set("Cache-Control", "no-cache");
230
+ res.sendFile(indexPath);
231
+ };
232
+ }
233
+ function registerUi(app, uiDir) {
234
+ app.use("/assets", express.static(join(uiDir, "assets"), { immutable: true, maxAge: "1y", index: false }));
235
+ app.use(express.static(uiDir, { index: false }));
236
+ app.use(spaFallback(join(uiDir, "index.html")));
237
+ }
238
+ function mountUi(app, options, uiDir) {
239
+ if (!options.ui)
240
+ return "disabled";
241
+ if (!existsSync(join(uiDir, "index.html")))
242
+ return "missing";
243
+ registerUi(app, uiDir);
244
+ return "served";
245
+ }
246
+ export function createApp(options) {
247
+ const app = express();
248
+ const voiceSessions = new VoiceSessions(bookManager);
249
+ const mcpHandler = createHttpHandler(scopedEngine, bookManager);
250
+ registerAccessPolicy(app);
251
+ registerMediaRoutes(app);
252
+ registerApiRoutes(app, voiceSessions);
253
+ registerMcpRoute(app, mcpHandler);
254
+ const uiDir = options.uiDir ?? defaultUiDir();
255
+ const uiStatus = mountUi(app, options, uiDir);
256
+ return { app, mcpHandler, voiceSessions, uiDir, uiStatus };
257
+ }
@@ -0,0 +1,332 @@
1
+ import { createB2 } from "@apicity/b2";
2
+ import { createHash } from "node:crypto";
3
+ import { createReadStream } from "node:fs";
4
+ import { open, readFile, stat } from "node:fs/promises";
5
+ import { extname } from "node:path";
6
+ import { getApiKey, requireApiKey } from "./api-key.js";
7
+ const JSON_CONTENT_TYPE = "application/json";
8
+ const TEXT_CONTENT_TYPE = "text/plain; charset=utf-8";
9
+ const MULTIPART_THRESHOLD_BYTES = 64 * 1024 * 1024;
10
+ const MULTIPART_PART_BYTES = 64 * 1024 * 1024;
11
+ function isNotFoundError(error) {
12
+ if (!error || typeof error !== "object")
13
+ return false;
14
+ const status = error.status;
15
+ return status === 404;
16
+ }
17
+ function cleanPrefix(value) {
18
+ return (value ?? "").replace(/^\/+|\/+$/g, "");
19
+ }
20
+ export function getArchiveConfig() {
21
+ const accessKeyId = requireApiKey("B2_ACCESS_KEY_ID");
22
+ const secretAccessKey = requireApiKey("B2_SECRET_ACCESS_KEY");
23
+ const region = process.env.B2_REGION;
24
+ const bucket = process.env.B2_BUCKET;
25
+ if (!region)
26
+ throw new Error("B2_REGION environment variable is not set");
27
+ if (!bucket)
28
+ throw new Error("B2_BUCKET environment variable is not set");
29
+ return {
30
+ accessKeyId,
31
+ secretAccessKey,
32
+ region,
33
+ bucket,
34
+ ...(process.env.B2_ENDPOINT ? { endpoint: process.env.B2_ENDPOINT } : {}),
35
+ prefix: cleanPrefix(process.env.B2_ARCHIVE_PREFIX),
36
+ };
37
+ }
38
+ export function missingArchiveConfigKeys() {
39
+ const names = ["B2_ACCESS_KEY_ID", "B2_SECRET_ACCESS_KEY", "B2_REGION", "B2_BUCKET"];
40
+ return names.filter((name) => !process.env[name] || getApiKey(name) === null);
41
+ }
42
+ export function createArchiveB2(config = getArchiveConfig()) {
43
+ return createB2({
44
+ accessKeyId: config.accessKeyId,
45
+ secretAccessKey: config.secretAccessKey,
46
+ region: config.region,
47
+ ...(config.endpoint ? { endpoint: config.endpoint } : {}),
48
+ timeout: 300_000,
49
+ });
50
+ }
51
+ export function archiveKey(config, key) {
52
+ return config.prefix ? `${config.prefix}/${key}` : key;
53
+ }
54
+ export function clipPrefix(sha256) {
55
+ return `clips/${sha256}/`;
56
+ }
57
+ export function clipKey(config, sha256, filename) {
58
+ return archiveKey(config, `${clipPrefix(sha256)}${filename}`);
59
+ }
60
+ export function assetSnapshotPrefix(config, sha256) {
61
+ return archiveKey(config, clipPrefix(sha256));
62
+ }
63
+ function flattenSnapshotPath(relativePath) {
64
+ return relativePath
65
+ .replace(/^\/+/, "")
66
+ .split("/")
67
+ .filter(Boolean)
68
+ .join("__");
69
+ }
70
+ export function assetSnapshotKey(config, sha256, relativePath) {
71
+ return `${assetSnapshotPrefix(config, sha256)}${flattenSnapshotPath(relativePath)}`;
72
+ }
73
+ export function indexKey(config, sha256) {
74
+ return clipKey(config, sha256, "archive.json");
75
+ }
76
+ export function transcriptJsonKey(config, sha256) {
77
+ return clipKey(config, sha256, "transcript.json");
78
+ }
79
+ export function transcriptTextKey(config, sha256) {
80
+ return clipKey(config, sha256, "transcript.txt");
81
+ }
82
+ export function chunkKey(config, sha256, index) {
83
+ return clipKey(config, sha256, `chunk-${String(index).padStart(3, "0")}.json`);
84
+ }
85
+ export function ingestKey(config, sha256, ingestId) {
86
+ return clipKey(config, sha256, `ingest-${ingestId}.json`);
87
+ }
88
+ export function topicMarkerKey(config, topicKey, sha256, ingestId) {
89
+ return archiveKey(config, `topics/${topicKey}/${sha256}-${ingestId}.json`);
90
+ }
91
+ export function personMarkerKey(config, personKey, sha256, ingestId) {
92
+ return archiveKey(config, `people/${personKey}/${sha256}-${ingestId}.json`);
93
+ }
94
+ export function normalizeArchiveKey(value) {
95
+ return value
96
+ .normalize("NFKD")
97
+ .replace(/[\u0300-\u036f]/g, "")
98
+ .toLowerCase()
99
+ .replace(/[^a-z0-9]+/g, "-")
100
+ .replace(/^-+|-+$/g, "")
101
+ .slice(0, 96);
102
+ }
103
+ export function archiveTags(values, source) {
104
+ const seen = new Set();
105
+ const tags = [];
106
+ for (const value of values ?? []) {
107
+ const label = value.trim().replace(/\s+/g, " ");
108
+ const key = normalizeArchiveKey(label);
109
+ if (!label || !key || seen.has(key))
110
+ continue;
111
+ seen.add(key);
112
+ tags.push({ key, label, source });
113
+ }
114
+ return tags;
115
+ }
116
+ function mergeTags(groups) {
117
+ const merged = new Map();
118
+ for (const group of groups) {
119
+ for (const tag of group) {
120
+ const existing = merged.get(tag.key);
121
+ if (!existing || (existing.source === "auto" && tag.source === "manual")) {
122
+ merged.set(tag.key, tag);
123
+ }
124
+ }
125
+ }
126
+ return [...merged.values()].sort((a, b) => a.label.localeCompare(b.label));
127
+ }
128
+ export function mergeArchiveIngests(ingests, args) {
129
+ const now = new Date().toISOString();
130
+ const sorted = [...ingests].sort((a, b) => a.created_at.localeCompare(b.created_at));
131
+ const existing = args.existing ?? null;
132
+ const createdAt = existing?.created_at ?? sorted[0]?.created_at ?? now;
133
+ const title = sorted.find((ingest) => ingest.title)?.title ?? existing?.title ?? null;
134
+ return {
135
+ schema_version: 1,
136
+ sha256: args.sha256,
137
+ archive_id: args.sha256,
138
+ title,
139
+ topics: mergeTags([existing?.topics ?? [], ...sorted.map((ingest) => ingest.topics)]),
140
+ people: mergeTags([existing?.people ?? [], ...sorted.map((ingest) => ingest.people)]),
141
+ sources: sorted.map((ingest) => ingest.source),
142
+ ingests: sorted.map((ingest) => ingest.ingest_id),
143
+ media: {
144
+ key: args.mediaKey,
145
+ filename: args.filename,
146
+ content_type: args.contentType,
147
+ size_bytes: args.sizeBytes,
148
+ },
149
+ transcript: args.transcript ?? existing?.transcript ?? null,
150
+ duration: args.duration ?? existing?.duration ?? null,
151
+ created_at: createdAt,
152
+ updated_at: now,
153
+ };
154
+ }
155
+ export function contentTypeForFilename(filename) {
156
+ switch (extname(filename).toLowerCase()) {
157
+ case ".aac":
158
+ return "audio/aac";
159
+ case ".flac":
160
+ return "audio/flac";
161
+ case ".gif":
162
+ return "image/gif";
163
+ case ".jpg":
164
+ case ".jpeg":
165
+ return "image/jpeg";
166
+ case ".json":
167
+ return "application/json";
168
+ case ".md":
169
+ return "text/markdown; charset=utf-8";
170
+ case ".mp4":
171
+ return "video/mp4";
172
+ case ".mov":
173
+ return "video/quicktime";
174
+ case ".webm":
175
+ return "video/webm";
176
+ case ".weba":
177
+ return "audio/webm";
178
+ case ".mkv":
179
+ return "video/x-matroska";
180
+ case ".mp3":
181
+ return "audio/mpeg";
182
+ case ".wav":
183
+ return "audio/wav";
184
+ case ".m4a":
185
+ return "audio/mp4";
186
+ case ".ogg":
187
+ return "audio/ogg";
188
+ case ".png":
189
+ return "image/png";
190
+ case ".txt":
191
+ return "text/plain; charset=utf-8";
192
+ case ".webp":
193
+ return "image/webp";
194
+ default:
195
+ return "application/octet-stream";
196
+ }
197
+ }
198
+ export async function hashFile(filePath) {
199
+ const hash = createHash("sha256");
200
+ await new Promise((resolve, reject) => {
201
+ const stream = createReadStream(filePath);
202
+ stream.on("data", (chunk) => hash.update(chunk));
203
+ stream.on("error", reject);
204
+ stream.on("end", resolve);
205
+ });
206
+ return hash.digest("hex");
207
+ }
208
+ export async function objectExists(b2, bucket, key) {
209
+ try {
210
+ await b2.objects.head({ bucket, key });
211
+ return true;
212
+ }
213
+ catch (error) {
214
+ if (isNotFoundError(error))
215
+ return false;
216
+ throw error;
217
+ }
218
+ }
219
+ export async function putJson(b2, bucket, key, value) {
220
+ await b2.objects.put({
221
+ bucket,
222
+ key,
223
+ body: JSON.stringify(value, null, 2),
224
+ contentType: JSON_CONTENT_TYPE,
225
+ });
226
+ }
227
+ export async function putText(b2, bucket, key, value) {
228
+ await b2.objects.put({
229
+ bucket,
230
+ key,
231
+ body: value,
232
+ contentType: TEXT_CONTENT_TYPE,
233
+ });
234
+ }
235
+ export async function getJson(b2, bucket, key) {
236
+ try {
237
+ const response = await b2.objects.get({ bucket, key });
238
+ const text = new TextDecoder().decode(response.body);
239
+ return JSON.parse(text);
240
+ }
241
+ catch (error) {
242
+ if (isNotFoundError(error))
243
+ return null;
244
+ throw error;
245
+ }
246
+ }
247
+ export async function getText(b2, bucket, key) {
248
+ try {
249
+ const response = await b2.objects.get({ bucket, key });
250
+ return new TextDecoder().decode(response.body);
251
+ }
252
+ catch (error) {
253
+ if (isNotFoundError(error))
254
+ return null;
255
+ throw error;
256
+ }
257
+ }
258
+ export async function listObjects(b2, bucket, prefix) {
259
+ const out = [];
260
+ let continuationToken;
261
+ do {
262
+ const response = await b2.objects.list({
263
+ bucket,
264
+ prefix,
265
+ ...(continuationToken ? { continuationToken } : {}),
266
+ });
267
+ const contents = response.contents;
268
+ out.push(...contents.map((item) => ({
269
+ key: item.key,
270
+ ...(item.size !== undefined ? { size: item.size } : {}),
271
+ ...(item.lastModified ? { lastModified: item.lastModified } : {}),
272
+ })));
273
+ continuationToken = response.nextContinuationToken;
274
+ } while (continuationToken);
275
+ return out;
276
+ }
277
+ export async function uploadFileToB2(b2, bucket, key, filePath, contentType) {
278
+ const fileStat = await stat(filePath);
279
+ if (fileStat.size <= MULTIPART_THRESHOLD_BYTES) {
280
+ const body = await readFile(filePath);
281
+ await b2.objects.put({
282
+ bucket,
283
+ key,
284
+ body: new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)),
285
+ contentType,
286
+ });
287
+ return;
288
+ }
289
+ const create = await b2.objects.createMultipartUpload({ bucket, key, contentType });
290
+ const parts = [];
291
+ const handle = await open(filePath, "r");
292
+ try {
293
+ let offset = 0;
294
+ let partNumber = 1;
295
+ while (offset < fileStat.size) {
296
+ const length = Math.min(MULTIPART_PART_BYTES, fileStat.size - offset);
297
+ const buffer = Buffer.allocUnsafe(length);
298
+ const read = await handle.read(buffer, 0, length, offset);
299
+ if (read.bytesRead <= 0)
300
+ break;
301
+ const chunk = buffer.subarray(0, read.bytesRead);
302
+ const uploaded = await b2.objects.uploadPart({
303
+ bucket,
304
+ key,
305
+ uploadId: create.uploadId,
306
+ partNumber,
307
+ body: new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength)),
308
+ });
309
+ if (!uploaded.eTag)
310
+ throw new Error(`B2 uploadPart did not return an ETag for ${key} part ${partNumber}`);
311
+ parts.push({ partNumber, eTag: uploaded.eTag });
312
+ offset += read.bytesRead;
313
+ partNumber += 1;
314
+ }
315
+ if (offset < fileStat.size) {
316
+ throw new Error(`Short read uploading ${key}: got ${offset} of ${fileStat.size} bytes`);
317
+ }
318
+ await b2.objects.completeMultipartUpload({
319
+ bucket,
320
+ key,
321
+ uploadId: create.uploadId,
322
+ parts,
323
+ });
324
+ }
325
+ catch (error) {
326
+ await b2.objects.abortMultipartUpload({ bucket, key, uploadId: create.uploadId }).catch(() => undefined);
327
+ throw error;
328
+ }
329
+ finally {
330
+ await handle.close();
331
+ }
332
+ }