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,58 @@
1
+ import { readFile, rm, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { queue, registerJobHandler } from "../queue.js";
5
+ import { runFfmpeg } from "../ffmpeg.js";
6
+ import { withLockedLocalLease } from "./_lease.js";
7
+ const LOCK_DURATION_MS = 60_000;
8
+ export function registerExtractFrameJob(engine) {
9
+ registerJobHandler("extract_frame", async (payload, ctx) => {
10
+ const { inputPath, timestamp, imageAssetId, imageAssetDir, sourceVideoId, bookId, width, height, mode, } = payload;
11
+ // ffmpeg has no API for "write to memory", so we stage the frame in a
12
+ // tempfile outside the asset directory and then route the bytes through
13
+ // the engine file API, which ingests it into CAS and versions the mapping.
14
+ const tempPath = join(tmpdir(), `videocity-extract-frame-${imageAssetId}-${Date.now()}.jpg`);
15
+ try {
16
+ const origin = await withLockedLocalLease(engine, imageAssetId, imageAssetDir, ctx, "extract", { durationMs: LOCK_DURATION_MS }, async () => {
17
+ await runFfmpeg([
18
+ "-y",
19
+ "-ss", String(timestamp),
20
+ "-i", inputPath,
21
+ "-frames:v", "1",
22
+ "-q:v", "2",
23
+ tempPath,
24
+ ]);
25
+ const outStat = await stat(tempPath).catch(() => null);
26
+ if (!outStat || outStat.size === 0) {
27
+ throw new Error(`ffmpeg produced no output for ${inputPath} at t=${timestamp}s (mode=${mode})`);
28
+ }
29
+ const buffer = await readFile(tempPath);
30
+ const writeResult = await engine.files.write(imageAssetId, "original.jpg", buffer);
31
+ if (!writeResult.ok) {
32
+ throw new Error(`failed to write original.jpg for ${imageAssetId}: ${writeResult.error.message}`);
33
+ }
34
+ const origin = {
35
+ origin: `${mode}_frame`,
36
+ source_asset: sourceVideoId,
37
+ timestamp,
38
+ width,
39
+ height,
40
+ };
41
+ await engine.metadata.artifacts.write(imageAssetId, "original", origin);
42
+ return origin;
43
+ }, { durationMs: LOCK_DURATION_MS, waitForActiveMs: 30_000 });
44
+ const jobId = `process-image-${imageAssetId}-${Date.now()}`;
45
+ await queue.enqueue(jobId, {
46
+ type: "process_image",
47
+ assetId: imageAssetId,
48
+ bookId,
49
+ extension: ".jpg",
50
+ provenance: origin,
51
+ });
52
+ return { success: true };
53
+ }
54
+ finally {
55
+ await rm(tempPath, { force: true }).catch(() => { });
56
+ }
57
+ });
58
+ }
@@ -0,0 +1,82 @@
1
+ import { registerJobHandler } from "../queue.js";
2
+ import { extractSourceAudio } from "../notebook-media-ops.js";
3
+ import { mutateNotebook } from "../notebook-mutations.js";
4
+ import { notebookCellFingerprint } from "./run-notebook.js";
5
+ async function writeAudioCellResult(engine, input, operation, result) {
6
+ const written = await mutateNotebook(engine, input.notebookId, operation, (notebook) => {
7
+ const cell = notebook.cells.find((candidate) => candidate.id === input.cellId);
8
+ if (!cell)
9
+ return notebook;
10
+ const updatedAt = new Date().toISOString();
11
+ const updatedCell = result.status === "completed"
12
+ ? {
13
+ ...cell,
14
+ outputArtifactId: result.artifactId,
15
+ inputs: {
16
+ ...(cell.inputs ?? {}),
17
+ durationSeconds: result.durationSeconds,
18
+ sourceCellId: input.sourceCellId,
19
+ state: "completed",
20
+ },
21
+ }
22
+ : {
23
+ ...cell,
24
+ inputs: {
25
+ ...(cell.inputs ?? {}),
26
+ state: "error",
27
+ },
28
+ };
29
+ return {
30
+ ...notebook,
31
+ cells: notebook.cells.map((candidate) => candidate.id === input.cellId ? updatedCell : candidate),
32
+ execution: {
33
+ ...(notebook.execution ?? {}),
34
+ [input.cellId]: {
35
+ fingerprint: notebookCellFingerprint(notebook, updatedCell),
36
+ status: result.status,
37
+ runId: input.runId,
38
+ ...(result.status === "completed"
39
+ ? { outputArtifactId: result.artifactId, completedAt: updatedAt }
40
+ : { error: result.error }),
41
+ updatedAt,
42
+ tool: "ffmpeg",
43
+ },
44
+ },
45
+ };
46
+ }, { cellId: input.cellId, runId: input.runId }, result.status === "completed" ? { artifactId: result.artifactId } : {});
47
+ if (!written.ok)
48
+ throw new Error(written.error.message);
49
+ }
50
+ export function registerExtractNotebookAudioJob(engine) {
51
+ registerJobHandler("extract_notebook_audio", async (payload) => {
52
+ const input = payload;
53
+ try {
54
+ const result = await extractSourceAudio(engine, input.sourceArtifactId, input.title, input.startSeconds === undefined && input.durationSeconds === undefined
55
+ ? undefined
56
+ : {
57
+ startSeconds: input.startSeconds ?? 0,
58
+ ...(input.durationSeconds === undefined
59
+ ? {}
60
+ : { durationSeconds: input.durationSeconds }),
61
+ });
62
+ await writeAudioCellResult(engine, input, "complete_extract_notebook_audio", {
63
+ status: "completed",
64
+ artifactId: result.artifactId,
65
+ durationSeconds: result.durationSeconds,
66
+ });
67
+ return {
68
+ success: true,
69
+ artifactId: result.artifactId,
70
+ durationSeconds: result.durationSeconds,
71
+ };
72
+ }
73
+ catch (cause) {
74
+ const message = cause instanceof Error ? cause.message : String(cause);
75
+ await writeAudioCellResult(engine, input, "fail_extract_notebook_audio", {
76
+ status: "error",
77
+ error: message,
78
+ }).catch(() => undefined);
79
+ throw cause;
80
+ }
81
+ });
82
+ }
@@ -0,0 +1,93 @@
1
+ import { queue, registerJobHandler } from "../queue.js";
2
+ import { captureFrame } from "../notebook-media-ops.js";
3
+ import { mutateNotebook } from "../notebook-mutations.js";
4
+ import { notebookCellFingerprint } from "./run-notebook.js";
5
+ async function writeFrameCellResult(engine, input, operation, result) {
6
+ const written = await mutateNotebook(engine, input.notebookId, operation, (notebook) => {
7
+ const cell = notebook.cells.find((candidate) => candidate.id === input.cellId);
8
+ if (!cell)
9
+ return notebook;
10
+ const updatedAt = new Date().toISOString();
11
+ const updatedCell = result.status === "completed"
12
+ ? {
13
+ ...cell,
14
+ outputArtifactId: result.artifactId,
15
+ inputs: {
16
+ ...(cell.inputs ?? {}),
17
+ atSeconds: result.atSeconds,
18
+ state: "completed",
19
+ },
20
+ }
21
+ : {
22
+ ...cell,
23
+ inputs: {
24
+ ...(cell.inputs ?? {}),
25
+ state: "error",
26
+ },
27
+ };
28
+ return {
29
+ ...notebook,
30
+ cells: notebook.cells.map((candidate) => candidate.id === input.cellId ? updatedCell : candidate),
31
+ execution: {
32
+ ...(notebook.execution ?? {}),
33
+ [input.cellId]: {
34
+ fingerprint: notebookCellFingerprint(notebook, updatedCell),
35
+ status: result.status,
36
+ runId: input.runId,
37
+ ...(result.status === "completed"
38
+ ? { outputArtifactId: result.artifactId, completedAt: updatedAt }
39
+ : { error: result.error }),
40
+ updatedAt,
41
+ tool: "ffmpeg",
42
+ },
43
+ },
44
+ };
45
+ }, { cellId: input.cellId, runId: input.runId }, result.status === "completed" ? { artifactId: result.artifactId } : {});
46
+ if (!written.ok)
47
+ throw new Error(written.error.message);
48
+ }
49
+ export function registerExtractNotebookFrameJob(engine) {
50
+ registerJobHandler("extract_notebook_frame", async (payload) => {
51
+ const input = payload;
52
+ try {
53
+ const result = await captureFrame(engine, input.sourceArtifactId, {
54
+ role: input.frameMode,
55
+ ...(input.frameMode === "time"
56
+ && typeof input.timestampSeconds === "number"
57
+ ? { timestampSeconds: input.timestampSeconds }
58
+ : {}),
59
+ title: input.title,
60
+ });
61
+ await queue.enqueue(`process-image-${result.artifactId}-${Date.now()}`, {
62
+ type: "process_image",
63
+ assetId: result.artifactId,
64
+ bookId: input.bookId,
65
+ extension: ".jpg",
66
+ provenance: {
67
+ origin: "extract_frame",
68
+ sourceArtifactId: input.sourceArtifactId,
69
+ frameMode: input.frameMode,
70
+ atSeconds: result.atSeconds,
71
+ },
72
+ });
73
+ await writeFrameCellResult(engine, input, "complete_extract_notebook_frame", {
74
+ status: "completed",
75
+ artifactId: result.artifactId,
76
+ atSeconds: result.atSeconds,
77
+ });
78
+ return {
79
+ success: true,
80
+ artifactId: result.artifactId,
81
+ atSeconds: result.atSeconds,
82
+ };
83
+ }
84
+ catch (cause) {
85
+ const message = cause instanceof Error ? cause.message : String(cause);
86
+ await writeFrameCellResult(engine, input, "fail_extract_notebook_frame", {
87
+ status: "error",
88
+ error: message,
89
+ }).catch(() => undefined);
90
+ throw cause;
91
+ }
92
+ });
93
+ }
@@ -0,0 +1,8 @@
1
+ import { splitSourceRange } from "../notebook-media-ops.js";
2
+ import { registerJobHandler } from "../queue.js";
3
+ export function registerExtractVideoClipJob(engine) {
4
+ registerJobHandler("extract_video_clip", async (payload) => {
5
+ const { sourceAssetId, startTime, endTime, title, } = payload;
6
+ return await splitSourceRange(engine, sourceAssetId, { startSeconds: startTime, endSeconds: endTime }, title);
7
+ });
8
+ }
@@ -0,0 +1,55 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { extname, join } from "node:path";
5
+ import { probeVideo } from "../ffmpeg.js";
6
+ import { registerJobHandler } from "../queue.js";
7
+ import { extractVideoPoster } from "../video-poster.js";
8
+ export function registerExtractVideoPosterJob(engine) {
9
+ registerJobHandler("extract_video_poster", async (payload, { signal }) => {
10
+ const { posterArtifactId, sourceFilename, sourceHash } = payload;
11
+ const needsPoster = async () => {
12
+ const manifest = await engine.files.manifest(posterArtifactId);
13
+ if (!manifest.ok)
14
+ throw new Error(manifest.error.message);
15
+ return !manifest.value.files.some((file) => file.name === "original.jpg")
16
+ && manifest.value.files.some((file) => file.name === sourceFilename && file.objectHash === sourceHash);
17
+ };
18
+ if (!await needsPoster())
19
+ return { skipped: true };
20
+ const source = await engine.files.read(posterArtifactId, sourceFilename);
21
+ if (!source.ok)
22
+ throw new Error(source.error.message);
23
+ if (createHash("sha256").update(source.value).digest("hex") !== sourceHash) {
24
+ return { skipped: true };
25
+ }
26
+ const scratch = await mkdtemp(join(tmpdir(), "videobook-poster-source-"));
27
+ try {
28
+ const sourcePath = join(scratch, `source${extname(sourceFilename)}`);
29
+ await writeFile(sourcePath, source.value);
30
+ const probe = await probeVideo(sourcePath, { signal });
31
+ const poster = await extractVideoPoster(sourcePath, probe.videoDuration || probe.duration, signal);
32
+ signal?.throwIfAborted();
33
+ if (!await needsPoster())
34
+ return { skipped: true };
35
+ const written = await engine.files.write(posterArtifactId, "original.jpg", poster);
36
+ if (!written.ok)
37
+ throw new Error(written.error.message);
38
+ return { success: true };
39
+ }
40
+ catch (error) {
41
+ if (!signal?.aborted && await needsPoster()) {
42
+ const recorded = await engine.metadata.artifacts.write(posterArtifactId, "poster-recovery", {
43
+ sourceHash,
44
+ failed: true,
45
+ });
46
+ if (!recorded.ok)
47
+ throw new Error(recorded.error.message);
48
+ }
49
+ throw error;
50
+ }
51
+ finally {
52
+ await rm(scratch, { recursive: true, force: true });
53
+ }
54
+ });
55
+ }
@@ -0,0 +1,25 @@
1
+ import { join } from "node:path";
2
+ import { registerJobHandler } from "../queue.js";
3
+ import { extractWaveform } from "../ffmpeg.js";
4
+ export function registerExtractWaveformJob(engine) {
5
+ registerJobHandler("extract_waveform", async (payload) => {
6
+ const { bookId: _bookId, waveformAssetId, videoFilename } = payload;
7
+ // Idempotent: a concurrent poll may have already cached this waveform.
8
+ const existing = await engine.metadata.waveforms.read(waveformAssetId);
9
+ if (existing.ok)
10
+ return { cached: true };
11
+ const dir = await engine.workspaces.resolveArtifact(waveformAssetId);
12
+ if (!dir.ok) {
13
+ throw new Error(`extract_waveform: resolveAssetDir failed for ${waveformAssetId}: ${dir.error.message}`);
14
+ }
15
+ const peaks = await extractWaveform(join(dir.value, videoFilename));
16
+ // Persist even when empty. A silent track — or one ffmpeg can't decode —
17
+ // caches as "no waveform" so we don't re-decode the same file on every
18
+ // sync. extractWaveform already logs the underlying ffmpeg failure.
19
+ const write = await engine.metadata.waveforms.write(waveformAssetId, peaks);
20
+ if (!write.ok) {
21
+ throw new Error(`extract_waveform: writeAudioWaveform failed for ${waveformAssetId}: ${write.error.message}`);
22
+ }
23
+ return { peaks: peaks.length };
24
+ });
25
+ }
@@ -0,0 +1,255 @@
1
+ import { join } from "node:path";
2
+ import { v7 as uuidv7 } from "uuid";
3
+ import { findPrimaryMediaFile } from "videobook-engine";
4
+ import { coldOpenDestination, coldOpenSource } from "../cold-open.js";
5
+ import { coldOpenRangeError } from "../cold-open-model.js";
6
+ import { probeVideo } from "../ffmpeg.js";
7
+ import { readGoal, saveGoal } from "../goal-store.js";
8
+ import { goalAssemblyStale } from "../goal-state.js";
9
+ import { finishingIdentityKey, watermarkProfileFor } from "../lightning-defaults.js";
10
+ import { mutateNotebook } from "../notebook-mutations.js";
11
+ import { registerMediaProbe } from "../notebook-media-ops.js";
12
+ import { queue, registerJobHandler } from "../queue.js";
13
+ import { applyWatermarkDef } from "../tools/apply-watermark.js";
14
+ import { coldOpenDefs } from "../tools/cold-open.js";
15
+ import { effectiveWatermarkProfile, readWatermarkConfig } from "../watermark-config.js";
16
+ const FINISH_MESSAGE = "Only a reviewed scene and video goal can finish with a cold open.";
17
+ const ASSEMBLY_MESSAGE = "The goal's timeline clips were edited. Keep those edits and place the revised videos manually.";
18
+ function textOf(result) {
19
+ return result.content?.find(entry => entry.type === "text")?.text ?? "";
20
+ }
21
+ function currentFinish(engine, goalId, identity) {
22
+ const goal = readGoal(engine, goalId);
23
+ if (goal.finish?.identity !== identity || goal.finish.status === "cancelled")
24
+ return null;
25
+ return goal;
26
+ }
27
+ function finishSourceId(engine, goal) {
28
+ if (goal.assembly && goalAssemblyStale(engine, goal))
29
+ throw new Error(ASSEMBLY_MESSAGE);
30
+ if (goal.combined?.status === "completed" && goal.combined.artifactId && !goalAssemblyStale(engine, goal))
31
+ return goal.combined.artifactId;
32
+ const selected = goal.stages.videos?.outputs[0]?.artifactId ?? goal.finish?.sourceArtifactId;
33
+ if (!selected)
34
+ throw new Error("Approve a video before finishing it");
35
+ return selected;
36
+ }
37
+ async function waitForQueuedJob(engine, id, signal) {
38
+ const deadline = Date.now() + 180_000;
39
+ while (Date.now() < deadline) {
40
+ signal.throwIfAborted();
41
+ const job = engine.jobs.queue.get(id);
42
+ if (!job)
43
+ throw new Error("The finishing job disappeared");
44
+ if (job.state === "done")
45
+ return;
46
+ if (job.state === "failed" || job.state === "aborted")
47
+ throw new Error(job.error?.message ?? `Finishing ${job.state}`);
48
+ await new Promise(resolve => setTimeout(resolve, 50));
49
+ }
50
+ throw new Error("Finishing timed out");
51
+ }
52
+ async function renderColdOpen(engine, goal, signal) {
53
+ const finish = goal.finish;
54
+ if (!finish?.coldOpenRequestId || finish.startSeconds == null || finish.endSeconds == null || !finish.sourceObjectHash) {
55
+ throw new Error("The cold open is missing its reviewed range");
56
+ }
57
+ const existing = finish.coldOpenArtifactId;
58
+ if (existing && engine.artifacts.get(existing).ok) {
59
+ const manifest = await engine.files.manifest(existing);
60
+ if (manifest.ok && manifest.value.files.some(file => file.name === "original.mp4"))
61
+ return existing;
62
+ }
63
+ const edit = coldOpenDefs(engine).find(def => def.name === "edit_cold_open");
64
+ if (!edit)
65
+ throw new Error("Cold open editing is unavailable");
66
+ const result = await edit.handler({
67
+ book_id: goal.bookId,
68
+ notebook_id: goal.notebookId,
69
+ asset_id: finish.sourceArtifactId,
70
+ source_object_hash: finish.sourceObjectHash,
71
+ start_time: finish.startSeconds,
72
+ end_time: finish.endSeconds,
73
+ request_id: finish.coldOpenRequestId,
74
+ });
75
+ const text = textOf(result);
76
+ if (result.isError)
77
+ throw new Error(text || "Cold open failed");
78
+ const parsed = JSON.parse(text);
79
+ if (typeof parsed.job_id === "number" && parsed.status !== "completed")
80
+ await waitForQueuedJob(engine, parsed.job_id, signal);
81
+ if (!parsed.output_asset_id)
82
+ throw new Error("Cold open did not return a video");
83
+ const saved = currentFinish(engine, goal.goalId, finish.identity ?? "");
84
+ if (saved?.finish) {
85
+ saveGoal(engine, { ...saved, finish: { ...saved.finish, coldOpenArtifactId: parsed.output_asset_id } });
86
+ }
87
+ return parsed.output_asset_id;
88
+ }
89
+ async function duplicatePlayableVideo(engine, sourceArtifactId, label) {
90
+ const source = await coldOpenSource(engine, sourceArtifactId);
91
+ const created = await engine.artifacts.create({ kind: "video", label });
92
+ if (!created.ok)
93
+ throw new Error(created.error.message);
94
+ const artifactId = created.value.artifactId;
95
+ const copied = await engine.files.copy(sourceArtifactId, source.file.name, artifactId, "original.mp4");
96
+ if (!copied.ok)
97
+ throw new Error(copied.error.message);
98
+ const manifest = await engine.files.manifest(artifactId);
99
+ if (!manifest.ok)
100
+ throw new Error(manifest.error.message);
101
+ const probe = await probeVideo(join(manifest.value.path, "original.mp4"));
102
+ await registerMediaProbe(engine, artifactId, "original.mp4", probe);
103
+ return artifactId;
104
+ }
105
+ async function burnSavedWatermark(engine, artifactId) {
106
+ const apply = applyWatermarkDef(engine);
107
+ const result = await apply.handler({
108
+ book_id: engine.book.get().bookId,
109
+ asset_id: artifactId,
110
+ platform: "tiktok",
111
+ mode: "burn",
112
+ wait: true,
113
+ });
114
+ const text = textOf(result);
115
+ if (result.isError)
116
+ throw new Error(text || "Watermark failed");
117
+ }
118
+ async function placeFinishedCell(engine, goal, artifactId, sourceArtifactId) {
119
+ const placed = await mutateNotebook(engine, goal.notebookId, "goal_finish", notebook => {
120
+ if (notebook.cells.some(cell => cell.outputArtifactId === artifactId))
121
+ return notebook;
122
+ const cell = engine.notebooks.createCell({
123
+ type: "video",
124
+ label: "Finished",
125
+ slot: coldOpenDestination(notebook, sourceArtifactId),
126
+ outputArtifactId: artifactId,
127
+ inputs: { sourceOperation: "goal_finish", sourceArtifactId, state: "completed" },
128
+ });
129
+ return { ...notebook, cells: [...notebook.cells, cell] };
130
+ }, { goalId: goal.goalId, artifactId });
131
+ if (!placed.ok)
132
+ throw new Error(placed.error.message);
133
+ }
134
+ async function runFinish(engine, payload, signal) {
135
+ const goal = currentFinish(engine, payload.goalId, payload.identity);
136
+ if (!goal?.finish)
137
+ return { finalArtifactId: "", coldOpenArtifactId: "" };
138
+ const coldOpenArtifactId = await renderColdOpen(engine, goal, signal);
139
+ const running = currentFinish(engine, payload.goalId, payload.identity);
140
+ if (!running?.finish)
141
+ return { finalArtifactId: "", coldOpenArtifactId };
142
+ const finalArtifactId = await duplicatePlayableVideo(engine, coldOpenArtifactId, "Finished");
143
+ await burnSavedWatermark(engine, finalArtifactId);
144
+ const manifest = await engine.files.manifest(finalArtifactId);
145
+ if (!manifest.ok)
146
+ throw new Error(manifest.error.message);
147
+ const file = findPrimaryMediaFile(manifest.value.files, "video");
148
+ if (!file)
149
+ throw new Error("The finished video is missing");
150
+ const probe = await probeVideo(join(manifest.value.path, file.name));
151
+ if (!probe.videoStream || probe.videoDuration < 0.5)
152
+ throw new Error("The finished video is not playable");
153
+ await placeFinishedCell(engine, running, finalArtifactId, running.finish.sourceArtifactId);
154
+ const latest = currentFinish(engine, payload.goalId, payload.identity);
155
+ if (!latest?.finish)
156
+ return { finalArtifactId, coldOpenArtifactId };
157
+ saveGoal(engine, {
158
+ ...latest,
159
+ status: "completed",
160
+ error: null,
161
+ finish: { ...latest.finish, status: "completed", coldOpenArtifactId, finalArtifactId, error: null },
162
+ });
163
+ return { finalArtifactId, coldOpenArtifactId };
164
+ }
165
+ export function registerFinishGoalJob(engine) {
166
+ registerJobHandler("finish_goal", async (input, context) => {
167
+ const goalId = typeof input.goalId === "string" ? input.goalId : "";
168
+ const identity = typeof input.identity === "string" ? input.identity : "";
169
+ try {
170
+ return await runFinish(engine, { goalId, identity }, context.signal);
171
+ }
172
+ catch (error) {
173
+ const goal = readGoal(engine, goalId);
174
+ if (goal.finish?.identity === identity && goal.finish.status === "running") {
175
+ saveGoal(engine, {
176
+ ...goal,
177
+ status: "review",
178
+ finish: { ...goal.finish, status: "failed", error: error instanceof Error ? error.message : String(error) },
179
+ });
180
+ }
181
+ throw error;
182
+ }
183
+ });
184
+ }
185
+ export async function queueGoalFinish(engine, goal, args) {
186
+ if (goal.planVersion !== 3)
187
+ throw new Error(FINISH_MESSAGE);
188
+ if (goal.status === "running")
189
+ throw new Error("Wait until the video is ready before finishing it");
190
+ if (!["review", "completed", "failed", "interrupted"].includes(goal.status))
191
+ throw new Error("Review the finished video before approving its cold open");
192
+ const source = await coldOpenSource(engine, finishSourceId(engine, goal));
193
+ const start = args.start_seconds;
194
+ const end = args.end_seconds;
195
+ if (start === undefined && end === undefined) {
196
+ if (goal.finish?.status === "completed" || goal.finish?.status === "running")
197
+ return goal;
198
+ const suggestion = {
199
+ status: "review",
200
+ sourceArtifactId: source.artifact.artifactId,
201
+ sourceObjectHash: source.file.objectHash,
202
+ finalArtifactId: null,
203
+ error: null,
204
+ };
205
+ return saveGoal(engine, { ...goal, status: "review", finish: suggestion });
206
+ }
207
+ if (typeof start !== "number" || typeof end !== "number")
208
+ throw new Error("Approve both the cold-open start and end");
209
+ const rangeError = coldOpenRangeError(start, end, source.durationSeconds);
210
+ if (rangeError)
211
+ throw new Error(rangeError);
212
+ const config = await readWatermarkConfig(engine, "tiktok");
213
+ const profile = effectiveWatermarkProfile(config, "tiktok");
214
+ const identity = finishingIdentityKey({
215
+ sourceObjectHash: source.file.objectHash,
216
+ startSeconds: start,
217
+ endSeconds: end,
218
+ profile: watermarkProfileFor(profile.platform, profile),
219
+ qrStartSeconds: config.profile?.qrStartSeconds,
220
+ qrEndSeconds: config.profile?.qrEndSeconds,
221
+ });
222
+ const same = goal.finish?.identity === identity;
223
+ if (same && goal.finish?.status === "completed" && goal.finish.finalArtifactId && engine.artifacts.get(goal.finish.finalArtifactId).ok) {
224
+ return saveGoal(engine, { ...goal, status: "completed", error: null });
225
+ }
226
+ if (same && goal.finish?.status === "running" && goal.finish.jobId) {
227
+ const active = engine.jobs.queue.get(goal.finish.jobId);
228
+ if (active && ["queued", "running", "completing"].includes(active.state))
229
+ return goal;
230
+ }
231
+ const finish = {
232
+ status: "running",
233
+ sourceArtifactId: source.artifact.artifactId,
234
+ sourceObjectHash: source.file.objectHash,
235
+ startSeconds: start,
236
+ endSeconds: end,
237
+ identity,
238
+ coldOpenArtifactId: same ? goal.finish?.coldOpenArtifactId ?? null : null,
239
+ coldOpenRequestId: same && goal.finish?.coldOpenRequestId ? goal.finish.coldOpenRequestId : uuidv7(),
240
+ finalArtifactId: null,
241
+ jobId: null,
242
+ error: null,
243
+ };
244
+ saveGoal(engine, { ...goal, status: "review", error: null, finish });
245
+ const job = await queue.enqueue(`goal-finish-${goal.goalId}-${uuidv7()}`, {
246
+ type: "finish_goal",
247
+ bookId: goal.bookId,
248
+ goalId: goal.goalId,
249
+ identity,
250
+ });
251
+ const queued = readGoal(engine, goal.goalId);
252
+ if (!queued.finish || queued.finish.identity !== identity || queued.finish.status !== "running")
253
+ return queued;
254
+ return saveGoal(engine, { ...queued, finish: { ...queued.finish, jobId: job.id } });
255
+ }
@@ -0,0 +1,11 @@
1
+ import { forgetAsset } from "../forget-asset.js";
2
+ import { registerJobHandler } from "../queue.js";
3
+ export function registerForgetAssetJob(engine) {
4
+ registerJobHandler("forget_asset", async (payload) => {
5
+ const input = payload;
6
+ return await forgetAsset(engine, {
7
+ assetId: input.forgetArtifactId,
8
+ clearReferences: input.clearReferences === true,
9
+ });
10
+ });
11
+ }