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,670 @@
1
+ import { findVideoFile } from "videobook-engine";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { WATERMARK_PLATFORMS } from "../watermark-platform.js";
5
+ import { getApiKey } from "../api-key.js";
6
+ import { probeVideo } from "../ffmpeg.js";
7
+ import { sampleEvenly } from "../frame-utils.js";
8
+ import { MAX_WATERMARK_FADE_SECONDS, } from "../jobs/burn-watermark.js";
9
+ import { resolveOsgImagePath } from "../osg-assets.js";
10
+ import { osgPlacementFingerprint, readOsgPlacementAnalysis, } from "../osg-placement-cache.js";
11
+ import { writeOsgSettings } from "../osg-settings.js";
12
+ import { logger } from "../logger.js";
13
+ import { queue } from "../queue.js";
14
+ import { scoreWatermarkCornersForCanvas, WATERMARK_MIN_DIM_FRACTION, WATERMARK_OSG_SIZE, watermarkOsgScale, } from "../remotion/osg-spec.js";
15
+ import { createWatermarkAsset, DEFAULT_HANDLE_PADDING, DEFAULT_HANDLE_ROUNDING, defaultUrl, findWatermarkAsset, MAX_HANDLE_PADDING, MAX_HANDLE_ROUNDING, MIN_HANDLE_PADDING, MIN_HANDLE_ROUNDING, QR_ONLY_BADGE_ASPECT, watermarkBadgeAspect, } from "../watermark-badge.js";
16
+ import { LIGHTNING_FADE_SECONDS, watermarkProfileToSave, watermarkWindowsFor } from "../lightning-defaults.js";
17
+ import { effectiveWatermarkProfile, normalizeHandle, readWatermarkConfig, recordHandle, saveWatermarkProfile } from "../watermark-config.js";
18
+ import { effectiveWatermarkTiming } from "../watermark-timing.js";
19
+ import { MAX_HANDLE_VERTICAL_PERCENT, MIN_HANDLE_VERTICAL_PERCENT, WATERMARK_HANDLE_PLACEMENTS, } from "../watermark-handle-position.js";
20
+ import { requireArtifactKind } from "./_shared.js";
21
+ import { waitForAssetReadyDef } from "./wait-for-asset-ready.js";
22
+ const PLACEMENT_TIMEOUT_MS = 300_000;
23
+ const BURN_TIMEOUT_MS = 600_000;
24
+ const MAX_PLACEMENT_FRAMES = 8;
25
+ const WATERMARK_POSITIONS = [
26
+ "top-left", "top-center", "top-right",
27
+ "middle-left", "middle-center", "middle-right",
28
+ "bottom-left", "bottom-center", "bottom-right",
29
+ ];
30
+ const WATERMARK_POSITION_OPTIONS = ["auto", ...WATERMARK_POSITIONS];
31
+ const ANALYSIS_OSG_POSITIONS = new Set(["top-left", "top-right", "bottom-left", "bottom-right"]);
32
+ const DEFAULT_POSITION = "bottom-right";
33
+ function uniformPositions(position) {
34
+ return { landscape: position, portrait: position, square: position };
35
+ }
36
+ function watermarkTimingError(fieldPrefix, startSeconds, endSeconds) {
37
+ const start = startSeconds ?? 0;
38
+ return endSeconds !== undefined && endSeconds <= start
39
+ ? `${fieldPrefix}_end_seconds must be greater than ${fieldPrefix}_start_seconds`
40
+ : null;
41
+ }
42
+ function watermarkVariantForApply(mode, includeQr, includeHandle) {
43
+ if (includeQr && !includeHandle)
44
+ return "qr";
45
+ if (!includeQr)
46
+ return "handle";
47
+ return mode === "burn" ? "qr" : "combined";
48
+ }
49
+ async function resolveWatermarkAsset(engine, handle, url, variant, appearance) {
50
+ const options = { variant, ...appearance };
51
+ const existing = await findWatermarkAsset(engine, handle, url, options);
52
+ if (existing)
53
+ return { ok: true, assetId: existing };
54
+ return createWatermarkAsset(engine, handle, url, options);
55
+ }
56
+ /** Coarse fallback: the corner analyze-video recommended for this clip. */
57
+ async function readRecommendedOsgPosition(engine, assetId) {
58
+ const result = await engine.files.read(assetId, ".original.analysis.json");
59
+ if (!result.ok)
60
+ return null;
61
+ try {
62
+ const raw = typeof result.value === "string"
63
+ ? result.value
64
+ : new TextDecoder().decode(result.value);
65
+ const analysis = JSON.parse(raw);
66
+ const position = analysis.recommendedOsgPosition;
67
+ return typeof position === "string" && ANALYSIS_OSG_POSITIONS.has(position)
68
+ ? position
69
+ : null;
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function listPlacementFrames(engine, assetId) {
76
+ const framesResult = await engine.files.listSubdir(assetId, "original_frames");
77
+ if (!framesResult.ok)
78
+ return [];
79
+ return sampleEvenly(framesResult.value.filter((f) => f.endsWith(".jpg")).sort((a, b) => parseFloat(a) - parseFloat(b)), MAX_PLACEMENT_FRAMES);
80
+ }
81
+ function placementFingerprint(videoFile, frameNames, scaling) {
82
+ return osgPlacementFingerprint(videoFile, frameNames, {
83
+ aspect: scaling.aspect,
84
+ minDimFraction: WATERMARK_MIN_DIM_FRACTION,
85
+ userScale: scaling.userScale,
86
+ });
87
+ }
88
+ /**
89
+ * Overlay placement: per-orientation corners. Never fails on placement
90
+ * problems — degrade: cache → vision job → analyze-video recommendation →
91
+ * bottom-right.
92
+ */
93
+ async function resolveOverlayPlacement(engine, bookId, assetId, assetDir, videoFile, position, scaling) {
94
+ if (position !== "auto") {
95
+ return { positions: uniformPositions(position), placementSource: "explicit" };
96
+ }
97
+ const frameNames = await listPlacementFrames(engine, assetId);
98
+ const fingerprint = placementFingerprint(videoFile, frameNames, scaling);
99
+ const cached = await readOsgPlacementAnalysis(engine, assetId);
100
+ if (cached && cached.fingerprint === fingerprint) {
101
+ return { positions: cached.positions, placementSource: "cached" };
102
+ }
103
+ if (getApiKey("OPENAI_API_KEY") && frameNames.length > 0) {
104
+ const jobId = `osg-placement-${assetId}-${Date.now()}`;
105
+ try {
106
+ await engine.jobs.failures.clear(assetId);
107
+ const result = await queue.enqueueAndWait(jobId, {
108
+ type: "osg_placement",
109
+ assetId,
110
+ assetDir,
111
+ bookId,
112
+ fingerprint,
113
+ userScale: scaling.userScale,
114
+ badgeAspect: scaling.aspect,
115
+ }, PLACEMENT_TIMEOUT_MS);
116
+ return { positions: result.analysis.positions, placementSource: "vision", jobId };
117
+ }
118
+ catch (err) {
119
+ logger.warn("watermark-placement-fallback", { assetId, error: err });
120
+ }
121
+ }
122
+ const recommended = await readRecommendedOsgPosition(engine, assetId);
123
+ return recommended
124
+ ? { positions: uniformPositions(recommended), placementSource: "analysis" }
125
+ : { positions: uniformPositions(DEFAULT_POSITION), placementSource: "default" };
126
+ }
127
+ async function scoreCachedCorner(videoPath, analysis, scaling, fallback) {
128
+ // Score corners against the source frame itself — a burned watermark
129
+ // lives in source pixels, so one corner serves every orientation.
130
+ try {
131
+ const probe = await probeVideo(videoPath);
132
+ return scoreWatermarkCornersForCanvas(probe.width, probe.height, analysis.regions, scaling).position;
133
+ }
134
+ catch (err) {
135
+ logger.warn("watermark-probe-fallback", { error: err });
136
+ return fallback;
137
+ }
138
+ }
139
+ /**
140
+ * Burn placement: one corner. Cached analyses resolve here; a fresh vision
141
+ * pass is handed to the burn job so the tool can return before it runs, with
142
+ * the analyze-video recommendation (or bottom-right) as its fallback.
143
+ */
144
+ async function resolveBurnPlacement(engine, assetId, videoFile, videoPath, position, scaling) {
145
+ if (position !== "auto") {
146
+ return { corner: position, placementSource: "explicit", visionRequest: null };
147
+ }
148
+ const frameNames = await listPlacementFrames(engine, assetId);
149
+ const fingerprint = placementFingerprint(videoFile, frameNames, scaling);
150
+ const cached = await readOsgPlacementAnalysis(engine, assetId);
151
+ if (cached && cached.fingerprint === fingerprint) {
152
+ return {
153
+ corner: await scoreCachedCorner(videoPath, cached, scaling, DEFAULT_POSITION),
154
+ placementSource: "cached",
155
+ visionRequest: null,
156
+ };
157
+ }
158
+ const recommended = await readRecommendedOsgPosition(engine, assetId);
159
+ const visionAvailable = Boolean(getApiKey("OPENAI_API_KEY")) && frameNames.length > 0;
160
+ return {
161
+ corner: recommended ?? DEFAULT_POSITION,
162
+ placementSource: recommended ? "analysis" : "default",
163
+ visionRequest: visionAvailable ? { fingerprint, badgeAspect: scaling.aspect } : null,
164
+ };
165
+ }
166
+ export function applyWatermarkDef(engine) {
167
+ return {
168
+ name: "apply_watermark",
169
+ description: "Burn a positioned QR code and/or an X or TikTok handle onto a video in place, each with its own opacity, padding and visibility window. wait=false returns once the burn is queued. mode=overlay stores a legacy render-time overlay instead.",
170
+ schema: {
171
+ book_id: z.string().describe("Book id"),
172
+ asset_id: z.string().describe("Video asset id to watermark"),
173
+ platform: z.enum(WATERMARK_PLATFORMS).optional().describe("Alias platform (default tiktok)"),
174
+ handle: z.string().optional().describe("X or TikTok alias without @ (default: saved alias for the platform)"),
175
+ url: z.string().optional().describe("URL the QR code resolves to (default: the selected X or TikTok profile URL)"),
176
+ save_default: z
177
+ .boolean()
178
+ .optional()
179
+ .describe("Remember this handle as the default for future watermarks"),
180
+ include_qr: z
181
+ .boolean()
182
+ .optional()
183
+ .describe("Include a separately positioned QR watermark (default true); false applies only the centered handle"),
184
+ include_handle: z
185
+ .boolean()
186
+ .optional()
187
+ .describe("Include the handle watermark (default true); false applies only the QR"),
188
+ position: z
189
+ .enum(WATERMARK_POSITION_OPTIONS)
190
+ .optional()
191
+ .describe("QR placement anchor. A fresh TikTok profile uses bottom-right; X uses auto"),
192
+ handle_vertical_percent: z
193
+ .number()
194
+ .min(MIN_HANDLE_VERTICAL_PERCENT)
195
+ .max(MAX_HANDLE_VERTICAL_PERCENT)
196
+ .optional()
197
+ .describe("Burn-mode centered-handle centerline as percent from the top (fresh TikTok 10, X 33; range 10-90). Ignored when handle_position attaches the handle to the QR"),
198
+ handle_position: z
199
+ .enum(WATERMARK_HANDLE_PLACEMENTS)
200
+ .optional()
201
+ .describe("Burn-mode handle placement: center (default), above-qr, on-qr, or below-qr"),
202
+ include_platform_icon: z
203
+ .boolean()
204
+ .optional()
205
+ .describe("Show the selected platform icon before the plain alias (default true)"),
206
+ include_handle_border: z
207
+ .boolean()
208
+ .optional()
209
+ .describe("Draw a border around the handle pill (default true)"),
210
+ handle_padding: z
211
+ .number()
212
+ .int()
213
+ .min(MIN_HANDLE_PADDING)
214
+ .max(MAX_HANDLE_PADDING)
215
+ .optional()
216
+ .describe(`Horizontal handle padding in reference pixels (default ${DEFAULT_HANDLE_PADDING}; range ${MIN_HANDLE_PADDING}-${MAX_HANDLE_PADDING})`),
217
+ handle_padding_y: z
218
+ .number()
219
+ .int()
220
+ .min(MIN_HANDLE_PADDING)
221
+ .max(MAX_HANDLE_PADDING)
222
+ .optional()
223
+ .describe(`Vertical handle padding in reference pixels (default ${DEFAULT_HANDLE_PADDING}; range ${MIN_HANDLE_PADDING}-${MAX_HANDLE_PADDING})`),
224
+ handle_rounding: z
225
+ .number()
226
+ .int()
227
+ .min(MIN_HANDLE_ROUNDING)
228
+ .max(MAX_HANDLE_ROUNDING)
229
+ .optional()
230
+ .describe(`Handle corner rounding percent (default ${DEFAULT_HANDLE_ROUNDING}; range ${MIN_HANDLE_ROUNDING}-${MAX_HANDLE_ROUNDING})`),
231
+ qr_start_seconds: z
232
+ .number()
233
+ .min(0)
234
+ .optional()
235
+ .describe("First second where the QR is visible (default clip start)"),
236
+ qr_end_seconds: z
237
+ .number()
238
+ .min(0)
239
+ .optional()
240
+ .describe("Last second where the QR is visible (default clip end)"),
241
+ handle_start_seconds: z
242
+ .number()
243
+ .min(0)
244
+ .optional()
245
+ .describe("First second where the handle is visible (default clip start)"),
246
+ handle_end_seconds: z
247
+ .number()
248
+ .min(0)
249
+ .optional()
250
+ .describe("Last second where the handle is visible (default clip end)"),
251
+ fade_seconds: z
252
+ .number()
253
+ .min(0)
254
+ .max(MAX_WATERMARK_FADE_SECONDS)
255
+ .optional()
256
+ .describe(`Burn-mode fade in/out length in seconds at a watermark edge that falls inside the clip (fresh TikTok ${LIGHTNING_FADE_SECONDS}, X 0.5; 0 keeps hard edges). Whole-clip watermarks never fade`),
257
+ qr_opacity: z
258
+ .number()
259
+ .min(0.05)
260
+ .max(1)
261
+ .optional()
262
+ .describe("QR opacity from 0.05 to 1 (fresh TikTok 0.37, X 0.5)"),
263
+ handle_opacity: z
264
+ .number()
265
+ .min(0.05)
266
+ .max(1)
267
+ .optional()
268
+ .describe("Handle opacity from 0.05 to 1 (fresh TikTok 0.28, X 0.5)"),
269
+ scale: z
270
+ .number()
271
+ .min(0.5)
272
+ .max(2)
273
+ .optional()
274
+ .describe("Size multiplier on the default badge size (default 1)"),
275
+ card_asset_id: z
276
+ .string()
277
+ .optional()
278
+ .describe("Reuse an existing image asset for the primary QR or handle watermark"),
279
+ mode: z
280
+ .enum(["burn", "overlay"])
281
+ .optional()
282
+ .describe("burn (default): composite into the video file and reprocess; overlay: store render-time OSG settings only"),
283
+ wait: z
284
+ .boolean()
285
+ .optional()
286
+ .describe("Burn mode: wait for the burn and reprocess to finish (default true). false returns status=accepted with the queue job_id as soon as the burn is queued"),
287
+ },
288
+ handler: async (args) => {
289
+ const { book_id, asset_id, handle, url, save_default, include_qr = true, include_handle = true, position: positionArg, handle_vertical_percent: handleVerticalArg, handle_position: handlePositionArg, platform: platformArg, include_platform_icon, include_handle_border: borderArg, handle_padding: paddingArg, handle_padding_y: paddingYArg, handle_rounding: roundingArg, qr_start_seconds, qr_end_seconds, handle_start_seconds, handle_end_seconds, fade_seconds: fadeArg, qr_opacity, handle_opacity, scale: scaleArg, card_asset_id, mode: modeArg, wait = true, } = args;
290
+ const platform = platformArg ?? "tiktok";
291
+ const config = await readWatermarkConfig(engine, platform);
292
+ const profile = effectiveWatermarkProfile(config, platform);
293
+ const position = positionArg ?? (profile.qrPosition === "auto" ? "auto" : profile.qrPosition);
294
+ const handle_vertical_percent = handleVerticalArg ?? profile.handleVerticalPercent;
295
+ const handle_position = handlePositionArg ?? profile.handlePlacement;
296
+ const include_handle_border = borderArg ?? profile.includeHandleBorder;
297
+ const handle_padding = paddingArg ?? profile.handlePaddingX;
298
+ const handle_padding_y = paddingYArg ?? profile.handlePaddingY;
299
+ const handle_rounding = roundingArg ?? profile.handleRounding;
300
+ const fade_seconds = fadeArg ?? profile.fadeSeconds;
301
+ const scale = scaleArg ?? profile.scale;
302
+ const mode = modeArg ?? profile.mode;
303
+ const includePlatformIcon = include_platform_icon ?? profile.includePlatformIcon;
304
+ let resolvedQrStartSeconds = qr_start_seconds;
305
+ let resolvedQrEndSeconds = qr_end_seconds;
306
+ let resolvedHandleStartSeconds = handle_start_seconds;
307
+ let resolvedHandleEndSeconds = handle_end_seconds;
308
+ const resolvedQrOpacity = qr_opacity ?? profile.qrOpacity;
309
+ const resolvedHandleOpacity = handle_opacity ?? profile.handleOpacity;
310
+ const callerSetTiming = qr_start_seconds !== undefined
311
+ || qr_end_seconds !== undefined
312
+ || handle_start_seconds !== undefined
313
+ || handle_end_seconds !== undefined;
314
+ const storedQrStart = config.profile?.qrStartSeconds;
315
+ const storedQrEnd = config.profile?.qrEndSeconds;
316
+ const storedAbsolute = !callerSetTiming
317
+ && (typeof storedQrStart === "number" || typeof storedQrEnd === "number");
318
+ if (!include_qr && !include_handle) {
319
+ return {
320
+ isError: true,
321
+ content: [{ type: "text", text: "include_qr or include_handle must be true" }],
322
+ };
323
+ }
324
+ const timingError = (include_handle
325
+ ? watermarkTimingError("handle", resolvedHandleStartSeconds, resolvedHandleEndSeconds)
326
+ : null) ?? (include_qr
327
+ ? watermarkTimingError("qr", resolvedQrStartSeconds, resolvedQrEndSeconds)
328
+ : null);
329
+ if (timingError) {
330
+ return {
331
+ isError: true,
332
+ content: [{ type: "text", text: timingError }],
333
+ };
334
+ }
335
+ const kindGate = await requireArtifactKind(engine, asset_id, ["video"], "apply_watermark");
336
+ if (kindGate)
337
+ return kindGate;
338
+ const manifestResult = await engine.files.manifest(asset_id);
339
+ if (!manifestResult.ok) {
340
+ return { isError: true, content: [{ type: "text", text: manifestResult.error.message }] };
341
+ }
342
+ const videoFile = findVideoFile(manifestResult.value.files);
343
+ if (!videoFile) {
344
+ return { isError: true, content: [{ type: "text", text: `No video file found in asset ${asset_id}` }] };
345
+ }
346
+ const assetDir = manifestResult.value.path;
347
+ const videoPath = join(assetDir, videoFile.name);
348
+ try {
349
+ const video = await probeVideo(videoPath);
350
+ const profileTimesTheClip = profile.handleEndSeconds != null || profile.qrLeadSeconds != null;
351
+ if (storedAbsolute) {
352
+ if (typeof storedQrStart === "number")
353
+ resolvedQrStartSeconds = storedQrStart;
354
+ if (typeof storedQrEnd === "number")
355
+ resolvedQrEndSeconds = storedQrEnd;
356
+ if (typeof config.profile?.handleStartSeconds === "number")
357
+ resolvedHandleStartSeconds = config.profile.handleStartSeconds;
358
+ if (typeof config.profile?.handleEndSeconds === "number")
359
+ resolvedHandleEndSeconds = config.profile.handleEndSeconds;
360
+ }
361
+ else if (profileTimesTheClip
362
+ && resolvedQrStartSeconds === undefined
363
+ && resolvedQrEndSeconds === undefined
364
+ && resolvedHandleStartSeconds === undefined
365
+ && resolvedHandleEndSeconds === undefined) {
366
+ const windows = watermarkWindowsFor(profile, video.videoDuration);
367
+ resolvedQrStartSeconds = windows.qrStartSeconds;
368
+ resolvedQrEndSeconds = windows.qrEndSeconds;
369
+ resolvedHandleStartSeconds = windows.handleStartSeconds;
370
+ resolvedHandleEndSeconds = windows.handleEndSeconds;
371
+ }
372
+ if (include_qr)
373
+ effectiveWatermarkTiming("QR watermark", video.videoDuration, resolvedQrStartSeconds, resolvedQrEndSeconds);
374
+ if (include_handle)
375
+ effectiveWatermarkTiming("Handle watermark", video.videoDuration, resolvedHandleStartSeconds, resolvedHandleEndSeconds);
376
+ if (mode === "overlay" && include_qr && include_handle && ((resolvedQrStartSeconds ?? 0) !== (resolvedHandleStartSeconds ?? 0)
377
+ || Math.min(resolvedQrEndSeconds ?? video.videoDuration, video.videoDuration)
378
+ !== Math.min(resolvedHandleEndSeconds ?? video.videoDuration, video.videoDuration)
379
+ || resolvedQrOpacity !== resolvedHandleOpacity)) {
380
+ throw new Error("Independent QR and handle timing or opacity requires mode=burn");
381
+ }
382
+ }
383
+ catch (error) {
384
+ return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
385
+ }
386
+ const fallbackHandle = config.savedHandle ?? config.authenticatedHandle ?? config.recent[0]
387
+ ?? profile.handle;
388
+ const resolvedHandle = normalizeHandle(handle ?? (include_handle || url === undefined ? fallbackHandle : "")) ?? "";
389
+ if (include_handle && !resolvedHandle) {
390
+ return {
391
+ isError: true,
392
+ content: [{ type: "text", text: "handle must be a non-empty string" }],
393
+ };
394
+ }
395
+ const storedProfileUrl = typeof config.profile?.profileUrl === "string" ? config.profile.profileUrl.trim() : "";
396
+ const resolvedUrl = (url ?? (storedProfileUrl || (resolvedHandle ? defaultUrl(resolvedHandle, platform) : ""))).trim();
397
+ if (include_qr && !resolvedUrl) {
398
+ return {
399
+ isError: true,
400
+ content: [{ type: "text", text: "url or handle must provide a QR destination" }],
401
+ };
402
+ }
403
+ const resolvedHandlePosition = include_qr
404
+ ? handle_position
405
+ : "center";
406
+ const appearance = {
407
+ platform,
408
+ includePlatformIcon,
409
+ includeHandleBorder: include_handle_border,
410
+ handlePadding: handle_padding,
411
+ handlePaddingY: handle_padding_y,
412
+ handleRounding: handle_rounding,
413
+ };
414
+ const savedProfile = watermarkProfileToSave(profile, {
415
+ handle: resolvedHandle,
416
+ profileUrl: resolvedUrl,
417
+ includePlatformIcon,
418
+ includeHandleBorder: include_handle_border,
419
+ handlePaddingX: handle_padding,
420
+ handlePaddingY: handle_padding_y,
421
+ handleRounding: handle_rounding,
422
+ scale,
423
+ mode,
424
+ handlePlacement: handle_position,
425
+ handleVerticalPercent: handle_vertical_percent,
426
+ handleOpacity: resolvedHandleOpacity,
427
+ qrOpacity: resolvedQrOpacity,
428
+ fadeSeconds: fade_seconds,
429
+ }, {
430
+ explicit: callerSetTiming,
431
+ qrStartSeconds: resolvedQrStartSeconds,
432
+ qrEndSeconds: resolvedQrEndSeconds,
433
+ handleStartSeconds: resolvedHandleStartSeconds,
434
+ handleEndSeconds: resolvedHandleEndSeconds,
435
+ });
436
+ let cardAssetId;
437
+ let qrAssetId = null;
438
+ let handleAssetId = null;
439
+ const variant = watermarkVariantForApply(mode, include_qr, include_handle);
440
+ if (card_asset_id) {
441
+ const cardGate = await requireArtifactKind(engine, card_asset_id, ["image"], "card_asset_id");
442
+ if (cardGate)
443
+ return cardGate;
444
+ cardAssetId = card_asset_id;
445
+ if (mode === "burn" && include_qr)
446
+ qrAssetId = card_asset_id;
447
+ if (mode === "burn" && include_handle && !include_qr)
448
+ handleAssetId = card_asset_id;
449
+ }
450
+ else {
451
+ const resolved = await resolveWatermarkAsset(engine, resolvedHandle, resolvedUrl, variant, appearance);
452
+ if (!resolved.ok) {
453
+ return {
454
+ isError: true,
455
+ content: [{ type: "text", text: resolved.error }],
456
+ };
457
+ }
458
+ cardAssetId = resolved.assetId;
459
+ if (mode === "burn" && include_qr)
460
+ qrAssetId = resolved.assetId;
461
+ if (mode === "burn" && include_handle && !include_qr)
462
+ handleAssetId = resolved.assetId;
463
+ }
464
+ if (mode === "burn" && include_handle && handleAssetId === null) {
465
+ const resolved = await resolveWatermarkAsset(engine, resolvedHandle, resolvedUrl, "handle", appearance);
466
+ if (!resolved.ok) {
467
+ return {
468
+ isError: true,
469
+ content: [{ type: "text", text: resolved.error }],
470
+ };
471
+ }
472
+ handleAssetId = resolved.assetId;
473
+ }
474
+ const badgeAspect = variant === "qr"
475
+ ? QR_ONLY_BADGE_ASPECT
476
+ : watermarkBadgeAspect(include_qr, handle_padding_y);
477
+ const scaling = { aspect: badgeAspect, userScale: scale };
478
+ const summary = {
479
+ asset_id,
480
+ card_asset_id: cardAssetId,
481
+ handle: resolvedHandle || null,
482
+ url: resolvedUrl,
483
+ include_qr,
484
+ include_handle,
485
+ qr_opacity: resolvedQrOpacity,
486
+ handle_opacity: resolvedHandleOpacity,
487
+ scale,
488
+ requested_position: position,
489
+ handle_vertical_percent,
490
+ handle_position: include_handle ? resolvedHandlePosition : null,
491
+ platform,
492
+ include_platform_icon: includePlatformIcon,
493
+ include_handle_border,
494
+ handle_padding,
495
+ handle_padding_y,
496
+ handle_rounding,
497
+ fade_seconds,
498
+ qr_start_seconds: include_qr ? resolvedQrStartSeconds ?? null : null,
499
+ qr_end_seconds: include_qr ? resolvedQrEndSeconds ?? null : null,
500
+ handle_start_seconds: include_handle ? resolvedHandleStartSeconds ?? null : null,
501
+ handle_end_seconds: include_handle ? resolvedHandleEndSeconds ?? null : null,
502
+ };
503
+ if (mode === "overlay") {
504
+ const overlayOpacity = include_qr ? resolvedQrOpacity : resolvedHandleOpacity;
505
+ const overlayStartSeconds = include_qr ? resolvedQrStartSeconds : resolvedHandleStartSeconds;
506
+ const overlayEndSeconds = include_qr ? resolvedQrEndSeconds : resolvedHandleEndSeconds;
507
+ const placement = await resolveOverlayPlacement(engine, book_id, asset_id, assetDir, videoFile, position, scaling);
508
+ const written = await writeOsgSettings(engine, asset_id, (orientation) => ({
509
+ osgImagePath: null,
510
+ osgAssetId: cardAssetId,
511
+ osgPosition: placement.positions[orientation],
512
+ osgSize: WATERMARK_OSG_SIZE,
513
+ osgScale: watermarkOsgScale(orientation, scale),
514
+ osgOpacity: overlayOpacity,
515
+ osgStartSeconds: overlayStartSeconds ?? null,
516
+ osgEndSeconds: overlayEndSeconds ?? null,
517
+ osgTailSeconds: null,
518
+ }));
519
+ if (!written.ok) {
520
+ return {
521
+ isError: true,
522
+ content: [{ type: "text", text: written.error }],
523
+ };
524
+ }
525
+ await recordHandle(resolvedHandle, { saveDefault: save_default, platform }, engine).catch(() => undefined);
526
+ if (save_default && resolvedHandle) {
527
+ await saveWatermarkProfile(savedProfile, platform, engine).catch(() => undefined);
528
+ }
529
+ return {
530
+ content: [{
531
+ type: "text",
532
+ text: JSON.stringify({
533
+ ...summary,
534
+ ...(placement.jobId !== undefined ? { job_id: placement.jobId } : {}),
535
+ mode: "overlay",
536
+ opacity: overlayOpacity,
537
+ positions: placement.positions,
538
+ placement_source: placement.placementSource,
539
+ }),
540
+ }],
541
+ };
542
+ }
543
+ // Burn mode: composite the badge into the source video so the change is
544
+ // visible in the notebook cell (renders and proxies both inherit it).
545
+ const badge = await resolveOsgImagePath(engine, cardAssetId);
546
+ if (!badge.ok) {
547
+ return {
548
+ isError: true,
549
+ content: [{ type: "text", text: badge.error }],
550
+ };
551
+ }
552
+ let handlePath;
553
+ if (include_handle) {
554
+ if (handleAssetId === null) {
555
+ return {
556
+ isError: true,
557
+ content: [{ type: "text", text: "Handle watermark asset is unavailable" }],
558
+ };
559
+ }
560
+ const handleBadge = handleAssetId === cardAssetId
561
+ ? badge
562
+ : await resolveOsgImagePath(engine, handleAssetId);
563
+ if (!handleBadge.ok) {
564
+ return {
565
+ isError: true,
566
+ content: [{ type: "text", text: handleBadge.error }],
567
+ };
568
+ }
569
+ handlePath = handleBadge.path;
570
+ }
571
+ // A handle-only burn has nothing to place; the corner is inert.
572
+ const placement = include_qr
573
+ ? await resolveBurnPlacement(engine, asset_id, videoFile, videoPath, position, scaling)
574
+ : { corner: DEFAULT_POSITION, placementSource: "fixed", visionRequest: null };
575
+ const burnJobId = `burn-watermark-${asset_id}-${Date.now()}`;
576
+ const payload = {
577
+ type: "burn_watermark",
578
+ assetId: asset_id,
579
+ assetDir,
580
+ bookId: book_id,
581
+ videoPath,
582
+ ...(include_qr ? { qrPath: badge.path } : {}),
583
+ ...(handlePath !== undefined ? { handlePath } : {}),
584
+ corner: placement.corner,
585
+ placementSource: placement.placementSource,
586
+ ...(placement.visionRequest ? { placement: placement.visionRequest } : {}),
587
+ qrOpacity: resolvedQrOpacity,
588
+ handleOpacity: resolvedHandleOpacity,
589
+ userScale: scale,
590
+ fadeSeconds: fade_seconds,
591
+ ...(include_handle ? { handleVerticalPercent: handle_vertical_percent } : {}),
592
+ ...(include_handle ? { handlePlacement: resolvedHandlePosition } : {}),
593
+ ...(include_handle ? { handlePaddingY: handle_padding_y } : {}),
594
+ ...(resolvedQrStartSeconds !== undefined
595
+ ? { qrStartSeconds: resolvedQrStartSeconds }
596
+ : {}),
597
+ ...(resolvedQrEndSeconds !== undefined
598
+ ? { qrEndSeconds: resolvedQrEndSeconds }
599
+ : {}),
600
+ ...(include_handle && resolvedHandleStartSeconds !== undefined
601
+ ? { handleStartSeconds: resolvedHandleStartSeconds }
602
+ : {}),
603
+ ...(include_handle && resolvedHandleEndSeconds !== undefined
604
+ ? { handleEndSeconds: resolvedHandleEndSeconds }
605
+ : {}),
606
+ };
607
+ const burnSummary = {
608
+ ...summary,
609
+ mode: "burn",
610
+ operation_id: burnJobId,
611
+ qr_asset_id: qrAssetId,
612
+ handle_asset_id: handleAssetId,
613
+ handle_position: include_handle ? resolvedHandlePosition : null,
614
+ };
615
+ await engine.jobs.failures.clear(asset_id);
616
+ await recordHandle(resolvedHandle, { saveDefault: save_default, platform }, engine).catch(() => undefined);
617
+ if (save_default && resolvedHandle) {
618
+ await saveWatermarkProfile(savedProfile, platform, engine).catch(() => undefined);
619
+ }
620
+ if (!wait) {
621
+ // Return as soon as the burn is queued; the frame-aware corner is
622
+ // settled inside the job, so it stays pending here.
623
+ const job = await queue.enqueue(burnJobId, payload);
624
+ const pending = placement.visionRequest !== null;
625
+ return {
626
+ content: [{
627
+ type: "text",
628
+ text: JSON.stringify({
629
+ ...burnSummary,
630
+ status: "accepted",
631
+ job_id: job.id,
632
+ position: include_qr && !pending ? placement.corner : null,
633
+ placement_source: pending ? "pending" : placement.placementSource,
634
+ }),
635
+ }],
636
+ };
637
+ }
638
+ let burned;
639
+ try {
640
+ burned = await queue.enqueueAndWait(burnJobId, payload, BURN_TIMEOUT_MS);
641
+ const readiness = await waitForAssetReadyDef(engine).handler({
642
+ asset_id,
643
+ book_id,
644
+ timeout_seconds: 300,
645
+ });
646
+ if (readiness.isError) {
647
+ const text = readiness.content.find((entry) => entry.type === "text")?.text;
648
+ throw new Error(text ?? "Post-watermark processing failed");
649
+ }
650
+ }
651
+ catch (err) {
652
+ return {
653
+ isError: true,
654
+ content: [{ type: "text", text: `Watermark failed: ${err instanceof Error ? err.message : String(err)}` }],
655
+ };
656
+ }
657
+ return {
658
+ content: [{
659
+ type: "text",
660
+ text: JSON.stringify({
661
+ ...burnSummary,
662
+ status: "completed",
663
+ position: include_qr ? burned.corner : null,
664
+ placement_source: burned.placementSource,
665
+ }),
666
+ }],
667
+ };
668
+ },
669
+ };
670
+ }