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
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,77 @@
1
+ import { accessSync, chmodSync, closeSync, constants, openSync, readSync, statSync } 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
+ export function ensureExecutable(path) {
44
+ try {
45
+ accessSync(path, constants.X_OK);
46
+ return true;
47
+ }
48
+ catch {
49
+ try {
50
+ chmodSync(path, statSync(path).mode | 0o111);
51
+ accessSync(path, constants.X_OK);
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ }
59
+ export function resolveFfprobePath(bundledPath, override, hostArch) {
60
+ if (override)
61
+ return override;
62
+ if (!bundledPath)
63
+ return "ffprobe";
64
+ const header = readBinaryHeader(bundledPath);
65
+ return header && binaryRunsOnHost(header, hostArch) ? bundledPath : "ffprobe";
66
+ }
67
+ const require = createRequire(import.meta.url);
68
+ export function bundledFfprobe() {
69
+ try {
70
+ const { path } = require("@ffprobe-installer/ffprobe");
71
+ return ensureExecutable(path) ? path : null;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ export const FFPROBE = resolveFfprobePath(bundledFfprobe(), process.env.VIDEOBOOK_FFPROBE_PATH, process.arch);