videobook 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
@@ -0,0 +1,757 @@
1
+ import { mkdtemp, rm, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { findVideoFile, } from "videobook-engine";
5
+ import { notebookGridTag } from "videobook-engine/notebook-grid";
6
+ import { FFMPEG, runFfmpeg } from "./ffmpeg.js";
7
+ import { formatTimestamp, generateTimestamps } from "./frame-utils.js";
8
+ import { execFile } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ const execFileAsync = promisify(execFile);
11
+ import { notebookCellFingerprint, runNotebookCell, topologicalOrder, } from "./jobs/run-notebook.js";
12
+ import { normalizeSceneVideo } from "./notebook-media-ops.js";
13
+ import { applyAnalysisResults, buildSevenStageScaffold, DEFAULT_ANALYSIS_PROMPT, MULTYCULTY_FEATURES, } from "./notebook-workflow.js";
14
+ import { sampleNotebookAnalysisFrames } from "./notebook-live-analysis.js";
15
+ import { MULTYCULTY_FIXTURE_OWNER } from "./notebook-fixtures.js";
16
+ import { ANALYSIS_REASONING_EFFORT, INTELLIGENCE_MODEL, } from "./openai-intelligence.js";
17
+ export const MULTYCULTY_FIXTURE_VERSION = 27;
18
+ const MULTYCULTY_FIXTURE_ANALYSIS = {
19
+ shots: [
20
+ {
21
+ startSeconds: 0,
22
+ endSeconds: 15,
23
+ label: "intro",
24
+ prompt: "Opening presenter shot establishing the discussion.",
25
+ },
26
+ {
27
+ startSeconds: 15,
28
+ endSeconds: 30,
29
+ label: "culture",
30
+ prompt: "The camera switches for the follow-up discussion beat.",
31
+ },
32
+ ],
33
+ };
34
+ const MULTYCULTY_FIXTURE_LLM_EXCHANGES = [{
35
+ provider: "openai",
36
+ model: INTELLIGENCE_MODEL,
37
+ stage: "notebook_shot_labels",
38
+ input: {
39
+ model: INTELLIGENCE_MODEL,
40
+ reasoning: { effort: ANALYSIS_REASONING_EFFORT },
41
+ store: false,
42
+ instructions: "Return one concise label and prompt for every supplied shot.",
43
+ input: [{
44
+ role: "user",
45
+ content: [{
46
+ type: "input_text",
47
+ text: [
48
+ DEFAULT_ANALYSIS_PROMPT,
49
+ "Source duration: 30 seconds.",
50
+ "Verified boundaries: 0 seconds, 15 seconds, 30 seconds.",
51
+ "Shot 1 opens on the presenter; shot 2 is the follow-up discussion beat.",
52
+ ].join("\n"),
53
+ }],
54
+ }],
55
+ },
56
+ response: {
57
+ id: "resp_multyculty_analysis_fixture",
58
+ object: "response",
59
+ created_at: 1,
60
+ status: "completed",
61
+ model: INTELLIGENCE_MODEL,
62
+ output: [{
63
+ type: "message",
64
+ id: "msg_multyculty_analysis_fixture",
65
+ role: "assistant",
66
+ status: "completed",
67
+ content: [{
68
+ type: "output_text",
69
+ text: JSON.stringify(MULTYCULTY_FIXTURE_ANALYSIS),
70
+ annotations: [],
71
+ }],
72
+ }],
73
+ },
74
+ }];
75
+ const MULTYCULTY_FIXTURE_EVIDENCE = {
76
+ visualCandidates: [{
77
+ id: "visual-001",
78
+ timestampSeconds: 15,
79
+ score: 42,
80
+ frameTimestamps: [14.5, 14.9, 15.1, 15.5],
81
+ }],
82
+ visualDecisions: [{
83
+ candidateId: "visual-001",
84
+ accepted: true,
85
+ cameraSwitch: true,
86
+ transitionType: "hard_cut",
87
+ confidence: 0.98,
88
+ rechecked: false,
89
+ }],
90
+ audioCandidates: [],
91
+ selectedAudioCandidateIds: [],
92
+ transcript: {
93
+ included: false,
94
+ timed: false,
95
+ },
96
+ };
97
+ const MULTYCULTY_FIXTURE_BOUNDARIES = [
98
+ {
99
+ id: "boundary-source-start",
100
+ timestampSeconds: 0,
101
+ kind: "source_start",
102
+ confidence: 1,
103
+ evidenceIds: [],
104
+ },
105
+ {
106
+ id: "boundary-camera-001",
107
+ timestampSeconds: 15,
108
+ kind: "camera_cut",
109
+ confidence: 0.98,
110
+ evidenceIds: ["visual-001"],
111
+ },
112
+ {
113
+ id: "boundary-source-end",
114
+ timestampSeconds: 30,
115
+ kind: "source_end",
116
+ confidence: 1,
117
+ evidenceIds: [],
118
+ },
119
+ ];
120
+ const SOURCE_SELECTION_LABEL = "Source selection";
121
+ const FINAL_LABEL = "Final";
122
+ const SECTION_LABELS = [
123
+ "Intro section video",
124
+ "Culture section video",
125
+ "German section video",
126
+ "Dunka section video",
127
+ ];
128
+ const ANIMATION_LABELS = [
129
+ "Intro animated video",
130
+ "Culture animated video",
131
+ ];
132
+ const COMPACT_FIXTURE_SLOTS = new Map([
133
+ ["source", { row: 0, column: 3 }],
134
+ ["analysis", { row: 1, column: 2 }],
135
+ ["source-extract-audio", { row: 1, column: 4 }],
136
+ ["analysis-output", { row: 2, column: 2 }],
137
+ ["split-video-1", { row: 3, column: 1 }],
138
+ ["split-video-2", { row: 3, column: 5 }],
139
+ ["scene-video-1", { row: 4, column: 1 }],
140
+ ["scene-video-2", { row: 4, column: 5 }],
141
+ ["frame-first-1", { row: 5, column: 0 }],
142
+ ["frame-last-1", { row: 5, column: 1 }],
143
+ ["scene-extract-audio-1", { row: 5, column: 2 }],
144
+ ["frame-first-2", { row: 5, column: 4 }],
145
+ ["frame-last-2", { row: 5, column: 5 }],
146
+ ["scene-extract-audio-2", { row: 5, column: 6 }],
147
+ ["prompt-1", { row: 6, column: 1 }],
148
+ ["prompt-2", { row: 6, column: 5 }],
149
+ ["animate-1", { row: 7, column: 1 }],
150
+ ["animate-2", { row: 7, column: 5 }],
151
+ ["assembly", { row: 8, column: 3 }],
152
+ ]);
153
+ const COMPACT_FIXTURE_ROLES = new Set(COMPACT_FIXTURE_SLOTS.keys());
154
+ function requiredArtifact(artifacts, label) {
155
+ const artifact = artifacts.find((candidate) => candidate.label === label);
156
+ if (!artifact)
157
+ throw new Error(`Multyculty fixture artifact is missing: ${label}`);
158
+ return artifact;
159
+ }
160
+ async function fixtureAnalysisFramesReady(engine, sourceArtifactId) {
161
+ const listed = engine.files.listSubdir(sourceArtifactId, "original_frames");
162
+ if (!listed.ok)
163
+ return false;
164
+ const frame = sampleNotebookAnalysisFrames(listed.value, 2)[0];
165
+ if (!frame)
166
+ return false;
167
+ const read = await engine.files.read(sourceArtifactId, `original_frames/${frame.name}`);
168
+ return read.ok && read.value.length > 0;
169
+ }
170
+ async function ensureFixtureAnalysisFrames(engine, sourceArtifactId, durationSeconds) {
171
+ if (await fixtureAnalysisFramesReady(engine, sourceArtifactId))
172
+ return false;
173
+ const manifest = await engine.files.manifest(sourceArtifactId);
174
+ if (!manifest.ok)
175
+ throw new Error(manifest.error.message);
176
+ const video = findVideoFile(manifest.value.files);
177
+ if (!video) {
178
+ throw new Error(`Multyculty source has no video file: ${sourceArtifactId}`);
179
+ }
180
+ const workspace = await mkdtemp(join(tmpdir(), "videobook-multyculty-frames-"));
181
+ let lastError;
182
+ try {
183
+ for (const timestamp of generateTimestamps(durationSeconds)) {
184
+ const name = `${formatTimestamp(timestamp)}.jpg`;
185
+ const framePath = join(workspace, name);
186
+ try {
187
+ await runFfmpeg([
188
+ "-y",
189
+ "-ss", String(timestamp),
190
+ "-i", join(manifest.value.path, video.name),
191
+ "-frames:v", "1",
192
+ "-q:v", "2",
193
+ framePath,
194
+ ]);
195
+ }
196
+ catch (error) {
197
+ lastError = error;
198
+ continue;
199
+ }
200
+ const output = await stat(framePath).catch(() => null);
201
+ if (!output || output.size === 0) {
202
+ lastError = new Error(`No frame at ${timestamp} seconds`);
203
+ continue;
204
+ }
205
+ const written = await engine.files.writeFromPath(sourceArtifactId, `original_frames/${name}`, framePath);
206
+ if (!written.ok)
207
+ throw new Error(written.error.message);
208
+ }
209
+ }
210
+ finally {
211
+ await rm(workspace, { recursive: true, force: true });
212
+ }
213
+ if (!await fixtureAnalysisFramesReady(engine, sourceArtifactId)) {
214
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
215
+ throw new Error(`Multyculty source frame extraction failed${detail}`);
216
+ }
217
+ return true;
218
+ }
219
+ function roleOf(cell) {
220
+ return typeof cell.inputs?.workflowRole === "string"
221
+ ? cell.inputs.workflowRole
222
+ : null;
223
+ }
224
+ function isCanonicalRole(role) {
225
+ return role === "source"
226
+ || role === "analysis"
227
+ || role === "analysis-output"
228
+ || role === "source-extract-audio"
229
+ || role === "assembly"
230
+ || /^split-video-\d+$/.test(role ?? "")
231
+ || /^scene-video-\d+$/.test(role ?? "")
232
+ || /^frame-(first|last)-\d+$/.test(role ?? "")
233
+ || /^scene-extract-audio-\d+$/.test(role ?? "")
234
+ || /^scene-audio-\d+$/.test(role ?? "")
235
+ || /^prompt-\d+$/.test(role ?? "")
236
+ || /^animate-\d+$/.test(role ?? "");
237
+ }
238
+ function fixtureRecord(document) {
239
+ const raw = document.fixture;
240
+ return raw && typeof raw === "object" && !Array.isArray(raw)
241
+ ? raw
242
+ : null;
243
+ }
244
+ function canonicalFixtureCells(document) {
245
+ return document.cells.filter((cell) => cell.inputs?.fixtureOwner === MULTYCULTY_FIXTURE_OWNER);
246
+ }
247
+ function withoutRunnableAnalysisFixture(document) {
248
+ let changed = false;
249
+ const cells = document.cells.map((cell) => {
250
+ if (roleOf(cell) !== "analysis"
251
+ || cell.inputs?.fixtureOwner !== MULTYCULTY_FIXTURE_OWNER
252
+ || (cell.inputs.fixtureAnalysis === undefined
253
+ && cell.inputs.analysisFixture === undefined)) {
254
+ return cell;
255
+ }
256
+ const inputs = { ...cell.inputs };
257
+ delete inputs.fixtureAnalysis;
258
+ delete inputs.analysisFixture;
259
+ changed = true;
260
+ return { ...cell, inputs };
261
+ });
262
+ return changed ? { ...document, cells } : document;
263
+ }
264
+ function isCurrentFixture(document) {
265
+ const fixture = fixtureRecord(document);
266
+ const cells = canonicalFixtureCells(document);
267
+ const roles = new Set(cells.map(roleOf));
268
+ const cellIds = new Set(cells.map((cell) => cell.id));
269
+ const edges = document.edges.filter((edge) => cellIds.has(edge.source) && cellIds.has(edge.target));
270
+ return fixture?.version === MULTYCULTY_FIXTURE_VERSION
271
+ && cells.length === 19
272
+ && roles.size === 19
273
+ && edges.length === 34
274
+ && roles.has("source")
275
+ && roles.has("analysis")
276
+ && roles.has("analysis-output")
277
+ && roles.has("source-extract-audio")
278
+ && roles.has("assembly")
279
+ && [1, 2].every((ordinal) => roles.has(`split-video-${ordinal}`)
280
+ && roles.has(`scene-video-${ordinal}`)
281
+ && roles.has(`frame-first-${ordinal}`)
282
+ && roles.has(`frame-last-${ordinal}`)
283
+ && roles.has(`scene-extract-audio-${ordinal}`)
284
+ && roles.has(`prompt-${ordinal}`)
285
+ && roles.has(`animate-${ordinal}`));
286
+ }
287
+ function ordinalForRole(cell) {
288
+ const match = roleOf(cell)?.match(/-(\d+)$/);
289
+ return match ? Number(match[1]) : null;
290
+ }
291
+ function compactFixture(document) {
292
+ const cells = document.cells
293
+ .filter((cell) => COMPACT_FIXTURE_ROLES.has(roleOf(cell) ?? ""))
294
+ .map((cell) => {
295
+ const role = roleOf(cell);
296
+ const slot = role ? COMPACT_FIXTURE_SLOTS.get(role) : undefined;
297
+ if (!slot)
298
+ return cell;
299
+ return {
300
+ ...cell,
301
+ slot,
302
+ ...(role === "assembly" ? { label: "Concat scenes" } : {}),
303
+ };
304
+ });
305
+ const cellIds = new Set(cells.map((cell) => cell.id));
306
+ return {
307
+ ...document,
308
+ cells,
309
+ edges: document.edges.filter((edge) => cellIds.has(edge.source) && cellIds.has(edge.target)),
310
+ };
311
+ }
312
+ async function promptFixtureArtifact(engine, cell) {
313
+ const artifact = await engine.artifacts.create({
314
+ kind: "script",
315
+ label: `${cell.label ?? notebookGridTag(cell.slot)} resolved prompt`,
316
+ });
317
+ if (!artifact.ok)
318
+ throw new Error(artifact.error.message);
319
+ const result = {
320
+ version: 1,
321
+ source: "multyculty-precomputed-fixture",
322
+ originalPrompt: cell.prompt ?? "",
323
+ resolvedPrompt: cell.prompt ?? "",
324
+ featureId: cell.inputs?.featureId ?? null,
325
+ featureBrief: cell.inputs?.featureBrief ?? null,
326
+ shotContext: cell.inputs?.shotContext ?? null,
327
+ audioContext: cell.inputs?.audioContext ?? null,
328
+ };
329
+ const written = await engine.files.write(artifact.value.artifactId, "prompt.json", JSON.stringify(result, null, 2));
330
+ if (!written.ok)
331
+ throw new Error(written.error.message);
332
+ return artifact.value.artifactId;
333
+ }
334
+ async function attachFixtureOutputs(engine, document, artifacts) {
335
+ const sourceArtifact = requiredArtifact(artifacts, SOURCE_SELECTION_LABEL);
336
+ const promptOutputs = new Map();
337
+ for (const cell of document.cells) {
338
+ if (roleOf(cell)?.startsWith("prompt-") && !cell.outputArtifactId) {
339
+ promptOutputs.set(cell.id, await promptFixtureArtifact(engine, cell));
340
+ }
341
+ }
342
+ const finalArtifact = artifacts.find((artifact) => artifact.label === FINAL_LABEL);
343
+ return {
344
+ ...document,
345
+ cells: document.cells.map((cell) => {
346
+ const role = roleOf(cell);
347
+ if (!role || !COMPACT_FIXTURE_ROLES.has(role))
348
+ return cell;
349
+ const ordinal = ordinalForRole(cell);
350
+ const feature = ordinal ? MULTYCULTY_FEATURES[ordinal - 1] : undefined;
351
+ const section = ordinal
352
+ ? artifacts.find((artifact) => artifact.label === SECTION_LABELS[ordinal - 1])
353
+ : undefined;
354
+ const animation = ordinal && ordinal <= ANIMATION_LABELS.length
355
+ ? artifacts.find((artifact) => artifact.label === ANIMATION_LABELS[ordinal - 1])
356
+ : undefined;
357
+ const promptOutput = promptOutputs.get(cell.id);
358
+ const outputArtifactId = role === "source"
359
+ ? sourceArtifact.artifactId
360
+ : role?.startsWith("scene-video-")
361
+ ? cell.outputArtifactId ?? section?.artifactId
362
+ : role.startsWith("animate-")
363
+ ? cell.outputArtifactId ?? animation?.artifactId
364
+ : role.startsWith("prompt-")
365
+ ? cell.outputArtifactId ?? promptOutput
366
+ : role === "assembly"
367
+ ? cell.outputArtifactId ?? finalArtifact?.artifactId
368
+ : cell.outputArtifactId;
369
+ const completed = Boolean(outputArtifactId);
370
+ return {
371
+ ...cell,
372
+ ...(outputArtifactId ? { outputArtifactId } : {}),
373
+ inputs: {
374
+ ...(cell.inputs ?? {}),
375
+ fixtureOwner: MULTYCULTY_FIXTURE_OWNER,
376
+ ...(feature
377
+ ? {
378
+ featureId: feature.id,
379
+ featureLabel: feature.label,
380
+ featureCaption: feature.caption,
381
+ }
382
+ : {}),
383
+ ...(role.startsWith("animate-")
384
+ ? {
385
+ fixtureBaseline: true,
386
+ requiresConfirmation: true,
387
+ }
388
+ : {}),
389
+ ...(role.startsWith("prompt-")
390
+ ? { resolvedPrompt: cell.prompt ?? "" }
391
+ : {}),
392
+ state: completed ? "completed" : "pending",
393
+ },
394
+ };
395
+ }),
396
+ };
397
+ }
398
+ function fixtureExecution(document) {
399
+ const execution = { ...(document.execution ?? {}) };
400
+ const completedAt = new Date().toISOString();
401
+ for (const cell of canonicalFixtureCells(document)) {
402
+ if (!cell.outputArtifactId)
403
+ continue;
404
+ execution[cell.id] = {
405
+ status: "completed",
406
+ outputArtifactId: cell.outputArtifactId,
407
+ completedAt,
408
+ fixtureBaseline: true,
409
+ };
410
+ }
411
+ return execution;
412
+ }
413
+ async function writeNotebook(engine, document) {
414
+ const written = await engine.notebooks.write(document);
415
+ if (!written.ok)
416
+ throw new Error(written.error.message);
417
+ const read = engine.notebooks.read(document.id);
418
+ if (!read.ok)
419
+ throw new Error(read.error.message);
420
+ return read.value;
421
+ }
422
+ async function runFixtureRole(engine, document, role) {
423
+ const cell = document.cells.find((candidate) => roleOf(candidate) === role);
424
+ if (!cell)
425
+ throw new Error(`Multyculty fixture cell is missing: ${role}`);
426
+ const result = await runNotebookCell(engine, engine.book.get().bookId, document.id, document, cell);
427
+ return result.document;
428
+ }
429
+ async function materializeLocalOutputs(engine, document) {
430
+ let current = document;
431
+ for (const ordered of topologicalOrder(current)) {
432
+ if (ordered.inputs?.fixtureOwner !== MULTYCULTY_FIXTURE_OWNER
433
+ || (ordered.operation !== "split_range"
434
+ && ordered.operation !== "extract_audio"
435
+ && ordered.inputs?.mediaRole !== "extract-audio-output")) {
436
+ continue;
437
+ }
438
+ const cell = current.cells.find((candidate) => candidate.id === ordered.id);
439
+ if (!cell)
440
+ throw new Error(`Multyculty fixture cell disappeared: ${ordered.id}`);
441
+ const result = await runNotebookCell(engine, engine.book.get().bookId, current.id, current, cell);
442
+ current = result.document;
443
+ }
444
+ current = await attachFixtureOutputs(engine, current, engine.artifacts.list());
445
+ for (const feature of MULTYCULTY_FEATURES.slice(0, 2)) {
446
+ const animation = current.cells.find((cell) => roleOf(cell) === `animate-${feature.ordinal}`);
447
+ const audio = current.cells.find((cell) => roleOf(cell) === `scene-extract-audio-${feature.ordinal}`);
448
+ if (!animation?.outputArtifactId || !audio?.outputArtifactId) {
449
+ throw new Error(`Multyculty feature ${feature.ordinal} is missing baseline media`);
450
+ }
451
+ const normalized = await normalizeSceneVideo(engine, animation.outputArtifactId, audio.outputArtifactId, `${animation.label ?? notebookGridTag(animation.slot)} normalized fixture`, 15);
452
+ current = await writeNotebook(engine, {
453
+ ...current,
454
+ cells: current.cells.map((cell) => cell.id === animation.id
455
+ ? {
456
+ ...cell,
457
+ outputArtifactId: normalized.artifactId,
458
+ inputs: {
459
+ ...(cell.inputs ?? {}),
460
+ normalizedFromArtifactId: animation.outputArtifactId,
461
+ normalizedDurationSeconds: normalized.durationSeconds,
462
+ state: "completed",
463
+ },
464
+ }
465
+ : cell),
466
+ });
467
+ }
468
+ current = await runFixtureRole(engine, current, "assembly");
469
+ const completedAt = new Date().toISOString();
470
+ const completedCells = current.cells.map((cell) => cell.inputs?.fixtureOwner === MULTYCULTY_FIXTURE_OWNER
471
+ && cell.outputArtifactId
472
+ ? {
473
+ ...cell,
474
+ inputs: {
475
+ ...(cell.inputs ?? {}),
476
+ state: "completed",
477
+ },
478
+ }
479
+ : cell);
480
+ const completedDocument = { ...current, cells: completedCells };
481
+ const execution = {
482
+ ...fixtureExecution(completedDocument),
483
+ };
484
+ for (const cell of completedCells) {
485
+ if (cell.inputs?.fixtureOwner !== MULTYCULTY_FIXTURE_OWNER
486
+ || !cell.outputArtifactId) {
487
+ continue;
488
+ }
489
+ execution[cell.id] = {
490
+ status: "completed",
491
+ fingerprint: notebookCellFingerprint(completedDocument, cell),
492
+ outputArtifactId: cell.outputArtifactId,
493
+ completedAt,
494
+ updatedAt: completedAt,
495
+ fixtureBaseline: true,
496
+ };
497
+ }
498
+ const fixture = fixtureRecord(completedDocument) ?? {};
499
+ return writeNotebook(engine, {
500
+ ...completedDocument,
501
+ execution,
502
+ lifecycleState: "finished",
503
+ fixture: {
504
+ ...fixture,
505
+ materializedAt: completedAt,
506
+ noPaidCalls: true,
507
+ },
508
+ });
509
+ }
510
+ /** Create the source media + artifacts a clean multyculty book needs before seed. */
511
+ export async function bootstrapMultycultySource(engine) {
512
+ if (engine.book.get().name !== "multyculty") {
513
+ throw new Error("The Multyculty workflow can only seed the multyculty book");
514
+ }
515
+ if (engine.notebooks.list().length === 0) {
516
+ const notebook = await engine.notebooks.create("Main");
517
+ if (!notebook.ok)
518
+ throw new Error(notebook.error.message);
519
+ }
520
+ const artifactSpecs = [
521
+ ["video", SOURCE_SELECTION_LABEL],
522
+ ["video", SECTION_LABELS[0]],
523
+ ["video", SECTION_LABELS[1]],
524
+ ["video", ANIMATION_LABELS[0]],
525
+ ["video", ANIMATION_LABELS[1]],
526
+ ["final", FINAL_LABEL],
527
+ ];
528
+ const existing = new Map(engine.artifacts.list().flatMap((artifact) => artifact.label
529
+ ? [[artifact.label, artifact.artifactId]]
530
+ : []));
531
+ const artifacts = new Map(existing);
532
+ for (const [kind, label] of artifactSpecs) {
533
+ if (artifacts.has(label))
534
+ continue;
535
+ const created = await engine.artifacts.create(kind, label);
536
+ if (!created.ok)
537
+ throw new Error(created.error.message);
538
+ artifacts.set(label, created.value.artifactId);
539
+ }
540
+ const sourceId = artifacts.get(SOURCE_SELECTION_LABEL);
541
+ if (!sourceId) {
542
+ throw new Error(`Missing the ${SOURCE_SELECTION_LABEL} artifact`);
543
+ }
544
+ const sourceManifest = await engine.files.manifest(sourceId);
545
+ const hasOriginal = sourceManifest.ok
546
+ && sourceManifest.value.files.some((file) => file.name === "original.mp4");
547
+ if (!hasOriginal) {
548
+ const mediaDir = await mkdtemp(join(tmpdir(), "videobook-multyculty-media-"));
549
+ try {
550
+ const sourcePath = join(mediaDir, "source.mp4");
551
+ await execFileAsync(FFMPEG, [
552
+ "-y",
553
+ "-f", "lavfi", "-i", "color=c=0x10261b:s=160x90:r=6:d=15",
554
+ "-f", "lavfi", "-i", "color=c=0x274b3a:s=160x90:r=6:d=15",
555
+ "-f", "lavfi", "-i", "sine=frequency=440:sample_rate=48000:duration=30",
556
+ "-filter_complex", "[0:v][1:v]concat=n=2:v=1:a=0[video]",
557
+ "-map", "[video]",
558
+ "-map", "2:a",
559
+ "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p",
560
+ "-c:a", "aac", "-shortest",
561
+ sourcePath,
562
+ ]);
563
+ const file = await engine.files.writeFromPath(sourceId, "original.mp4", sourcePath);
564
+ if (!file.ok)
565
+ throw new Error(file.error.message);
566
+ for (const label of ANIMATION_LABELS) {
567
+ const artifactId = artifacts.get(label);
568
+ if (!artifactId)
569
+ continue;
570
+ const animationManifest = await engine.files.manifest(artifactId);
571
+ const animationHasOriginal = animationManifest.ok
572
+ && animationManifest.value.files.some((entry) => entry.name === "original.mp4");
573
+ if (animationHasOriginal)
574
+ continue;
575
+ const animationFile = await engine.files.writeFromPath(artifactId, "original.mp4", sourcePath);
576
+ if (!animationFile.ok)
577
+ throw new Error(animationFile.error.message);
578
+ }
579
+ }
580
+ finally {
581
+ await rm(mediaDir, { recursive: true, force: true });
582
+ }
583
+ }
584
+ const manifest = await engine.files.manifest(sourceId);
585
+ if (!manifest.ok)
586
+ throw new Error(manifest.error.message);
587
+ const objectHash = manifest.value.files.find((entry) => entry.name === "original.mp4")?.objectHash
588
+ ?? manifest.value.files[0]?.objectHash;
589
+ if (!objectHash)
590
+ throw new Error("Source media missing object hash");
591
+ const streams = engine.streams.list(sourceId);
592
+ const hasAudio = streams.ok
593
+ && streams.value.some((stream) => stream.kind === "audio");
594
+ if (!hasAudio) {
595
+ const stream = await engine.streams.register({
596
+ artifactId: sourceId,
597
+ sourcePath: "original.mp4",
598
+ objectHash,
599
+ streamIndex: 1,
600
+ kind: "audio",
601
+ timeBase: { numerator: 1, denominator: 48_000 },
602
+ durationTicks: 1_440_000,
603
+ codec: "aac",
604
+ audio: {
605
+ sampleRateHz: 48_000,
606
+ channels: 2,
607
+ channelLayout: "stereo",
608
+ },
609
+ });
610
+ if (!stream.ok)
611
+ throw new Error(stream.error.message);
612
+ }
613
+ }
614
+ export async function seedMultycultyWorkflow(engine, options = {}) {
615
+ if (engine.book.get().name !== "multyculty") {
616
+ throw new Error("The Multyculty workflow can only seed the multyculty book");
617
+ }
618
+ await bootstrapMultycultySource(engine);
619
+ const notebook = engine.notebooks.list().find((candidate) => candidate.name.toLowerCase() === "multyculty")
620
+ ?? engine.notebooks.list()[0];
621
+ if (!notebook)
622
+ throw new Error("The Multyculty book has no notebook");
623
+ const artifacts = engine.artifacts.list();
624
+ const sourceArtifact = requiredArtifact(artifacts, SOURCE_SELECTION_LABEL);
625
+ await ensureFixtureAnalysisFrames(engine, sourceArtifact.artifactId, 30);
626
+ if (isCurrentFixture(notebook)) {
627
+ const runnable = withoutRunnableAnalysisFixture(notebook);
628
+ const persisted = runnable === notebook
629
+ ? notebook
630
+ : await writeNotebook(engine, runnable);
631
+ const fixture = fixtureRecord(persisted);
632
+ if (options.materializeLocalOutputs
633
+ && typeof fixture?.materializedAt !== "string") {
634
+ return materializeLocalOutputs(engine, persisted);
635
+ }
636
+ return persisted;
637
+ }
638
+ const streams = engine.streams.list(sourceArtifact.artifactId);
639
+ if (!streams.ok)
640
+ throw new Error(streams.error.message);
641
+ const sourceAudio = streams.value.find((stream) => stream.kind === "audio");
642
+ if (!sourceAudio)
643
+ throw new Error("The Multyculty source has no audio stream");
644
+ const previousRevision = engine.head;
645
+ const scaffold = buildSevenStageScaffold(engine, {
646
+ ...notebook,
647
+ cells: [],
648
+ edges: [],
649
+ execution: {},
650
+ generationPlans: [],
651
+ notebookRunPlans: [],
652
+ }, sourceArtifact.artifactId);
653
+ const source = scaffold.cells.find((cell) => roleOf(cell) === "source");
654
+ const analysis = scaffold.cells.find((cell) => roleOf(cell) === "analysis");
655
+ if (!source || !analysis) {
656
+ throw new Error("Multyculty scaffold missing source or analysis workflow roles");
657
+ }
658
+ const configured = {
659
+ ...scaffold,
660
+ cells: scaffold.cells.map((cell) => {
661
+ if (cell.id === source.id) {
662
+ return {
663
+ ...cell,
664
+ outputArtifactId: sourceArtifact.artifactId,
665
+ inputs: {
666
+ ...(cell.inputs ?? {}),
667
+ sourceProject: "multiculty",
668
+ sourceTitle: "We Were WRONG About Rupert Lowe",
669
+ sourceUrl: "https://www.youtube.com/watch?v=b-3853ow72Q",
670
+ selectedRangeSeconds: [1490, 1520],
671
+ streamId: sourceAudio.streamId,
672
+ objectHash: sourceAudio.objectHash,
673
+ state: "completed",
674
+ },
675
+ };
676
+ }
677
+ if (cell.id === analysis.id) {
678
+ return {
679
+ ...cell,
680
+ prompt: cell.prompt ?? DEFAULT_ANALYSIS_PROMPT,
681
+ inputs: {
682
+ ...(cell.inputs ?? {}),
683
+ sourceDurationSeconds: 30,
684
+ state: "pending",
685
+ },
686
+ };
687
+ }
688
+ if (roleOf(cell) === "source-extract-audio") {
689
+ return {
690
+ ...cell,
691
+ outputArtifactId: undefined,
692
+ inputs: {
693
+ ...(cell.inputs ?? {}),
694
+ artifactId: sourceArtifact.artifactId,
695
+ streamId: sourceAudio.streamId,
696
+ objectHash: sourceAudio.objectHash,
697
+ state: "pending",
698
+ },
699
+ };
700
+ }
701
+ return cell;
702
+ }),
703
+ };
704
+ const fanned = await applyAnalysisResults(engine, configured, analysis.id, {
705
+ rawOutput: MULTYCULTY_FIXTURE_ANALYSIS,
706
+ sourceDurationSeconds: 30,
707
+ request: {
708
+ mode: "multyculty-fixture",
709
+ provider: "openai",
710
+ model: INTELLIGENCE_MODEL,
711
+ reasoning: { effort: ANALYSIS_REASONING_EFFORT },
712
+ pipelineVersion: 3,
713
+ stages: [
714
+ "visual_candidate_detection",
715
+ "camera_validation",
716
+ "audio_boundary_selection",
717
+ "shot_labeling",
718
+ ],
719
+ },
720
+ sourceArtifactId: sourceArtifact.artifactId,
721
+ llmExchanges: MULTYCULTY_FIXTURE_LLM_EXCHANGES,
722
+ evidence: MULTYCULTY_FIXTURE_EVIDENCE,
723
+ boundaries: MULTYCULTY_FIXTURE_BOUNDARIES,
724
+ materializeWorkflow: true,
725
+ });
726
+ const withOutputs = await attachFixtureOutputs(engine, compactFixture(fanned), artifacts);
727
+ const fixtureOwnedIds = new Set(withOutputs.cells
728
+ .filter((cell) => isCanonicalRole(roleOf(cell)))
729
+ .map((cell) => cell.id));
730
+ const migrated = {
731
+ ...withOutputs,
732
+ name: "Multyculty",
733
+ cells: withOutputs.cells.map((cell) => fixtureOwnedIds.has(cell.id)
734
+ ? {
735
+ ...cell,
736
+ inputs: {
737
+ ...(cell.inputs ?? {}),
738
+ fixtureOwner: MULTYCULTY_FIXTURE_OWNER,
739
+ },
740
+ }
741
+ : cell),
742
+ description: "Compact offline example from source analysis through two generated scenes and a playable concat.",
743
+ lifecycleState: "finished",
744
+ fixture: {
745
+ version: MULTYCULTY_FIXTURE_VERSION,
746
+ owner: MULTYCULTY_FIXTURE_OWNER,
747
+ previousRevision,
748
+ ensuredAt: new Date().toISOString(),
749
+ noPaidCalls: true,
750
+ },
751
+ };
752
+ migrated.execution = fixtureExecution(migrated);
753
+ const seeded = await writeNotebook(engine, migrated);
754
+ return options.materializeLocalOutputs
755
+ ? materializeLocalOutputs(engine, seeded)
756
+ : seeded;
757
+ }