videobook 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (558) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +192 -0
  45. package/dist/cli/doctor-command.js +255 -0
  46. package/dist/cli/entry.js +2 -0
  47. package/dist/cli/main.js +91 -0
  48. package/dist/cli/mcp-command.js +82 -0
  49. package/dist/cli/mcp-relay.js +51 -0
  50. package/dist/cli/open-browser.js +14 -0
  51. package/dist/cli/run-command.js +187 -0
  52. package/dist/cli/serve-command.js +57 -0
  53. package/dist/cli/stdio-guard.js +8 -0
  54. package/dist/cold-open-model.js +7 -0
  55. package/dist/cold-open.js +79 -0
  56. package/dist/command-reference.js +95 -0
  57. package/dist/content-store.js +74 -0
  58. package/dist/cost.js +8 -0
  59. package/dist/crop-geometry.js +23 -0
  60. package/dist/dancing-workflow.js +446 -0
  61. package/dist/data-home.js +44 -0
  62. package/dist/diagnostics.js +109 -0
  63. package/dist/dialog-quality.js +34 -0
  64. package/dist/downloads-dest.js +56 -0
  65. package/dist/editing-moments.js +194 -0
  66. package/dist/elevenlabs-audio-extension.js +66 -0
  67. package/dist/engine.js +54 -0
  68. package/dist/env-secrets.js +184 -0
  69. package/dist/ffmpeg-binary.js +12 -0
  70. package/dist/ffmpeg.js +425 -0
  71. package/dist/ffprobe-binary.js +55 -0
  72. package/dist/forget-asset.js +216 -0
  73. package/dist/frame-utils.js +27 -0
  74. package/dist/generation-credit-error.js +11 -0
  75. package/dist/generation-input-mentions.js +117 -0
  76. package/dist/generation-input-policy.js +68 -0
  77. package/dist/generation-reference-mentions.js +97 -0
  78. package/dist/goal-characters.js +49 -0
  79. package/dist/goal-plan-v3.js +273 -0
  80. package/dist/goal-plan.js +220 -0
  81. package/dist/goal-planner-prompt.js +28 -0
  82. package/dist/goal-prompt-budget.js +4 -0
  83. package/dist/goal-reference-images.js +25 -0
  84. package/dist/goal-state.js +261 -0
  85. package/dist/goal-stills.js +153 -0
  86. package/dist/goal-store.js +144 -0
  87. package/dist/goal-styles.js +144 -0
  88. package/dist/goal-v3-runtime.js +157 -0
  89. package/dist/guides/character_schema_guide.md +70 -0
  90. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  91. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  92. package/dist/guides/image_reproduction_guide.md +84 -0
  93. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  94. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  95. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  96. package/dist/http-access.js +49 -0
  97. package/dist/http-byte-range.js +31 -0
  98. package/dist/image-compress.js +77 -0
  99. package/dist/image-edit-default.js +1 -0
  100. package/dist/image-edit-models.js +41 -0
  101. package/dist/image-edit-settings.js +11 -0
  102. package/dist/image-region-edit.js +32 -0
  103. package/dist/image-subject-map.js +523 -0
  104. package/dist/image.js +46 -0
  105. package/dist/index.js +12 -0
  106. package/dist/job-deadline.js +60 -0
  107. package/dist/jobs/_alibaba-upload.js +30 -0
  108. package/dist/jobs/_audio-chunks.js +31 -0
  109. package/dist/jobs/_download-into-asset.js +15 -0
  110. package/dist/jobs/_fal-shared.js +22 -0
  111. package/dist/jobs/_fal-upload.js +35 -0
  112. package/dist/jobs/_image-edit-adapter.js +213 -0
  113. package/dist/jobs/_kie-poll.js +119 -0
  114. package/dist/jobs/_kie-upload.js +32 -0
  115. package/dist/jobs/_lease.js +163 -0
  116. package/dist/jobs/_persist-output.js +19 -0
  117. package/dist/jobs/_storage-retry.js +21 -0
  118. package/dist/jobs/_transcribe.js +245 -0
  119. package/dist/jobs/_translate-transcript.js +270 -0
  120. package/dist/jobs/analyze-alive-captions.js +10 -0
  121. package/dist/jobs/analyze-editing-moments.js +104 -0
  122. package/dist/jobs/analyze-jokes.js +143 -0
  123. package/dist/jobs/analyze-keyword-placement.js +155 -0
  124. package/dist/jobs/analyze-music.js +14 -0
  125. package/dist/jobs/analyze-video.js +282 -0
  126. package/dist/jobs/apply-cuts.js +83 -0
  127. package/dist/jobs/apply-sfx.js +107 -0
  128. package/dist/jobs/archive-clip.js +457 -0
  129. package/dist/jobs/assemble-goal.js +98 -0
  130. package/dist/jobs/audio-boost.js +71 -0
  131. package/dist/jobs/audio-mix.js +161 -0
  132. package/dist/jobs/backfill-asset-tags.js +144 -0
  133. package/dist/jobs/burn-watermark.js +316 -0
  134. package/dist/jobs/change-video-speed.js +176 -0
  135. package/dist/jobs/clean-video-audio.js +207 -0
  136. package/dist/jobs/cold-open.js +166 -0
  137. package/dist/jobs/combine-goal.js +108 -0
  138. package/dist/jobs/complete-provider-task.js +185 -0
  139. package/dist/jobs/copy-library-asset.js +33 -0
  140. package/dist/jobs/cost-estimate-assessment.js +113 -0
  141. package/dist/jobs/crop-image.js +47 -0
  142. package/dist/jobs/crop-video.js +71 -0
  143. package/dist/jobs/describe-image.js +181 -0
  144. package/dist/jobs/detect-scenes.js +33 -0
  145. package/dist/jobs/doctor-fix.js +100 -0
  146. package/dist/jobs/download-video.js +426 -0
  147. package/dist/jobs/enqueue-search-index.js +24 -0
  148. package/dist/jobs/even-split.js +122 -0
  149. package/dist/jobs/export-sequence.js +365 -0
  150. package/dist/jobs/extend-audio.js +229 -0
  151. package/dist/jobs/extract-audio.js +72 -0
  152. package/dist/jobs/extract-frame.js +58 -0
  153. package/dist/jobs/extract-notebook-audio.js +82 -0
  154. package/dist/jobs/extract-notebook-frame.js +93 -0
  155. package/dist/jobs/extract-video-clip.js +8 -0
  156. package/dist/jobs/extract-video-poster.js +55 -0
  157. package/dist/jobs/extract-waveform.js +25 -0
  158. package/dist/jobs/finish-goal.js +255 -0
  159. package/dist/jobs/forget-asset.js +11 -0
  160. package/dist/jobs/generate-acestep-cover.js +219 -0
  161. package/dist/jobs/generate-media.js +70 -0
  162. package/dist/jobs/image-subjects.js +1059 -0
  163. package/dist/jobs/index.js +18 -0
  164. package/dist/jobs/inject-first-frame.js +175 -0
  165. package/dist/jobs/isolate-vocals.js +69 -0
  166. package/dist/jobs/kie-paygate.js +177 -0
  167. package/dist/jobs/label-music-sections.js +5 -0
  168. package/dist/jobs/osg-placement.js +135 -0
  169. package/dist/jobs/pad-video.js +72 -0
  170. package/dist/jobs/plan-goal.js +97 -0
  171. package/dist/jobs/post-record.js +21 -0
  172. package/dist/jobs/post-to-x.js +21 -0
  173. package/dist/jobs/prepare-search-reference.js +190 -0
  174. package/dist/jobs/prepare-temporal-search.js +16 -0
  175. package/dist/jobs/process-audio.js +67 -0
  176. package/dist/jobs/process-image.js +58 -0
  177. package/dist/jobs/process-upload.js +166 -0
  178. package/dist/jobs/process-video.js +438 -0
  179. package/dist/jobs/register-all.js +161 -0
  180. package/dist/jobs/relayout-notebook.js +78 -0
  181. package/dist/jobs/remove-sound.js +299 -0
  182. package/dist/jobs/render-project.js +380 -0
  183. package/dist/jobs/render-qr-outro.js +138 -0
  184. package/dist/jobs/render-timeline.js +283 -0
  185. package/dist/jobs/repair-search-indexes.js +42 -0
  186. package/dist/jobs/replace-video-audio.js +64 -0
  187. package/dist/jobs/restore-archive-clip.js +53 -0
  188. package/dist/jobs/reverse-video.js +53 -0
  189. package/dist/jobs/rewrite-script.js +173 -0
  190. package/dist/jobs/run-notebook.js +3335 -0
  191. package/dist/jobs/semantic-index.js +797 -0
  192. package/dist/jobs/smooth-scene-concat.js +257 -0
  193. package/dist/jobs/splice-video.js +280 -0
  194. package/dist/jobs/split-video.js +127 -0
  195. package/dist/jobs/suggest-cuts.js +149 -0
  196. package/dist/jobs/suggest-sfx.js +135 -0
  197. package/dist/jobs/sync-video-to-audio.js +329 -0
  198. package/dist/jobs/tag-asset.js +266 -0
  199. package/dist/jobs/task-failure.js +16 -0
  200. package/dist/jobs/timewarp-video.js +152 -0
  201. package/dist/jobs/transcribe-video-audio.js +117 -0
  202. package/dist/jobs/transcribe.js +35 -0
  203. package/dist/jobs/translate-transcript.js +55 -0
  204. package/dist/jobs/trim-audio.js +133 -0
  205. package/dist/jobs/trim-video.js +291 -0
  206. package/dist/jobs/xai-paygate.js +26 -0
  207. package/dist/keyword-placement-cache.js +74 -0
  208. package/dist/lazy-render.js +142 -0
  209. package/dist/library-browse.js +78 -0
  210. package/dist/lightning-defaults.js +206 -0
  211. package/dist/load-env.js +9 -0
  212. package/dist/local-search-models.js +206 -0
  213. package/dist/local-upload.js +318 -0
  214. package/dist/logger.js +69 -0
  215. package/dist/media/adapters/alibaba.js +20 -0
  216. package/dist/media/adapters/elevenlabs.js +58 -0
  217. package/dist/media/adapters/fal.js +45 -0
  218. package/dist/media/adapters/index.js +26 -0
  219. package/dist/media/adapters/kie.js +120 -0
  220. package/dist/media/adapters/pending.js +21 -0
  221. package/dist/media/adapters/types.js +5 -0
  222. package/dist/media/adapters/xai.js +18 -0
  223. package/dist/media/catalog.js +236 -0
  224. package/dist/media/complete.js +191 -0
  225. package/dist/media/cost.js +45 -0
  226. package/dist/media/descriptor.js +157 -0
  227. package/dist/media/endpoints.js +11 -0
  228. package/dist/media/frame-options.js +18 -0
  229. package/dist/media/insert-video-timing.js +17 -0
  230. package/dist/media/json-schema.js +75 -0
  231. package/dist/media/legacy-task-types.js +129 -0
  232. package/dist/media/listing.js +24 -0
  233. package/dist/media/model-id.js +14 -0
  234. package/dist/media/preview.js +86 -0
  235. package/dist/media/prompt-dialects.js +65 -0
  236. package/dist/media/ref-fields.js +106 -0
  237. package/dist/media/refs.js +131 -0
  238. package/dist/media/request.js +188 -0
  239. package/dist/media/types.js +6 -0
  240. package/dist/media/wire.js +161 -0
  241. package/dist/media-library.js +535 -0
  242. package/dist/media-output-path.js +33 -0
  243. package/dist/multyculty-workflow.js +757 -0
  244. package/dist/notebook-action-catalog.js +608 -0
  245. package/dist/notebook-cell-outputs.js +726 -0
  246. package/dist/notebook-credit-retry.js +45 -0
  247. package/dist/notebook-fixtures.js +8 -0
  248. package/dist/notebook-generation-contract.js +77 -0
  249. package/dist/notebook-generation-controls.js +145 -0
  250. package/dist/notebook-goal-layout.js +40 -0
  251. package/dist/notebook-graph.js +37 -0
  252. package/dist/notebook-image-edit.js +84 -0
  253. package/dist/notebook-initial-generation-prompt.js +222 -0
  254. package/dist/notebook-live-analysis.js +941 -0
  255. package/dist/notebook-media-ops.js +579 -0
  256. package/dist/notebook-mutations.js +20 -0
  257. package/dist/notebook-placement.js +41 -0
  258. package/dist/notebook-provider-failure.js +87 -0
  259. package/dist/notebook-published-post.js +26 -0
  260. package/dist/notebook-reference-resolver.js +227 -0
  261. package/dist/notebook-relayout.js +270 -0
  262. package/dist/notebook-run-plan.js +519 -0
  263. package/dist/notebook-shots.js +162 -0
  264. package/dist/notebook-workflow.js +856 -0
  265. package/dist/openai-intelligence.js +99 -0
  266. package/dist/original-media-file.js +12 -0
  267. package/dist/osg-assets.js +36 -0
  268. package/dist/osg-placement-cache.js +64 -0
  269. package/dist/osg-settings.js +27 -0
  270. package/dist/paid-tools.js +22 -0
  271. package/dist/platform-guidance.js +26 -0
  272. package/dist/posts/dropbox-auth.js +91 -0
  273. package/dist/posts/dropbox-upload.js +132 -0
  274. package/dist/posts/icloud-drive.js +134 -0
  275. package/dist/posts/icloud-status.js +64 -0
  276. package/dist/posts/post-contract.js +2 -0
  277. package/dist/posts/post-copy-format.js +43 -0
  278. package/dist/posts/post-copy.js +193 -0
  279. package/dist/posts/post-folder.js +18 -0
  280. package/dist/posts/post-metrics.js +119 -0
  281. package/dist/posts/post-store.js +340 -0
  282. package/dist/posts/x-post-target.js +43 -0
  283. package/dist/preview-payload.js +90 -0
  284. package/dist/prompt-surfaces.js +22 -0
  285. package/dist/provider-prompt-transform.js +77 -0
  286. package/dist/provider-status-link.js +21 -0
  287. package/dist/provider-task-record.js +78 -0
  288. package/dist/provider-task-status.js +47 -0
  289. package/dist/provider-usage.js +294 -0
  290. package/dist/queue.js +806 -0
  291. package/dist/reference-video-duration.js +17 -0
  292. package/dist/remotion/BlurFillVideo.js +51 -0
  293. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  294. package/dist/remotion/OsgOverlay.js +30 -0
  295. package/dist/remotion/QrOutroComposition.js +47 -0
  296. package/dist/remotion/RenderComposition.js +11 -0
  297. package/dist/remotion/browser.js +59 -0
  298. package/dist/remotion/constants.js +35 -0
  299. package/dist/remotion/index.js +66 -0
  300. package/dist/remotion/keyword-graphics.js +151 -0
  301. package/dist/remotion/keyword-placement.js +182 -0
  302. package/dist/remotion/osg-spec.js +198 -0
  303. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  304. package/dist/remotion/qr-outro-audio.js +1 -0
  305. package/dist/remotion/qr-outro-spec.js +211 -0
  306. package/dist/remotion/shared-bundle.js +37 -0
  307. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  308. package/dist/remotion/subtitles/Highlight.js +58 -0
  309. package/dist/remotion/subtitles/Impact.js +67 -0
  310. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  311. package/dist/remotion/subtitles/alive-layout.js +321 -0
  312. package/dist/remotion/subtitles/alive-types.js +17 -0
  313. package/dist/remotion/subtitles/chunker.js +68 -0
  314. package/dist/remotion/subtitles/segments.js +42 -0
  315. package/dist/remotion/subtitles/styles.js +4 -0
  316. package/dist/remotion/subtitles/types.js +1 -0
  317. package/dist/remotion/util.js +18 -0
  318. package/dist/remotion-cancellation.js +15 -0
  319. package/dist/rename-helpers.js +204 -0
  320. package/dist/rhythm/analyze.js +128 -0
  321. package/dist/rhythm/contract.js +14 -0
  322. package/dist/rhythm/draft-model.js +19 -0
  323. package/dist/rhythm/energy.js +133 -0
  324. package/dist/rhythm/fft.js +44 -0
  325. package/dist/rhythm/model.js +71 -0
  326. package/dist/rhythm/music-analysis.js +124 -0
  327. package/dist/rhythm/music-draft.js +261 -0
  328. package/dist/rhythm/onset.js +101 -0
  329. package/dist/rhythm/run-worker.js +51 -0
  330. package/dist/rhythm/score.js +47 -0
  331. package/dist/rhythm/section-labels.js +168 -0
  332. package/dist/rhythm/sequence-rhythm.js +67 -0
  333. package/dist/rhythm/synthesize.js +103 -0
  334. package/dist/rhythm/timing.js +98 -0
  335. package/dist/rhythm/track.js +320 -0
  336. package/dist/rhythm/visual-fit.js +4 -0
  337. package/dist/rhythm/worker.js +25 -0
  338. package/dist/scene-detect.js +200 -0
  339. package/dist/scene-transition.js +12 -0
  340. package/dist/scheduled-posts.js +200 -0
  341. package/dist/search-index-coverage.js +13 -0
  342. package/dist/search-index-media.js +71 -0
  343. package/dist/search-reference-store.js +60 -0
  344. package/dist/search-scoring.js +17 -0
  345. package/dist/serve.js +150 -0
  346. package/dist/server-lock.js +143 -0
  347. package/dist/server.js +42 -0
  348. package/dist/srt.js +16 -0
  349. package/dist/tag-backfill-state.js +121 -0
  350. package/dist/tag-settings.js +30 -0
  351. package/dist/tag-vocabulary.js +237 -0
  352. package/dist/test-queue-cleanup.js +1 -0
  353. package/dist/time-parse.js +50 -0
  354. package/dist/timeline-helpers.js +554 -0
  355. package/dist/timewarp.js +87 -0
  356. package/dist/tools/_image-inputs.js +9 -0
  357. package/dist/tools/_media-refs.js +84 -0
  358. package/dist/tools/_shared.js +59 -0
  359. package/dist/tools/_suggest-shared.js +279 -0
  360. package/dist/tools/add-timeline-audio.js +73 -0
  361. package/dist/tools/alive-captions.js +157 -0
  362. package/dist/tools/analyze-jokes.js +56 -0
  363. package/dist/tools/analyze-video.js +66 -0
  364. package/dist/tools/animate-keywords.js +217 -0
  365. package/dist/tools/api-status.js +42 -0
  366. package/dist/tools/application-commands.js +34 -0
  367. package/dist/tools/apply-cuts.js +112 -0
  368. package/dist/tools/apply-sfx.js +152 -0
  369. package/dist/tools/apply-watermark.js +670 -0
  370. package/dist/tools/archive-clip.js +74 -0
  371. package/dist/tools/asset-history.js +61 -0
  372. package/dist/tools/asset-manifest.js +24 -0
  373. package/dist/tools/asset-tags.js +151 -0
  374. package/dist/tools/assign-slot.js +97 -0
  375. package/dist/tools/audio-boost.js +107 -0
  376. package/dist/tools/audio-mix.js +121 -0
  377. package/dist/tools/book-usage.js +16 -0
  378. package/dist/tools/chain.js +136 -0
  379. package/dist/tools/change-video-speed.js +105 -0
  380. package/dist/tools/chat-history.js +5 -0
  381. package/dist/tools/chat.js +848 -0
  382. package/dist/tools/clean-video-audio.js +97 -0
  383. package/dist/tools/clear-failed-tasks.js +66 -0
  384. package/dist/tools/cold-open.js +114 -0
  385. package/dist/tools/command-plan.js +1680 -0
  386. package/dist/tools/commands.js +1666 -0
  387. package/dist/tools/concat-images.js +236 -0
  388. package/dist/tools/connect-service.js +55 -0
  389. package/dist/tools/copy-file-to-clipboard.js +58 -0
  390. package/dist/tools/create-character.js +114 -0
  391. package/dist/tools/create-qart.js +253 -0
  392. package/dist/tools/create-qr-outro.js +149 -0
  393. package/dist/tools/create-qrbtf.js +215 -0
  394. package/dist/tools/create-watermark.js +97 -0
  395. package/dist/tools/crop-asset.js +18 -0
  396. package/dist/tools/crop-image.js +120 -0
  397. package/dist/tools/crop-video.js +97 -0
  398. package/dist/tools/delete-asset.js +104 -0
  399. package/dist/tools/describe-image.js +69 -0
  400. package/dist/tools/detect-scenes.js +44 -0
  401. package/dist/tools/doctor.js +126 -0
  402. package/dist/tools/download-video.js +114 -0
  403. package/dist/tools/duplicate-asset.js +184 -0
  404. package/dist/tools/edit-timeline.js +59 -0
  405. package/dist/tools/editing-moments.js +105 -0
  406. package/dist/tools/estimate-cost.js +171 -0
  407. package/dist/tools/even-split.js +295 -0
  408. package/dist/tools/export-asset-file.js +22 -0
  409. package/dist/tools/export-asset.js +140 -0
  410. package/dist/tools/export-sequence.js +250 -0
  411. package/dist/tools/export-to-downloads.js +54 -0
  412. package/dist/tools/export-transcript-srt.js +79 -0
  413. package/dist/tools/extend-audio.js +100 -0
  414. package/dist/tools/extract-audio.js +174 -0
  415. package/dist/tools/extract-frame.js +108 -0
  416. package/dist/tools/extract-notebook-audio.js +173 -0
  417. package/dist/tools/extract-notebook-frame.js +160 -0
  418. package/dist/tools/extract-video-clip.js +101 -0
  419. package/dist/tools/generate-media.js +157 -0
  420. package/dist/tools/generation-payload.js +48 -0
  421. package/dist/tools/get-timeline.js +12 -0
  422. package/dist/tools/get-watermark-config.js +38 -0
  423. package/dist/tools/goal.js +644 -0
  424. package/dist/tools/image-subjects.js +711 -0
  425. package/dist/tools/index.js +323 -0
  426. package/dist/tools/inject-first-frame.js +154 -0
  427. package/dist/tools/invert-image.js +70 -0
  428. package/dist/tools/isolate-vocals.js +66 -0
  429. package/dist/tools/list-assets.js +98 -0
  430. package/dist/tools/list-cell-generations.js +225 -0
  431. package/dist/tools/list-elevenlabs-voices.js +163 -0
  432. package/dist/tools/log-wrapper.js +29 -0
  433. package/dist/tools/make-cover-song.js +95 -0
  434. package/dist/tools/mark-asset-seen.js +23 -0
  435. package/dist/tools/media-library.js +245 -0
  436. package/dist/tools/move-timeline-audio.js +35 -0
  437. package/dist/tools/music-analysis.js +60 -0
  438. package/dist/tools/music-draft.js +29 -0
  439. package/dist/tools/pad-video.js +87 -0
  440. package/dist/tools/post-record.js +83 -0
  441. package/dist/tools/post.js +168 -0
  442. package/dist/tools/prompt-history.js +164 -0
  443. package/dist/tools/provider-key-status.js +248 -0
  444. package/dist/tools/qrbtf-render.js +849 -0
  445. package/dist/tools/read-asset-metadata.js +26 -0
  446. package/dist/tools/read-file-at-commit.js +29 -0
  447. package/dist/tools/read-transcript.js +41 -0
  448. package/dist/tools/reconcile-project-work.js +1452 -0
  449. package/dist/tools/recover-asset.js +28 -0
  450. package/dist/tools/remove-sound.js +72 -0
  451. package/dist/tools/remove-timeline-audio.js +34 -0
  452. package/dist/tools/remove-timeline-slot.js +40 -0
  453. package/dist/tools/rename-all.js +434 -0
  454. package/dist/tools/rename-asset.js +34 -0
  455. package/dist/tools/render-settings.js +47 -0
  456. package/dist/tools/render-timeline.js +120 -0
  457. package/dist/tools/render.js +384 -0
  458. package/dist/tools/reorder-character-images.js +45 -0
  459. package/dist/tools/replace-video-audio.js +133 -0
  460. package/dist/tools/restore-archive-clips.js +209 -0
  461. package/dist/tools/restore-asset-version.js +37 -0
  462. package/dist/tools/retry-generation.js +54 -0
  463. package/dist/tools/reverse-video.js +74 -0
  464. package/dist/tools/rewrite-prompt.js +388 -0
  465. package/dist/tools/rewrite-script.js +67 -0
  466. package/dist/tools/schema-from-apicity.js +66 -0
  467. package/dist/tools/search-archive.js +150 -0
  468. package/dist/tools/semantic-search.js +1214 -0
  469. package/dist/tools/sequence-rhythm.js +43 -0
  470. package/dist/tools/set-character-metadata.js +84 -0
  471. package/dist/tools/set-env-secret.js +40 -0
  472. package/dist/tools/set-osg.js +143 -0
  473. package/dist/tools/show-folder.js +37 -0
  474. package/dist/tools/smooth-scene-concat.js +122 -0
  475. package/dist/tools/splice-video.js +149 -0
  476. package/dist/tools/split-video.js +202 -0
  477. package/dist/tools/suggest-cuts.js +45 -0
  478. package/dist/tools/suggest-sfx.js +43 -0
  479. package/dist/tools/sync-video-to-audio.js +115 -0
  480. package/dist/tools/tag-backfill.js +177 -0
  481. package/dist/tools/timewarp-video.js +85 -0
  482. package/dist/tools/tool-def.js +1 -0
  483. package/dist/tools/tool-json-schema.js +116 -0
  484. package/dist/tools/transcribe.js +90 -0
  485. package/dist/tools/translate-subtitles.js +59 -0
  486. package/dist/tools/trim-audio.js +139 -0
  487. package/dist/tools/trim-video.js +168 -0
  488. package/dist/tools/update-script.js +25 -0
  489. package/dist/tools/update-timeline-audio.js +40 -0
  490. package/dist/tools/upload-file.js +41 -0
  491. package/dist/tools/verify-x-mentions.js +141 -0
  492. package/dist/tools/video-editing-command-contract.js +291 -0
  493. package/dist/tools/video-source-reference.js +30 -0
  494. package/dist/tools/video-time-input.js +18 -0
  495. package/dist/tools/videobook-domain.js +2006 -0
  496. package/dist/tools/wait-for-asset-ready.js +143 -0
  497. package/dist/tools/workspace-actions.js +54 -0
  498. package/dist/transcript-projection.js +202 -0
  499. package/dist/transcript-srt.js +33 -0
  500. package/dist/upload.js +76 -0
  501. package/dist/usage-providers.js +25 -0
  502. package/dist/video-analysis-projection.js +46 -0
  503. package/dist/video-poster.js +33 -0
  504. package/dist/video-proxy.js +99 -0
  505. package/dist/vision-regions.js +111 -0
  506. package/dist/voice-actions.js +90 -0
  507. package/dist/voice-audio.js +55 -0
  508. package/dist/voice-config.js +122 -0
  509. package/dist/voice-continuation.js +54 -0
  510. package/dist/voice-contract.js +1 -0
  511. package/dist/voice-http.js +145 -0
  512. package/dist/voice-reply-progress.js +55 -0
  513. package/dist/voice-session.js +992 -0
  514. package/dist/watermark-badge.js +259 -0
  515. package/dist/watermark-config.js +111 -0
  516. package/dist/watermark-handle-position.js +55 -0
  517. package/dist/watermark-platform.js +15 -0
  518. package/dist/watermark-timing.js +16 -0
  519. package/dist/workspace-action-catalog.js +315 -0
  520. package/dist/workspace-action-contract.js +21 -0
  521. package/dist/workspace-action-groups.js +98 -0
  522. package/dist/workspace-action-surfaces.js +7 -0
  523. package/dist/x/account.js +88 -0
  524. package/dist/x/authenticated-handle.js +30 -0
  525. package/dist/x/oauth-flow.js +191 -0
  526. package/dist/x/post-contract.js +1 -0
  527. package/dist/x/post-metadata.js +44 -0
  528. package/dist/x/post-to-x.js +456 -0
  529. package/dist/x/published-post-cell.js +90 -0
  530. package/dist/x/x-tokens.js +69 -0
  531. package/dist/zod-slots.js +334 -0
  532. package/package.json +92 -4
  533. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  534. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  535. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  536. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  537. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  538. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  539. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  540. package/ui/assets/index-DzPAnCUj.css +1 -0
  541. package/ui/assets/index-kS1905s5.js +37 -0
  542. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  543. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  544. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  545. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  546. package/ui/assets/ui-Oirr9VOf.css +1 -0
  547. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  548. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  549. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  550. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  551. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  552. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  553. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  554. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  555. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  556. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  557. package/ui/favicon.svg +8 -0
  558. package/ui/index.html +126 -0
package/dist/ffmpeg.js ADDED
@@ -0,0 +1,425 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import * as fsp from "node:fs/promises";
4
+ import { logger } from "./logger.js";
5
+ import { FFMPEG } from "./ffmpeg-binary.js";
6
+ import { FFPROBE } from "./ffprobe-binary.js";
7
+ export { FFMPEG, FFPROBE };
8
+ const execFileAsync = promisify(execFile);
9
+ const DEFAULT_FFMPEG_TIMEOUT_MS = 30 * 60_000;
10
+ const DEFAULT_FFPROBE_TIMEOUT_MS = 2 * 60_000;
11
+ export const H264_MEDIUM = ["-c:v", "libx264", "-preset", "medium", "-crf", "23", "-profile:v", "high", "-level:v", "4.0", "-pix_fmt", "yuv420p"];
12
+ // `-ac 2` downmixes any source to stereo. The native `aac` encoder rejects
13
+ // >2-channel layouts (e.g. 5.1 sources fail with "Unsupported channel layout
14
+ // '6 channels'"), and stereo is the right delivery format for videocity's
15
+ // web/social targets anyway.
16
+ export const AUDIO_OPTS = ["-c:a", "aac", "-b:a", "192k", "-ac", "2"];
17
+ function rationalValue(value) {
18
+ if (typeof value !== "string")
19
+ return null;
20
+ const [numerator, denominator] = value.split("/").map(Number);
21
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator === 0)
22
+ return null;
23
+ return numerator / denominator;
24
+ }
25
+ function rationalParts(value, separator = "/") {
26
+ if (typeof value !== "string")
27
+ return null;
28
+ const parts = value.split(separator);
29
+ if (parts.length !== 2)
30
+ return null;
31
+ const numerator = Number(parts[0]);
32
+ const denominator = Number(parts[1]);
33
+ if (!Number.isSafeInteger(numerator)
34
+ || !Number.isSafeInteger(denominator)
35
+ || numerator <= 0
36
+ || denominator <= 0)
37
+ return null;
38
+ return { numerator, denominator };
39
+ }
40
+ function durationTicks(stream, timeBase, fallbackSeconds) {
41
+ const direct = Number(stream.duration_ts);
42
+ if (Number.isSafeInteger(direct) && direct > 0)
43
+ return direct;
44
+ return Math.max(1, Math.round(fallbackSeconds * timeBase.denominator / timeBase.numerator));
45
+ }
46
+ function rotationDegrees(stream) {
47
+ const sideData = Array.isArray(stream.side_data_list)
48
+ ? stream.side_data_list
49
+ : [];
50
+ const sideRotation = sideData
51
+ .map((entry) => Number(entry.rotation))
52
+ .find(Number.isFinite);
53
+ const tags = typeof stream.tags === "object" && stream.tags !== null
54
+ ? stream.tags
55
+ : {};
56
+ const taggedRotation = Number(tags.rotate);
57
+ return Math.round(sideRotation ?? (Number.isFinite(taggedRotation) ? taggedRotation : 0));
58
+ }
59
+ function videoProbeStream(stream, fallbackSeconds) {
60
+ if (!stream)
61
+ return null;
62
+ const timeBase = rationalParts(stream.time_base) ?? { numerator: 1, denominator: 1_000 };
63
+ const nominalFrameRate = rationalParts(stream.r_frame_rate);
64
+ const averageFrameRate = rationalParts(stream.avg_frame_rate);
65
+ return {
66
+ index: Number.isSafeInteger(Number(stream.index)) ? Number(stream.index) : 0,
67
+ timeBase,
68
+ durationTicks: durationTicks(stream, timeBase, fallbackSeconds),
69
+ codec: typeof stream.codec_name === "string" ? stream.codec_name : "unknown",
70
+ video: {
71
+ width: Math.max(1, Number(stream.width) || 1),
72
+ height: Math.max(1, Number(stream.height) || 1),
73
+ rotationDegrees: rotationDegrees(stream),
74
+ pixelAspect: rationalParts(stream.sample_aspect_ratio, ":")
75
+ ?? { numerator: 1, denominator: 1 },
76
+ ...(nominalFrameRate ? { nominalFrameRate } : {}),
77
+ ...(averageFrameRate ? { averageFrameRate } : {}),
78
+ },
79
+ };
80
+ }
81
+ function audioProbeStream(stream, fallbackSeconds) {
82
+ if (!stream)
83
+ return null;
84
+ const sampleRateHz = Math.max(1, Number(stream.sample_rate) || 48_000);
85
+ const timeBase = rationalParts(stream.time_base)
86
+ ?? { numerator: 1, denominator: sampleRateHz };
87
+ const channels = Math.max(1, Number(stream.channels) || 1);
88
+ const channelLayout = typeof stream.channel_layout === "string"
89
+ ? stream.channel_layout
90
+ : channels === 1 ? "mono" : channels === 2 ? "stereo" : `${channels} channels`;
91
+ return {
92
+ index: Number.isSafeInteger(Number(stream.index)) ? Number(stream.index) : 0,
93
+ timeBase,
94
+ durationTicks: durationTicks(stream, timeBase, fallbackSeconds),
95
+ codec: typeof stream.codec_name === "string" ? stream.codec_name : "unknown",
96
+ audio: { sampleRateHz, channels, channelLayout },
97
+ };
98
+ }
99
+ function taggedDuration(value) {
100
+ if (typeof value !== "string")
101
+ return null;
102
+ const match = /^(\d+):(\d+):(\d+(?:\.\d+)?)$/.exec(value);
103
+ if (!match)
104
+ return null;
105
+ return Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]);
106
+ }
107
+ function streamDuration(stream, fallback) {
108
+ if (!stream)
109
+ return 0;
110
+ const direct = Number(stream.duration);
111
+ if (Number.isFinite(direct) && direct > 0)
112
+ return direct;
113
+ const durationTs = Number(stream.duration_ts);
114
+ const timeBase = rationalValue(stream.time_base);
115
+ if (Number.isFinite(durationTs) && timeBase !== null && durationTs * timeBase > 0) {
116
+ return durationTs * timeBase;
117
+ }
118
+ const tags = typeof stream.tags === "object" && stream.tags !== null
119
+ ? stream.tags
120
+ : undefined;
121
+ const tagged = taggedDuration(tags?.DURATION ?? tags?.duration);
122
+ return tagged !== null && tagged > 0 ? tagged : fallback;
123
+ }
124
+ function interruptionMessage(processName, error, options, timeoutMs) {
125
+ if (options.signal?.aborted) {
126
+ const reason = options.signal.reason;
127
+ const detail = reason instanceof Error ? reason.message : String(reason ?? "aborted");
128
+ return `${processName} cancelled: ${detail}`;
129
+ }
130
+ const processError = error;
131
+ if (processError.killed && processError.signal) {
132
+ return `${processName} timed out after ${timeoutMs}ms`;
133
+ }
134
+ return null;
135
+ }
136
+ export async function probeVideo(filePath, options = {}) {
137
+ let stdout;
138
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FFPROBE_TIMEOUT_MS;
139
+ try {
140
+ const result = await execFileAsync(FFPROBE, [
141
+ "-hide_banner",
142
+ "-v", "error",
143
+ "-print_format", "json",
144
+ "-show_streams",
145
+ "-show_format",
146
+ filePath,
147
+ ], {
148
+ encoding: "utf8",
149
+ killSignal: "SIGKILL",
150
+ signal: options.signal,
151
+ timeout: timeoutMs,
152
+ });
153
+ stdout = result.stdout;
154
+ }
155
+ catch (error) {
156
+ const interruption = interruptionMessage("ffprobe", error, options, timeoutMs);
157
+ if (interruption)
158
+ throw new Error(`${interruption} for ${filePath}`);
159
+ const e = error;
160
+ const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString() ?? "";
161
+ const exit = e.code !== undefined ? String(e.code) : "?";
162
+ let size = "missing";
163
+ try {
164
+ const { size: bytes } = await fsp.stat(filePath);
165
+ size = `${bytes} bytes`;
166
+ }
167
+ catch { /* file gone */ }
168
+ throw new Error(`ffprobe failed for ${filePath} (exit ${exit}, ${size}): ${stderr.trim() || e.message}`);
169
+ }
170
+ const data = JSON.parse(stdout);
171
+ const videoStream = data.streams?.find((s) => s.codec_type === "video");
172
+ const width = Number(videoStream?.width ?? 0);
173
+ const height = Number(videoStream?.height ?? 0);
174
+ const duration = Number(data.format?.duration ?? 0);
175
+ const videoDuration = streamDuration(videoStream, duration);
176
+ let fps = 30;
177
+ const rFrameRate = videoStream?.r_frame_rate;
178
+ if (typeof rFrameRate === "string" && rFrameRate.includes("/")) {
179
+ const [num, den] = rFrameRate.split("/").map(Number);
180
+ if (den > 0)
181
+ fps = num / den;
182
+ }
183
+ const avgFps = rationalValue(videoStream?.avg_frame_rate);
184
+ const audioStream = data.streams?.find((s) => s.codec_type === "audio");
185
+ const hasAudio = audioStream !== undefined;
186
+ const audioDuration = audioStream ? streamDuration(audioStream, duration) : null;
187
+ const videoCodec = typeof videoStream?.codec_name === "string" ? videoStream.codec_name : null;
188
+ const audioCodec = typeof audioStream?.codec_name === "string" ? audioStream.codec_name : null;
189
+ return {
190
+ width,
191
+ height,
192
+ duration,
193
+ videoDuration,
194
+ audioDuration,
195
+ fps,
196
+ avgFps,
197
+ hasAudio,
198
+ videoCodec,
199
+ audioCodec,
200
+ videoStream: videoProbeStream(videoStream, videoDuration),
201
+ audioStream: audioProbeStream(audioStream, audioDuration ?? duration),
202
+ };
203
+ }
204
+ export function summarizeFfmpegStderr(stderr, maxChars = 600) {
205
+ const lines = stderr.split("\n").map((l) => l.trimEnd()).filter(Boolean);
206
+ const meaningful = lines.filter((l) => {
207
+ if (/^ffmpeg version /.test(l))
208
+ return false;
209
+ if (/^\s*built with /.test(l))
210
+ return false;
211
+ if (/^\s*configuration:/.test(l))
212
+ return false;
213
+ if (/^\s*lib\w+\s+\d/.test(l))
214
+ return false;
215
+ if (/^(frame|size)=/.test(l))
216
+ return false;
217
+ return true;
218
+ });
219
+ const tail = meaningful.slice(-15).join("\n");
220
+ if (tail.length <= maxChars)
221
+ return tail;
222
+ if (maxChars <= 0)
223
+ return "";
224
+ const truncationMarker = "\n...\n";
225
+ if (maxChars <= truncationMarker.length)
226
+ return tail.slice(0, maxChars);
227
+ const availableChars = maxChars - truncationMarker.length;
228
+ const headChars = Math.ceil(availableChars / 2);
229
+ const tailChars = availableChars - headChars;
230
+ const suffix = tailChars > 0 ? tail.slice(-tailChars) : "";
231
+ return `${tail.slice(0, headChars)}${truncationMarker}${suffix}`;
232
+ }
233
+ export function withQuietFfmpegArgs(args) {
234
+ const hasLoglevel = args.includes("-loglevel") || args.includes("-v");
235
+ const hasHideBanner = args.includes("-hide_banner");
236
+ const prefix = [];
237
+ if (!hasHideBanner)
238
+ prefix.push("-hide_banner");
239
+ if (!hasLoglevel)
240
+ prefix.push("-loglevel", "error");
241
+ return prefix.length === 0 ? args : [...prefix, ...args];
242
+ }
243
+ export async function runFfmpeg(args, options = {}) {
244
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FFMPEG_TIMEOUT_MS;
245
+ try {
246
+ await execFileAsync(FFMPEG, withQuietFfmpegArgs(args), {
247
+ encoding: "utf8",
248
+ killSignal: "SIGKILL",
249
+ signal: options.signal,
250
+ timeout: timeoutMs,
251
+ });
252
+ }
253
+ catch (error) {
254
+ const interruption = interruptionMessage("ffmpeg", error, options, timeoutMs);
255
+ if (interruption)
256
+ throw new Error(interruption);
257
+ const stderrRaw = error instanceof Error ? error.stderr : undefined;
258
+ const fallback = error instanceof Error ? error.message : String(error);
259
+ const summary = stderrRaw ? summarizeFfmpegStderr(stderrRaw) : "";
260
+ throw new Error(`ffmpeg failed: ${summary || fallback}`);
261
+ }
262
+ }
263
+ let _FFmpeggy = null;
264
+ const SAMPLE_RATE = 8000;
265
+ // Fine-grained RMS accumulators (~768 KB). We fold these in halves as audio
266
+ // streams in, so memory stays constant regardless of clip length — see
267
+ // extractWaveform.
268
+ const WAVEFORM_FINE_BUCKETS = 1 << 16;
269
+ /**
270
+ * Streams an asset's decoded audio and folds it into a bounded set of RMS
271
+ * accumulators, returning `barCount` normalised peaks (0..1).
272
+ *
273
+ * The earlier implementation buffered the entire PCM stream via execFile with a
274
+ * 50 MB `maxBuffer`. A video whose audio decoded to more than ~52 minutes of
275
+ * 8 kHz mono s16le overflowed that cap; ffmpeg was killed mid-stream, the error
276
+ * was swallowed, and an empty waveform came back — which never cached and so
277
+ * re-ran the multi-second decode on every poll. Streaming removes the cap (and
278
+ * the failure mode) entirely: memory is O(WAVEFORM_FINE_BUCKETS), not
279
+ * O(duration).
280
+ */
281
+ export async function extractWaveform(videoPath, barCount, streamIndex, signal) {
282
+ signal?.throwIfAborted();
283
+ const sumSq = new Float64Array(WAVEFORM_FINE_BUCKETS);
284
+ const count = new Int32Array(WAVEFORM_FINE_BUCKETS);
285
+ let samplesPerBucket = 1;
286
+ let writeIdx = 0;
287
+ let inBucket = 0;
288
+ let totalSamples = 0;
289
+ // Halve resolution in place when the buckets fill: merge each adjacent pair,
290
+ // double the samples-per-bucket, and resume writing at the midpoint. This
291
+ // caps the accumulators at WAVEFORM_FINE_BUCKETS for any clip length.
292
+ const foldPairs = () => {
293
+ const half = WAVEFORM_FINE_BUCKETS >> 1;
294
+ for (let i = 0; i < half; i++) {
295
+ sumSq[i] = sumSq[2 * i] + sumSq[2 * i + 1];
296
+ count[i] = count[2 * i] + count[2 * i + 1];
297
+ }
298
+ sumSq.fill(0, half);
299
+ count.fill(0, half);
300
+ samplesPerBucket *= 2;
301
+ writeIdx = half;
302
+ };
303
+ const addSample = (sample) => {
304
+ sumSq[writeIdx] += sample * sample;
305
+ inBucket++;
306
+ totalSamples++;
307
+ if (inBucket === samplesPerBucket) {
308
+ count[writeIdx] = inBucket;
309
+ writeIdx++;
310
+ inBucket = 0;
311
+ if (writeIdx === WAVEFORM_FINE_BUCKETS)
312
+ foldPairs();
313
+ }
314
+ };
315
+ try {
316
+ await new Promise((resolve, reject) => {
317
+ const child = spawn(FFMPEG, withQuietFfmpegArgs([
318
+ "-i", videoPath, "-map", streamIndex === undefined ? "0:a:0" : `0:${streamIndex}`, "-vn", "-ac", "1", "-ar", String(SAMPLE_RATE), "-f", "s16le", "pipe:1",
319
+ ]), { stdio: ["ignore", "pipe", "pipe"] });
320
+ const abort = () => child.kill("SIGTERM");
321
+ signal?.addEventListener("abort", abort, { once: true });
322
+ if (signal?.aborted)
323
+ abort();
324
+ let carry = -1; // a single low byte left over when a chunk splits a sample
325
+ child.stdout.on("data", (chunk) => {
326
+ let offset = 0;
327
+ if (carry !== -1 && chunk.length > 0) {
328
+ addSample((((carry | (chunk[0] << 8)) << 16) >> 16)); // sign-extend 16-bit LE
329
+ offset = 1;
330
+ carry = -1;
331
+ }
332
+ const usableBytes = chunk.length - offset;
333
+ const end = offset + usableBytes - (usableBytes & 1);
334
+ for (let i = offset; i < end; i += 2)
335
+ addSample(chunk.readInt16LE(i));
336
+ carry = usableBytes & 1 ? chunk[chunk.length - 1] : -1;
337
+ });
338
+ child.stderr.resume();
339
+ child.on("error", reject);
340
+ child.on("close", (code) => {
341
+ signal?.removeEventListener("abort", abort);
342
+ // No samples means no decodable audio (no audio stream, or a decode
343
+ // error). Surface it once, but return empty rather than rejecting so a
344
+ // genuinely silent clip caches as "no waveform" instead of churning.
345
+ if (totalSamples === 0 && code !== 0) {
346
+ logger.warn("waveform-empty", { exitCode: code });
347
+ }
348
+ resolve();
349
+ });
350
+ });
351
+ signal?.throwIfAborted();
352
+ if (totalSamples === 0)
353
+ return [];
354
+ if (inBucket > 0)
355
+ count[writeIdx++] = inBucket; // finalise the partial bucket
356
+ const usedBuckets = writeIdx;
357
+ // Default: 100 bars per second of audio (0.01s resolution), clamped 20..1000.
358
+ if (barCount === undefined) {
359
+ const durationSec = totalSamples / SAMPLE_RATE;
360
+ barCount = Math.max(20, Math.min(1000, Math.round(durationSec * 100)));
361
+ }
362
+ barCount = Math.min(barCount, usedBuckets);
363
+ // True RMS per bar: sum the squared energy and sample counts across the
364
+ // fine buckets the bar spans, then take the root-mean-square.
365
+ const peaks = new Array(barCount);
366
+ for (let b = 0; b < barCount; b++) {
367
+ const start = Math.floor((b * usedBuckets) / barCount);
368
+ const stop = Math.max(start + 1, Math.floor(((b + 1) * usedBuckets) / barCount));
369
+ let energy = 0;
370
+ let samples = 0;
371
+ for (let i = start; i < stop && i < usedBuckets; i++) {
372
+ energy += sumSq[i];
373
+ samples += count[i];
374
+ }
375
+ peaks[b] = samples > 0 ? Math.sqrt(energy / samples) : 0;
376
+ }
377
+ const max = Math.max(...peaks, 1);
378
+ return peaks.map((p) => p / max);
379
+ }
380
+ catch (err) {
381
+ signal?.throwIfAborted();
382
+ // Only reached if ffmpeg fails to spawn (e.g. binary missing). The stream
383
+ // never buffers, so the old maxBuffer failure mode can't recur here.
384
+ logger.error("waveform-failed", { error: err });
385
+ return [];
386
+ }
387
+ }
388
+ export async function isThumbnailBlank(imagePath) {
389
+ try {
390
+ const { stdout } = await execFileAsync(FFMPEG, withQuietFfmpegArgs([
391
+ "-i", imagePath,
392
+ "-vf", "format=gray",
393
+ "-f", "rawvideo",
394
+ "-pix_fmt", "gray",
395
+ "pipe:1",
396
+ ]), { encoding: "buffer", maxBuffer: 10 * 1024 * 1024 });
397
+ const buf = stdout;
398
+ if (buf.length === 0)
399
+ return true;
400
+ let sum = 0;
401
+ for (let i = 0; i < buf.length; i++)
402
+ sum += buf[i];
403
+ const mean = sum / buf.length;
404
+ let variance = 0;
405
+ for (let i = 0; i < buf.length; i++) {
406
+ const diff = buf[i] - mean;
407
+ variance += diff * diff;
408
+ }
409
+ const stddev = Math.sqrt(variance / buf.length);
410
+ // Near-black or uniform solid color
411
+ return mean < 10 || stddev < 5;
412
+ }
413
+ catch {
414
+ return false;
415
+ }
416
+ }
417
+ export async function getFFmpeggy() {
418
+ if (!_FFmpeggy) {
419
+ const mod = await import("ffmpeggy");
420
+ _FFmpeggy = mod.FFmpeggy;
421
+ _FFmpeggy.DefaultConfig.ffmpegBin = FFMPEG;
422
+ _FFmpeggy.DefaultConfig.ffprobeBin = FFPROBE;
423
+ }
424
+ return _FFmpeggy;
425
+ }
@@ -0,0 +1,55 @@
1
+ import { closeSync, openSync, readSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ const MACH_O_64_MAGIC_LE = 0xfeedfacf;
4
+ const FAT_MAGIC_BE = 0xcafebabe;
5
+ const CPU_TYPE_X86_64 = 0x01000007;
6
+ const CPU_TYPE_ARM64 = 0x0100000c;
7
+ /** Reads the CPU type from a Mach-O header; null when it is not Mach-O. */
8
+ export function machOArch(header) {
9
+ if (header.length < 8)
10
+ return null;
11
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
12
+ if (view.getUint32(0, false) === FAT_MAGIC_BE)
13
+ return "universal";
14
+ if (view.getUint32(0, true) !== MACH_O_64_MAGIC_LE)
15
+ return null;
16
+ const cpuType = view.getUint32(4, true);
17
+ if (cpuType === CPU_TYPE_ARM64)
18
+ return "arm64";
19
+ if (cpuType === CPU_TYPE_X86_64)
20
+ return "x64";
21
+ return "other";
22
+ }
23
+ /** Whether a binary with this header can run on the host CPU. */
24
+ export function binaryRunsOnHost(header, hostArch) {
25
+ const arch = machOArch(header);
26
+ return arch === null || arch === "universal" || arch === hostArch;
27
+ }
28
+ export function readBinaryHeader(path) {
29
+ try {
30
+ const fd = openSync(path, "r");
31
+ try {
32
+ const header = new Uint8Array(8);
33
+ return readSync(fd, header, 0, 8, 0) === 8 ? header : null;
34
+ }
35
+ finally {
36
+ closeSync(fd);
37
+ }
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * ffprobe-static ships an x86_64 build under darwin/arm64, which cannot spawn
45
+ * on Apple Silicon without Rosetta. An explicit override wins; otherwise the
46
+ * bundled binary is used only when its CPU matches, else `ffprobe` on PATH.
47
+ */
48
+ export function resolveFfprobePath(bundledPath, override, hostArch) {
49
+ if (override)
50
+ return override;
51
+ const header = readBinaryHeader(bundledPath);
52
+ return header && binaryRunsOnHost(header, hostArch) ? bundledPath : "ffprobe";
53
+ }
54
+ const require = createRequire(import.meta.url);
55
+ export const FFPROBE = resolveFfprobePath(require("ffprobe-static").path, process.env.VIDEOBOOK_FFPROBE_PATH, process.arch);
@@ -0,0 +1,216 @@
1
+ import { markDescendantsStale } from "./notebook-action-catalog.js";
2
+ import { mutateNotebook } from "./notebook-mutations.js";
3
+ import { removeArtifactFromTimeline } from "./tools/delete-asset.js";
4
+ const CELL_INPUT_ARTIFACT_KEYS = [
5
+ "firstFrameArtifactId",
6
+ "lastFrameArtifactId",
7
+ "providerArtifactId",
8
+ ];
9
+ function isString(value) {
10
+ return typeof value === "string" && value.length > 0;
11
+ }
12
+ function inputVersionIds(inputs) {
13
+ return Array.isArray(inputs.outputVersionArtifactIds)
14
+ ? inputs.outputVersionArtifactIds.filter(isString)
15
+ : [];
16
+ }
17
+ function cellHoldsArtifact(cell, artifactId) {
18
+ const inputs = cell.inputs ?? {};
19
+ return cell.outputArtifactId === artifactId
20
+ || (cell.references ?? []).some((reference) => reference.targetId === artifactId)
21
+ || (cell.pinnedResults ?? []).some((pinned) => pinned.artifactId === artifactId)
22
+ || CELL_INPUT_ARTIFACT_KEYS.some((key) => inputs[key] === artifactId)
23
+ || inputVersionIds(inputs).includes(artifactId);
24
+ }
25
+ function detachedInputs(cell, artifactId) {
26
+ const inputs = { ...(cell.inputs ?? {}) };
27
+ for (const key of CELL_INPUT_ARTIFACT_KEYS) {
28
+ if (inputs[key] === artifactId)
29
+ delete inputs[key];
30
+ }
31
+ if (Array.isArray(inputs.outputVersionArtifactIds)) {
32
+ inputs.outputVersionArtifactIds = inputVersionIds(inputs)
33
+ .filter((id) => id !== artifactId);
34
+ }
35
+ if (cell.outputArtifactId === artifactId)
36
+ inputs.state = "ready";
37
+ return inputs;
38
+ }
39
+ function detachedCell(cell, artifactId) {
40
+ const references = (cell.references ?? [])
41
+ .filter((reference) => reference.targetId !== artifactId);
42
+ const pinnedResults = (cell.pinnedResults ?? [])
43
+ .filter((pinned) => pinned.artifactId !== artifactId);
44
+ return {
45
+ ...cell,
46
+ ...(cell.outputArtifactId === artifactId ? { outputArtifactId: undefined } : {}),
47
+ inputs: detachedInputs(cell, artifactId),
48
+ ...(cell.references ? { references } : {}),
49
+ ...(cell.pinnedResults ? { pinnedResults } : {}),
50
+ };
51
+ }
52
+ /** A cleared cell reads as never run, so a re-run refills it. */
53
+ function detachedExecution(document, cellIds) {
54
+ const execution = { ...(document.execution ?? {}) };
55
+ for (const cellId of cellIds) {
56
+ const current = execution[cellId];
57
+ if (!current || typeof current !== "object")
58
+ continue;
59
+ execution[cellId] = {
60
+ ...current,
61
+ status: "ready",
62
+ stale: false,
63
+ outputArtifactId: undefined,
64
+ providerArtifactId: undefined,
65
+ error: undefined,
66
+ };
67
+ }
68
+ return execution;
69
+ }
70
+ function detachedDocument(document, artifactId) {
71
+ const cellIds = document.cells
72
+ .filter((cell) => cellHoldsArtifact(cell, artifactId))
73
+ .map((cell) => cell.id);
74
+ if (cellIds.length === 0)
75
+ return { document, cellIds };
76
+ const detached = {
77
+ ...document,
78
+ cells: document.cells.map((cell) => cellIds.includes(cell.id) ? detachedCell(cell, artifactId) : cell),
79
+ execution: detachedExecution(document, cellIds),
80
+ };
81
+ return {
82
+ document: cellIds.reduce((next, cellId) => markDescendantsStale(next, cellId), detached),
83
+ cellIds,
84
+ };
85
+ }
86
+ async function detachFromNotebooks(engine, artifactId) {
87
+ let cleared = 0;
88
+ for (const notebook of engine.notebooks.list()) {
89
+ if (!notebook.cells.some((cell) => cellHoldsArtifact(cell, artifactId))) {
90
+ continue;
91
+ }
92
+ let cellIds = [];
93
+ const result = await mutateNotebook(engine, notebook.id, "forget_asset", (document) => {
94
+ const detached = detachedDocument(document, artifactId);
95
+ cellIds = detached.cellIds;
96
+ return detached.document;
97
+ }, { artifactId }, { artifactId });
98
+ if (!result.ok)
99
+ throw new Error(result.error.message);
100
+ cleared += cellIds.length;
101
+ }
102
+ return cleared;
103
+ }
104
+ function referencesOf(details) {
105
+ const references = details?.references;
106
+ if (!Array.isArray(references))
107
+ return [];
108
+ return references.flatMap((entry) => {
109
+ if (typeof entry !== "object" || entry === null)
110
+ return [];
111
+ const row = entry;
112
+ return isString(row.kind) && isString(row.id)
113
+ ? [{ kind: row.kind, id: row.id }]
114
+ : [];
115
+ });
116
+ }
117
+ function remainingForgetReferences(engine, artifactId) {
118
+ const references = engine.artifacts.deletionReferences(artifactId, { deleteOwnedMedia: true });
119
+ if (!references.ok)
120
+ throw new Error(references.error.message);
121
+ const clearable = new Set(engine.sequences.getPrimary().clips
122
+ .filter((clip) => clip.source.artifactId === artifactId).map((clip) => `sequenceClip:${clip.clipId}`));
123
+ for (const notebook of engine.notebooks.list()) {
124
+ for (const cell of notebook.cells) {
125
+ const cellId = `${notebook.id}/${cell.id}`;
126
+ if (cell.outputArtifactId === artifactId)
127
+ clearable.add(`cell.outputArtifact:${cellId}`);
128
+ for (const reference of cell.references ?? []) {
129
+ if (reference.targetId === artifactId)
130
+ clearable.add(`cell.reference:${cellId}/${reference.id}`);
131
+ }
132
+ for (const result of cell.pinnedResults ?? []) {
133
+ if (result.artifactId === artifactId)
134
+ clearable.add(`pinnedSearchResult:${cellId}/${result.id}`);
135
+ }
136
+ }
137
+ }
138
+ return references.value.filter((reference) => !clearable.has(`${reference.kind}:${reference.id}`));
139
+ }
140
+ function inUseOutcome(artifactId, label, references, clearedCells = 0) {
141
+ return { status: "in_use", artifactId, label, references, clearedCells,
142
+ forgottenObjects: 0, sharedObjects: 0, reclaimedBytes: 0 };
143
+ }
144
+ /**
145
+ * Deletes an artifact and destroys the bytes behind it.
146
+ *
147
+ * `engine.artifacts.delete` clears the catalog rows and the workspace copy,
148
+ * but the content objects survive — that is what makes a duplicate import
149
+ * free and an old revision restorable. Forgetting finishes the job through
150
+ * `storage.deleteObject`, which is left unforced on purpose: an object another
151
+ * artifact still references is kept, so forgetting one copy of shared bytes
152
+ * never blanks the other.
153
+ */
154
+ export async function forgetAsset(engine, input) {
155
+ const artifact = engine.artifacts.get(input.assetId);
156
+ if (!artifact.ok)
157
+ throw new Error(artifact.error.message);
158
+ const artifactId = artifact.value.artifactId;
159
+ const label = artifact.value.label ?? null;
160
+ if (input.clearReferences) {
161
+ const blockers = remainingForgetReferences(engine, artifactId);
162
+ if (blockers.length > 0)
163
+ return inUseOutcome(artifactId, label, blockers);
164
+ const timeline = await removeArtifactFromTimeline(engine, artifactId, { dryRun: true });
165
+ if (timeline.error)
166
+ throw new Error(timeline.error);
167
+ }
168
+ const manifest = await engine.files.manifest(artifactId, { includeDotfiles: true });
169
+ const hashes = [...new Set(manifest.ok ? manifest.value.files.map((file) => file.objectHash) : [])];
170
+ const clearedCells = input.clearReferences
171
+ ? await detachFromNotebooks(engine, artifactId)
172
+ : 0;
173
+ if (input.clearReferences) {
174
+ const timeline = await removeArtifactFromTimeline(engine, artifactId);
175
+ if (timeline.error)
176
+ throw new Error(timeline.error);
177
+ }
178
+ const deleted = await engine.artifacts.delete(artifactId, {
179
+ deleteOwnedMedia: true,
180
+ });
181
+ if (!deleted.ok) {
182
+ if (deleted.error.code !== "IN_USE")
183
+ throw new Error(deleted.error.message);
184
+ return inUseOutcome(artifactId, label, referencesOf(deleted.error.details), clearedCells);
185
+ }
186
+ let forgottenObjects = 0;
187
+ let sharedObjects = 0;
188
+ let reclaimedBytes = 0;
189
+ for (const hash of hashes) {
190
+ const result = await engine.storage.deleteObject(hash);
191
+ if (result.ok) {
192
+ forgottenObjects += 1;
193
+ if (result.value.deletedLocal)
194
+ reclaimedBytes += result.value.sizeBytes;
195
+ continue;
196
+ }
197
+ // Bytes another artifact stores, or an object row that never existed.
198
+ if (result.error.code === "IN_USE") {
199
+ sharedObjects += 1;
200
+ continue;
201
+ }
202
+ if (result.error.code === "NOT_FOUND")
203
+ continue;
204
+ throw new Error(result.error.message);
205
+ }
206
+ return {
207
+ status: "forgotten",
208
+ artifactId,
209
+ label,
210
+ references: [],
211
+ clearedCells,
212
+ forgottenObjects,
213
+ sharedObjects,
214
+ reclaimedBytes,
215
+ };
216
+ }