videobook 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
package/dist/srt.js ADDED
@@ -0,0 +1,16 @@
1
+ export function srtTimestamp(seconds) {
2
+ const millis = Math.max(0, Math.round(seconds * 1000));
3
+ const hours = Math.floor(millis / 3_600_000);
4
+ const minutes = Math.floor((millis % 3_600_000) / 60_000);
5
+ const secs = Math.floor((millis % 60_000) / 1000);
6
+ const remainder = millis % 1000;
7
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")},${String(remainder).padStart(3, "0")}`;
8
+ }
9
+ export function srtContent(cues) {
10
+ return cues.map((cue, index) => [
11
+ String(index + 1),
12
+ `${srtTimestamp(cue.startSeconds)} --> ${srtTimestamp(cue.endSeconds)}`,
13
+ cue.text,
14
+ "",
15
+ ].join("\n")).join("\n");
16
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Durable state for tagging a library that already exists.
3
+ *
4
+ * A backfill is long, interruptible work over a catalog that changes while it
5
+ * runs: assets get created, deleted and re-cut mid-pass. Two decisions follow
6
+ * from that.
7
+ *
8
+ * The cursor is the last artifact id visited, not an offset. Offsets over a
9
+ * changing catalog silently skip and repeat rows — delete one asset behind the
10
+ * cursor and every later page shifts under it — while an id cursor resumes at
11
+ * exactly the right place whatever happened meanwhile.
12
+ *
13
+ * State lives in the book's own settings, so a restart mid-pass resumes rather
14
+ * than starting over, and a book that is never opened again carries no
15
+ * orphaned progress anywhere else.
16
+ */
17
+ const SETTINGS_KEY = "asset-tag-backfill";
18
+ export const EMPTY_COUNTS = {
19
+ current: 0,
20
+ tagged: 0,
21
+ empty: 0,
22
+ needsAnalysis: 0,
23
+ unsupported: 0,
24
+ skipped: 0,
25
+ failed: 0,
26
+ };
27
+ const IDLE_STATE = {
28
+ runId: null,
29
+ status: "idle",
30
+ cursor: null,
31
+ scanned: 0,
32
+ counts: EMPTY_COUNTS,
33
+ allowAnalysis: false,
34
+ force: false,
35
+ startedAt: null,
36
+ updatedAt: null,
37
+ finishedAt: null,
38
+ lastError: null,
39
+ };
40
+ function count(value) {
41
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
42
+ ? Math.floor(value)
43
+ : 0;
44
+ }
45
+ function counts(value) {
46
+ const row = (typeof value === "object" && value !== null ? value : {});
47
+ return {
48
+ current: count(row.current),
49
+ tagged: count(row.tagged),
50
+ empty: count(row.empty),
51
+ needsAnalysis: count(row.needsAnalysis),
52
+ unsupported: count(row.unsupported),
53
+ skipped: count(row.skipped),
54
+ failed: count(row.failed),
55
+ };
56
+ }
57
+ function status(value) {
58
+ return value === "running" || value === "paused" || value === "cancelled"
59
+ || value === "completed"
60
+ ? value
61
+ : "idle";
62
+ }
63
+ function timestamp(value) {
64
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
65
+ }
66
+ export function readBackfillState(engine) {
67
+ const stored = engine.settings.get(SETTINGS_KEY);
68
+ if (!stored)
69
+ return { ...IDLE_STATE };
70
+ return {
71
+ runId: typeof stored.runId === "string" ? stored.runId : null,
72
+ status: status(stored.status),
73
+ cursor: typeof stored.cursor === "string" ? stored.cursor : null,
74
+ scanned: count(stored.scanned),
75
+ counts: counts(stored.counts),
76
+ allowAnalysis: stored.allowAnalysis === true,
77
+ force: stored.force === true,
78
+ startedAt: timestamp(stored.startedAt),
79
+ updatedAt: timestamp(stored.updatedAt),
80
+ finishedAt: timestamp(stored.finishedAt),
81
+ lastError: typeof stored.lastError === "string" ? stored.lastError : null,
82
+ };
83
+ }
84
+ export function writeBackfillState(engine, state) {
85
+ const next = { ...state, updatedAt: Date.now() };
86
+ const written = engine.settings.set(SETTINGS_KEY, next);
87
+ if (!written.ok)
88
+ throw new Error(written.error.message);
89
+ return next;
90
+ }
91
+ /** Adds one asset's outcome to a running tally. */
92
+ export function recordOutcome(current, outcome) {
93
+ const next = { ...current };
94
+ switch (outcome) {
95
+ case "tagged":
96
+ next.tagged += 1;
97
+ break;
98
+ case "already_current":
99
+ next.current += 1;
100
+ break;
101
+ case "no_useful_evidence":
102
+ next.empty += 1;
103
+ break;
104
+ case "no_evidence":
105
+ case "analysis_requested":
106
+ case "analysis_unavailable":
107
+ next.needsAnalysis += 1;
108
+ break;
109
+ case "unsupported_kind":
110
+ next.unsupported += 1;
111
+ break;
112
+ case "missing_asset":
113
+ case "no_source_content":
114
+ next.skipped += 1;
115
+ break;
116
+ default:
117
+ next.failed += 1;
118
+ break;
119
+ }
120
+ return next;
121
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Per-book configuration for automatic tagging.
3
+ *
4
+ * Automatic tagging is on by default for new assets, because an asset nobody
5
+ * can find is the problem the feature exists to solve. Turning it off stops
6
+ * the coordinator from being queued at all — it does not disable tags: manual
7
+ * tagging, filtering and search keep working exactly as before, which is also
8
+ * what a book with no provider key gets.
9
+ */
10
+ const SETTINGS_KEY = "asset-tagging";
11
+ const DEFAULT_TAGGING_SETTINGS = {
12
+ automaticEnabled: true,
13
+ analysisOnNewAssets: true,
14
+ };
15
+ export function readTaggingSettings(engine) {
16
+ const stored = engine.settings.get(SETTINGS_KEY);
17
+ if (!stored)
18
+ return { ...DEFAULT_TAGGING_SETTINGS };
19
+ return {
20
+ automaticEnabled: stored.automaticEnabled !== false,
21
+ analysisOnNewAssets: stored.analysisOnNewAssets !== false,
22
+ };
23
+ }
24
+ export function writeTaggingSettings(engine, update) {
25
+ const next = { ...readTaggingSettings(engine), ...update };
26
+ const written = engine.settings.set(SETTINGS_KEY, next);
27
+ if (!written.ok)
28
+ throw new Error(written.error.message);
29
+ return next;
30
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The editorial vocabulary automatic tagging is allowed to emit.
3
+ *
4
+ * The engine normalizes labels into identities; it does not decide which words
5
+ * are worth storing. That judgement lives here, because a tag only earns its
6
+ * place if it helps someone — or a tool — find, group, match or sequence
7
+ * footage. Letting each model invent its own grouping labels produces
8
+ * "closeup", "close up" and "tight shot" on three different clips that should
9
+ * have landed together, so standard terms are pinned to one canonical spelling
10
+ * and everything outside the vocabulary is dropped rather than guessed at.
11
+ *
12
+ * The version below is part of the tag job's fence: bump it whenever the
13
+ * vocabulary or the selection rules change meaningfully, and previously
14
+ * tagged assets become eligible for a re-run.
15
+ */
16
+ import { AUTOMATIC_TAGS_PER_SNAPSHOT_MAX, normalizeTagLabel, normalizeTagPrefix, tagCanonicalKey } from "videobook-engine/tag-values";
17
+ export const TAG_ANALYZER_VERSION = "editorial-tags-v2";
18
+ /** The ceiling is a ceiling, never a quota: sparse evidence emits fewer. */
19
+ export const MAX_AUTOMATIC_TAGS = AUTOMATIC_TAGS_PER_SNAPSHOT_MAX;
20
+ /**
21
+ * Visible composition and role. Named people never come from a model: a name
22
+ * in a transcript does not identify who is on screen, so identities may only
23
+ * arrive from user-confirmed project metadata.
24
+ */
25
+ const PEOPLE = [
26
+ { label: "no people", aliases: ["empty", "unpopulated", "nobody", "no one"] },
27
+ { label: "solo subject", aliases: ["one person", "single person", "solo", "individual"] },
28
+ { label: "two people", aliases: ["pair", "couple", "duo", "two subjects"] },
29
+ { label: "small group", aliases: ["group", "three people", "few people", "small gathering"] },
30
+ { label: "crowd", aliases: ["crowded", "many people", "audience", "large group"] },
31
+ { label: "interviewer", aliases: ["questioner"] },
32
+ { label: "interviewee", aliases: ["subject being interviewed", "respondent"] },
33
+ { label: "presenter", aliases: ["host"] },
34
+ ];
35
+ /** Settings useful for matching coverage, plus the two axes worth grouping by. */
36
+ const PLACES = [
37
+ { label: "interior", aliases: ["indoors", "inside", "indoor"] },
38
+ { label: "exterior", aliases: ["outdoors", "outside", "outdoor"] },
39
+ { label: "day", aliases: ["daytime", "daylight", "morning", "afternoon"] },
40
+ { label: "night", aliases: ["nighttime", "after dark", "evening"] },
41
+ { label: "kitchen", aliases: ["galley"] },
42
+ { label: "street", aliases: ["sidewalk", "roadside", "city street"] },
43
+ { label: "studio", aliases: ["soundstage"] },
44
+ { label: "office", aliases: ["workplace"] },
45
+ { label: "bedroom", aliases: [] },
46
+ { label: "living room", aliases: ["lounge"] },
47
+ { label: "restaurant", aliases: ["cafe", "diner", "bar"] },
48
+ { label: "store", aliases: ["shop", "market", "supermarket"] },
49
+ { label: "car", aliases: ["vehicle interior"] },
50
+ { label: "beach", aliases: ["shore", "seaside"] },
51
+ { label: "park", aliases: ["garden"] },
52
+ { label: "forest", aliases: ["woods", "woodland"] },
53
+ { label: "stage", aliases: ["venue", "concert stage"] },
54
+ { label: "gym", aliases: ["fitness studio"] },
55
+ { label: "rooftop", aliases: ["roof"] },
56
+ ];
57
+ /**
58
+ * Framing, supported camera movement, coverage and audio character. Movement
59
+ * terms are only ever accepted from evidence that can actually establish them;
60
+ * sparse, evenly spaced frames cannot, so the extractors do not offer them.
61
+ */
62
+ const EDITING = [
63
+ { label: "wide shot", aliases: ["wide", "long shot", "full shot", "establishing wide"] },
64
+ { label: "medium shot", aliases: ["medium", "mid shot", "waist up"] },
65
+ { label: "close-up", aliases: ["close up", "closeup", "tight shot", "cu"] },
66
+ { label: "extreme close-up", aliases: ["extreme close up", "extreme closeup", "macro shot"] },
67
+ { label: "establishing shot", aliases: ["establishing", "scene setter"] },
68
+ { label: "insert", aliases: ["insert shot", "cutaway", "detail shot"] },
69
+ { label: "product shot", aliases: ["product", "packshot"] },
70
+ { label: "static", aliases: ["locked off", "tripod", "still camera", "fixed camera"] },
71
+ { label: "pan", aliases: ["panning", "whip pan"] },
72
+ { label: "tilt", aliases: ["tilting"] },
73
+ { label: "handheld", aliases: ["hand held", "shaky", "run and gun"] },
74
+ { label: "tracking", aliases: ["dolly", "steadicam", "follow shot", "tracking shot"] },
75
+ { label: "interview", aliases: ["sit down interview"] },
76
+ { label: "talking head", aliases: [] },
77
+ { label: "dialogue", aliases: ["conversation", "speech", "talking"] },
78
+ { label: "ambience", aliases: ["ambient", "room tone", "atmosphere", "background sound"] },
79
+ { label: "music", aliases: ["musical", "score", "soundtrack"] },
80
+ { label: "voiceover", aliases: ["voice over", "narration", "narrator"] },
81
+ ];
82
+ const VOCABULARY = {
83
+ people: PEOPLE,
84
+ places: PLACES,
85
+ editing: EDITING,
86
+ // Automatic tagging never mints custom tags; that facet is the user's.
87
+ custom: [],
88
+ };
89
+ /**
90
+ * Words that describe how footage feels or how good it is. They group nothing:
91
+ * every clip in a project can be "beautiful", and no editor searches for it.
92
+ */
93
+ const BANNED_TERMS = new Set([
94
+ "beautiful", "stunning", "amazing", "gorgeous", "great", "good", "nice",
95
+ "cool", "epic", "dramatic", "moody", "vibe", "vibes", "aesthetic", "mood",
96
+ "cinematic", "masterpiece", "high quality", "professional", "hd", "4k",
97
+ "video", "footage", "clip", "content", "scene", "shot", "media", "image",
98
+ "photo", "picture", "frame", "camera", "misc", "other", "general",
99
+ "interesting", "modern", "classic", "colorful", "bright", "dark",
100
+ "atmospheric", "emotional", "energetic", "calm", "peaceful", "happy", "sad",
101
+ ]);
102
+ function normalizeTerm(value) {
103
+ return value
104
+ .normalize("NFKC")
105
+ .toLowerCase()
106
+ .replace(/[_/]+/gu, " ")
107
+ .replace(/[^\p{L}\p{N}\s-]+/gu, "")
108
+ .replace(/\s+/gu, " ")
109
+ .trim();
110
+ }
111
+ const ALIAS_INDEX = new Map(Object.entries(VOCABULARY)
112
+ .flatMap(([facet, entries]) => entries.flatMap((entry) => [entry.label, ...entry.aliases].map((term) => [`${facet}:${normalizeTerm(term)}`, { facet, label: entry.label }]))));
113
+ /** Every canonical label a facet may carry, for prompts and documentation. */
114
+ export function vocabularyFor(facet) {
115
+ return VOCABULARY[facet].map((entry) => entry.label);
116
+ }
117
+ export function isFrameTag(tag) {
118
+ if (tag.facet === "places")
119
+ return true;
120
+ if (tag.facet === "people")
121
+ return PEOPLE.slice(0, 5).some((entry) => entry.label === tag.label);
122
+ return tag.facet === "editing" && EDITING.slice(0, 7).some((entry) => entry.label === tag.label);
123
+ }
124
+ export function frameVocabularyFor(facet) {
125
+ return vocabularyFor(facet).filter((label) => isFrameTag({ facet, label }));
126
+ }
127
+ export function resolveTagInput(input, existing = []) {
128
+ const label = normalizeTagLabel(input.label);
129
+ const key = tagCanonicalKey(label);
130
+ const facets = input.facet ? [input.facet] : ["people", "places", "editing"];
131
+ for (const facet of facets) {
132
+ const known = canonicalizeTag(facet, label);
133
+ if (known)
134
+ return { ...known, label: tagCanonicalKey(known.label) === key ? label : known.label };
135
+ }
136
+ const previous = !input.facet && existing.find((tag) => tagCanonicalKey(tag.label) === key);
137
+ return { facet: input.facet ?? (previous ? previous.facet : "custom"), label };
138
+ }
139
+ export function matchesTagPrefix(tag, prefix) {
140
+ const normalized = normalizeTagPrefix(prefix);
141
+ if (tagCanonicalKey(tag.label).startsWith(normalized))
142
+ return true;
143
+ const canonical = canonicalizeTag(tag.facet, tag.label);
144
+ if (!canonical)
145
+ return false;
146
+ return VOCABULARY[tag.facet].some((entry) => entry.label === canonical.label
147
+ && [entry.label, ...entry.aliases].some((alias) => normalizeTagPrefix(alias).startsWith(normalized)));
148
+ }
149
+ /**
150
+ * A place the vocabulary does not list can still be worth keeping — "subway",
151
+ * "rooftop bar" — but only when it reads as a concrete setting rather than an
152
+ * impression. One or two plain words, nothing banned, nothing numeric.
153
+ */
154
+ function acceptsUnlistedPlace(term) {
155
+ if (BANNED_TERMS.has(term))
156
+ return false;
157
+ const words = term.split(" ");
158
+ if (words.length === 0 || words.length > 2)
159
+ return false;
160
+ return words.every((word) => word.length >= 3 && word.length <= 20 && /^[\p{L}-]+$/u.test(word)
161
+ && !BANNED_TERMS.has(word) && !["not", "without", "never", "maybe", "possibly"].includes(word));
162
+ }
163
+ /**
164
+ * Maps one raw term onto the vocabulary, or rejects it. People and editing are
165
+ * closed sets: an unrecognized term there is a model inventing a grouping
166
+ * label, which is exactly what the vocabulary exists to prevent.
167
+ *
168
+ * `allowUnlisted` opens the places facet to settings the vocabulary does not
169
+ * list, and belongs only where the caller knows the term is a setting — a
170
+ * model that labelled it `places`, or the analysis field that holds nothing
171
+ * else. Offered speculatively it would accept anything short: try "cooking" as
172
+ * a place and it becomes one.
173
+ */
174
+ export function canonicalizeTag(facet, raw, options = {}) {
175
+ const term = normalizeTerm(raw);
176
+ if (!term || term.length > 40)
177
+ return null;
178
+ const known = ALIAS_INDEX.get(`${facet}:${term}`);
179
+ if (known)
180
+ return known;
181
+ if (options.allowUnlisted && facet === "places" && acceptsUnlistedPlace(term)) {
182
+ return { facet, label: term };
183
+ }
184
+ return null;
185
+ }
186
+ /**
187
+ * Finds vocabulary terms inside a phrase. Analysis fields are written as prose
188
+ * — "A domestic kitchen", "shot handheld on a busy street" — so a whole-string
189
+ * match would discard almost everything useful. Matching is on word
190
+ * boundaries, so "interior" is found in "interior hallway" while "day" is not
191
+ * found in "daydream".
192
+ */
193
+ export function matchVocabularyTerms(facet, phrase) {
194
+ const text = normalizeTerm(phrase);
195
+ if (!text || /\b(?:no|not|without|never|isnt|isn't|maybe|possibly)\b/u.test(text))
196
+ return [];
197
+ const matches = [];
198
+ for (const entry of VOCABULARY[facet]) {
199
+ for (const term of [entry.label, ...entry.aliases]) {
200
+ const normalized = normalizeTerm(term);
201
+ if (!normalized)
202
+ continue;
203
+ const pattern = new RegExp(`(?<![\\p{L}\\p{N}])${normalized.replace(/[-]/gu, "[- ]")}(?![\\p{L}\\p{N}])`, "u");
204
+ if (pattern.test(text)) {
205
+ matches.push({ facet, label: entry.label });
206
+ break;
207
+ }
208
+ }
209
+ }
210
+ return matches;
211
+ }
212
+ /**
213
+ * Final selection: canonical identities, no duplicates across facets, capped.
214
+ * Order is preserved because extractors offer their best-supported evidence
215
+ * first, so a cap trims the weakest claims rather than an arbitrary tail.
216
+ */
217
+ export function selectEditorialTags(candidates, limit = MAX_AUTOMATIC_TAGS) {
218
+ const selected = [];
219
+ const seen = new Set();
220
+ for (const candidate of candidates) {
221
+ if (selected.length >= limit)
222
+ break;
223
+ // Candidates reaching selection have already been vetted by an extractor,
224
+ // so an unlisted place here is one that was accepted upstream.
225
+ const canonical = canonicalizeTag(candidate.facet, candidate.label, {
226
+ allowUnlisted: true,
227
+ });
228
+ if (!canonical)
229
+ continue;
230
+ const identity = `${canonical.facet}:${normalizeTerm(canonical.label)}`;
231
+ if (seen.has(identity))
232
+ continue;
233
+ seen.add(identity);
234
+ selected.push(canonical);
235
+ }
236
+ return selected;
237
+ }
@@ -0,0 +1 @@
1
+ export const testQueueCleanup = {};
@@ -0,0 +1,50 @@
1
+ // --- Time parsing (port of ~/videocity-mcp/src/time_parse.py) ---
2
+ const COLON_SUFFIX_RE = /\s*(?:s|sec|secs|second|seconds)\s*$/i;
3
+ const H = "(?:h|hr|hrs|hour|hours)";
4
+ const M = "(?:m|min|mins|minute|minutes)";
5
+ const S = "(?:s|sec|secs|second|seconds)";
6
+ const SHORTHAND_RE = new RegExp(`^(?:(\\d+)\\s*${H})?\\s*(?:(\\d+)\\s*${M})?\\s*(?:(\\d+(?:\\.\\d+)?)\\s*${S}?)?\\s*$`, "i");
7
+ function parseBareNumeric(text) {
8
+ const n = Number(text);
9
+ return Number.isFinite(n) ? n : undefined;
10
+ }
11
+ function parseColonFormat(text) {
12
+ if (!text.includes(":"))
13
+ return undefined;
14
+ const cleaned = text.replace(COLON_SUFFIX_RE, "");
15
+ const parts = cleaned.split(":");
16
+ if (parts.length !== 2 && parts.length !== 3)
17
+ return undefined;
18
+ if (!parts.every((p) => /^\d+(\.\d+)?$/.test(p)))
19
+ return undefined;
20
+ if (parts.length === 2) {
21
+ return parseFloat(parts[0]) * 60 + parseFloat(parts[1]);
22
+ }
23
+ return parseFloat(parts[0]) * 3600 + parseFloat(parts[1]) * 60 + parseFloat(parts[2]);
24
+ }
25
+ function parseShorthand(text) {
26
+ const match = SHORTHAND_RE.exec(text);
27
+ if (!match || !match.slice(1).some(Boolean))
28
+ return undefined;
29
+ const hours = parseInt(match[1] || "0", 10);
30
+ const minutes = parseInt(match[2] || "0", 10);
31
+ const seconds = parseFloat(match[3] || "0");
32
+ return hours * 3600 + minutes * 60 + seconds;
33
+ }
34
+ const PARSERS = [parseBareNumeric, parseColonFormat, parseShorthand];
35
+ function parseTime(input) {
36
+ if (input === undefined || input === "")
37
+ return undefined;
38
+ if (typeof input === "number")
39
+ return input;
40
+ const text = input.trim();
41
+ if (!text)
42
+ return undefined;
43
+ for (const parser of PARSERS) {
44
+ const result = parser(text);
45
+ if (result !== undefined)
46
+ return result;
47
+ }
48
+ throw new Error(`Cannot parse timestamp: ${JSON.stringify(input)}`);
49
+ }
50
+ export { parseTime };