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,438 @@
1
+ import { basename, dirname, extname, join } from "node:path";
2
+ import { copyFile, mkdir, mkdtemp, readFile as fsReadFile, rm, writeFile as fsWriteFile, } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { enqueueAssetTagging } from "./tag-asset.js";
5
+ import { logger } from "../logger.js";
6
+ import { registerJobHandler, queue } from "../queue.js";
7
+ import { withLocalLease } from "./_lease.js";
8
+ import { enqueueLazyAnalysisIfSlotted } from "../lazy-render.js";
9
+ import { probeVideo, runFfmpeg, extractWaveform, isThumbnailBlank, H264_MEDIUM, AUDIO_OPTS, } from "../ffmpeg.js";
10
+ import { formatTimestamp, generateTimestamps } from "../frame-utils.js";
11
+ import { ingestWorkspaceOutputs } from "./_persist-output.js";
12
+ import { getApiKey } from "../api-key.js";
13
+ import { withTransientStorageRetry } from "./_storage-retry.js";
14
+ import { videoProxyMetadata, videoProxyValidationError, } from "../video-proxy.js";
15
+ export function planVideoTranscriptJobs(input) {
16
+ if (input.skipDownstreamJobs || !input.hasAudio)
17
+ return [];
18
+ const jobs = [];
19
+ if (input.hasOpenAiKey) {
20
+ jobs.push({
21
+ jobId: `transcribe-openai-${input.assetId}-${input.identity}`,
22
+ payload: {
23
+ type: "transcribe_video_audio",
24
+ videoPath: join(input.assetDir, input.videoFilename),
25
+ assetId: input.assetId,
26
+ assetDir: input.assetDir,
27
+ bookId: input.bookId,
28
+ sourceFilename: "original.mp3",
29
+ mediaRevision: input.identity,
30
+ },
31
+ });
32
+ }
33
+ if (input.hasElevenLabsKey) {
34
+ jobs.push({
35
+ jobId: `transcribe-elevenlabs-${input.assetId}-${input.identity}`,
36
+ payload: {
37
+ type: "transcribe",
38
+ audioPath: join(input.assetDir, "original.mp3"),
39
+ audioFilename: "original.mp3",
40
+ tagAudioEvents: true,
41
+ diarize: true,
42
+ assetId: input.assetId,
43
+ assetDir: input.assetDir,
44
+ bookId: input.bookId,
45
+ },
46
+ });
47
+ }
48
+ return jobs;
49
+ }
50
+ async function enqueueVideoTranscriptJobs(jobs) {
51
+ for (const job of jobs) {
52
+ try {
53
+ await queue.enqueue(job.jobId, job.payload);
54
+ }
55
+ catch (err) {
56
+ logger.warn("process-video-enqueue-failed", { jobType: job.payload.type, error: err });
57
+ }
58
+ }
59
+ }
60
+ async function videoTranscriptIdentity(engine, assetId, videoFilename) {
61
+ const manifest = await engine.files.manifest(assetId);
62
+ if (!manifest.ok)
63
+ return String(Date.now());
64
+ const file = manifest.value.files.find((entry) => entry.name === videoFilename)
65
+ ?? manifest.value.files.find((entry) => entry.name === "original.mp4")
66
+ ?? manifest.value.files.find((entry) => entry.name === "original.mp3");
67
+ return file?.objectHash ?? String(Date.now());
68
+ }
69
+ // Write a derived binary file, skipping the write when the bytes are unchanged.
70
+ //
71
+ // The engine commits each write scoped to that file's path. A scoped
72
+ // `git commit -- <path>` with no diff exits non-zero ("nothing to commit" on
73
+ // stdout, empty stderr) whenever the rest of the tree is dirty — which it is
74
+ // on a force re-process, since we just deleted original.jpg + frames. Audio is
75
+ // stream-copied during watermarking, so re-extracting original.mp3 yields bytes
76
+ // identical to the already-committed file; the scoped commit then finds nothing
77
+ // and the engine surfaces a bare "Failed to commit file write:". Skipping the
78
+ // no-op write lets idempotent re-processing succeed. Mirrors
79
+ // writePlanFileIfChanged in plan-notebook.ts.
80
+ async function writeBinaryIfChanged(engine, assetId, filename, data) {
81
+ const existing = await engine.files.read(assetId, filename);
82
+ if (existing.ok && existing.value.equals(data))
83
+ return;
84
+ const written = await engine.files.write(assetId, filename, data);
85
+ if (!written.ok) {
86
+ throw new Error(`writeFile ${filename} failed for ${assetId}: ${written.error.message}`);
87
+ }
88
+ }
89
+ export async function registerMediaStreams(engine, assetId, sourcePath, probe) {
90
+ const manifest = await engine.files.manifest(assetId);
91
+ if (!manifest.ok) {
92
+ throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
93
+ }
94
+ const objectHash = manifest.value.files.find((file) => file.name === sourcePath)?.objectHash;
95
+ if (!objectHash)
96
+ throw new Error(`Media source is missing from the manifest: ${sourcePath}`);
97
+ for (const stream of [probe.videoStream, probe.audioStream]) {
98
+ if (!stream)
99
+ continue;
100
+ const registered = await engine.streams.register({
101
+ artifactId: assetId,
102
+ sourcePath,
103
+ objectHash,
104
+ streamIndex: stream.index,
105
+ kind: stream.video ? "video" : "audio",
106
+ timeBase: stream.timeBase,
107
+ durationTicks: stream.durationTicks,
108
+ codec: stream.codec,
109
+ ...(stream.video ? { video: stream.video } : {}),
110
+ ...(stream.audio ? { audio: stream.audio } : {}),
111
+ });
112
+ if (!registered.ok) {
113
+ throw new Error(`Failed to register media stream for ${assetId}: ${registered.error.message}`);
114
+ }
115
+ }
116
+ }
117
+ async function snapshotAssetFile(engine, assetId, filename, targetPath) {
118
+ const manifest = await engine.files.manifest(assetId);
119
+ if (!manifest.ok) {
120
+ throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
121
+ }
122
+ if (!manifest.value.files.some((file) => file.name === filename))
123
+ return false;
124
+ const snapshot = await engine.files.readAtRevision(manifest.value.artifactId, filename, engine.head);
125
+ if (!snapshot.ok) {
126
+ throw new Error(`Failed to read immutable ${filename} for ${assetId}: ${snapshot.error.message}`);
127
+ }
128
+ await fsWriteFile(targetPath, snapshot.value);
129
+ return true;
130
+ }
131
+ async function persistProxyMetadata(engine, assetId, sourceFilename, proxy) {
132
+ const manifest = await engine.files.manifest(assetId);
133
+ if (!manifest.ok) {
134
+ throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
135
+ }
136
+ const sourceFile = manifest.value.files.find((file) => file.name === sourceFilename);
137
+ const proxyFile = manifest.value.files.find((file) => file.name === "proxy.mp4");
138
+ if (!sourceFile || !proxyFile) {
139
+ throw new Error(`Failed to record video proxy metadata for ${assetId}`);
140
+ }
141
+ const written = await engine.metadata.artifacts.write(assetId, "proxy", videoProxyMetadata(sourceFile, proxyFile, proxy));
142
+ if (!written.ok) {
143
+ throw new Error(`Failed to record video proxy metadata for ${assetId}: ${written.error.message}`);
144
+ }
145
+ }
146
+ export async function processVideoFiles(engine, payload, signal) {
147
+ const sourceWorkspace = await mkdtemp(join(tmpdir(), "videobook-source-"));
148
+ const sourceFilename = basename(payload.videoPath);
149
+ const sourcePath = join(sourceWorkspace, sourceFilename);
150
+ try {
151
+ if (!await snapshotAssetFile(engine, payload.assetId, sourceFilename, sourcePath)) {
152
+ await copyFile(payload.videoPath, sourcePath);
153
+ }
154
+ return await processVideoSnapshot(engine, { ...payload, videoPath: sourcePath }, signal);
155
+ }
156
+ finally {
157
+ await rm(sourceWorkspace, { recursive: true, force: true });
158
+ }
159
+ }
160
+ async function processVideoSnapshot(engine, payload, signal) {
161
+ const { videoPath, assetId, assetDir, bookId, provenance, force, skipDownstreamJobs, preserveAudioArtifacts, reviveVisualAnalysis, } = payload;
162
+ signal?.throwIfAborted();
163
+ const probe = await probeVideo(videoPath, { signal });
164
+ if (probe.videoCodec === null || probe.width <= 0 || probe.height <= 0) {
165
+ throw new Error(`process_video requires a video stream: ${assetId}`);
166
+ }
167
+ const { width, height, duration } = probe;
168
+ const sourceFilename = basename(videoPath);
169
+ const importedSource = await engine.files.writeFromPath(assetId, sourceFilename, videoPath);
170
+ if (!importedSource.ok) {
171
+ throw new Error(`Failed to persist source video ${sourceFilename}: ${importedSource.error.message}`);
172
+ }
173
+ // Generate original.mp4 alongside non-mp4 uploads so downstream consumers
174
+ // (browser <video>, ffmpeg jobs, transcribe) get a canonical container.
175
+ // The source file (original.mkv / .mov / etc.) stays on disk as the
176
+ // archival truth — only the derived mp4 is fresh work.
177
+ let activeVideoPath = videoPath;
178
+ const sourceExt = extname(videoPath).toLowerCase();
179
+ if (sourceExt !== ".mp4") {
180
+ const tempPath = join(dirname(videoPath), "canonical.mp4");
181
+ // Explicit -map drops subtitle/data/attachment streams that the mp4
182
+ // muxer can't handle (e.g. subrip from MKV rips), which would otherwise
183
+ // fail the whole transcode. Also pin to the first audio track so files
184
+ // with multiple language tracks pick a deterministic stream.
185
+ //
186
+ // Stream-copy when the source codec is already mp4-compatible (h264
187
+ // video, aac audio). For a 10-min h264 mkv this turns a multi-minute
188
+ // re-encode into a few-second remux. Fall back to libx264 / aac for
189
+ // anything else (hevc, vp9, dts, opus, etc).
190
+ const canCopyVideo = probe.videoCodec === "h264";
191
+ const canCopyAudio = probe.audioCodec === "aac";
192
+ const videoArgs = canCopyVideo ? ["-c:v", "copy"] : H264_MEDIUM;
193
+ const audioArgs = !probe.hasAudio
194
+ ? ["-an"]
195
+ : canCopyAudio
196
+ ? ["-c:a", "copy"]
197
+ : AUDIO_OPTS;
198
+ const transcodeArgs = [
199
+ "-y",
200
+ "-i", videoPath,
201
+ "-map", "0:v:0",
202
+ ...(probe.hasAudio ? ["-map", "0:a:0"] : []),
203
+ ...videoArgs,
204
+ ...audioArgs,
205
+ "-movflags", "+faststart",
206
+ "-f", "mp4",
207
+ tempPath,
208
+ ];
209
+ await runFfmpeg(transcodeArgs, { signal });
210
+ const mp4Buf = await fsReadFile(tempPath);
211
+ await writeBinaryIfChanged(engine, assetId, "original.mp4", mp4Buf);
212
+ activeVideoPath = tempPath;
213
+ }
214
+ const activeSourcePath = sourceExt === ".mp4" ? sourceFilename : "original.mp4";
215
+ const activeProbe = sourceExt === ".mp4" ? probe : await probeVideo(activeVideoPath, { signal });
216
+ await registerMediaStreams(engine, assetId, activeSourcePath, activeProbe);
217
+ let frameCount = 0;
218
+ {
219
+ await engine.metadata.artifacts.write(assetId, "original", { ...provenance, width, height, duration, fps: probe.fps, hasAudio: probe.hasAudio });
220
+ if (duration <= 0)
221
+ return { success: true, width, height, duration, frame_count: 0 };
222
+ if (force) {
223
+ const currentManifest = await engine.files.manifest(assetId);
224
+ if (currentManifest.ok) {
225
+ const staleFiles = currentManifest.value.files
226
+ .map((file) => file.name)
227
+ .filter((filename) => filename === "original.jpg" ||
228
+ filename === "proxy.mp4" ||
229
+ filename.startsWith("original_frames/") ||
230
+ ["landscape", "portrait", "square"].some((orientation) => filename === `${orientation}.mp4` ||
231
+ filename === `${orientation}.jpg` ||
232
+ filename.startsWith(`${orientation}_frames/`)));
233
+ for (const filename of staleFiles) {
234
+ await engine.files.delete(assetId, filename);
235
+ }
236
+ }
237
+ await rm(join(assetDir, "original_frames"), { recursive: true, force: true });
238
+ await rm(join(assetDir, "original.jpg"), { force: true }).catch(() => { });
239
+ // Renders are derived from the old source video — drop them so the UI
240
+ // falls back to original.mp4 and prompts a re-render.
241
+ for (const orient of ["landscape", "portrait", "square"]) {
242
+ await rm(join(assetDir, `${orient}.mp4`), { force: true }).catch(() => { });
243
+ await rm(join(assetDir, `${orient}.jpg`), { force: true }).catch(() => { });
244
+ await rm(join(assetDir, `${orient}_frames`), { recursive: true, force: true }).catch(() => { });
245
+ await engine.metadata.artifacts.delete(assetId, `${orient}.render`);
246
+ }
247
+ await engine.metadata.artifacts.delete(assetId, "proxy");
248
+ }
249
+ const proxyWorkspace = await mkdtemp(join(tmpdir(), "videobook-proxy-"));
250
+ const proxyPath = join(proxyWorkspace, "proxy.mp4");
251
+ try {
252
+ let proxyProbe = null;
253
+ if (await snapshotAssetFile(engine, assetId, "proxy.mp4", proxyPath)) {
254
+ try {
255
+ const candidate = await probeVideo(proxyPath, { signal });
256
+ if (videoProxyValidationError(activeProbe, candidate) === null) {
257
+ proxyProbe = candidate;
258
+ }
259
+ }
260
+ catch {
261
+ proxyProbe = null;
262
+ }
263
+ }
264
+ if (!proxyProbe) {
265
+ const currentManifest = await engine.files.manifest(assetId);
266
+ if (!currentManifest.ok) {
267
+ throw new Error(`Failed to read media manifest for ${assetId}: ${currentManifest.error.message}`);
268
+ }
269
+ if (currentManifest.value.files.some((file) => file.name === "proxy.mp4")) {
270
+ const deleted = await engine.files.delete(assetId, "proxy.mp4");
271
+ if (!deleted.ok)
272
+ throw new Error(deleted.error.message);
273
+ }
274
+ await engine.metadata.artifacts.delete(assetId, "proxy");
275
+ const proxySourcePath = join(proxyWorkspace, `source${extname(activeSourcePath)}`);
276
+ if (!await snapshotAssetFile(engine, assetId, activeSourcePath, proxySourcePath)) {
277
+ throw new Error(`Media source is missing from the manifest: ${activeSourcePath}`);
278
+ }
279
+ const proxyArgs = (fpsMode) => [
280
+ "-y",
281
+ "-i", proxySourcePath,
282
+ ...fpsMode,
283
+ "-map", "0:v:0",
284
+ ...(activeProbe.hasAudio ? ["-map", "0:a:0"] : []),
285
+ "-vf",
286
+ "scale=1280:720:force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2",
287
+ "-c:v", "libx264",
288
+ "-preset", "veryfast",
289
+ "-crf", "28",
290
+ "-pix_fmt", "yuv420p",
291
+ ...(activeProbe.hasAudio ? AUDIO_OPTS : ["-an"]),
292
+ "-movflags", "+faststart",
293
+ proxyPath,
294
+ ];
295
+ // Passthrough keeps the source's exact frame timing — retimed
296
+ // (change_video_speed) and VFR sources would otherwise be resampled
297
+ // back onto the nominal frame grid, dropping frames and drifting the
298
+ // proxy away from the canonical duration. Fall back to the default
299
+ // sync mode for sources whose timestamps the muxer rejects.
300
+ try {
301
+ await runFfmpeg(proxyArgs(["-fps_mode:v", "passthrough"]), { signal });
302
+ }
303
+ catch {
304
+ await runFfmpeg(proxyArgs([]), { signal });
305
+ }
306
+ const candidate = await probeVideo(proxyPath, { signal });
307
+ const validationError = videoProxyValidationError(activeProbe, candidate);
308
+ if (validationError) {
309
+ throw new Error(`Invalid video proxy for ${assetId}: ${validationError}`);
310
+ }
311
+ const proxy = await engine.files.writeFromPath(assetId, "proxy.mp4", proxyPath);
312
+ if (!proxy.ok)
313
+ throw new Error(proxy.error.message);
314
+ proxyProbe = candidate;
315
+ }
316
+ await persistProxyMetadata(engine, assetId, activeSourcePath, proxyProbe);
317
+ }
318
+ finally {
319
+ await rm(proxyWorkspace, { recursive: true, force: true });
320
+ }
321
+ const timestamps = generateTimestamps(duration);
322
+ frameCount = timestamps.length;
323
+ const framesDir = join(assetDir, "original_frames");
324
+ await mkdir(framesDir, { recursive: true });
325
+ for (const ts of timestamps) {
326
+ const framePath = join(framesDir, `${formatTimestamp(ts)}.jpg`);
327
+ try {
328
+ await runFfmpeg([
329
+ "-y",
330
+ "-ss", String(ts),
331
+ "-i", activeVideoPath,
332
+ "-frames:v", "1",
333
+ "-q:v", "2",
334
+ "-strict", "unofficial",
335
+ framePath,
336
+ ], { signal });
337
+ }
338
+ catch {
339
+ // frame extraction failure is non-fatal
340
+ }
341
+ }
342
+ // Extract original.mp3 from the video (kept in sync with original.mp4 so
343
+ // standalone audio reads — timeline, transcription — match the source).
344
+ const existingMp3 = preserveAudioArtifacts
345
+ ? await engine.files.read(assetId, "original.mp3")
346
+ : null;
347
+ if (probe.hasAudio && (!existingMp3 || !existingMp3.ok)) {
348
+ const mp3TempPath = join(assetDir, "original.mp3.tmp");
349
+ await runFfmpeg(["-y", "-i", activeVideoPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", "-f", "mp3", mp3TempPath], { signal });
350
+ try {
351
+ const mp3Buf = await fsReadFile(mp3TempPath);
352
+ await writeBinaryIfChanged(engine, assetId, "original.mp3", mp3Buf);
353
+ }
354
+ finally {
355
+ await rm(mp3TempPath, { force: true });
356
+ }
357
+ }
358
+ const existingWaveform = preserveAudioArtifacts
359
+ ? await engine.metadata.waveforms.read(assetId)
360
+ : null;
361
+ if (!existingWaveform || !existingWaveform.ok) {
362
+ const waveform = await extractWaveform(activeVideoPath);
363
+ if (waveform.length > 0) {
364
+ const wavResult = await engine.metadata.waveforms.write(assetId, waveform);
365
+ if (!wavResult.ok) {
366
+ logger.warn("waveform-write-failed", { assetId, error: wavResult.error });
367
+ }
368
+ }
369
+ }
370
+ // Create original.jpg from first frame if not already set (e.g., from OG image)
371
+ const thumbResult = await engine.files.read(assetId, "original.jpg");
372
+ if (!thumbResult.ok) {
373
+ try {
374
+ const firstFrameBuf = await fsReadFile(join(framesDir, "0.00.jpg"));
375
+ await engine.files.write(assetId, "original.jpg", firstFrameBuf);
376
+ }
377
+ catch { /* frame not available */ }
378
+ }
379
+ // If thumbnail is blank (all black/grey), replace with a mid-video frame
380
+ const thumbPath = join(assetDir, "original.jpg");
381
+ if (await isThumbnailBlank(thumbPath) && timestamps.length > 1) {
382
+ const midTs = timestamps[Math.floor(timestamps.length / 2)];
383
+ try {
384
+ const midFrameBuf = await fsReadFile(join(framesDir, `${formatTimestamp(midTs)}.jpg`));
385
+ await engine.files.write(assetId, "original.jpg", midFrameBuf);
386
+ }
387
+ catch { /* mid-frame not available */ }
388
+ }
389
+ await ingestWorkspaceOutputs(engine, assetId, ["original_frames"], "process_video", { width, height, duration, frame_count: frameCount });
390
+ }
391
+ // Speech-to-text stays eager: cheap and useful as
392
+ // a standalone artifact even if the asset never lands in a slot. Vision
393
+ // analysis (Gemini) is gated to slot drop in tools/assign-slot.ts — plus
394
+ // the slot-gated revival below for re-processed (trimmed/re-downloaded)
395
+ // videos, whose analysis sidecar was just invalidated.
396
+ const transcriptJobs = planVideoTranscriptJobs({
397
+ skipDownstreamJobs: Boolean(skipDownstreamJobs),
398
+ hasAudio: probe.hasAudio,
399
+ assetId,
400
+ assetDir,
401
+ bookId,
402
+ videoFilename: activeSourcePath,
403
+ identity: await videoTranscriptIdentity(engine, assetId, activeSourcePath),
404
+ hasOpenAiKey: Boolean(getApiKey("OPENAI_API_KEY")),
405
+ hasElevenLabsKey: Boolean(getApiKey("ELEVENLABS_API_KEY")),
406
+ });
407
+ if (process.env.NODE_ENV !== "test") {
408
+ await queue.enqueue(`semantic-index-${assetId}-${Date.now()}`, {
409
+ type: "semantic_index",
410
+ assetId,
411
+ bookId,
412
+ });
413
+ // Analysis revival below still respects slotting; tagging does not,
414
+ // because a Library clip has to be findable without being placed first.
415
+ await enqueueAssetTagging(engine, assetId);
416
+ if (!skipDownstreamJobs && probe.hasAudio) {
417
+ await enqueueVideoTranscriptJobs(transcriptJobs);
418
+ }
419
+ else if (!skipDownstreamJobs || reviveVisualAnalysis) {
420
+ // Silent video: no transcribe job will run, so revive the slot-drop
421
+ // vision analysis here; transcribe_video_audio's tail owns it for
422
+ // videos with audio (after the fresh transcript lands).
423
+ try {
424
+ await enqueueLazyAnalysisIfSlotted(engine, bookId, assetId);
425
+ }
426
+ catch {
427
+ // best-effort — analysis revival must not fail processing
428
+ }
429
+ }
430
+ }
431
+ return { success: true, width, height, duration, frame_count: frameCount };
432
+ }
433
+ export function registerProcessVideoJob(engine) {
434
+ registerJobHandler("process_video", async (payload, ctx) => {
435
+ const processPayload = payload;
436
+ return withLocalLease(engine, processPayload.assetId, ctx, "process", async () => withTransientStorageRetry(() => processVideoFiles(engine, processPayload, ctx.signal), ctx.signal));
437
+ });
438
+ }
@@ -0,0 +1,161 @@
1
+ import { registerPostRecordJobs } from "./post-record.js";
2
+ import { registerColdOpenJobs } from "./cold-open.js";
3
+ import { bookManager } from "../engine.js";
4
+ import { registerCopyLibraryAssetJob } from "./copy-library-asset.js";
5
+ import { registerTranscribeJob } from "./transcribe.js";
6
+ import { registerTranslateTranscriptJob } from "./translate-transcript.js";
7
+ import { registerDownloadVideoJob } from "./download-video.js";
8
+ import { registerArchiveClipJob } from "./archive-clip.js";
9
+ import { registerRestoreArchiveClipJob } from "./restore-archive-clip.js";
10
+ import { registerDoctorFixJob } from "./doctor-fix.js";
11
+ import { registerTrimVideoJob } from "./trim-video.js";
12
+ import { registerTrimAudioJob } from "./trim-audio.js";
13
+ import { registerSplitVideoJob } from "./split-video.js";
14
+ import { registerExtractVideoClipJob } from "./extract-video-clip.js";
15
+ import { registerEvenSplitJob } from "./even-split.js";
16
+ import { registerPadVideoJob } from "./pad-video.js";
17
+ import { registerCropVideoJob } from "./crop-video.js";
18
+ import { registerCropImageJob } from "./crop-image.js";
19
+ import { registerApplyCutsJob } from "./apply-cuts.js";
20
+ import { registerApplySfxJob } from "./apply-sfx.js";
21
+ import { registerChangeVideoSpeedJob } from "./change-video-speed.js";
22
+ import { registerTimewarpVideoJob } from "./timewarp-video.js";
23
+ import { registerSyncVideoToAudioJob } from "./sync-video-to-audio.js";
24
+ import { registerAudioBoostJob } from "./audio-boost.js";
25
+ import { registerAudioMixJob } from "./audio-mix.js";
26
+ import { registerReverseVideoJob } from "./reverse-video.js";
27
+ import { registerInjectFirstFrameJob } from "./inject-first-frame.js";
28
+ import { registerExtractFrameJob } from "./extract-frame.js";
29
+ import { registerExtractNotebookFrameJob } from "./extract-notebook-frame.js";
30
+ import { registerExtractNotebookAudioJob } from "./extract-notebook-audio.js";
31
+ import { registerExtractAudioJob } from "./extract-audio.js";
32
+ import { registerAnalyzeMusicJob } from "./analyze-music.js";
33
+ import { registerPlanGoalJob } from "./plan-goal.js";
34
+ import { registerAssembleGoalJob } from "./assemble-goal.js";
35
+ import { registerCombineGoalJob } from "./combine-goal.js";
36
+ import { registerFinishGoalJob } from "./finish-goal.js";
37
+ import { registerLabelMusicSectionsJob } from "./label-music-sections.js";
38
+ import { registerEditingMomentsJob } from "./analyze-editing-moments.js";
39
+ import { registerExtractWaveformJob } from "./extract-waveform.js";
40
+ import { registerExtractVideoPosterJob } from "./extract-video-poster.js";
41
+ import { registerReplaceVideoAudioJob } from "./replace-video-audio.js";
42
+ import { registerSpliceVideoJob } from "./splice-video.js";
43
+ import { registerSmoothSceneConcatJob } from "./smooth-scene-concat.js";
44
+ import { registerCompleteProviderTaskJob } from "./complete-provider-task.js";
45
+ import { registerRenderProjectJob } from "./render-project.js";
46
+ import { registerRenderQrOutroJob } from "./render-qr-outro.js";
47
+ import { registerProcessVideoJob } from "./process-video.js";
48
+ import { registerProcessAudioJob } from "./process-audio.js";
49
+ import { registerTranscribeVideoAudioJob } from "./transcribe-video-audio.js";
50
+ import { registerAnalyzeVideoJob } from "./analyze-video.js";
51
+ import { registerAnalyzeKeywordPlacementJob } from "./analyze-keyword-placement.js";
52
+ import { registerAnalyzeAliveCaptionsJob } from "./analyze-alive-captions.js";
53
+ import { registerOsgPlacementJob } from "./osg-placement.js";
54
+ import { registerBurnWatermarkJob } from "./burn-watermark.js";
55
+ import { registerSuggestCutsJob } from "./suggest-cuts.js";
56
+ import { registerSuggestSfxJob } from "./suggest-sfx.js";
57
+ import { registerAnalyzeJokesJob } from "./analyze-jokes.js";
58
+ import { registerDetectScenesJob } from "./detect-scenes.js";
59
+ import { registerDescribeImageJob } from "./describe-image.js";
60
+ import { registerRewriteScriptJob } from "./rewrite-script.js";
61
+ import { registerRenderTimelineJob } from "./render-timeline.js";
62
+ import { registerExportSequenceJob } from "./export-sequence.js";
63
+ import { registerProcessImageJob } from "./process-image.js";
64
+ import { registerIsolateVocalsJob } from "./isolate-vocals.js";
65
+ import { registerCleanVideoAudioJob } from "./clean-video-audio.js";
66
+ import { registerPostToXJob } from "./post-to-x.js";
67
+ import { registerAcestepCoverJob } from "./generate-acestep-cover.js";
68
+ import { registerExtendAudioJob } from "./extend-audio.js";
69
+ import { registerRemoveSoundJob } from "./remove-sound.js";
70
+ import { registerRunNotebookJob } from "./run-notebook.js";
71
+ import { registerRelayoutNotebookJob } from "./relayout-notebook.js";
72
+ import { registerRepairSearchIndexesJob } from "./repair-search-indexes.js";
73
+ import { registerSemanticIndexJob } from "./semantic-index.js";
74
+ import { registerBackfillAssetTagsJob } from "./backfill-asset-tags.js";
75
+ import { registerForgetAssetJob } from "./forget-asset.js";
76
+ import { registerTagAssetJob } from "./tag-asset.js";
77
+ import { registerPrepareSearchReferenceJob } from "./prepare-search-reference.js";
78
+ import { registerPrepareTemporalSearchJob } from "./prepare-temporal-search.js";
79
+ import { registerImageSubjectJobs } from "./image-subjects.js";
80
+ import { registerGenerateMediaJob } from "./generate-media.js";
81
+ export function registerAllJobHandlers(engine, books = bookManager) {
82
+ registerTranscribeJob(engine);
83
+ registerTranslateTranscriptJob(engine);
84
+ registerDownloadVideoJob(engine);
85
+ registerArchiveClipJob(engine);
86
+ registerRestoreArchiveClipJob(engine);
87
+ registerDoctorFixJob(engine);
88
+ registerTrimVideoJob(engine);
89
+ registerTrimAudioJob(engine);
90
+ registerSplitVideoJob(engine);
91
+ registerExtractVideoClipJob(engine);
92
+ registerColdOpenJobs(engine);
93
+ registerEvenSplitJob(engine);
94
+ registerPadVideoJob(engine);
95
+ registerCropVideoJob(engine);
96
+ registerCropImageJob(engine);
97
+ registerApplyCutsJob(engine);
98
+ registerApplySfxJob(engine);
99
+ registerChangeVideoSpeedJob(engine);
100
+ registerTimewarpVideoJob(engine);
101
+ registerSyncVideoToAudioJob(engine);
102
+ registerAudioBoostJob(engine);
103
+ registerAudioMixJob(engine);
104
+ registerReverseVideoJob(engine);
105
+ registerInjectFirstFrameJob(engine);
106
+ registerExtractFrameJob(engine);
107
+ registerExtractNotebookFrameJob(engine);
108
+ registerExtractNotebookAudioJob(engine);
109
+ registerExtractAudioJob(engine);
110
+ registerExtractWaveformJob(engine);
111
+ registerExtractVideoPosterJob(engine);
112
+ registerAnalyzeMusicJob(engine);
113
+ registerPlanGoalJob(engine);
114
+ registerAssembleGoalJob(engine);
115
+ registerCombineGoalJob(engine);
116
+ registerFinishGoalJob(engine);
117
+ registerLabelMusicSectionsJob(engine);
118
+ registerEditingMomentsJob(engine);
119
+ registerReplaceVideoAudioJob(engine);
120
+ registerSpliceVideoJob(engine);
121
+ registerSmoothSceneConcatJob(engine);
122
+ registerCompleteProviderTaskJob(engine);
123
+ registerRenderProjectJob(engine);
124
+ registerRenderQrOutroJob(engine);
125
+ registerProcessVideoJob(engine);
126
+ registerProcessAudioJob(engine);
127
+ registerTranscribeVideoAudioJob(engine);
128
+ registerAnalyzeVideoJob(engine);
129
+ registerAnalyzeKeywordPlacementJob(engine);
130
+ registerAnalyzeAliveCaptionsJob(engine);
131
+ registerOsgPlacementJob(engine);
132
+ registerBurnWatermarkJob(engine);
133
+ registerSuggestCutsJob(engine);
134
+ registerSuggestSfxJob(engine);
135
+ registerAnalyzeJokesJob(engine);
136
+ registerDetectScenesJob(engine);
137
+ registerDescribeImageJob(engine);
138
+ registerRewriteScriptJob(engine);
139
+ registerRenderTimelineJob(engine);
140
+ registerExportSequenceJob(engine);
141
+ registerProcessImageJob(engine);
142
+ registerIsolateVocalsJob(engine);
143
+ registerCleanVideoAudioJob(engine);
144
+ registerGenerateMediaJob(engine);
145
+ registerAcestepCoverJob(engine);
146
+ registerExtendAudioJob(engine);
147
+ registerRemoveSoundJob(engine);
148
+ registerPostToXJob(engine);
149
+ registerPostRecordJobs(engine);
150
+ registerRunNotebookJob(engine);
151
+ registerRelayoutNotebookJob(engine);
152
+ registerSemanticIndexJob(engine);
153
+ registerTagAssetJob(engine);
154
+ registerBackfillAssetTagsJob(engine);
155
+ registerForgetAssetJob(engine);
156
+ registerRepairSearchIndexesJob(engine);
157
+ registerPrepareSearchReferenceJob(engine);
158
+ registerPrepareTemporalSearchJob(engine);
159
+ registerImageSubjectJobs(engine);
160
+ registerCopyLibraryAssetJob(books);
161
+ }
@@ -0,0 +1,78 @@
1
+ import { notebookGridTag } from "videobook-engine/notebook-grid";
2
+ import { notebookDocumentsEqual, relayoutNotebookDocument, } from "../notebook-relayout.js";
3
+ import { withNotebookMutation } from "../notebook-mutations.js";
4
+ import { registerJobHandler, resolveBookEngine } from "../queue.js";
5
+ export async function executeRelayoutNotebook(engine, payload) {
6
+ return await withNotebookMutation(payload.notebookId, async () => {
7
+ if (payload.expectedRevision && payload.expectedRevision !== engine.head) {
8
+ throw new Error(`Stale notebook revision: expected ${payload.expectedRevision}`);
9
+ }
10
+ return await relayoutNotebook(engine, payload);
11
+ });
12
+ }
13
+ async function relayoutNotebook(engine, payload) {
14
+ const before = engine.notebooks.read(payload.notebookId);
15
+ if (!before.ok)
16
+ throw new Error(before.error.message);
17
+ const next = relayoutNotebookDocument(engine, before.value);
18
+ if (notebookDocumentsEqual(before.value, next)) {
19
+ return {
20
+ changed: false,
21
+ notebook_id: payload.notebookId,
22
+ cell_count: next.cells.length,
23
+ edge_count: next.edges.length,
24
+ revision: engine.head,
25
+ cells: next.cells.map((cell) => ({
26
+ id: cell.id,
27
+ type: cell.type,
28
+ address: notebookGridTag(cell.slot),
29
+ slot: cell.slot,
30
+ output_artifact_id: cell.outputArtifactId ?? null,
31
+ })),
32
+ };
33
+ }
34
+ const write = await engine.notebooks.write(next, {
35
+ operation: "relayout_notebook",
36
+ details: { notebookId: payload.notebookId },
37
+ });
38
+ if (!write.ok)
39
+ throw new Error(write.error.message);
40
+ const after = engine.notebooks.read(payload.notebookId);
41
+ if (!after.ok)
42
+ throw new Error(after.error.message);
43
+ return {
44
+ changed: true,
45
+ notebook_id: payload.notebookId,
46
+ cell_count: after.value.cells.length,
47
+ edge_count: after.value.edges.length,
48
+ revision: write.value.hash,
49
+ cells: after.value.cells.map((cell) => ({
50
+ id: cell.id,
51
+ type: cell.type,
52
+ address: notebookGridTag(cell.slot),
53
+ slot: cell.slot,
54
+ output_artifact_id: cell.outputArtifactId ?? null,
55
+ })),
56
+ };
57
+ }
58
+ export function registerRelayoutNotebookJob(_fs) {
59
+ registerJobHandler("relayout_notebook", async (payload) => {
60
+ const notebookId = typeof payload.notebookId === "string"
61
+ ? payload.notebookId
62
+ : "";
63
+ const bookId = typeof payload.bookId === "string" ? payload.bookId : "";
64
+ if (!notebookId)
65
+ throw new Error("relayout_notebook requires notebookId");
66
+ if (!bookId)
67
+ throw new Error("relayout_notebook requires bookId");
68
+ const engine = await resolveBookEngine(bookId);
69
+ return executeRelayoutNotebook(engine, {
70
+ type: "relayout_notebook",
71
+ bookId,
72
+ notebookId,
73
+ ...(typeof payload.expectedRevision === "string"
74
+ ? { expectedRevision: payload.expectedRevision }
75
+ : {}),
76
+ });
77
+ });
78
+ }