videobook 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (558) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +192 -0
  45. package/dist/cli/doctor-command.js +255 -0
  46. package/dist/cli/entry.js +2 -0
  47. package/dist/cli/main.js +91 -0
  48. package/dist/cli/mcp-command.js +82 -0
  49. package/dist/cli/mcp-relay.js +51 -0
  50. package/dist/cli/open-browser.js +14 -0
  51. package/dist/cli/run-command.js +187 -0
  52. package/dist/cli/serve-command.js +57 -0
  53. package/dist/cli/stdio-guard.js +8 -0
  54. package/dist/cold-open-model.js +7 -0
  55. package/dist/cold-open.js +79 -0
  56. package/dist/command-reference.js +95 -0
  57. package/dist/content-store.js +74 -0
  58. package/dist/cost.js +8 -0
  59. package/dist/crop-geometry.js +23 -0
  60. package/dist/dancing-workflow.js +446 -0
  61. package/dist/data-home.js +44 -0
  62. package/dist/diagnostics.js +109 -0
  63. package/dist/dialog-quality.js +34 -0
  64. package/dist/downloads-dest.js +56 -0
  65. package/dist/editing-moments.js +194 -0
  66. package/dist/elevenlabs-audio-extension.js +66 -0
  67. package/dist/engine.js +54 -0
  68. package/dist/env-secrets.js +184 -0
  69. package/dist/ffmpeg-binary.js +12 -0
  70. package/dist/ffmpeg.js +425 -0
  71. package/dist/ffprobe-binary.js +55 -0
  72. package/dist/forget-asset.js +216 -0
  73. package/dist/frame-utils.js +27 -0
  74. package/dist/generation-credit-error.js +11 -0
  75. package/dist/generation-input-mentions.js +117 -0
  76. package/dist/generation-input-policy.js +68 -0
  77. package/dist/generation-reference-mentions.js +97 -0
  78. package/dist/goal-characters.js +49 -0
  79. package/dist/goal-plan-v3.js +273 -0
  80. package/dist/goal-plan.js +220 -0
  81. package/dist/goal-planner-prompt.js +28 -0
  82. package/dist/goal-prompt-budget.js +4 -0
  83. package/dist/goal-reference-images.js +25 -0
  84. package/dist/goal-state.js +261 -0
  85. package/dist/goal-stills.js +153 -0
  86. package/dist/goal-store.js +144 -0
  87. package/dist/goal-styles.js +144 -0
  88. package/dist/goal-v3-runtime.js +157 -0
  89. package/dist/guides/character_schema_guide.md +70 -0
  90. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  91. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  92. package/dist/guides/image_reproduction_guide.md +84 -0
  93. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  94. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  95. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  96. package/dist/http-access.js +49 -0
  97. package/dist/http-byte-range.js +31 -0
  98. package/dist/image-compress.js +77 -0
  99. package/dist/image-edit-default.js +1 -0
  100. package/dist/image-edit-models.js +41 -0
  101. package/dist/image-edit-settings.js +11 -0
  102. package/dist/image-region-edit.js +32 -0
  103. package/dist/image-subject-map.js +523 -0
  104. package/dist/image.js +46 -0
  105. package/dist/index.js +12 -0
  106. package/dist/job-deadline.js +60 -0
  107. package/dist/jobs/_alibaba-upload.js +30 -0
  108. package/dist/jobs/_audio-chunks.js +31 -0
  109. package/dist/jobs/_download-into-asset.js +15 -0
  110. package/dist/jobs/_fal-shared.js +22 -0
  111. package/dist/jobs/_fal-upload.js +35 -0
  112. package/dist/jobs/_image-edit-adapter.js +213 -0
  113. package/dist/jobs/_kie-poll.js +119 -0
  114. package/dist/jobs/_kie-upload.js +32 -0
  115. package/dist/jobs/_lease.js +163 -0
  116. package/dist/jobs/_persist-output.js +19 -0
  117. package/dist/jobs/_storage-retry.js +21 -0
  118. package/dist/jobs/_transcribe.js +245 -0
  119. package/dist/jobs/_translate-transcript.js +270 -0
  120. package/dist/jobs/analyze-alive-captions.js +10 -0
  121. package/dist/jobs/analyze-editing-moments.js +104 -0
  122. package/dist/jobs/analyze-jokes.js +143 -0
  123. package/dist/jobs/analyze-keyword-placement.js +155 -0
  124. package/dist/jobs/analyze-music.js +14 -0
  125. package/dist/jobs/analyze-video.js +282 -0
  126. package/dist/jobs/apply-cuts.js +83 -0
  127. package/dist/jobs/apply-sfx.js +107 -0
  128. package/dist/jobs/archive-clip.js +457 -0
  129. package/dist/jobs/assemble-goal.js +98 -0
  130. package/dist/jobs/audio-boost.js +71 -0
  131. package/dist/jobs/audio-mix.js +161 -0
  132. package/dist/jobs/backfill-asset-tags.js +144 -0
  133. package/dist/jobs/burn-watermark.js +316 -0
  134. package/dist/jobs/change-video-speed.js +176 -0
  135. package/dist/jobs/clean-video-audio.js +207 -0
  136. package/dist/jobs/cold-open.js +166 -0
  137. package/dist/jobs/combine-goal.js +108 -0
  138. package/dist/jobs/complete-provider-task.js +185 -0
  139. package/dist/jobs/copy-library-asset.js +33 -0
  140. package/dist/jobs/cost-estimate-assessment.js +113 -0
  141. package/dist/jobs/crop-image.js +47 -0
  142. package/dist/jobs/crop-video.js +71 -0
  143. package/dist/jobs/describe-image.js +181 -0
  144. package/dist/jobs/detect-scenes.js +33 -0
  145. package/dist/jobs/doctor-fix.js +100 -0
  146. package/dist/jobs/download-video.js +426 -0
  147. package/dist/jobs/enqueue-search-index.js +24 -0
  148. package/dist/jobs/even-split.js +122 -0
  149. package/dist/jobs/export-sequence.js +365 -0
  150. package/dist/jobs/extend-audio.js +229 -0
  151. package/dist/jobs/extract-audio.js +72 -0
  152. package/dist/jobs/extract-frame.js +58 -0
  153. package/dist/jobs/extract-notebook-audio.js +82 -0
  154. package/dist/jobs/extract-notebook-frame.js +93 -0
  155. package/dist/jobs/extract-video-clip.js +8 -0
  156. package/dist/jobs/extract-video-poster.js +55 -0
  157. package/dist/jobs/extract-waveform.js +25 -0
  158. package/dist/jobs/finish-goal.js +255 -0
  159. package/dist/jobs/forget-asset.js +11 -0
  160. package/dist/jobs/generate-acestep-cover.js +219 -0
  161. package/dist/jobs/generate-media.js +70 -0
  162. package/dist/jobs/image-subjects.js +1059 -0
  163. package/dist/jobs/index.js +18 -0
  164. package/dist/jobs/inject-first-frame.js +175 -0
  165. package/dist/jobs/isolate-vocals.js +69 -0
  166. package/dist/jobs/kie-paygate.js +177 -0
  167. package/dist/jobs/label-music-sections.js +5 -0
  168. package/dist/jobs/osg-placement.js +135 -0
  169. package/dist/jobs/pad-video.js +72 -0
  170. package/dist/jobs/plan-goal.js +97 -0
  171. package/dist/jobs/post-record.js +21 -0
  172. package/dist/jobs/post-to-x.js +21 -0
  173. package/dist/jobs/prepare-search-reference.js +190 -0
  174. package/dist/jobs/prepare-temporal-search.js +16 -0
  175. package/dist/jobs/process-audio.js +67 -0
  176. package/dist/jobs/process-image.js +58 -0
  177. package/dist/jobs/process-upload.js +166 -0
  178. package/dist/jobs/process-video.js +438 -0
  179. package/dist/jobs/register-all.js +161 -0
  180. package/dist/jobs/relayout-notebook.js +78 -0
  181. package/dist/jobs/remove-sound.js +299 -0
  182. package/dist/jobs/render-project.js +380 -0
  183. package/dist/jobs/render-qr-outro.js +138 -0
  184. package/dist/jobs/render-timeline.js +283 -0
  185. package/dist/jobs/repair-search-indexes.js +42 -0
  186. package/dist/jobs/replace-video-audio.js +64 -0
  187. package/dist/jobs/restore-archive-clip.js +53 -0
  188. package/dist/jobs/reverse-video.js +53 -0
  189. package/dist/jobs/rewrite-script.js +173 -0
  190. package/dist/jobs/run-notebook.js +3335 -0
  191. package/dist/jobs/semantic-index.js +797 -0
  192. package/dist/jobs/smooth-scene-concat.js +257 -0
  193. package/dist/jobs/splice-video.js +280 -0
  194. package/dist/jobs/split-video.js +127 -0
  195. package/dist/jobs/suggest-cuts.js +149 -0
  196. package/dist/jobs/suggest-sfx.js +135 -0
  197. package/dist/jobs/sync-video-to-audio.js +329 -0
  198. package/dist/jobs/tag-asset.js +266 -0
  199. package/dist/jobs/task-failure.js +16 -0
  200. package/dist/jobs/timewarp-video.js +152 -0
  201. package/dist/jobs/transcribe-video-audio.js +117 -0
  202. package/dist/jobs/transcribe.js +35 -0
  203. package/dist/jobs/translate-transcript.js +55 -0
  204. package/dist/jobs/trim-audio.js +133 -0
  205. package/dist/jobs/trim-video.js +291 -0
  206. package/dist/jobs/xai-paygate.js +26 -0
  207. package/dist/keyword-placement-cache.js +74 -0
  208. package/dist/lazy-render.js +142 -0
  209. package/dist/library-browse.js +78 -0
  210. package/dist/lightning-defaults.js +206 -0
  211. package/dist/load-env.js +9 -0
  212. package/dist/local-search-models.js +206 -0
  213. package/dist/local-upload.js +318 -0
  214. package/dist/logger.js +69 -0
  215. package/dist/media/adapters/alibaba.js +20 -0
  216. package/dist/media/adapters/elevenlabs.js +58 -0
  217. package/dist/media/adapters/fal.js +45 -0
  218. package/dist/media/adapters/index.js +26 -0
  219. package/dist/media/adapters/kie.js +120 -0
  220. package/dist/media/adapters/pending.js +21 -0
  221. package/dist/media/adapters/types.js +5 -0
  222. package/dist/media/adapters/xai.js +18 -0
  223. package/dist/media/catalog.js +236 -0
  224. package/dist/media/complete.js +191 -0
  225. package/dist/media/cost.js +45 -0
  226. package/dist/media/descriptor.js +157 -0
  227. package/dist/media/endpoints.js +11 -0
  228. package/dist/media/frame-options.js +18 -0
  229. package/dist/media/insert-video-timing.js +17 -0
  230. package/dist/media/json-schema.js +75 -0
  231. package/dist/media/legacy-task-types.js +129 -0
  232. package/dist/media/listing.js +24 -0
  233. package/dist/media/model-id.js +14 -0
  234. package/dist/media/preview.js +86 -0
  235. package/dist/media/prompt-dialects.js +65 -0
  236. package/dist/media/ref-fields.js +106 -0
  237. package/dist/media/refs.js +131 -0
  238. package/dist/media/request.js +188 -0
  239. package/dist/media/types.js +6 -0
  240. package/dist/media/wire.js +161 -0
  241. package/dist/media-library.js +535 -0
  242. package/dist/media-output-path.js +33 -0
  243. package/dist/multyculty-workflow.js +757 -0
  244. package/dist/notebook-action-catalog.js +608 -0
  245. package/dist/notebook-cell-outputs.js +726 -0
  246. package/dist/notebook-credit-retry.js +45 -0
  247. package/dist/notebook-fixtures.js +8 -0
  248. package/dist/notebook-generation-contract.js +77 -0
  249. package/dist/notebook-generation-controls.js +145 -0
  250. package/dist/notebook-goal-layout.js +40 -0
  251. package/dist/notebook-graph.js +37 -0
  252. package/dist/notebook-image-edit.js +84 -0
  253. package/dist/notebook-initial-generation-prompt.js +222 -0
  254. package/dist/notebook-live-analysis.js +941 -0
  255. package/dist/notebook-media-ops.js +579 -0
  256. package/dist/notebook-mutations.js +20 -0
  257. package/dist/notebook-placement.js +41 -0
  258. package/dist/notebook-provider-failure.js +87 -0
  259. package/dist/notebook-published-post.js +26 -0
  260. package/dist/notebook-reference-resolver.js +227 -0
  261. package/dist/notebook-relayout.js +270 -0
  262. package/dist/notebook-run-plan.js +519 -0
  263. package/dist/notebook-shots.js +162 -0
  264. package/dist/notebook-workflow.js +856 -0
  265. package/dist/openai-intelligence.js +99 -0
  266. package/dist/original-media-file.js +12 -0
  267. package/dist/osg-assets.js +36 -0
  268. package/dist/osg-placement-cache.js +64 -0
  269. package/dist/osg-settings.js +27 -0
  270. package/dist/paid-tools.js +22 -0
  271. package/dist/platform-guidance.js +26 -0
  272. package/dist/posts/dropbox-auth.js +91 -0
  273. package/dist/posts/dropbox-upload.js +132 -0
  274. package/dist/posts/icloud-drive.js +134 -0
  275. package/dist/posts/icloud-status.js +64 -0
  276. package/dist/posts/post-contract.js +2 -0
  277. package/dist/posts/post-copy-format.js +43 -0
  278. package/dist/posts/post-copy.js +193 -0
  279. package/dist/posts/post-folder.js +18 -0
  280. package/dist/posts/post-metrics.js +119 -0
  281. package/dist/posts/post-store.js +340 -0
  282. package/dist/posts/x-post-target.js +43 -0
  283. package/dist/preview-payload.js +90 -0
  284. package/dist/prompt-surfaces.js +22 -0
  285. package/dist/provider-prompt-transform.js +77 -0
  286. package/dist/provider-status-link.js +21 -0
  287. package/dist/provider-task-record.js +78 -0
  288. package/dist/provider-task-status.js +47 -0
  289. package/dist/provider-usage.js +294 -0
  290. package/dist/queue.js +806 -0
  291. package/dist/reference-video-duration.js +17 -0
  292. package/dist/remotion/BlurFillVideo.js +51 -0
  293. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  294. package/dist/remotion/OsgOverlay.js +30 -0
  295. package/dist/remotion/QrOutroComposition.js +47 -0
  296. package/dist/remotion/RenderComposition.js +11 -0
  297. package/dist/remotion/browser.js +59 -0
  298. package/dist/remotion/constants.js +35 -0
  299. package/dist/remotion/index.js +66 -0
  300. package/dist/remotion/keyword-graphics.js +151 -0
  301. package/dist/remotion/keyword-placement.js +182 -0
  302. package/dist/remotion/osg-spec.js +198 -0
  303. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  304. package/dist/remotion/qr-outro-audio.js +1 -0
  305. package/dist/remotion/qr-outro-spec.js +211 -0
  306. package/dist/remotion/shared-bundle.js +37 -0
  307. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  308. package/dist/remotion/subtitles/Highlight.js +58 -0
  309. package/dist/remotion/subtitles/Impact.js +67 -0
  310. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  311. package/dist/remotion/subtitles/alive-layout.js +321 -0
  312. package/dist/remotion/subtitles/alive-types.js +17 -0
  313. package/dist/remotion/subtitles/chunker.js +68 -0
  314. package/dist/remotion/subtitles/segments.js +42 -0
  315. package/dist/remotion/subtitles/styles.js +4 -0
  316. package/dist/remotion/subtitles/types.js +1 -0
  317. package/dist/remotion/util.js +18 -0
  318. package/dist/remotion-cancellation.js +15 -0
  319. package/dist/rename-helpers.js +204 -0
  320. package/dist/rhythm/analyze.js +128 -0
  321. package/dist/rhythm/contract.js +14 -0
  322. package/dist/rhythm/draft-model.js +19 -0
  323. package/dist/rhythm/energy.js +133 -0
  324. package/dist/rhythm/fft.js +44 -0
  325. package/dist/rhythm/model.js +71 -0
  326. package/dist/rhythm/music-analysis.js +124 -0
  327. package/dist/rhythm/music-draft.js +261 -0
  328. package/dist/rhythm/onset.js +101 -0
  329. package/dist/rhythm/run-worker.js +51 -0
  330. package/dist/rhythm/score.js +47 -0
  331. package/dist/rhythm/section-labels.js +168 -0
  332. package/dist/rhythm/sequence-rhythm.js +67 -0
  333. package/dist/rhythm/synthesize.js +103 -0
  334. package/dist/rhythm/timing.js +98 -0
  335. package/dist/rhythm/track.js +320 -0
  336. package/dist/rhythm/visual-fit.js +4 -0
  337. package/dist/rhythm/worker.js +25 -0
  338. package/dist/scene-detect.js +200 -0
  339. package/dist/scene-transition.js +12 -0
  340. package/dist/scheduled-posts.js +200 -0
  341. package/dist/search-index-coverage.js +13 -0
  342. package/dist/search-index-media.js +71 -0
  343. package/dist/search-reference-store.js +60 -0
  344. package/dist/search-scoring.js +17 -0
  345. package/dist/serve.js +150 -0
  346. package/dist/server-lock.js +143 -0
  347. package/dist/server.js +42 -0
  348. package/dist/srt.js +16 -0
  349. package/dist/tag-backfill-state.js +121 -0
  350. package/dist/tag-settings.js +30 -0
  351. package/dist/tag-vocabulary.js +237 -0
  352. package/dist/test-queue-cleanup.js +1 -0
  353. package/dist/time-parse.js +50 -0
  354. package/dist/timeline-helpers.js +554 -0
  355. package/dist/timewarp.js +87 -0
  356. package/dist/tools/_image-inputs.js +9 -0
  357. package/dist/tools/_media-refs.js +84 -0
  358. package/dist/tools/_shared.js +59 -0
  359. package/dist/tools/_suggest-shared.js +279 -0
  360. package/dist/tools/add-timeline-audio.js +73 -0
  361. package/dist/tools/alive-captions.js +157 -0
  362. package/dist/tools/analyze-jokes.js +56 -0
  363. package/dist/tools/analyze-video.js +66 -0
  364. package/dist/tools/animate-keywords.js +217 -0
  365. package/dist/tools/api-status.js +42 -0
  366. package/dist/tools/application-commands.js +34 -0
  367. package/dist/tools/apply-cuts.js +112 -0
  368. package/dist/tools/apply-sfx.js +152 -0
  369. package/dist/tools/apply-watermark.js +670 -0
  370. package/dist/tools/archive-clip.js +74 -0
  371. package/dist/tools/asset-history.js +61 -0
  372. package/dist/tools/asset-manifest.js +24 -0
  373. package/dist/tools/asset-tags.js +151 -0
  374. package/dist/tools/assign-slot.js +97 -0
  375. package/dist/tools/audio-boost.js +107 -0
  376. package/dist/tools/audio-mix.js +121 -0
  377. package/dist/tools/book-usage.js +16 -0
  378. package/dist/tools/chain.js +136 -0
  379. package/dist/tools/change-video-speed.js +105 -0
  380. package/dist/tools/chat-history.js +5 -0
  381. package/dist/tools/chat.js +848 -0
  382. package/dist/tools/clean-video-audio.js +97 -0
  383. package/dist/tools/clear-failed-tasks.js +66 -0
  384. package/dist/tools/cold-open.js +114 -0
  385. package/dist/tools/command-plan.js +1680 -0
  386. package/dist/tools/commands.js +1666 -0
  387. package/dist/tools/concat-images.js +236 -0
  388. package/dist/tools/connect-service.js +55 -0
  389. package/dist/tools/copy-file-to-clipboard.js +58 -0
  390. package/dist/tools/create-character.js +114 -0
  391. package/dist/tools/create-qart.js +253 -0
  392. package/dist/tools/create-qr-outro.js +149 -0
  393. package/dist/tools/create-qrbtf.js +215 -0
  394. package/dist/tools/create-watermark.js +97 -0
  395. package/dist/tools/crop-asset.js +18 -0
  396. package/dist/tools/crop-image.js +120 -0
  397. package/dist/tools/crop-video.js +97 -0
  398. package/dist/tools/delete-asset.js +104 -0
  399. package/dist/tools/describe-image.js +69 -0
  400. package/dist/tools/detect-scenes.js +44 -0
  401. package/dist/tools/doctor.js +126 -0
  402. package/dist/tools/download-video.js +114 -0
  403. package/dist/tools/duplicate-asset.js +184 -0
  404. package/dist/tools/edit-timeline.js +59 -0
  405. package/dist/tools/editing-moments.js +105 -0
  406. package/dist/tools/estimate-cost.js +171 -0
  407. package/dist/tools/even-split.js +295 -0
  408. package/dist/tools/export-asset-file.js +22 -0
  409. package/dist/tools/export-asset.js +140 -0
  410. package/dist/tools/export-sequence.js +250 -0
  411. package/dist/tools/export-to-downloads.js +54 -0
  412. package/dist/tools/export-transcript-srt.js +79 -0
  413. package/dist/tools/extend-audio.js +100 -0
  414. package/dist/tools/extract-audio.js +174 -0
  415. package/dist/tools/extract-frame.js +108 -0
  416. package/dist/tools/extract-notebook-audio.js +173 -0
  417. package/dist/tools/extract-notebook-frame.js +160 -0
  418. package/dist/tools/extract-video-clip.js +101 -0
  419. package/dist/tools/generate-media.js +157 -0
  420. package/dist/tools/generation-payload.js +48 -0
  421. package/dist/tools/get-timeline.js +12 -0
  422. package/dist/tools/get-watermark-config.js +38 -0
  423. package/dist/tools/goal.js +644 -0
  424. package/dist/tools/image-subjects.js +711 -0
  425. package/dist/tools/index.js +323 -0
  426. package/dist/tools/inject-first-frame.js +154 -0
  427. package/dist/tools/invert-image.js +70 -0
  428. package/dist/tools/isolate-vocals.js +66 -0
  429. package/dist/tools/list-assets.js +98 -0
  430. package/dist/tools/list-cell-generations.js +225 -0
  431. package/dist/tools/list-elevenlabs-voices.js +163 -0
  432. package/dist/tools/log-wrapper.js +29 -0
  433. package/dist/tools/make-cover-song.js +95 -0
  434. package/dist/tools/mark-asset-seen.js +23 -0
  435. package/dist/tools/media-library.js +245 -0
  436. package/dist/tools/move-timeline-audio.js +35 -0
  437. package/dist/tools/music-analysis.js +60 -0
  438. package/dist/tools/music-draft.js +29 -0
  439. package/dist/tools/pad-video.js +87 -0
  440. package/dist/tools/post-record.js +83 -0
  441. package/dist/tools/post.js +168 -0
  442. package/dist/tools/prompt-history.js +164 -0
  443. package/dist/tools/provider-key-status.js +248 -0
  444. package/dist/tools/qrbtf-render.js +849 -0
  445. package/dist/tools/read-asset-metadata.js +26 -0
  446. package/dist/tools/read-file-at-commit.js +29 -0
  447. package/dist/tools/read-transcript.js +41 -0
  448. package/dist/tools/reconcile-project-work.js +1452 -0
  449. package/dist/tools/recover-asset.js +28 -0
  450. package/dist/tools/remove-sound.js +72 -0
  451. package/dist/tools/remove-timeline-audio.js +34 -0
  452. package/dist/tools/remove-timeline-slot.js +40 -0
  453. package/dist/tools/rename-all.js +434 -0
  454. package/dist/tools/rename-asset.js +34 -0
  455. package/dist/tools/render-settings.js +47 -0
  456. package/dist/tools/render-timeline.js +120 -0
  457. package/dist/tools/render.js +384 -0
  458. package/dist/tools/reorder-character-images.js +45 -0
  459. package/dist/tools/replace-video-audio.js +133 -0
  460. package/dist/tools/restore-archive-clips.js +209 -0
  461. package/dist/tools/restore-asset-version.js +37 -0
  462. package/dist/tools/retry-generation.js +54 -0
  463. package/dist/tools/reverse-video.js +74 -0
  464. package/dist/tools/rewrite-prompt.js +388 -0
  465. package/dist/tools/rewrite-script.js +67 -0
  466. package/dist/tools/schema-from-apicity.js +66 -0
  467. package/dist/tools/search-archive.js +150 -0
  468. package/dist/tools/semantic-search.js +1214 -0
  469. package/dist/tools/sequence-rhythm.js +43 -0
  470. package/dist/tools/set-character-metadata.js +84 -0
  471. package/dist/tools/set-env-secret.js +40 -0
  472. package/dist/tools/set-osg.js +143 -0
  473. package/dist/tools/show-folder.js +37 -0
  474. package/dist/tools/smooth-scene-concat.js +122 -0
  475. package/dist/tools/splice-video.js +149 -0
  476. package/dist/tools/split-video.js +202 -0
  477. package/dist/tools/suggest-cuts.js +45 -0
  478. package/dist/tools/suggest-sfx.js +43 -0
  479. package/dist/tools/sync-video-to-audio.js +115 -0
  480. package/dist/tools/tag-backfill.js +177 -0
  481. package/dist/tools/timewarp-video.js +85 -0
  482. package/dist/tools/tool-def.js +1 -0
  483. package/dist/tools/tool-json-schema.js +116 -0
  484. package/dist/tools/transcribe.js +90 -0
  485. package/dist/tools/translate-subtitles.js +59 -0
  486. package/dist/tools/trim-audio.js +139 -0
  487. package/dist/tools/trim-video.js +168 -0
  488. package/dist/tools/update-script.js +25 -0
  489. package/dist/tools/update-timeline-audio.js +40 -0
  490. package/dist/tools/upload-file.js +41 -0
  491. package/dist/tools/verify-x-mentions.js +141 -0
  492. package/dist/tools/video-editing-command-contract.js +291 -0
  493. package/dist/tools/video-source-reference.js +30 -0
  494. package/dist/tools/video-time-input.js +18 -0
  495. package/dist/tools/videobook-domain.js +2006 -0
  496. package/dist/tools/wait-for-asset-ready.js +143 -0
  497. package/dist/tools/workspace-actions.js +54 -0
  498. package/dist/transcript-projection.js +202 -0
  499. package/dist/transcript-srt.js +33 -0
  500. package/dist/upload.js +76 -0
  501. package/dist/usage-providers.js +25 -0
  502. package/dist/video-analysis-projection.js +46 -0
  503. package/dist/video-poster.js +33 -0
  504. package/dist/video-proxy.js +99 -0
  505. package/dist/vision-regions.js +111 -0
  506. package/dist/voice-actions.js +90 -0
  507. package/dist/voice-audio.js +55 -0
  508. package/dist/voice-config.js +122 -0
  509. package/dist/voice-continuation.js +54 -0
  510. package/dist/voice-contract.js +1 -0
  511. package/dist/voice-http.js +145 -0
  512. package/dist/voice-reply-progress.js +55 -0
  513. package/dist/voice-session.js +992 -0
  514. package/dist/watermark-badge.js +259 -0
  515. package/dist/watermark-config.js +111 -0
  516. package/dist/watermark-handle-position.js +55 -0
  517. package/dist/watermark-platform.js +15 -0
  518. package/dist/watermark-timing.js +16 -0
  519. package/dist/workspace-action-catalog.js +315 -0
  520. package/dist/workspace-action-contract.js +21 -0
  521. package/dist/workspace-action-groups.js +98 -0
  522. package/dist/workspace-action-surfaces.js +7 -0
  523. package/dist/x/account.js +88 -0
  524. package/dist/x/authenticated-handle.js +30 -0
  525. package/dist/x/oauth-flow.js +191 -0
  526. package/dist/x/post-contract.js +1 -0
  527. package/dist/x/post-metadata.js +44 -0
  528. package/dist/x/post-to-x.js +456 -0
  529. package/dist/x/published-post-cell.js +90 -0
  530. package/dist/x/x-tokens.js +69 -0
  531. package/dist/zod-slots.js +334 -0
  532. package/package.json +92 -4
  533. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  534. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  535. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  536. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  537. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  538. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  539. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  540. package/ui/assets/index-DzPAnCUj.css +1 -0
  541. package/ui/assets/index-kS1905s5.js +37 -0
  542. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  543. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  544. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  545. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  546. package/ui/assets/ui-Oirr9VOf.css +1 -0
  547. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  548. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  549. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  550. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  551. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  552. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  553. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  554. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  555. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  556. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  557. package/ui/favicon.svg +8 -0
  558. package/ui/index.html +126 -0
@@ -0,0 +1,117 @@
1
+ import { join } from "node:path";
2
+ import { findVideoFile } from "videobook-engine";
3
+ import { getApiKey } from "../api-key.js";
4
+ import { probeVideo } from "../ffmpeg.js";
5
+ import { logger } from "../logger.js";
6
+ import { parseSegments } from "../remotion/subtitles/segments.js";
7
+ import { buildPhraseLines, phraseFrameTime } from "../remotion/subtitles/alive-layout.js";
8
+ import { HOP_SECONDS, VOCAL_SAMPLE_RATE, decodeVocalPcm, detectOnsets, envelopeSeries, rmsFrames, snapWordStarts, wordEnergies, } from "./audio.js";
9
+ import { detectPhraseRegions, extractPhraseFrames } from "./frames.js";
10
+ import { aliveFingerprint, readAliveAnalysis, writeAliveAnalysis } from "./model.js";
11
+ import { ALIVE_ANALYSIS_VERSION } from "./schema.js";
12
+ const ENVELOPE_HZ = 20;
13
+ function segmentInputs(segments, audio, onsets) {
14
+ const words = segments.flatMap((segment) => segment.words);
15
+ const starts = snapWordStarts(words, onsets);
16
+ const energies = audio ? wordEnergies(words, audio.rms, audio.hopSeconds) : words.map(() => 0.5);
17
+ let cursor = 0;
18
+ return segments.map((segment) => segment.words.map((word) => {
19
+ const index = cursor++;
20
+ return { raw: word.word, start: word.start, end: word.end, onsetStart: starts[index], energy: energies[index] };
21
+ }));
22
+ }
23
+ function toPhrase(lines, index) {
24
+ const frameTime = phraseFrameTime(lines);
25
+ return { id: `p${index + 1}`, frameTime: Math.round(frameTime * 1000) / 1000, regions: [], lines };
26
+ }
27
+ /** Phrases, vocal onsets and energy for a transcript; regions are attached separately. */
28
+ function buildAliveAnalysis(input) {
29
+ const { audio } = input;
30
+ const onsets = audio ? detectOnsets(audio.rms, audio.hopSeconds) : [];
31
+ return {
32
+ version: ALIVE_ANALYSIS_VERSION,
33
+ fingerprint: input.fingerprint,
34
+ analyzedAt: new Date().toISOString(),
35
+ durationSeconds: input.durationSeconds,
36
+ sourceWidth: input.sourceWidth,
37
+ sourceHeight: input.sourceHeight,
38
+ envelopeHz: ENVELOPE_HZ,
39
+ envelope: audio ? envelopeSeries(audio.rms, ENVELOPE_HZ, audio.hopSeconds) : [],
40
+ onsets,
41
+ regionsAnalyzed: false,
42
+ phrases: buildPhraseLines(segmentInputs(input.segments, audio, onsets)).map(toPhrase),
43
+ };
44
+ }
45
+ function attachRegions(analysis, regions) {
46
+ return {
47
+ ...analysis,
48
+ regionsAnalyzed: true,
49
+ phrases: analysis.phrases.map((phrase) => ({ ...phrase, regions: regions.get(phrase.id) ?? [] })),
50
+ };
51
+ }
52
+ /** Locate the video file and timed transcript an analysis is keyed on. */
53
+ export async function aliveSource(engine, assetId) {
54
+ const manifest = await engine.files.manifest(assetId);
55
+ if (!manifest.ok)
56
+ throw new Error(manifest.error.message);
57
+ const videoFile = findVideoFile(manifest.value.files);
58
+ if (!videoFile)
59
+ throw new Error(`No video file found in ${assetId}.`);
60
+ const transcript = await engine.files.read(assetId, "elevenlabs.json");
61
+ if (!transcript.ok)
62
+ throw new Error(`No timed transcript found for ${assetId}. Run /transcribe @${assetId} first.`);
63
+ return {
64
+ videoPath: join(manifest.value.path, videoFile.name),
65
+ fingerprint: aliveFingerprint(videoFile, transcript.value),
66
+ transcript: transcript.value,
67
+ };
68
+ }
69
+ export async function cachedAliveAnalysis(engine, assetId, fingerprint) {
70
+ const cached = await readAliveAnalysis(engine, assetId);
71
+ return cached && cached.fingerprint === fingerprint ? cached : null;
72
+ }
73
+ async function readAudio(videoPath, hasAudio, signal) {
74
+ if (!hasAudio)
75
+ return null;
76
+ const pcm = await decodeVocalPcm(videoPath, signal);
77
+ return pcm ? { rms: rmsFrames(pcm, VOCAL_SAMPLE_RATE, HOP_SECONDS), hopSeconds: HOP_SECONDS } : null;
78
+ }
79
+ async function withRegions(analysis, videoPath, signal) {
80
+ const apiKey = getApiKey("OPENAI_API_KEY");
81
+ if (!apiKey || analysis.phrases.length === 0)
82
+ return analysis;
83
+ try {
84
+ const frames = await extractPhraseFrames(videoPath, analysis.phrases, analysis.durationSeconds, signal);
85
+ return attachRegions(analysis, await detectPhraseRegions(frames, apiKey, signal));
86
+ }
87
+ catch (error) {
88
+ signal.throwIfAborted();
89
+ // Placement degrades to the default columns; captions must not fail on vision.
90
+ logger.warn("alive-captions-regions-failed", { error });
91
+ return analysis;
92
+ }
93
+ }
94
+ /**
95
+ * Full analysis for one video: transcript phrases, vocal onsets and energy,
96
+ * then per-phrase face/text regions when a vision provider is configured.
97
+ * Persists and returns the result; a fresh cache hit is returned as-is.
98
+ */
99
+ export async function analyzeAliveCaptions(engine, assetId, signal, force = false) {
100
+ const source = await aliveSource(engine, assetId);
101
+ const cached = force ? null : await cachedAliveAnalysis(engine, assetId, source.fingerprint);
102
+ if (cached)
103
+ return cached;
104
+ const probe = await probeVideo(source.videoPath, { signal });
105
+ const segments = parseSegments(JSON.parse(source.transcript.toString()));
106
+ const base = buildAliveAnalysis({
107
+ segments,
108
+ audio: await readAudio(source.videoPath, probe.hasAudio, signal),
109
+ durationSeconds: probe.duration,
110
+ sourceWidth: probe.width,
111
+ sourceHeight: probe.height,
112
+ fingerprint: source.fingerprint,
113
+ });
114
+ const analysis = await withRegions(base, source.videoPath, signal);
115
+ await writeAliveAnalysis(engine, assetId, analysis);
116
+ return analysis;
117
+ }
@@ -0,0 +1,179 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { runFfmpeg } from "../ffmpeg.js";
5
+ /**
6
+ * Vocal timing for alive captions: decode a band-limited mono track, then
7
+ * derive a loudness envelope, vocal onsets and per-word energy from it.
8
+ */
9
+ export const VOCAL_SAMPLE_RATE = 8_000;
10
+ export const HOP_SECONDS = 0.01;
11
+ const ONSET_HISTORY_FRAMES = 4;
12
+ const ONSET_PEAK_RADIUS = 3;
13
+ const ONSET_MIN_SPACING_SECONDS = 0.06;
14
+ const ONSET_MIN_NOVELTY = 0.35;
15
+ const EPSILON = 1e-4;
16
+ const ENERGY_WINDOW_SECONDS = 0.3;
17
+ const DEFAULT_SNAP_SECONDS = 0.15;
18
+ /** Decode the first audio stream as 8 kHz mono float PCM filtered to the vocal band; callers check the probe for audio first. */
19
+ export async function decodeVocalPcm(inputPath, signal) {
20
+ const dir = await mkdtemp(join(tmpdir(), "videobook-alive-audio-"));
21
+ const output = join(dir, "vocal.f32");
22
+ try {
23
+ await runFfmpeg([
24
+ "-y", "-i", inputPath,
25
+ "-map", "0:a:0", "-vn",
26
+ "-af", "highpass=f=250,lowpass=f=3500",
27
+ "-ac", "1", "-ar", String(VOCAL_SAMPLE_RATE),
28
+ "-f", "f32le", output,
29
+ ], { signal });
30
+ const bytes = await readFile(output).catch(() => null);
31
+ if (!bytes || bytes.length < 4)
32
+ return null;
33
+ const aligned = new Uint8Array(bytes.length - (bytes.length % 4));
34
+ aligned.set(bytes.subarray(0, aligned.length));
35
+ return new Float32Array(aligned.buffer);
36
+ }
37
+ finally {
38
+ await rm(dir, { recursive: true, force: true });
39
+ }
40
+ }
41
+ /** RMS per hop over a two-hop window. */
42
+ export function rmsFrames(samples, sampleRate, hopSeconds = HOP_SECONDS) {
43
+ const hop = Math.max(1, Math.round(sampleRate * hopSeconds));
44
+ const window = hop * 2;
45
+ const count = Math.ceil(samples.length / hop);
46
+ const frames = new Float32Array(count);
47
+ for (let frame = 0; frame < count; frame++) {
48
+ const begin = frame * hop;
49
+ const end = Math.min(samples.length, begin + window);
50
+ let sum = 0;
51
+ for (let index = begin; index < end; index++)
52
+ sum += samples[index] * samples[index];
53
+ frames[frame] = Math.sqrt(sum / Math.max(1, end - begin));
54
+ }
55
+ return frames;
56
+ }
57
+ function percentile(values, fraction) {
58
+ if (values.length === 0)
59
+ return 0;
60
+ const sorted = Array.from(values).sort((a, b) => a - b);
61
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.floor(fraction * (sorted.length - 1))));
62
+ return sorted[index];
63
+ }
64
+ function historyMean(rms, frame) {
65
+ const from = Math.max(0, frame - ONSET_HISTORY_FRAMES);
66
+ if (frame === from)
67
+ return 0;
68
+ let sum = 0;
69
+ for (let index = from; index < frame; index++)
70
+ sum += rms[index];
71
+ return sum / (frame - from);
72
+ }
73
+ /** Log-energy rise of each frame over its recent history, gated by a noise floor. */
74
+ function onsetNovelty(rms) {
75
+ const floor = percentile(rms, 0.2) * 2 + EPSILON;
76
+ const novelty = new Float32Array(rms.length);
77
+ for (let frame = 0; frame < rms.length; frame++) {
78
+ if (rms[frame] < floor)
79
+ continue;
80
+ novelty[frame] = Math.max(0, Math.log(rms[frame] + EPSILON) - Math.log(historyMean(rms, frame) + EPSILON));
81
+ }
82
+ return novelty;
83
+ }
84
+ function noveltyThreshold(novelty) {
85
+ const active = Array.from(novelty).filter((value) => value > 0);
86
+ if (active.length === 0)
87
+ return Infinity;
88
+ const mean = active.reduce((sum, value) => sum + value, 0) / active.length;
89
+ const variance = active.reduce((sum, value) => sum + (value - mean) ** 2, 0) / active.length;
90
+ return Math.max(ONSET_MIN_NOVELTY, mean + Math.sqrt(variance));
91
+ }
92
+ function isLocalPeak(novelty, frame) {
93
+ const from = Math.max(0, frame - ONSET_PEAK_RADIUS);
94
+ const to = Math.min(novelty.length - 1, frame + ONSET_PEAK_RADIUS);
95
+ for (let index = from; index <= to; index++) {
96
+ if (novelty[index] > novelty[frame])
97
+ return false;
98
+ }
99
+ return true;
100
+ }
101
+ /** Onset times in seconds, ascending. */
102
+ export function detectOnsets(rms, hopSeconds = HOP_SECONDS) {
103
+ const novelty = onsetNovelty(rms);
104
+ const threshold = noveltyThreshold(novelty);
105
+ const onsets = [];
106
+ for (let frame = 0; frame < novelty.length; frame++) {
107
+ if (novelty[frame] < threshold || !isLocalPeak(novelty, frame))
108
+ continue;
109
+ const time = frame * hopSeconds;
110
+ if (onsets.length > 0 && time - onsets[onsets.length - 1] < ONSET_MIN_SPACING_SECONDS)
111
+ continue;
112
+ onsets.push(Math.round(time * 1000) / 1000);
113
+ }
114
+ return onsets;
115
+ }
116
+ function firstAtOrAfter(sorted, value) {
117
+ let low = 0;
118
+ let high = sorted.length;
119
+ while (low < high) {
120
+ const mid = (low + high) >> 1;
121
+ if (sorted[mid] < value)
122
+ low = mid + 1;
123
+ else
124
+ high = mid;
125
+ }
126
+ return low;
127
+ }
128
+ /** Nearest onset to `start` within ±maxShift that is not before `notBefore`; else `start`. */
129
+ export function snapToOnset(start, onsets, notBefore, maxShift = DEFAULT_SNAP_SECONDS) {
130
+ const from = firstAtOrAfter(onsets, Math.max(start - maxShift, notBefore));
131
+ let best = start;
132
+ let bestDistance = Infinity;
133
+ for (let index = from; index < onsets.length && onsets[index] <= start + maxShift; index++) {
134
+ const distance = Math.abs(onsets[index] - start);
135
+ if (distance < bestDistance) {
136
+ best = onsets[index];
137
+ bestDistance = distance;
138
+ }
139
+ }
140
+ return best;
141
+ }
142
+ /** Snap each word start to a vocal onset, keeping word order strictly increasing. */
143
+ export function snapWordStarts(words, onsets, maxShift = DEFAULT_SNAP_SECONDS) {
144
+ const snapped = [];
145
+ for (const word of words) {
146
+ const previous = snapped.length > 0 ? snapped[snapped.length - 1] + 0.04 : 0;
147
+ snapped.push(snapToOnset(word.start, onsets, previous, maxShift));
148
+ }
149
+ return snapped;
150
+ }
151
+ function meanRms(rms, start, end, hopSeconds) {
152
+ const from = Math.max(0, Math.floor(start / hopSeconds));
153
+ const to = Math.min(rms.length, Math.max(from + 1, Math.ceil(end / hopSeconds)));
154
+ let sum = 0;
155
+ for (let index = from; index < to; index++)
156
+ sum += rms[index];
157
+ return to > from ? sum / (to - from) : 0;
158
+ }
159
+ /** Per-word vocal energy normalized so the loudest ~5% of words reach 1. */
160
+ export function wordEnergies(words, rms, hopSeconds = HOP_SECONDS) {
161
+ const raw = words.map((word) => meanRms(rms, word.start, Math.min(word.end, word.start + ENERGY_WINDOW_SECONDS), hopSeconds));
162
+ const reference = percentile(raw, 0.95);
163
+ if (reference <= 0)
164
+ return words.map(() => 0.5);
165
+ return raw.map((value) => Math.round(Math.min(1, value / reference) * 100) / 100);
166
+ }
167
+ /** Downsample RMS frames to `hz` peak buckets normalized to 0..1. */
168
+ export function envelopeSeries(rms, hz, hopSeconds = HOP_SECONDS) {
169
+ const perBucket = Math.max(1, Math.round(1 / (hz * hopSeconds)));
170
+ const peaks = [];
171
+ for (let begin = 0; begin < rms.length; begin += perBucket) {
172
+ let peak = 0;
173
+ for (let index = begin; index < Math.min(rms.length, begin + perBucket); index++)
174
+ peak = Math.max(peak, rms[index]);
175
+ peaks.push(peak);
176
+ }
177
+ const reference = percentile(peaks, 0.99) || 1;
178
+ return peaks.map((peak) => Math.round(Math.min(1, peak / reference) * 1000) / 1000);
179
+ }
@@ -0,0 +1,59 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { runFfmpeg } from "../ffmpeg.js";
5
+ import { detectOccupiedRegions } from "../vision-regions.js";
6
+ const FRAMES_PER_REQUEST = 10;
7
+ const SYSTEM_PROMPT = `You are a precise video-layout analyst. Each attached image is a frame from the middle of a sung or spoken phrase that will get large stacked caption typography.
8
+
9
+ For every frame, identify all occupied regions the captions must not cover:
10
+ - every visible human face, including partial faces and faces on posters or screens
11
+ - every existing visible text block, subtitle, caption, title, logo, watermark, or graphic
12
+
13
+ Return one entry for every cue_id. Coordinates use a normalized image scale from 0 to 1000: x and y are the top-left corner, followed by width and height. Use tight boxes around the visible occupied content. Do not return boxes for bodies, clothing, scenery, instruments, microphones, or ordinary objects. Return an empty occupied_regions array only when none of the listed content is visible. Never omit, rename, or duplicate a cue_id.`;
14
+ function clampFrameTime(time, duration) {
15
+ return Math.max(0, Math.min(Math.max(0, duration - 0.05), time));
16
+ }
17
+ export async function extractPhraseFrames(videoPath, phrases, duration, signal) {
18
+ const dir = await mkdtemp(join(tmpdir(), "videobook-alive-frames-"));
19
+ try {
20
+ const frames = [];
21
+ for (const phrase of phrases) {
22
+ signal?.throwIfAborted();
23
+ const frameTime = clampFrameTime(phrase.frameTime, duration);
24
+ const framePath = join(dir, `${phrase.id}.jpg`);
25
+ await runFfmpeg([
26
+ "-y", "-ss", frameTime.toFixed(3), "-i", videoPath,
27
+ "-frames:v", "1", "-vf", "scale=768:768:force_original_aspect_ratio=decrease", "-q:v", "3",
28
+ framePath,
29
+ ], { signal });
30
+ frames.push({ id: phrase.id, frameTime, buffer: await readFile(framePath) });
31
+ }
32
+ return frames;
33
+ }
34
+ finally {
35
+ await rm(dir, { recursive: true, force: true });
36
+ }
37
+ }
38
+ function batches(items, size) {
39
+ const out = [];
40
+ for (let index = 0; index < items.length; index += size)
41
+ out.push(items.slice(index, index + size));
42
+ return out;
43
+ }
44
+ /** Face/text/graphic boxes per phrase frame, normalized to the source frame. */
45
+ export async function detectPhraseRegions(frames, apiKey, signal) {
46
+ const regions = new Map();
47
+ for (const batch of batches(frames, FRAMES_PER_REQUEST)) {
48
+ const found = await detectOccupiedRegions(batch.map((frame) => ({ id: frame.id, label: `timestamp=${frame.frameTime.toFixed(3)}s`, buffer: frame.buffer })), {
49
+ apiKey,
50
+ signal,
51
+ instructions: SYSTEM_PROMPT,
52
+ schemaName: "alive_caption_regions",
53
+ errorLabel: "Alive caption placement",
54
+ });
55
+ for (const [id, boxes] of found)
56
+ regions.set(id, boxes);
57
+ }
58
+ return regions;
59
+ }
@@ -0,0 +1,37 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DEFAULT_ALIVE_SETTINGS } from "../remotion/subtitles/alive-types.js";
3
+ import { ALIVE_ANALYSIS_VERSION, aliveAnalysisSchema, aliveSettingsSchema } from "./schema.js";
4
+ const ALIVE_ANALYSIS_METADATA_KEY = "alive-captions.analysis";
5
+ const ALIVE_SETTINGS_METADATA_KEY = "alive-captions.settings";
6
+ export function aliveFingerprint(videoFile, transcript) {
7
+ return createHash("sha256").update(JSON.stringify({
8
+ version: ALIVE_ANALYSIS_VERSION,
9
+ video: { name: videoFile.name, size: videoFile.sizeBytes, objectHash: videoFile.objectHash },
10
+ transcript: createHash("sha256").update(transcript).digest("hex"),
11
+ })).digest("hex");
12
+ }
13
+ export async function readAliveAnalysis(engine, assetId) {
14
+ const result = await engine.metadata.artifacts.read(assetId, ALIVE_ANALYSIS_METADATA_KEY);
15
+ if (!result.ok)
16
+ return null;
17
+ const parsed = aliveAnalysisSchema.safeParse(result.value);
18
+ return parsed.success ? parsed.data : null;
19
+ }
20
+ export async function writeAliveAnalysis(engine, assetId, analysis) {
21
+ const result = await engine.metadata.artifacts.write(assetId, ALIVE_ANALYSIS_METADATA_KEY, aliveAnalysisSchema.parse(analysis));
22
+ if (!result.ok)
23
+ throw new Error(`Failed to persist alive caption analysis: ${result.error.message}`);
24
+ }
25
+ /** Stored settings merged over the defaults; unreadable fields fall back individually. */
26
+ export async function readAliveSettings(engine, assetId) {
27
+ const result = await engine.metadata.artifacts.read(assetId, ALIVE_SETTINGS_METADATA_KEY);
28
+ if (!result.ok)
29
+ return DEFAULT_ALIVE_SETTINGS;
30
+ const partial = aliveSettingsSchema.partial().safeParse(result.value);
31
+ return partial.success ? { ...DEFAULT_ALIVE_SETTINGS, ...partial.data } : DEFAULT_ALIVE_SETTINGS;
32
+ }
33
+ export async function writeAliveSettings(engine, assetId, settings) {
34
+ const result = await engine.metadata.artifacts.write(assetId, ALIVE_SETTINGS_METADATA_KEY, aliveSettingsSchema.parse(settings));
35
+ if (!result.ok)
36
+ throw new Error(`Failed to persist alive caption settings: ${result.error.message}`);
37
+ }
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { ALIVE_MOTIONS, ALIVE_STYLES } from "../remotion/subtitles/alive-types.js";
3
+ /** Client-safe (zod only) schemas for stored alive caption analysis and settings. */
4
+ export const ALIVE_ANALYSIS_VERSION = 1;
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/);
6
+ export const aliveSettingsSchema = z.object({
7
+ style: z.enum(ALIVE_STYLES),
8
+ motion: z.enum(ALIVE_MOTIONS),
9
+ energySizing: z.boolean(),
10
+ snapToOnsets: z.boolean(),
11
+ avoidFaces: z.boolean(),
12
+ avoidText: z.boolean(),
13
+ textColor: hexColor,
14
+ hitColor: hexColor,
15
+ }).strict();
16
+ const regionSchema = z.object({
17
+ kind: z.enum(["face", "text", "graphic"]),
18
+ confidence: z.number().min(0).max(1),
19
+ x: z.number().min(0).max(1),
20
+ y: z.number().min(0).max(1),
21
+ width: z.number().positive().max(1),
22
+ height: z.number().positive().max(1),
23
+ }).strict();
24
+ const wordSchema = z.object({
25
+ text: z.string().min(1),
26
+ transcriptStart: z.number().nonnegative(),
27
+ transcriptEnd: z.number().nonnegative(),
28
+ onsetStart: z.number().nonnegative(),
29
+ energy: z.number().min(0).max(1),
30
+ }).strict();
31
+ const phraseSchema = z.object({
32
+ id: z.string().min(1),
33
+ frameTime: z.number().nonnegative(),
34
+ regions: z.array(regionSchema),
35
+ lines: z.array(z.array(wordSchema).min(1)).min(1),
36
+ }).strict();
37
+ export const aliveAnalysisSchema = z.object({
38
+ version: z.literal(ALIVE_ANALYSIS_VERSION),
39
+ fingerprint: z.string().min(1),
40
+ analyzedAt: z.string().min(1),
41
+ durationSeconds: z.number().nonnegative(),
42
+ sourceWidth: z.number().positive(),
43
+ sourceHeight: z.number().positive(),
44
+ envelopeHz: z.number().positive(),
45
+ envelope: z.array(z.number().min(0).max(1)),
46
+ onsets: z.array(z.number().nonnegative()),
47
+ regionsAnalyzed: z.boolean(),
48
+ phrases: z.array(phraseSchema),
49
+ }).strict();
@@ -0,0 +1,9 @@
1
+ import { resolveAliveCues } from "../remotion/subtitles/alive-layout.js";
2
+ import { analyzeAliveCaptions } from "./analyze.js";
3
+ import { readAliveSettings } from "./model.js";
4
+ /** Cues positioned for one render target, analysing the video first when the cache is stale. */
5
+ export async function aliveRenderTrack(engine, assetId, geometry, signal) {
6
+ const analysis = await analyzeAliveCaptions(engine, assetId, signal);
7
+ const settings = await readAliveSettings(engine, assetId);
8
+ return { settings, cues: resolveAliveCues(analysis.phrases, settings, geometry) };
9
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Defensive readers for provider API keys.
3
+ *
4
+ * Tool tests set `process.env.KIE_API_KEY = "test-key"` and enqueue a job
5
+ * without waiting for it. `afterEach` restores the real key, then the
6
+ * background worker dequeues and bills real money. These helpers reject
7
+ * placeholder values at the API call site so the worker fails closed.
8
+ *
9
+ * Job handlers MUST go through these — never read `process.env.*_API_KEY`
10
+ * directly.
11
+ */
12
+ const PLACEHOLDER_PATTERNS = [/^test-/i, /^sk-test-/i];
13
+ export function isPlaceholderApiKey(value) {
14
+ return PLACEHOLDER_PATTERNS.some((re) => re.test(value));
15
+ }
16
+ export function requireApiKey(name) {
17
+ const value = process.env[name];
18
+ if (!value)
19
+ throw new Error(`${name} environment variable is not set`);
20
+ if (isPlaceholderApiKey(value)) {
21
+ throw new Error(`${name} is a placeholder ("${value}"). Refusing to call the real API. ` +
22
+ `If a test enqueued this job, the worker dequeued it after the test ended — fix the test, not this guard.`);
23
+ }
24
+ return value;
25
+ }
26
+ export function getApiKey(name) {
27
+ const value = process.env[name];
28
+ if (!value)
29
+ return null;
30
+ if (isPlaceholderApiKey(value))
31
+ return null;
32
+ return value;
33
+ }
@@ -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);