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.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/bin/videobook.js +7 -0
- package/dist/acestep/client.js +203 -0
- package/dist/acestep/config.js +62 -0
- package/dist/acestep/errors.js +8 -0
- package/dist/acestep/process.js +175 -0
- package/dist/alive-captions/analyze.js +117 -0
- package/dist/alive-captions/audio.js +179 -0
- package/dist/alive-captions/frames.js +59 -0
- package/dist/alive-captions/model.js +37 -0
- package/dist/alive-captions/schema.js +49 -0
- package/dist/alive-captions/track.js +9 -0
- package/dist/api-key.js +33 -0
- package/dist/app-state.js +56 -0
- package/dist/app.js +257 -0
- package/dist/archive-b2.js +332 -0
- package/dist/archive-store.js +169 -0
- package/dist/artifact-label.js +36 -0
- package/dist/artifact-refs.js +49 -0
- package/dist/asset-tag-extraction.js +169 -0
- package/dist/asset-tags.js +228 -0
- package/dist/asset-usage.js +23 -0
- package/dist/audio-cue.js +71 -0
- package/dist/audio-encoding.js +32 -0
- package/dist/audio-sync-request.js +1 -0
- package/dist/audio-sync.js +223 -0
- package/dist/audio-trim-range.js +31 -0
- package/dist/book-context.js +31 -0
- package/dist/book-manager.js +374 -0
- package/dist/catalog-gc.js +26 -0
- package/dist/chain-executor.js +815 -0
- package/dist/chain-generation.js +115 -0
- package/dist/chain-plan.js +746 -0
- package/dist/chain-step-review.js +60 -0
- package/dist/character-name.js +25 -0
- package/dist/chat-generation-routing.js +73 -0
- package/dist/chat-generation-settings.js +78 -0
- package/dist/chat-models.js +44 -0
- package/dist/chat-prompt-rewrite.js +67 -0
- package/dist/chat-router.js +628 -0
- package/dist/chat-view-actions.js +49 -0
- package/dist/cli/args.js +242 -0
- package/dist/cli/config-command.js +103 -0
- package/dist/cli/doctor-command.js +289 -0
- package/dist/cli/entry.js +2 -0
- package/dist/cli/key-report.js +16 -0
- package/dist/cli/main.js +104 -0
- package/dist/cli/mcp-command.js +88 -0
- package/dist/cli/mcp-relay.js +51 -0
- package/dist/cli/open-browser.js +14 -0
- package/dist/cli/run-command.js +231 -0
- package/dist/cli/serve-command.js +62 -0
- package/dist/cli/setup-command.js +60 -0
- package/dist/cli/stdio-guard.js +8 -0
- package/dist/cold-open-model.js +7 -0
- package/dist/cold-open.js +79 -0
- package/dist/command-reference.js +95 -0
- package/dist/content-store.js +74 -0
- package/dist/cost.js +8 -0
- package/dist/crop-geometry.js +23 -0
- package/dist/dancing-workflow.js +447 -0
- package/dist/data-home.js +44 -0
- package/dist/diagnostics.js +109 -0
- package/dist/dialog-quality.js +34 -0
- package/dist/downloads-dest.js +56 -0
- package/dist/editing-moments.js +194 -0
- package/dist/elevenlabs-audio-extension.js +66 -0
- package/dist/engine.js +54 -0
- package/dist/env-file.js +91 -0
- package/dist/env-secrets.js +148 -0
- package/dist/ffmpeg-binary.js +12 -0
- package/dist/ffmpeg.js +425 -0
- package/dist/ffprobe-binary.js +77 -0
- package/dist/forget-asset.js +216 -0
- package/dist/frame-utils.js +27 -0
- package/dist/generation-credit-error.js +11 -0
- package/dist/generation-input-mentions.js +117 -0
- package/dist/generation-input-policy.js +68 -0
- package/dist/generation-reference-mentions.js +97 -0
- package/dist/goal-characters.js +49 -0
- package/dist/goal-plan-v3.js +273 -0
- package/dist/goal-plan.js +220 -0
- package/dist/goal-planner-prompt.js +28 -0
- package/dist/goal-prompt-budget.js +4 -0
- package/dist/goal-reference-images.js +25 -0
- package/dist/goal-state.js +261 -0
- package/dist/goal-stills.js +153 -0
- package/dist/goal-store.js +144 -0
- package/dist/goal-styles.js +144 -0
- package/dist/goal-v3-runtime.js +157 -0
- package/dist/guides/character_schema_guide.md +70 -0
- package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
- package/dist/guides/grok_imagine_prompting_guide.md +72 -0
- package/dist/guides/image_reproduction_guide.md +84 -0
- package/dist/guides/kling_3_0_prompting_guide.md +136 -0
- package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
- package/dist/guides/seedance_2_prompting_guide.md +172 -0
- package/dist/http-access.js +49 -0
- package/dist/http-byte-range.js +31 -0
- package/dist/hydration.js +56 -0
- package/dist/image-compress.js +77 -0
- package/dist/image-edit-default.js +1 -0
- package/dist/image-edit-models.js +41 -0
- package/dist/image-edit-settings.js +11 -0
- package/dist/image-region-edit.js +32 -0
- package/dist/image-subject-map.js +523 -0
- package/dist/image.js +46 -0
- package/dist/index.js +12 -0
- package/dist/job-deadline.js +60 -0
- package/dist/jobs/_alibaba-upload.js +30 -0
- package/dist/jobs/_audio-chunks.js +31 -0
- package/dist/jobs/_download-into-asset.js +15 -0
- package/dist/jobs/_fal-shared.js +22 -0
- package/dist/jobs/_fal-upload.js +35 -0
- package/dist/jobs/_image-edit-adapter.js +213 -0
- package/dist/jobs/_kie-poll.js +119 -0
- package/dist/jobs/_kie-upload.js +32 -0
- package/dist/jobs/_lease.js +163 -0
- package/dist/jobs/_persist-output.js +19 -0
- package/dist/jobs/_storage-retry.js +21 -0
- package/dist/jobs/_transcribe.js +245 -0
- package/dist/jobs/_translate-transcript.js +270 -0
- package/dist/jobs/analyze-alive-captions.js +10 -0
- package/dist/jobs/analyze-editing-moments.js +104 -0
- package/dist/jobs/analyze-jokes.js +143 -0
- package/dist/jobs/analyze-keyword-placement.js +155 -0
- package/dist/jobs/analyze-music.js +14 -0
- package/dist/jobs/analyze-video.js +282 -0
- package/dist/jobs/apply-cuts.js +83 -0
- package/dist/jobs/apply-sfx.js +107 -0
- package/dist/jobs/archive-clip.js +458 -0
- package/dist/jobs/assemble-goal.js +98 -0
- package/dist/jobs/audio-boost.js +71 -0
- package/dist/jobs/audio-mix.js +161 -0
- package/dist/jobs/backfill-asset-tags.js +144 -0
- package/dist/jobs/burn-watermark.js +316 -0
- package/dist/jobs/change-video-speed.js +176 -0
- package/dist/jobs/clean-video-audio.js +207 -0
- package/dist/jobs/cold-open.js +166 -0
- package/dist/jobs/combine-goal.js +108 -0
- package/dist/jobs/complete-provider-task.js +185 -0
- package/dist/jobs/copy-library-asset.js +33 -0
- package/dist/jobs/cost-estimate-assessment.js +113 -0
- package/dist/jobs/crop-image.js +47 -0
- package/dist/jobs/crop-video.js +71 -0
- package/dist/jobs/describe-image.js +181 -0
- package/dist/jobs/detect-scenes.js +33 -0
- package/dist/jobs/doctor-fix.js +100 -0
- package/dist/jobs/download-video.js +427 -0
- package/dist/jobs/enqueue-search-index.js +24 -0
- package/dist/jobs/even-split.js +122 -0
- package/dist/jobs/export-sequence.js +365 -0
- package/dist/jobs/extend-audio.js +229 -0
- package/dist/jobs/extract-audio.js +72 -0
- package/dist/jobs/extract-frame.js +58 -0
- package/dist/jobs/extract-notebook-audio.js +82 -0
- package/dist/jobs/extract-notebook-frame.js +93 -0
- package/dist/jobs/extract-video-clip.js +8 -0
- package/dist/jobs/extract-video-poster.js +55 -0
- package/dist/jobs/extract-waveform.js +25 -0
- package/dist/jobs/finish-goal.js +255 -0
- package/dist/jobs/forget-asset.js +11 -0
- package/dist/jobs/generate-acestep-cover.js +219 -0
- package/dist/jobs/generate-media.js +70 -0
- package/dist/jobs/image-subjects.js +1059 -0
- package/dist/jobs/index.js +18 -0
- package/dist/jobs/inject-first-frame.js +175 -0
- package/dist/jobs/isolate-vocals.js +69 -0
- package/dist/jobs/kie-paygate.js +177 -0
- package/dist/jobs/label-music-sections.js +5 -0
- package/dist/jobs/osg-placement.js +135 -0
- package/dist/jobs/pad-video.js +72 -0
- package/dist/jobs/plan-goal.js +97 -0
- package/dist/jobs/post-record.js +21 -0
- package/dist/jobs/post-to-x.js +21 -0
- package/dist/jobs/prepare-search-reference.js +190 -0
- package/dist/jobs/prepare-temporal-search.js +16 -0
- package/dist/jobs/process-audio.js +67 -0
- package/dist/jobs/process-image.js +58 -0
- package/dist/jobs/process-upload.js +166 -0
- package/dist/jobs/process-video.js +438 -0
- package/dist/jobs/register-all.js +161 -0
- package/dist/jobs/relayout-notebook.js +78 -0
- package/dist/jobs/remove-sound.js +299 -0
- package/dist/jobs/render-project.js +380 -0
- package/dist/jobs/render-qr-outro.js +138 -0
- package/dist/jobs/render-timeline.js +283 -0
- package/dist/jobs/repair-search-indexes.js +42 -0
- package/dist/jobs/replace-video-audio.js +64 -0
- package/dist/jobs/restore-archive-clip.js +53 -0
- package/dist/jobs/reverse-video.js +53 -0
- package/dist/jobs/rewrite-script.js +173 -0
- package/dist/jobs/run-notebook.js +3335 -0
- package/dist/jobs/semantic-index.js +797 -0
- package/dist/jobs/smooth-scene-concat.js +257 -0
- package/dist/jobs/splice-video.js +280 -0
- package/dist/jobs/split-video.js +127 -0
- package/dist/jobs/suggest-cuts.js +149 -0
- package/dist/jobs/suggest-sfx.js +135 -0
- package/dist/jobs/sync-video-to-audio.js +329 -0
- package/dist/jobs/tag-asset.js +266 -0
- package/dist/jobs/task-failure.js +16 -0
- package/dist/jobs/timewarp-video.js +152 -0
- package/dist/jobs/transcribe-video-audio.js +117 -0
- package/dist/jobs/transcribe.js +35 -0
- package/dist/jobs/translate-transcript.js +55 -0
- package/dist/jobs/trim-audio.js +133 -0
- package/dist/jobs/trim-video.js +291 -0
- package/dist/jobs/xai-paygate.js +26 -0
- package/dist/keyword-placement-cache.js +74 -0
- package/dist/lazy-render.js +142 -0
- package/dist/library-browse.js +78 -0
- package/dist/lightning-defaults.js +206 -0
- package/dist/load-env.js +12 -0
- package/dist/local-search-models.js +206 -0
- package/dist/local-upload.js +318 -0
- package/dist/logger.js +69 -0
- package/dist/media/adapters/alibaba.js +20 -0
- package/dist/media/adapters/elevenlabs.js +58 -0
- package/dist/media/adapters/fal.js +45 -0
- package/dist/media/adapters/index.js +26 -0
- package/dist/media/adapters/kie.js +120 -0
- package/dist/media/adapters/pending.js +21 -0
- package/dist/media/adapters/types.js +5 -0
- package/dist/media/adapters/xai.js +18 -0
- package/dist/media/catalog.js +236 -0
- package/dist/media/complete.js +191 -0
- package/dist/media/cost.js +45 -0
- package/dist/media/descriptor.js +157 -0
- package/dist/media/endpoints.js +11 -0
- package/dist/media/frame-options.js +18 -0
- package/dist/media/insert-video-timing.js +17 -0
- package/dist/media/json-schema.js +75 -0
- package/dist/media/legacy-task-types.js +129 -0
- package/dist/media/listing.js +24 -0
- package/dist/media/model-id.js +14 -0
- package/dist/media/preview.js +86 -0
- package/dist/media/prompt-dialects.js +65 -0
- package/dist/media/ref-fields.js +106 -0
- package/dist/media/refs.js +131 -0
- package/dist/media/request.js +188 -0
- package/dist/media/types.js +6 -0
- package/dist/media/wire.js +161 -0
- package/dist/media-library.js +535 -0
- package/dist/media-output-path.js +33 -0
- package/dist/multyculty-workflow.js +757 -0
- package/dist/notebook-action-catalog.js +608 -0
- package/dist/notebook-cell-outputs.js +726 -0
- package/dist/notebook-credit-retry.js +45 -0
- package/dist/notebook-fixtures.js +8 -0
- package/dist/notebook-generation-contract.js +77 -0
- package/dist/notebook-generation-controls.js +145 -0
- package/dist/notebook-goal-layout.js +40 -0
- package/dist/notebook-graph.js +37 -0
- package/dist/notebook-image-edit.js +84 -0
- package/dist/notebook-initial-generation-prompt.js +222 -0
- package/dist/notebook-live-analysis.js +941 -0
- package/dist/notebook-media-ops.js +579 -0
- package/dist/notebook-mutations.js +20 -0
- package/dist/notebook-placement.js +41 -0
- package/dist/notebook-provider-failure.js +87 -0
- package/dist/notebook-published-post.js +26 -0
- package/dist/notebook-reference-resolver.js +227 -0
- package/dist/notebook-relayout.js +270 -0
- package/dist/notebook-run-plan.js +519 -0
- package/dist/notebook-shots.js +162 -0
- package/dist/notebook-workflow.js +856 -0
- package/dist/one-password.js +200 -0
- package/dist/openai-intelligence.js +99 -0
- package/dist/original-media-file.js +12 -0
- package/dist/osg-assets.js +36 -0
- package/dist/osg-placement-cache.js +64 -0
- package/dist/osg-settings.js +27 -0
- package/dist/paid-tools.js +22 -0
- package/dist/platform-guidance.js +26 -0
- package/dist/posts/dropbox-auth.js +91 -0
- package/dist/posts/dropbox-upload.js +132 -0
- package/dist/posts/icloud-drive.js +134 -0
- package/dist/posts/icloud-status.js +64 -0
- package/dist/posts/post-contract.js +2 -0
- package/dist/posts/post-copy-format.js +43 -0
- package/dist/posts/post-copy.js +193 -0
- package/dist/posts/post-folder.js +18 -0
- package/dist/posts/post-metrics.js +119 -0
- package/dist/posts/post-store.js +340 -0
- package/dist/posts/x-post-target.js +43 -0
- package/dist/preview-payload.js +90 -0
- package/dist/prompt-surfaces.js +22 -0
- package/dist/provider-prompt-transform.js +77 -0
- package/dist/provider-status-link.js +21 -0
- package/dist/provider-task-record.js +78 -0
- package/dist/provider-task-status.js +47 -0
- package/dist/provider-usage.js +294 -0
- package/dist/queue.js +810 -0
- package/dist/reference-video-duration.js +17 -0
- package/dist/remotion/BlurFillVideo.js +51 -0
- package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
- package/dist/remotion/OsgOverlay.js +30 -0
- package/dist/remotion/QrOutroComposition.js +47 -0
- package/dist/remotion/RenderComposition.js +11 -0
- package/dist/remotion/browser.js +59 -0
- package/dist/remotion/constants.js +35 -0
- package/dist/remotion/index.js +66 -0
- package/dist/remotion/keyword-graphics.js +151 -0
- package/dist/remotion/keyword-placement.js +182 -0
- package/dist/remotion/osg-spec.js +198 -0
- package/dist/remotion/public/qr-outro-impact.wav +0 -0
- package/dist/remotion/qr-outro-audio.js +1 -0
- package/dist/remotion/qr-outro-spec.js +211 -0
- package/dist/remotion/shared-bundle.js +37 -0
- package/dist/remotion/subtitles/AliveCaptions.js +83 -0
- package/dist/remotion/subtitles/Highlight.js +58 -0
- package/dist/remotion/subtitles/Impact.js +67 -0
- package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
- package/dist/remotion/subtitles/alive-layout.js +321 -0
- package/dist/remotion/subtitles/alive-types.js +17 -0
- package/dist/remotion/subtitles/chunker.js +68 -0
- package/dist/remotion/subtitles/segments.js +42 -0
- package/dist/remotion/subtitles/styles.js +4 -0
- package/dist/remotion/subtitles/types.js +1 -0
- package/dist/remotion/util.js +18 -0
- package/dist/remotion-cancellation.js +15 -0
- package/dist/rename-helpers.js +204 -0
- package/dist/rhythm/analyze.js +128 -0
- package/dist/rhythm/contract.js +14 -0
- package/dist/rhythm/draft-model.js +19 -0
- package/dist/rhythm/energy.js +133 -0
- package/dist/rhythm/fft.js +44 -0
- package/dist/rhythm/model.js +71 -0
- package/dist/rhythm/music-analysis.js +124 -0
- package/dist/rhythm/music-draft.js +261 -0
- package/dist/rhythm/onset.js +101 -0
- package/dist/rhythm/run-worker.js +51 -0
- package/dist/rhythm/score.js +47 -0
- package/dist/rhythm/section-labels.js +168 -0
- package/dist/rhythm/sequence-rhythm.js +67 -0
- package/dist/rhythm/synthesize.js +103 -0
- package/dist/rhythm/timing.js +98 -0
- package/dist/rhythm/track.js +320 -0
- package/dist/rhythm/visual-fit.js +4 -0
- package/dist/rhythm/worker.js +25 -0
- package/dist/scene-detect.js +200 -0
- package/dist/scene-transition.js +12 -0
- package/dist/scheduled-posts.js +200 -0
- package/dist/search-index-coverage.js +13 -0
- package/dist/search-index-media.js +71 -0
- package/dist/search-reference-store.js +60 -0
- package/dist/search-scoring.js +17 -0
- package/dist/serve.js +125 -0
- package/dist/server-lock.js +143 -0
- package/dist/server.js +42 -0
- package/dist/srt.js +16 -0
- package/dist/tag-backfill-state.js +121 -0
- package/dist/tag-settings.js +30 -0
- package/dist/tag-vocabulary.js +237 -0
- package/dist/test-queue-cleanup.js +1 -0
- package/dist/time-parse.js +50 -0
- package/dist/timeline-helpers.js +554 -0
- package/dist/timewarp.js +87 -0
- package/dist/tools/_image-inputs.js +9 -0
- package/dist/tools/_media-refs.js +84 -0
- package/dist/tools/_shared.js +59 -0
- package/dist/tools/_suggest-shared.js +279 -0
- package/dist/tools/add-timeline-audio.js +73 -0
- package/dist/tools/alive-captions.js +157 -0
- package/dist/tools/analyze-jokes.js +56 -0
- package/dist/tools/analyze-video.js +66 -0
- package/dist/tools/animate-keywords.js +217 -0
- package/dist/tools/api-status.js +42 -0
- package/dist/tools/application-commands.js +34 -0
- package/dist/tools/apply-cuts.js +112 -0
- package/dist/tools/apply-sfx.js +152 -0
- package/dist/tools/apply-watermark.js +670 -0
- package/dist/tools/archive-clip.js +74 -0
- package/dist/tools/asset-history.js +61 -0
- package/dist/tools/asset-manifest.js +24 -0
- package/dist/tools/asset-tags.js +151 -0
- package/dist/tools/assign-slot.js +97 -0
- package/dist/tools/audio-boost.js +107 -0
- package/dist/tools/audio-mix.js +121 -0
- package/dist/tools/book-usage.js +16 -0
- package/dist/tools/chain.js +136 -0
- package/dist/tools/change-video-speed.js +105 -0
- package/dist/tools/chat-history.js +5 -0
- package/dist/tools/chat.js +848 -0
- package/dist/tools/clean-video-audio.js +97 -0
- package/dist/tools/clear-failed-tasks.js +66 -0
- package/dist/tools/cold-open.js +114 -0
- package/dist/tools/command-plan.js +1680 -0
- package/dist/tools/commands.js +1666 -0
- package/dist/tools/concat-images.js +236 -0
- package/dist/tools/connect-service.js +55 -0
- package/dist/tools/copy-file-to-clipboard.js +58 -0
- package/dist/tools/create-character.js +114 -0
- package/dist/tools/create-qart.js +253 -0
- package/dist/tools/create-qr-outro.js +149 -0
- package/dist/tools/create-qrbtf.js +215 -0
- package/dist/tools/create-watermark.js +97 -0
- package/dist/tools/crop-asset.js +18 -0
- package/dist/tools/crop-image.js +120 -0
- package/dist/tools/crop-video.js +97 -0
- package/dist/tools/delete-asset.js +104 -0
- package/dist/tools/describe-image.js +69 -0
- package/dist/tools/detect-scenes.js +44 -0
- package/dist/tools/doctor.js +126 -0
- package/dist/tools/download-video.js +114 -0
- package/dist/tools/duplicate-asset.js +184 -0
- package/dist/tools/edit-timeline.js +59 -0
- package/dist/tools/editing-moments.js +105 -0
- package/dist/tools/estimate-cost.js +171 -0
- package/dist/tools/even-split.js +295 -0
- package/dist/tools/export-asset-file.js +22 -0
- package/dist/tools/export-asset.js +140 -0
- package/dist/tools/export-sequence.js +250 -0
- package/dist/tools/export-to-downloads.js +54 -0
- package/dist/tools/export-transcript-srt.js +79 -0
- package/dist/tools/extend-audio.js +100 -0
- package/dist/tools/extract-audio.js +174 -0
- package/dist/tools/extract-frame.js +108 -0
- package/dist/tools/extract-notebook-audio.js +173 -0
- package/dist/tools/extract-notebook-frame.js +160 -0
- package/dist/tools/extract-video-clip.js +101 -0
- package/dist/tools/generate-media.js +157 -0
- package/dist/tools/generation-payload.js +48 -0
- package/dist/tools/get-timeline.js +12 -0
- package/dist/tools/get-watermark-config.js +38 -0
- package/dist/tools/goal.js +644 -0
- package/dist/tools/image-subjects.js +711 -0
- package/dist/tools/index.js +323 -0
- package/dist/tools/inject-first-frame.js +154 -0
- package/dist/tools/invert-image.js +70 -0
- package/dist/tools/isolate-vocals.js +66 -0
- package/dist/tools/list-assets.js +98 -0
- package/dist/tools/list-cell-generations.js +225 -0
- package/dist/tools/list-elevenlabs-voices.js +163 -0
- package/dist/tools/log-wrapper.js +29 -0
- package/dist/tools/make-cover-song.js +95 -0
- package/dist/tools/mark-asset-seen.js +23 -0
- package/dist/tools/media-library.js +245 -0
- package/dist/tools/move-timeline-audio.js +35 -0
- package/dist/tools/music-analysis.js +60 -0
- package/dist/tools/music-draft.js +29 -0
- package/dist/tools/pad-video.js +87 -0
- package/dist/tools/post-record.js +83 -0
- package/dist/tools/post.js +168 -0
- package/dist/tools/prompt-history.js +164 -0
- package/dist/tools/provider-key-status.js +248 -0
- package/dist/tools/qrbtf-render.js +849 -0
- package/dist/tools/read-asset-metadata.js +26 -0
- package/dist/tools/read-file-at-commit.js +29 -0
- package/dist/tools/read-transcript.js +41 -0
- package/dist/tools/reconcile-project-work.js +1452 -0
- package/dist/tools/recover-asset.js +28 -0
- package/dist/tools/remove-sound.js +72 -0
- package/dist/tools/remove-timeline-audio.js +34 -0
- package/dist/tools/remove-timeline-slot.js +40 -0
- package/dist/tools/rename-all.js +434 -0
- package/dist/tools/rename-asset.js +34 -0
- package/dist/tools/render-settings.js +47 -0
- package/dist/tools/render-timeline.js +120 -0
- package/dist/tools/render.js +384 -0
- package/dist/tools/reorder-character-images.js +45 -0
- package/dist/tools/replace-video-audio.js +133 -0
- package/dist/tools/restore-archive-clips.js +209 -0
- package/dist/tools/restore-asset-version.js +37 -0
- package/dist/tools/retry-generation.js +54 -0
- package/dist/tools/reverse-video.js +74 -0
- package/dist/tools/rewrite-prompt.js +388 -0
- package/dist/tools/rewrite-script.js +67 -0
- package/dist/tools/schema-from-apicity.js +66 -0
- package/dist/tools/search-archive.js +150 -0
- package/dist/tools/semantic-search.js +1214 -0
- package/dist/tools/sequence-rhythm.js +43 -0
- package/dist/tools/set-character-metadata.js +84 -0
- package/dist/tools/set-env-secret.js +40 -0
- package/dist/tools/set-osg.js +143 -0
- package/dist/tools/show-folder.js +37 -0
- package/dist/tools/smooth-scene-concat.js +122 -0
- package/dist/tools/splice-video.js +149 -0
- package/dist/tools/split-video.js +202 -0
- package/dist/tools/suggest-cuts.js +45 -0
- package/dist/tools/suggest-sfx.js +43 -0
- package/dist/tools/sync-video-to-audio.js +115 -0
- package/dist/tools/tag-backfill.js +177 -0
- package/dist/tools/timewarp-video.js +85 -0
- package/dist/tools/tool-def.js +1 -0
- package/dist/tools/tool-json-schema.js +116 -0
- package/dist/tools/transcribe.js +90 -0
- package/dist/tools/translate-subtitles.js +59 -0
- package/dist/tools/trim-audio.js +139 -0
- package/dist/tools/trim-video.js +168 -0
- package/dist/tools/update-script.js +25 -0
- package/dist/tools/update-timeline-audio.js +40 -0
- package/dist/tools/upload-file.js +41 -0
- package/dist/tools/verify-x-mentions.js +141 -0
- package/dist/tools/video-editing-command-contract.js +291 -0
- package/dist/tools/video-source-reference.js +30 -0
- package/dist/tools/video-time-input.js +18 -0
- package/dist/tools/videobook-domain.js +2021 -0
- package/dist/tools/wait-for-asset-ready.js +143 -0
- package/dist/tools/workspace-actions.js +54 -0
- package/dist/transcript-projection.js +202 -0
- package/dist/transcript-srt.js +33 -0
- package/dist/upload.js +76 -0
- package/dist/usage-providers.js +25 -0
- package/dist/video-analysis-projection.js +46 -0
- package/dist/video-poster.js +33 -0
- package/dist/video-proxy.js +99 -0
- package/dist/vision-regions.js +111 -0
- package/dist/voice-actions.js +90 -0
- package/dist/voice-audio.js +55 -0
- package/dist/voice-config.js +122 -0
- package/dist/voice-continuation.js +54 -0
- package/dist/voice-contract.js +1 -0
- package/dist/voice-http.js +145 -0
- package/dist/voice-reply-progress.js +55 -0
- package/dist/voice-session.js +992 -0
- package/dist/watermark-badge.js +259 -0
- package/dist/watermark-config.js +111 -0
- package/dist/watermark-handle-position.js +55 -0
- package/dist/watermark-platform.js +15 -0
- package/dist/watermark-timing.js +16 -0
- package/dist/workspace-action-catalog.js +315 -0
- package/dist/workspace-action-contract.js +21 -0
- package/dist/workspace-action-groups.js +98 -0
- package/dist/workspace-action-surfaces.js +7 -0
- package/dist/x/account.js +88 -0
- package/dist/x/authenticated-handle.js +30 -0
- package/dist/x/oauth-flow.js +191 -0
- package/dist/x/post-contract.js +1 -0
- package/dist/x/post-metadata.js +44 -0
- package/dist/x/post-to-x.js +456 -0
- package/dist/x/published-post-cell.js +90 -0
- package/dist/x/x-tokens.js +69 -0
- package/dist/ytdlp-binary.js +65 -0
- package/dist/zod-slots.js +334 -0
- package/package.json +84 -4
- package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
- package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
- package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
- package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
- package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
- package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
- package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
- package/ui/assets/index-DzPAnCUj.css +1 -0
- package/ui/assets/index-kS1905s5.js +37 -0
- package/ui/assets/inspector-MlzQbTVo.js +1 -0
- package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
- package/ui/assets/music-video-Cbe80kmQ.js +1 -0
- package/ui/assets/music-video-DPaiKKEq.css +1 -0
- package/ui/assets/ui-Oirr9VOf.css +1 -0
- package/ui/assets/ui-xo_Y1yQg.js +2 -0
- package/ui/assets/vendor-D2rfiZ5j.css +1 -0
- package/ui/assets/vendor-DfTCWgKm.js +19 -0
- package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
- package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
- package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
- package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
- package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
- package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
- package/ui/assets/videobook-Qlp9HQFu.js +20 -0
- package/ui/favicon.svg +8 -0
- package/ui/index.html +126 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { getApiKey } from "../api-key.js";
|
|
5
|
+
function normalizeUrl(url) {
|
|
6
|
+
return /^https?:\/\//i.test(url) ? url : `https://${url}`;
|
|
7
|
+
}
|
|
8
|
+
export function archiveClipDef(engine) {
|
|
9
|
+
return {
|
|
10
|
+
name: "archive_clip",
|
|
11
|
+
description: "Save a URL or a video or audio asset to the global clip archive (B2 when configured, otherwise local), deduplicated by content and transcribed; restore it with restore_archive_clips.",
|
|
12
|
+
schema: {
|
|
13
|
+
url: z.string().optional().describe("Source URL to download in full"),
|
|
14
|
+
asset_id: z.string().optional().describe("Existing video/audio asset ID to archive"),
|
|
15
|
+
book_id: z.string().uuid().describe("Book UUID"),
|
|
16
|
+
title: z.string().optional().describe("Optional archive title"),
|
|
17
|
+
topics: z.array(z.string()).optional().default([]).describe("Search topics to attach to this clip"),
|
|
18
|
+
people: z.array(z.string()).optional().default([]).describe("Named people to attach to this clip"),
|
|
19
|
+
language_code: z.string().optional().describe("Language code (e.g. 'en'). Auto-detected if omitted."),
|
|
20
|
+
tag_audio_events: z.boolean().optional().default(true).describe("Tag audio events like laughter, music, etc."),
|
|
21
|
+
diarize: z.boolean().optional().default(true).describe("Enable speaker diarization"),
|
|
22
|
+
},
|
|
23
|
+
handler: async (args) => {
|
|
24
|
+
const { url, asset_id, book_id, title, topics, people, language_code, tag_audio_events, diarize, } = args;
|
|
25
|
+
if ((url ? 1 : 0) + (asset_id ? 1 : 0) !== 1) {
|
|
26
|
+
return {
|
|
27
|
+
isError: true,
|
|
28
|
+
content: [{ type: "text", text: "archive_clip requires exactly one of url or asset_id" }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!getApiKey("ELEVENLABS_API_KEY")) {
|
|
32
|
+
return {
|
|
33
|
+
isError: true,
|
|
34
|
+
content: [{ type: "text", text: "ELEVENLABS_API_KEY environment variable is not set or is a placeholder" }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (asset_id) {
|
|
38
|
+
const manifest = await engine.files.manifest(asset_id);
|
|
39
|
+
if (!manifest.ok) {
|
|
40
|
+
return {
|
|
41
|
+
isError: true,
|
|
42
|
+
content: [{ type: "text", text: manifest.error.message }],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const ingestId = randomUUID();
|
|
47
|
+
const jobId = `archive-clip-${ingestId}`;
|
|
48
|
+
await queue.enqueue(jobId, {
|
|
49
|
+
type: "archive_clip",
|
|
50
|
+
bookId: book_id,
|
|
51
|
+
...(asset_id ? { assetId: asset_id } : {}),
|
|
52
|
+
...(url ? { url: normalizeUrl(url) } : {}),
|
|
53
|
+
...(title ? { title } : {}),
|
|
54
|
+
topics,
|
|
55
|
+
people,
|
|
56
|
+
tagAudioEvents: tag_audio_events,
|
|
57
|
+
diarize,
|
|
58
|
+
ingestId,
|
|
59
|
+
...(language_code ? { languageCode: language_code } : {}),
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
content: [{
|
|
63
|
+
type: "text",
|
|
64
|
+
text: JSON.stringify({
|
|
65
|
+
status: "enqueued",
|
|
66
|
+
job_id: jobId,
|
|
67
|
+
ingest_id: ingestId,
|
|
68
|
+
archive_id: null,
|
|
69
|
+
}),
|
|
70
|
+
}],
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
async function enrichCommitsWithRewrite(engine, commits, artifact) {
|
|
3
|
+
if (artifact.kind !== "script")
|
|
4
|
+
return commits;
|
|
5
|
+
return Promise.all(commits.map(async (commit) => {
|
|
6
|
+
const result = await engine.metadata.artifacts.readAtRevision(artifact.artifactId, "original", commit.hash);
|
|
7
|
+
if (!result.ok || result.value.origin !== "describe_image") {
|
|
8
|
+
return commit;
|
|
9
|
+
}
|
|
10
|
+
const metadata = result.value;
|
|
11
|
+
return {
|
|
12
|
+
...commit,
|
|
13
|
+
rewrite: {
|
|
14
|
+
model: metadata.model ?? "gpt-5.6-sol",
|
|
15
|
+
variant: metadata.variant ?? "full",
|
|
16
|
+
...(metadata.platform ? { platform: metadata.platform } : {}),
|
|
17
|
+
...(metadata.customPrompt
|
|
18
|
+
? { customPrompt: metadata.customPrompt }
|
|
19
|
+
: {}),
|
|
20
|
+
...(metadata.processed_at
|
|
21
|
+
? { processedAt: metadata.processed_at }
|
|
22
|
+
: {}),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
export function assetHistoryDef(engine) {
|
|
28
|
+
return {
|
|
29
|
+
name: "asset_history",
|
|
30
|
+
description: "Get immutable Dolt revision history for an asset",
|
|
31
|
+
schema: {
|
|
32
|
+
asset_id: z.string().describe("Artifact UUID"),
|
|
33
|
+
book_id: z.string().describe("Book id"),
|
|
34
|
+
limit: z.number().optional().describe("Maximum revisions (default 20)"),
|
|
35
|
+
},
|
|
36
|
+
handler: async (args) => {
|
|
37
|
+
const { asset_id, limit } = args;
|
|
38
|
+
try {
|
|
39
|
+
const artifact = engine.artifacts.get(asset_id);
|
|
40
|
+
if (!artifact.ok)
|
|
41
|
+
throw new Error(artifact.error.message);
|
|
42
|
+
const commits = engine.history.artifact(artifact.value.artifactId, limit);
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: JSON.stringify(await enrichCommitsWithRewrite(engine, commits, artifact.value)),
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: error instanceof Error ? error.message : String(error),
|
|
56
|
+
}],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function assetManifestDef(engine) {
|
|
3
|
+
return {
|
|
4
|
+
name: "asset_manifest",
|
|
5
|
+
description: "List all files in an asset directory",
|
|
6
|
+
schema: {
|
|
7
|
+
asset_id: z.string().describe("Asset ID (e.g. img-cute-cat, vid-running-dog)"),
|
|
8
|
+
book_id: z.string().describe("Book id"),
|
|
9
|
+
},
|
|
10
|
+
handler: async (args) => {
|
|
11
|
+
const { asset_id, book_id: _book_id } = args;
|
|
12
|
+
const result = await engine.files.manifest(asset_id);
|
|
13
|
+
if (!result.ok) {
|
|
14
|
+
return {
|
|
15
|
+
isError: true,
|
|
16
|
+
content: [{ type: "text", text: result.error.message }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: JSON.stringify(result.value) }],
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual tag editing and tag discovery over MCP.
|
|
3
|
+
*
|
|
4
|
+
* Every operation is book-scoped: the handler runs against the engine bound
|
|
5
|
+
* to `book_id`, so a Library edit made from another book still lands in the
|
|
6
|
+
* book that owns the asset. Manual intent is all these tools can express —
|
|
7
|
+
* the automatic set belongs to the tagging jobs, and nothing here can add to
|
|
8
|
+
* it, relabel it, or clear a dismissal that a user meant to keep.
|
|
9
|
+
*/
|
|
10
|
+
import { TAG_FACETS, tagCanonicalKey } from "videobook-engine";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { assetTagSummary, readAssetTagSummaries, tagFacetCounts } from "../asset-tags.js";
|
|
13
|
+
import { matchesTagPrefix, resolveTagInput } from "../tag-vocabulary.js";
|
|
14
|
+
const facetSchema = z.enum(TAG_FACETS).describe("Optional category; inferred from the label when omitted. Use custom for a free-form tag with a separate identity");
|
|
15
|
+
const labelSchema = z.string().min(1).describe("Display label; the engine normalizes it into the canonical identity");
|
|
16
|
+
/** One tag filter term: a label, optionally pinned to a single facet. */
|
|
17
|
+
export const tagSelectorSchema = z.object({
|
|
18
|
+
facet: facetSchema.optional(),
|
|
19
|
+
label: labelSchema,
|
|
20
|
+
});
|
|
21
|
+
function json(value) {
|
|
22
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
23
|
+
}
|
|
24
|
+
function failure(cause) {
|
|
25
|
+
return {
|
|
26
|
+
isError: true,
|
|
27
|
+
content: [{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: cause instanceof Error ? cause.message : String(cause),
|
|
30
|
+
}],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Tag operations address artifacts, and the Library also lists synthetic
|
|
35
|
+
* notebook rows whose id is a notebook id. Resolving through the artifact
|
|
36
|
+
* catalog first turns that into a clear error instead of a foreign-key
|
|
37
|
+
* failure from the write.
|
|
38
|
+
*/
|
|
39
|
+
function resolveArtifact(engine, assetId) {
|
|
40
|
+
const artifact = engine.artifacts.get(assetId);
|
|
41
|
+
if (!artifact.ok)
|
|
42
|
+
throw new Error(artifact.error.message);
|
|
43
|
+
return artifact.value;
|
|
44
|
+
}
|
|
45
|
+
function tagState(engine, assetId) {
|
|
46
|
+
const state = engine.tags.read(assetId);
|
|
47
|
+
if (!state.ok)
|
|
48
|
+
throw new Error(state.error.message);
|
|
49
|
+
return assetTagSummary(state.value);
|
|
50
|
+
}
|
|
51
|
+
function matchingIdentities(engine, assetId, input) {
|
|
52
|
+
const state = engine.tags.read(assetId);
|
|
53
|
+
if (!state.ok)
|
|
54
|
+
throw new Error(state.error.message);
|
|
55
|
+
const existing = [...state.value.manual, ...state.value.automatic, ...state.value.dismissed];
|
|
56
|
+
const tag = resolveTagInput(input, existing);
|
|
57
|
+
const key = tagCanonicalKey(tag.label);
|
|
58
|
+
const aliases = existing.filter((entry) => entry.facet === tag.facet
|
|
59
|
+
&& tagCanonicalKey(resolveTagInput(entry).label) === key);
|
|
60
|
+
const identities = [...new Map([tag, ...aliases].map((entry) => [`${entry.facet}/${tagCanonicalKey(entry.label)}`, { facet: entry.facet, label: entry.label }])).values()];
|
|
61
|
+
return { tag, identities };
|
|
62
|
+
}
|
|
63
|
+
async function applyToIdentities(engine, assetId, identities, op) {
|
|
64
|
+
for (const identity of identities) {
|
|
65
|
+
const result = op === "remove"
|
|
66
|
+
? await engine.tags.remove(assetId, identity)
|
|
67
|
+
: await engine.tags.restore(assetId, identity);
|
|
68
|
+
if (!result.ok)
|
|
69
|
+
throw new Error(result.error.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function editAssetTag(engine, edit) {
|
|
73
|
+
if (edit.entity_id && edit.op !== "add")
|
|
74
|
+
throw new Error("entity_id applies only to op add");
|
|
75
|
+
const artifact = resolveArtifact(engine, edit.asset_id);
|
|
76
|
+
const assetId = artifact.artifactId;
|
|
77
|
+
const { tag, identities } = matchingIdentities(engine, assetId, edit);
|
|
78
|
+
if (edit.op === "add") {
|
|
79
|
+
const added = await engine.tags.add(assetId, { ...tag, ...(edit.entity_id ? { entityId: edit.entity_id } : {}) });
|
|
80
|
+
if (!added.ok)
|
|
81
|
+
throw new Error(added.error.message);
|
|
82
|
+
}
|
|
83
|
+
await applyToIdentities(engine, assetId, identities, edit.op === "remove" ? "remove" : "restore");
|
|
84
|
+
return json({ asset_id: assetId, tags: tagState(engine, assetId) });
|
|
85
|
+
}
|
|
86
|
+
export function assetTagDefs(engine) {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
name: "edit_asset_tag",
|
|
90
|
+
description: "Add, remove or restore one tag on an asset. add makes a manual tag that survives re-analysis; removing an automatic tag records a dismissal so re-analysis cannot bring it back; restore clears that dismissal.",
|
|
91
|
+
annotations: { idempotentHint: true },
|
|
92
|
+
schema: {
|
|
93
|
+
book_id: z.string().describe("Book that owns the asset"),
|
|
94
|
+
asset_id: z.string().describe("Artifact id to edit"),
|
|
95
|
+
op: z.enum(["add", "remove", "restore"]),
|
|
96
|
+
facet: facetSchema.optional(),
|
|
97
|
+
label: labelSchema,
|
|
98
|
+
entity_id: z.string().optional().describe("With op add: an already-confirmed entity to reference; never invent one"),
|
|
99
|
+
},
|
|
100
|
+
handler: async (args) => {
|
|
101
|
+
try {
|
|
102
|
+
return await editAssetTag(engine, args);
|
|
103
|
+
}
|
|
104
|
+
catch (cause) {
|
|
105
|
+
return failure(cause);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "suggest_asset_tags",
|
|
111
|
+
description: "List the tags in use in a book with the number of assets carrying each, optionally narrowed by prefix or facet. Use it for autocomplete and to discover the groups available for filtering.",
|
|
112
|
+
annotations: { readOnlyHint: true },
|
|
113
|
+
schema: {
|
|
114
|
+
book_id: z.string().describe("Book to inspect"),
|
|
115
|
+
prefix: z.string().optional().describe("Match tags starting with this text; omit for the most used tags"),
|
|
116
|
+
facet: facetSchema.optional(),
|
|
117
|
+
kinds: z.array(z.enum(["audio", "image", "video", "final"])).optional()
|
|
118
|
+
.describe("Restrict counting to these artifact kinds"),
|
|
119
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
120
|
+
asset_id: z.string().optional().describe("Return this asset's own tag state alongside the suggestions"),
|
|
121
|
+
},
|
|
122
|
+
handler: async (args) => {
|
|
123
|
+
const { prefix, facet, kinds, limit, asset_id } = args;
|
|
124
|
+
try {
|
|
125
|
+
const ids = engine.artifacts.list().filter((artifact) => !kinds
|
|
126
|
+
|| kinds.some((kind) => kind === artifact.kind)).map((artifact) => artifact.artifactId);
|
|
127
|
+
const summaries = [...readAssetTagSummaries(engine, ids).values()];
|
|
128
|
+
const suggestions = tagFacetCounts(summaries.map((tags) => ({ tags })), Infinity)
|
|
129
|
+
.filter((tag) => (!facet || tag.facet === facet) && matchesTagPrefix(tag, prefix ?? ""))
|
|
130
|
+
.slice(0, limit ?? 50);
|
|
131
|
+
const asset = asset_id
|
|
132
|
+
? tagState(engine, resolveArtifact(engine, asset_id).artifactId)
|
|
133
|
+
: null;
|
|
134
|
+
return json({
|
|
135
|
+
facets: TAG_FACETS,
|
|
136
|
+
suggestions: suggestions.map((row) => ({
|
|
137
|
+
facet: row.facet,
|
|
138
|
+
key: row.key,
|
|
139
|
+
label: row.label,
|
|
140
|
+
assets: row.assets,
|
|
141
|
+
})),
|
|
142
|
+
...(asset ? { asset_id, tags: asset } : {}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
return failure(cause);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { queue } from "../queue.js";
|
|
3
|
+
import { enqueueLazyTimelineRender } from "../lazy-render.js";
|
|
4
|
+
import { readTimeline, resolveArtifact, slotIndex, timelineFailure, timelineInput, timelinePayload, writeTimeline, } from "../timeline-helpers.js";
|
|
5
|
+
async function enqueueLazyAnalysis(engine, asset, bookId) {
|
|
6
|
+
if (!process.env.OPENAI_API_KEY)
|
|
7
|
+
return null;
|
|
8
|
+
if (asset.kind !== "video" && asset.kind !== "image")
|
|
9
|
+
return null;
|
|
10
|
+
const manifest = await engine.files.manifest(asset.artifactId);
|
|
11
|
+
if (!manifest.ok)
|
|
12
|
+
return null;
|
|
13
|
+
if ((await engine.files.read(asset.artifactId, ".original.analysis.json")).ok)
|
|
14
|
+
return null;
|
|
15
|
+
if (asset.kind === "video") {
|
|
16
|
+
const transcription = await engine.files.read(asset.artifactId, ".analysis.gpt4otrans.json");
|
|
17
|
+
const jobId = `auto-analyze-${asset.artifactId}`;
|
|
18
|
+
await queue.enqueue(jobId, {
|
|
19
|
+
type: "analyze_video",
|
|
20
|
+
bookId,
|
|
21
|
+
assetId: asset.artifactId,
|
|
22
|
+
assetDir: manifest.value.path,
|
|
23
|
+
hasTranscription: transcription.ok,
|
|
24
|
+
});
|
|
25
|
+
return { jobId, kind: "analyze_video" };
|
|
26
|
+
}
|
|
27
|
+
const original = manifest.value.files.find((file) => /^original\.(jpe?g|png|webp)$/i.test(file.name));
|
|
28
|
+
if (!original)
|
|
29
|
+
return null;
|
|
30
|
+
const jobId = `auto-describe-${asset.artifactId}`;
|
|
31
|
+
await queue.enqueue(jobId, {
|
|
32
|
+
type: "describe_image",
|
|
33
|
+
bookId,
|
|
34
|
+
sourceAssetId: asset.artifactId,
|
|
35
|
+
sourceImageFilename: original.name,
|
|
36
|
+
});
|
|
37
|
+
return { jobId, kind: "describe_image" };
|
|
38
|
+
}
|
|
39
|
+
export function assignSlotDef(engine) {
|
|
40
|
+
return {
|
|
41
|
+
name: "assign_slot",
|
|
42
|
+
description: "Assign an artifact to a typed timeline slot",
|
|
43
|
+
schema: {
|
|
44
|
+
book_id: z.string().uuid(),
|
|
45
|
+
asset_id: z.string(),
|
|
46
|
+
slot: z.string(),
|
|
47
|
+
},
|
|
48
|
+
handler: async (args) => {
|
|
49
|
+
const index = slotIndex(String(args.slot));
|
|
50
|
+
if (index === null)
|
|
51
|
+
return timelineFailure("Slot must use the form s01, s02, and so on");
|
|
52
|
+
const asset = resolveArtifact(engine, String(args.asset_id));
|
|
53
|
+
if (!asset)
|
|
54
|
+
return timelineFailure(`Artifact not found: ${String(args.asset_id)}`);
|
|
55
|
+
const timeline = readTimeline(engine);
|
|
56
|
+
if (index > timeline.slots.length) {
|
|
57
|
+
return timelineFailure("Typed timelines cannot contain empty gaps; assign the next available slot first");
|
|
58
|
+
}
|
|
59
|
+
const slots = timeline.slots.map((slot) => ({
|
|
60
|
+
id: slot.id,
|
|
61
|
+
artifactId: slot.artifactId,
|
|
62
|
+
...(slot.volume === undefined ? {} : { volume: slot.volume }),
|
|
63
|
+
...(slot.audioFadeIn === undefined ? {} : { audioFadeIn: slot.audioFadeIn }),
|
|
64
|
+
...(slot.audioFadeOut === undefined ? {} : { audioFadeOut: slot.audioFadeOut }),
|
|
65
|
+
}));
|
|
66
|
+
const replacement = index < slots.length
|
|
67
|
+
? { ...slots[index], artifactId: asset.artifactId }
|
|
68
|
+
: { artifactId: asset.artifactId };
|
|
69
|
+
if (index < slots.length)
|
|
70
|
+
slots[index] = replacement;
|
|
71
|
+
else
|
|
72
|
+
slots.push(replacement);
|
|
73
|
+
const result = await writeTimeline(engine, { ...timelineInput(timeline), slots });
|
|
74
|
+
if (!result.ok)
|
|
75
|
+
return timelineFailure(result.error.message);
|
|
76
|
+
const response = timelinePayload(result.value);
|
|
77
|
+
const bookId = String(args.book_id);
|
|
78
|
+
try {
|
|
79
|
+
const analysis = await enqueueLazyAnalysis(engine, asset, bookId);
|
|
80
|
+
if (analysis)
|
|
81
|
+
response.auto_analyze = analysis;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
delete response.auto_analyze;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const render = await enqueueLazyTimelineRender(engine, bookId, asset.artifactId, asset);
|
|
88
|
+
if (render)
|
|
89
|
+
response.auto_render = render;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
delete response.auto_render;
|
|
93
|
+
}
|
|
94
|
+
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { findAudioFile, findVideoFile } from "videobook-engine";
|
|
5
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
6
|
+
import { audioOnlyOutputOptions, videoAudioOutputOptions } from "../audio-encoding.js";
|
|
7
|
+
const BOOST_MIN = 1.0;
|
|
8
|
+
const BOOST_MAX = 4.0;
|
|
9
|
+
function validateBoost(boost) {
|
|
10
|
+
if (boost === 1.0)
|
|
11
|
+
return "Boost 1.0 is a no-op — nothing to change";
|
|
12
|
+
if (boost < BOOST_MIN || boost > BOOST_MAX)
|
|
13
|
+
return `Boost must be between ${BOOST_MIN} and ${BOOST_MAX}, got ${boost}`;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function audioBoostDef(engine) {
|
|
17
|
+
return {
|
|
18
|
+
name: "audio_boost",
|
|
19
|
+
description: "Boost audio volume of a video or audio asset using a linear multiplier (1.0–4.0). Operates in-place with git versioning.",
|
|
20
|
+
schema: {
|
|
21
|
+
asset_id: z.string().describe("Video or audio artifact id"),
|
|
22
|
+
book_id: z.string().describe("Book UUID"),
|
|
23
|
+
boost: z.number().describe("Volume multiplier (1.0–4.0). >1 = louder."),
|
|
24
|
+
},
|
|
25
|
+
handler: async (args) => {
|
|
26
|
+
const { asset_id, book_id, boost } = args;
|
|
27
|
+
const boostError = validateBoost(boost);
|
|
28
|
+
if (boostError) {
|
|
29
|
+
return {
|
|
30
|
+
isError: true,
|
|
31
|
+
content: [{ type: "text", text: boostError }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const artifactResult = engine.artifacts.get(asset_id);
|
|
35
|
+
if (!artifactResult.ok) {
|
|
36
|
+
return {
|
|
37
|
+
isError: true,
|
|
38
|
+
content: [{ type: "text", text: artifactResult.error.message }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const isVideo = artifactResult.value.kind === "video";
|
|
42
|
+
const isAudio = artifactResult.value.kind === "audio";
|
|
43
|
+
if (!isVideo && !isAudio) {
|
|
44
|
+
return {
|
|
45
|
+
isError: true,
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: `audio_boost only supports video or audio artifacts, got ${artifactResult.value.kind} artifact ${asset_id}`,
|
|
49
|
+
}],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
53
|
+
if (!manifestResult.ok) {
|
|
54
|
+
return {
|
|
55
|
+
isError: true,
|
|
56
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const manifest = manifestResult.value;
|
|
60
|
+
const file = isVideo
|
|
61
|
+
? findVideoFile(manifest.files)
|
|
62
|
+
: findAudioFile(manifest.files, artifactResult.value.kind);
|
|
63
|
+
if (!file) {
|
|
64
|
+
return {
|
|
65
|
+
isError: true,
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: `No ${isVideo ? "video" : "audio"} file found in asset ${asset_id}`,
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const assetDir = manifest.path;
|
|
73
|
+
const inputPath = join(assetDir, file.name);
|
|
74
|
+
try {
|
|
75
|
+
const source = await probeVideo(inputPath);
|
|
76
|
+
if (!source.hasAudio)
|
|
77
|
+
throw new Error("Source has no playable audio stream");
|
|
78
|
+
if (isVideo)
|
|
79
|
+
videoAudioOutputOptions(inputPath);
|
|
80
|
+
else
|
|
81
|
+
audioOnlyOutputOptions(inputPath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
|
|
85
|
+
}
|
|
86
|
+
const ext = extname(file.name) || (isVideo ? ".mp4" : ".mp3");
|
|
87
|
+
const tempPath = join(assetDir, `boost_pending${ext}`);
|
|
88
|
+
const jobId = `boost-${asset_id}-${Date.now()}`;
|
|
89
|
+
await queue.enqueue(jobId, {
|
|
90
|
+
type: "audio_boost",
|
|
91
|
+
inputPath,
|
|
92
|
+
outputPath: tempPath,
|
|
93
|
+
boost,
|
|
94
|
+
isVideo,
|
|
95
|
+
assetId: asset_id,
|
|
96
|
+
assetDir,
|
|
97
|
+
bookId: book_id,
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: JSON.stringify({ asset_id, job_id: jobId, status: "enqueued" }),
|
|
103
|
+
}],
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { findAudioFile, findVideoFile, } from "videobook-engine";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { audioOnlyOutputOptions, videoAudioOutputOptions } from "../audio-encoding.js";
|
|
6
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
7
|
+
import { queue } from "../queue.js";
|
|
8
|
+
function toolError(message) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [{ type: "text", text: message }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function normalizeAssetId(assetId) {
|
|
15
|
+
return assetId.trim().replace(/^@/, "");
|
|
16
|
+
}
|
|
17
|
+
function findMediaFile(files, kind) {
|
|
18
|
+
return kind === "video"
|
|
19
|
+
? findVideoFile(files)
|
|
20
|
+
: findAudioFile(files, kind);
|
|
21
|
+
}
|
|
22
|
+
export async function resolveAudioMixAsset(engine, assetIdInput) {
|
|
23
|
+
const assetId = normalizeAssetId(assetIdInput);
|
|
24
|
+
const artifact = engine.artifacts.get(assetId);
|
|
25
|
+
if (!artifact.ok)
|
|
26
|
+
throw new Error(artifact.error.message);
|
|
27
|
+
if (artifact.value.kind !== "video" && artifact.value.kind !== "audio") {
|
|
28
|
+
throw new Error(`Audio mix artifacts must be video or audio, got ${artifact.value.kind} artifact ${assetId}`);
|
|
29
|
+
}
|
|
30
|
+
const kind = artifact.value.kind;
|
|
31
|
+
const manifestResult = await engine.files.manifest(assetId);
|
|
32
|
+
if (!manifestResult.ok)
|
|
33
|
+
throw new Error(manifestResult.error.message);
|
|
34
|
+
const file = findMediaFile(manifestResult.value.files, kind);
|
|
35
|
+
if (!file) {
|
|
36
|
+
throw new Error(`No ${kind} file found in artifact ${assetId}`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
assetId,
|
|
40
|
+
kind,
|
|
41
|
+
assetDir: manifestResult.value.path,
|
|
42
|
+
filename: file.name,
|
|
43
|
+
path: join(manifestResult.value.path, file.name),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function audioMixDef(engine) {
|
|
47
|
+
return {
|
|
48
|
+
name: "audio_mix",
|
|
49
|
+
description: "Mix a video or audio layer asset into a target video or audio asset in place, starting at 0 seconds at volume 0-100; the target keeps its duration and picture.",
|
|
50
|
+
schema: {
|
|
51
|
+
target_asset_id: z.string().describe("Artifact id to edit in-place (video or audio)"),
|
|
52
|
+
layer_asset_id: z.string().describe("Artifact whose audio is layered onto the target (video or audio artifact id)"),
|
|
53
|
+
volume: z.number().min(0).max(100).describe("Layer volume percentage from 0 to 100"),
|
|
54
|
+
book_id: z.string().describe("Book UUID"),
|
|
55
|
+
},
|
|
56
|
+
handler: async (args) => {
|
|
57
|
+
const { target_asset_id, layer_asset_id, volume, book_id } = args;
|
|
58
|
+
let target;
|
|
59
|
+
let layer;
|
|
60
|
+
try {
|
|
61
|
+
[target, layer] = await Promise.all([
|
|
62
|
+
resolveAudioMixAsset(engine, target_asset_id),
|
|
63
|
+
resolveAudioMixAsset(engine, layer_asset_id),
|
|
64
|
+
]);
|
|
65
|
+
const [targetProbe, layerProbe] = await Promise.all([
|
|
66
|
+
probeVideo(target.path),
|
|
67
|
+
probeVideo(layer.path),
|
|
68
|
+
]);
|
|
69
|
+
if (!layerProbe.hasAudio || (layerProbe.audioDuration ?? layerProbe.duration) <= 0) {
|
|
70
|
+
throw new Error("Layer asset has no playable audio stream");
|
|
71
|
+
}
|
|
72
|
+
if (target.kind === "video") {
|
|
73
|
+
if (!targetProbe.videoStream || targetProbe.videoDuration <= 0) {
|
|
74
|
+
throw new Error("Video target has no playable video stream");
|
|
75
|
+
}
|
|
76
|
+
videoAudioOutputOptions(target.filename);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (!targetProbe.hasAudio || (targetProbe.audioDuration ?? targetProbe.duration) <= 0) {
|
|
80
|
+
throw new Error("Audio target has no playable audio stream");
|
|
81
|
+
}
|
|
82
|
+
audioOnlyOutputOptions(target.filename);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return toolError(error instanceof Error ? error.message : String(error));
|
|
87
|
+
}
|
|
88
|
+
const token = randomUUID();
|
|
89
|
+
const jobId = `audio-mix-${target.assetId}-${token}`;
|
|
90
|
+
try {
|
|
91
|
+
await queue.enqueueAndWait(jobId, {
|
|
92
|
+
type: "audio_mix",
|
|
93
|
+
targetPath: target.path,
|
|
94
|
+
targetFilename: target.filename,
|
|
95
|
+
targetKind: target.kind,
|
|
96
|
+
layerPath: layer.path,
|
|
97
|
+
layerAssetId: layer.assetId,
|
|
98
|
+
volume,
|
|
99
|
+
assetId: target.assetId,
|
|
100
|
+
assetDir: target.assetDir,
|
|
101
|
+
bookId: book_id,
|
|
102
|
+
}, 1_800_000);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return toolError(`Audio mix failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
content: [{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: JSON.stringify({
|
|
111
|
+
target_asset_id: target.assetId,
|
|
112
|
+
layer_asset_id: layer.assetId,
|
|
113
|
+
volume,
|
|
114
|
+
job_id: jobId,
|
|
115
|
+
status: "completed",
|
|
116
|
+
}),
|
|
117
|
+
}],
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { settleFinishedUsage, usageIncomplete } from "../provider-usage.js";
|
|
3
|
+
export function bookUsageDef(engine) {
|
|
4
|
+
return {
|
|
5
|
+
name: "get_book_usage",
|
|
6
|
+
description: "Read recorded provider tokens and USD cost for this book, with estimated and unavailable prices, activity/model breakdowns, and the latest 20 requests. Tracking starts when usage recording is enabled for this book.",
|
|
7
|
+
schema: { book_id: z.string().uuid() },
|
|
8
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
9
|
+
handler: async () => {
|
|
10
|
+
await settleFinishedUsage(engine);
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: JSON.stringify({ ...engine.usage.summary(), recent: engine.usage.recent(), incomplete: usageIncomplete(engine) }) }],
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|