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,171 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getCostProvider } from "../cost.js";
|
|
3
|
+
import { referenceVideoDuration } from "../reference-video-duration.js";
|
|
4
|
+
import { previewPlaceholderUrl } from "../preview-payload.js";
|
|
5
|
+
import { DEFAULT_IMAGE_EDIT_MODEL_ID, findImageEditModel } from "../image-edit-models.js";
|
|
6
|
+
import { tryResolveModel } from "../media/descriptor.js";
|
|
7
|
+
import { estimateGeneration } from "../media/cost.js";
|
|
8
|
+
import { normalizeRefs } from "../media/refs.js";
|
|
9
|
+
import { REF_ROLES } from "../media/types.js";
|
|
10
|
+
const GENERATION_TOOLS = new Set(["generate_image", "generate_video", "generate_audio"]);
|
|
11
|
+
const REWRITE_MODEL = "gpt-5.6-sol";
|
|
12
|
+
const callItem = z.object({
|
|
13
|
+
tool_name: z.string().describe("Tool whose cost to estimate: generate_image, generate_video, generate_audio, edit_image_subjects or rewrite_prompt"),
|
|
14
|
+
input: z.record(z.string(), z.unknown()).describe("Same arguments object you'd pass to the tool"),
|
|
15
|
+
});
|
|
16
|
+
const schema = {
|
|
17
|
+
calls: z
|
|
18
|
+
.array(callItem)
|
|
19
|
+
.min(1)
|
|
20
|
+
.describe("One or more (tool_name, input) pairs. The result sums all entries."),
|
|
21
|
+
};
|
|
22
|
+
const FREE_ESTIMATE = {
|
|
23
|
+
usd: 0,
|
|
24
|
+
currency: "USD",
|
|
25
|
+
source: "free",
|
|
26
|
+
breakdown: {},
|
|
27
|
+
rateAsOf: null,
|
|
28
|
+
warnings: [],
|
|
29
|
+
};
|
|
30
|
+
function unavailable(warning) {
|
|
31
|
+
return { ...FREE_ESTIMATE, warnings: [warning] };
|
|
32
|
+
}
|
|
33
|
+
function text(value) {
|
|
34
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
function placeholderRefs(refs) {
|
|
37
|
+
const placeholders = {};
|
|
38
|
+
for (const role of REF_ROLES) {
|
|
39
|
+
const list = refs[role];
|
|
40
|
+
if (list)
|
|
41
|
+
placeholders[role] = list.map((ref) => ref.startsWith("http") ? ref : previewPlaceholderUrl(ref));
|
|
42
|
+
}
|
|
43
|
+
return placeholders;
|
|
44
|
+
}
|
|
45
|
+
async function sourceDuration(engine, refs) {
|
|
46
|
+
for (const ref of refs) {
|
|
47
|
+
const meta = await engine.metadata.artifacts.read(ref.replace(/^@/, ""), "original");
|
|
48
|
+
if (meta.ok && typeof meta.value?.duration === "number" && meta.value.duration > 0)
|
|
49
|
+
return meta.value.duration;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
async function mediaHints(engine, refs) {
|
|
54
|
+
const videos = (refs.video ?? []).filter((ref) => !ref.startsWith("http"));
|
|
55
|
+
const videoSeconds = videos.length > 0 ? await referenceVideoDuration(engine, videos) : undefined;
|
|
56
|
+
if (videoSeconds !== undefined)
|
|
57
|
+
return { inputDurationSeconds: videoSeconds };
|
|
58
|
+
const audioSeconds = await sourceDuration(engine, (refs.audio ?? []).filter((ref) => !ref.startsWith("http")));
|
|
59
|
+
return audioSeconds === undefined ? {} : { inputDurationSeconds: audioSeconds };
|
|
60
|
+
}
|
|
61
|
+
async function estimateMedia(engine, input) {
|
|
62
|
+
const modelId = text(input.model);
|
|
63
|
+
const model = modelId ? tryResolveModel(modelId) : null;
|
|
64
|
+
if (!model)
|
|
65
|
+
return unavailable(`unknown model '${modelId ?? ""}'`);
|
|
66
|
+
const refs = normalizeRefs(input.refs);
|
|
67
|
+
const options = input.options !== null && typeof input.options === "object" && !Array.isArray(input.options)
|
|
68
|
+
? input.options
|
|
69
|
+
: {};
|
|
70
|
+
const result = estimateGeneration(model, {
|
|
71
|
+
prompt: text(input.prompt),
|
|
72
|
+
refUrls: placeholderRefs(refs),
|
|
73
|
+
options,
|
|
74
|
+
mode: text(input.mode),
|
|
75
|
+
}, await mediaHints(engine, refs));
|
|
76
|
+
return result.ok ? result.estimate : unavailable(result.error);
|
|
77
|
+
}
|
|
78
|
+
function estimateImageEdit(input) {
|
|
79
|
+
if (input.model === "sharp/layer-composite" && input.operation === "remove")
|
|
80
|
+
return { ...FREE_ESTIMATE };
|
|
81
|
+
const editModel = findImageEditModel(text(input.model) ?? DEFAULT_IMAGE_EDIT_MODEL_ID);
|
|
82
|
+
const model = editModel ? tryResolveModel(editModel.catalogModel) : null;
|
|
83
|
+
if (!model)
|
|
84
|
+
return unavailable(`unknown image edit model '${String(input.model)}'`);
|
|
85
|
+
const imageCount = Array.isArray(input.image_refs) && input.image_refs.length > 0 ? input.image_refs.length : 1;
|
|
86
|
+
const result = estimateGeneration(model, {
|
|
87
|
+
prompt: text(input.prompt) ?? "edit",
|
|
88
|
+
refUrls: { image: Array.from({ length: imageCount }, (_, index) => `https://example.test/edit-${index + 1}.png`) },
|
|
89
|
+
});
|
|
90
|
+
return result.ok ? result.estimate : unavailable(result.error);
|
|
91
|
+
}
|
|
92
|
+
function estimateRewrite(input) {
|
|
93
|
+
return getCostProvider().estimate({
|
|
94
|
+
provider: "openai",
|
|
95
|
+
payload: { model: REWRITE_MODEL, input: text(input.prompt) ?? text(input.text) ?? "", max_output_tokens: 1000 },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function estimateOne(engine, toolName, input) {
|
|
99
|
+
if (GENERATION_TOOLS.has(toolName))
|
|
100
|
+
return estimateMedia(engine, input);
|
|
101
|
+
if (toolName === "edit_image_subjects")
|
|
102
|
+
return estimateImageEdit(input);
|
|
103
|
+
if (toolName === "rewrite_prompt")
|
|
104
|
+
return estimateRewrite(input);
|
|
105
|
+
return unavailable(`no cost adapter registered for tool '${toolName}'`);
|
|
106
|
+
}
|
|
107
|
+
function sumEstimates(estimates) {
|
|
108
|
+
if (estimates.length === 1)
|
|
109
|
+
return estimates[0];
|
|
110
|
+
const usd = estimates.reduce((acc, e) => acc + e.usd, 0);
|
|
111
|
+
const warnings = estimates.flatMap((e) => e.warnings);
|
|
112
|
+
const allFree = estimates.every((e) => e.source === "free");
|
|
113
|
+
const sources = new Set(estimates.map((e) => e.source));
|
|
114
|
+
return {
|
|
115
|
+
usd,
|
|
116
|
+
currency: "USD",
|
|
117
|
+
source: allFree ? "free" : sources.size === 1 ? estimates[0].source : "tokens-heuristic+table",
|
|
118
|
+
breakdown: {},
|
|
119
|
+
rateAsOf: estimates.find((e) => e.rateAsOf)?.rateAsOf ?? null,
|
|
120
|
+
warnings,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function estimateCostDef(engine, books) {
|
|
124
|
+
let injectedBookId = null;
|
|
125
|
+
try {
|
|
126
|
+
injectedBookId = engine.book.get().bookId;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
injectedBookId = null;
|
|
130
|
+
}
|
|
131
|
+
const estimateCall = async (call) => {
|
|
132
|
+
const bookId = call.input.book_id;
|
|
133
|
+
if (typeof bookId !== "string" || !bookId || bookId === injectedBookId) {
|
|
134
|
+
return estimateOne(engine, call.tool_name, call.input);
|
|
135
|
+
}
|
|
136
|
+
if (!books)
|
|
137
|
+
throw new Error(`Book not found: ${bookId}`);
|
|
138
|
+
const book = await books.ensure(bookId);
|
|
139
|
+
if (!book.ok)
|
|
140
|
+
throw new Error(book.error.message);
|
|
141
|
+
return estimateOne(book.value.engine, call.tool_name, call.input);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
name: "estimate_cost",
|
|
145
|
+
description: "Estimate the USD cost of one or more generate_image, generate_video or generate_audio calls, given as {tool_name, input}, before running them. Models without published rates return $0 with a warning.",
|
|
146
|
+
schema,
|
|
147
|
+
handler: async (args) => {
|
|
148
|
+
const calls = args.calls;
|
|
149
|
+
let estimates;
|
|
150
|
+
try {
|
|
151
|
+
estimates = await Promise.all(calls.map(estimateCall));
|
|
152
|
+
}
|
|
153
|
+
catch (cause) {
|
|
154
|
+
return {
|
|
155
|
+
isError: true,
|
|
156
|
+
content: [{ type: "text", text: cause instanceof Error ? cause.message : String(cause) }],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const total = sumEstimates(estimates);
|
|
160
|
+
return {
|
|
161
|
+
content: [{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: JSON.stringify({
|
|
164
|
+
estimate: total,
|
|
165
|
+
per_call: estimates.map((e, i) => ({ tool_name: calls[i].tool_name, estimate: e })),
|
|
166
|
+
}),
|
|
167
|
+
}],
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { findVideoFile } from "videobook-engine";
|
|
5
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
6
|
+
import { validateMediaOutputPaths } from "../media-output-path.js";
|
|
7
|
+
// Default first-chunk target for no-arg ad-hoc slicing. Caller can override
|
|
8
|
+
// via target_secs (e.g. [10] or [10, 15]) for tailored chunk lengths.
|
|
9
|
+
const DEFAULT_TARGET_SEC = 7;
|
|
10
|
+
// ffmpeg can't reliably produce a usable mp4 below this. Mirrors split-video.ts.
|
|
11
|
+
const MIN_CHUNK_SEC = 0.5;
|
|
12
|
+
/**
|
|
13
|
+
* Slice the source into N+1 chunks: one chunk per target length, plus a
|
|
14
|
+
* trailing "leftover" chunk holding whatever's left. Caller decides whether
|
|
15
|
+
* to keep the leftover.
|
|
16
|
+
*
|
|
17
|
+
* Why a 1-second backup on the last target? Most gens accept 9s clips just
|
|
18
|
+
* as readily as 10s, so when the targets sum would leave a sub-MIN tail,
|
|
19
|
+
* dropping 1s from the last target is the cheapest way to still get
|
|
20
|
+
* magic-sized targeted chunks.
|
|
21
|
+
*/
|
|
22
|
+
export function planEvenSplit(D, targets) {
|
|
23
|
+
if (!Number.isFinite(D) || D <= 0) {
|
|
24
|
+
return { ok: false, error: "Invalid source duration." };
|
|
25
|
+
}
|
|
26
|
+
if (targets.length === 0) {
|
|
27
|
+
return { ok: false, error: "At least one target_secs value is required." };
|
|
28
|
+
}
|
|
29
|
+
for (const t of targets) {
|
|
30
|
+
if (!Number.isFinite(t) || t <= 0) {
|
|
31
|
+
return { ok: false, error: `Invalid target_secs value: ${t}.` };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Build a plan if these targets are workable: sum must fit within D,
|
|
35
|
+
// and the trailing leftover must be either exactly zero or ≥ MIN.
|
|
36
|
+
const tryPlan = (ts) => {
|
|
37
|
+
const sum = ts.reduce((a, b) => a + b, 0);
|
|
38
|
+
if (sum > D)
|
|
39
|
+
return null;
|
|
40
|
+
const leftover = D - sum;
|
|
41
|
+
if (leftover > 0 && leftover < MIN_CHUNK_SEC)
|
|
42
|
+
return null;
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let cursor = 0;
|
|
45
|
+
for (const t of ts) {
|
|
46
|
+
chunks.push({ start: cursor, end: cursor + t, duration: t, exact: true });
|
|
47
|
+
cursor += t;
|
|
48
|
+
}
|
|
49
|
+
const hasLeftover = leftover >= MIN_CHUNK_SEC;
|
|
50
|
+
if (hasLeftover) {
|
|
51
|
+
chunks.push({ start: cursor, end: D, duration: leftover, exact: false });
|
|
52
|
+
}
|
|
53
|
+
return { chunks, hasLeftover };
|
|
54
|
+
};
|
|
55
|
+
// Try targets as-is first.
|
|
56
|
+
const direct = tryPlan(targets);
|
|
57
|
+
if (direct) {
|
|
58
|
+
return { ok: true, chunks: direct.chunks, usedTargets: targets, hasLeftover: direct.hasLeftover };
|
|
59
|
+
}
|
|
60
|
+
// Backup: knock 1s off the last target. Covers two failure modes:
|
|
61
|
+
// (a) last target overshoots D (e.g. D=9.5, [10] → [9])
|
|
62
|
+
// (b) sub-MIN tail (e.g. D=10.4, [10] → [9] with 1.4s leftover)
|
|
63
|
+
const last = targets[targets.length - 1];
|
|
64
|
+
if (last > 1) {
|
|
65
|
+
const backed = [...targets.slice(0, -1), last - 1];
|
|
66
|
+
const backedPlan = tryPlan(backed);
|
|
67
|
+
if (backedPlan) {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
chunks: backedPlan.chunks,
|
|
71
|
+
usedTargets: backed,
|
|
72
|
+
hasLeftover: backedPlan.hasLeftover,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const sum = targets.reduce((a, b) => a + b, 0);
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: `Source ${D.toFixed(2)}s is too short to split off targets [${targets.join(", ")}] (sum ${sum.toFixed(2)}s).`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function parseSingleTarget(input) {
|
|
83
|
+
if (typeof input === "number")
|
|
84
|
+
return input;
|
|
85
|
+
// Strip a trailing unit suffix like "s" / " sec" so "10s" parses cleanly.
|
|
86
|
+
const stripped = input.trim().replace(/\s*s(ec(onds?)?)?$/i, "");
|
|
87
|
+
return Number(stripped);
|
|
88
|
+
}
|
|
89
|
+
function parseTargetSecs(input) {
|
|
90
|
+
if (input === undefined || input.length === 0)
|
|
91
|
+
return [DEFAULT_TARGET_SEC];
|
|
92
|
+
return input.map(parseSingleTarget);
|
|
93
|
+
}
|
|
94
|
+
// Numeric suffixes "1".."N" mark chunk order; trim the source label to its
|
|
95
|
+
// last 2 words so labels stay short. Mirrors split-video.ts.
|
|
96
|
+
function deriveChunkName(source, index) {
|
|
97
|
+
const base = source.label ?? source.artifactId;
|
|
98
|
+
const words = base.split(/[\s-]+/u).filter((w) => w.length > 0);
|
|
99
|
+
const tail = words.slice(-2).join("-") || base;
|
|
100
|
+
return `${tail}-${index}`;
|
|
101
|
+
}
|
|
102
|
+
export function splitVideoByDurationsDef(engine) {
|
|
103
|
+
return {
|
|
104
|
+
name: "split_video_by_durations",
|
|
105
|
+
description: "Cut a video into chunks of the target_secs lengths (e.g. [10, 15]) plus a leftover chunk, as new video assets; the source is kept. With destination, the chunks are written as files into an existing asset instead, without the leftover.",
|
|
106
|
+
schema: {
|
|
107
|
+
asset_id: z.string().describe("Source video artifact id"),
|
|
108
|
+
book_id: z.string().describe("Book UUID"),
|
|
109
|
+
// Accept array of strings/numbers; chat positional args arrive as strings
|
|
110
|
+
// (possibly with unit suffixes like "10s"), MCP clients typically pass numbers.
|
|
111
|
+
target_secs: z
|
|
112
|
+
.array(z.union([z.number(), z.string()]))
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("Ordered list of target chunk lengths in seconds (default [7]). Each entry accepts '7', '7s', or 7. A leftover chunk holds whatever's left after these."),
|
|
115
|
+
destination: z
|
|
116
|
+
.object({
|
|
117
|
+
asset_id: z.string().describe("Existing artifact id to write chunk files into"),
|
|
118
|
+
filenames: z.array(z.string()).describe("One filename per target_secs entry; written as siblings inside the destination asset's directory"),
|
|
119
|
+
})
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("When set, write each chunk to <destination.asset_id>/<filenames[i]> instead of minting fresh video assets. filenames.length must equal target_secs.length; no leftover chunk is produced in destination mode."),
|
|
122
|
+
},
|
|
123
|
+
handler: async (args) => {
|
|
124
|
+
const { asset_id, book_id, target_secs, destination } = args;
|
|
125
|
+
const targets = parseTargetSecs(target_secs);
|
|
126
|
+
// Destination-mode arity: one filename per target, and no leftover —
|
|
127
|
+
// the caller has named exactly which siblings to write inside the
|
|
128
|
+
// destination asset. Reject early so we don't probe ffmpeg for nothing.
|
|
129
|
+
if (destination !== undefined && destination.filenames.length !== targets.length) {
|
|
130
|
+
return {
|
|
131
|
+
isError: true,
|
|
132
|
+
content: [{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `destination.filenames length (${destination.filenames.length}) must equal target_secs length (${targets.length})`,
|
|
135
|
+
}],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const sourceArtifact = engine.artifacts.get(asset_id);
|
|
139
|
+
if (!sourceArtifact.ok) {
|
|
140
|
+
return {
|
|
141
|
+
isError: true,
|
|
142
|
+
content: [{ type: "text", text: sourceArtifact.error.message }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
146
|
+
if (!manifestResult.ok) {
|
|
147
|
+
return {
|
|
148
|
+
isError: true,
|
|
149
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const manifest = manifestResult.value;
|
|
153
|
+
const videoFile = findVideoFile(manifest.files);
|
|
154
|
+
if (!videoFile) {
|
|
155
|
+
return {
|
|
156
|
+
isError: true,
|
|
157
|
+
content: [{ type: "text", text: `No video file found in asset ${asset_id}` }],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const sourcePath = join(manifest.path, videoFile.name);
|
|
161
|
+
let sourceDurationSec;
|
|
162
|
+
try {
|
|
163
|
+
sourceDurationSec = (await probeVideo(sourcePath)).duration;
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return {
|
|
167
|
+
isError: true,
|
|
168
|
+
content: [{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: `Failed to probe source duration: ${err instanceof Error ? err.message : String(err)}`,
|
|
171
|
+
}],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const plan = planEvenSplit(sourceDurationSec, targets);
|
|
175
|
+
if (!plan.ok) {
|
|
176
|
+
return {
|
|
177
|
+
isError: true,
|
|
178
|
+
content: [{ type: "text", text: plan.error }],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// Destination mode produces one output per target — no leftover —
|
|
182
|
+
// because the caller declared the exact set of filenames to write.
|
|
183
|
+
// The targets-only plan already enforces summing fits under D, so
|
|
184
|
+
// hasLeftover signals "leftover would have spilled" and we refuse.
|
|
185
|
+
if (destination !== undefined && plan.hasLeftover) {
|
|
186
|
+
return {
|
|
187
|
+
isError: true,
|
|
188
|
+
content: [{
|
|
189
|
+
type: "text",
|
|
190
|
+
text: `Destination mode does not support leftover chunks: targets sum to ${plan.usedTargets.reduce((a, b) => a + b, 0).toFixed(2)}s but source is ${sourceDurationSec.toFixed(2)}s`,
|
|
191
|
+
}],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
let created;
|
|
195
|
+
let destinationMode = false;
|
|
196
|
+
if (destination !== undefined) {
|
|
197
|
+
destinationMode = true;
|
|
198
|
+
try {
|
|
199
|
+
destination.filenames = await validateMediaOutputPaths(engine, destination.asset_id, destination.filenames, ".mp4");
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
isError: true,
|
|
204
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const destManifest = await engine.files.manifest(destination.asset_id);
|
|
208
|
+
if (!destManifest.ok) {
|
|
209
|
+
return {
|
|
210
|
+
isError: true,
|
|
211
|
+
content: [{ type: "text", text: `Destination asset ${destination.asset_id} not found: ${destManifest.error.message}` }],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// In destination mode `plan.chunks` has exactly N entries (no leftover);
|
|
215
|
+
// pair them with the caller's filenames positionally.
|
|
216
|
+
created = plan.chunks.map((chunk, i) => ({
|
|
217
|
+
assetId: destination.asset_id,
|
|
218
|
+
assetDir: destManifest.value.path,
|
|
219
|
+
outputPath: join(destManifest.value.path, destination.filenames[i]),
|
|
220
|
+
start: chunk.start,
|
|
221
|
+
end: chunk.end,
|
|
222
|
+
duration: chunk.duration,
|
|
223
|
+
exact: chunk.exact,
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
created = [];
|
|
228
|
+
for (let i = 0; i < plan.chunks.length; i++) {
|
|
229
|
+
const chunk = plan.chunks[i];
|
|
230
|
+
const name = deriveChunkName(sourceArtifact.value, i + 1);
|
|
231
|
+
const c = await engine.artifacts.create(sourceArtifact.value.kind, name);
|
|
232
|
+
if (!c.ok) {
|
|
233
|
+
return {
|
|
234
|
+
isError: true,
|
|
235
|
+
content: [{ type: "text", text: `Failed to create chunk ${i + 1} asset: ${c.error.message}` }],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
created.push({
|
|
239
|
+
assetId: c.value.artifactId,
|
|
240
|
+
assetDir: c.value.path,
|
|
241
|
+
outputPath: join(c.value.path, "original.mp4"),
|
|
242
|
+
start: chunk.start,
|
|
243
|
+
end: chunk.end,
|
|
244
|
+
duration: chunk.duration,
|
|
245
|
+
exact: chunk.exact,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const jobId = `even-split-${asset_id}-${Date.now()}`;
|
|
250
|
+
// Fire-and-forget: the chunk assets/files were already created above, so
|
|
251
|
+
// the UI can pick them up immediately. ffmpeg failures now surface via the
|
|
252
|
+
// asset-work failed state like every other queued tool, not this response.
|
|
253
|
+
await queue.enqueue(jobId, {
|
|
254
|
+
type: "even_split",
|
|
255
|
+
sourcePath,
|
|
256
|
+
sourceAssetId: asset_id,
|
|
257
|
+
chunks: created,
|
|
258
|
+
bookId: book_id,
|
|
259
|
+
destination: destinationMode ? { assetId: destination.asset_id } : undefined,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: JSON.stringify(destinationMode
|
|
265
|
+
? {
|
|
266
|
+
source_asset_id: asset_id,
|
|
267
|
+
source_duration_sec: sourceDurationSec,
|
|
268
|
+
target_secs: targets,
|
|
269
|
+
used_target_secs: plan.usedTargets,
|
|
270
|
+
destination: {
|
|
271
|
+
asset_id: destination.asset_id,
|
|
272
|
+
filenames: destination.filenames,
|
|
273
|
+
},
|
|
274
|
+
job_id: jobId,
|
|
275
|
+
status: "enqueued",
|
|
276
|
+
}
|
|
277
|
+
: {
|
|
278
|
+
source_asset_id: asset_id,
|
|
279
|
+
source_duration_sec: sourceDurationSec,
|
|
280
|
+
target_secs: targets,
|
|
281
|
+
used_target_secs: plan.usedTargets,
|
|
282
|
+
has_leftover: plan.hasLeftover,
|
|
283
|
+
chunks: created.map((c) => ({
|
|
284
|
+
asset_id: c.assetId,
|
|
285
|
+
duration: c.duration,
|
|
286
|
+
exact: c.exact,
|
|
287
|
+
})),
|
|
288
|
+
job_id: jobId,
|
|
289
|
+
status: "enqueued",
|
|
290
|
+
}),
|
|
291
|
+
}],
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { copyFileToClipboardDef } from "./copy-file-to-clipboard.js";
|
|
3
|
+
import { exportToDownloadsDef } from "./export-to-downloads.js";
|
|
4
|
+
import { showFolderDef } from "./show-folder.js";
|
|
5
|
+
const DESTINATIONS = ["downloads", "clipboard", "finder"];
|
|
6
|
+
export function exportAssetFileDef(engine) {
|
|
7
|
+
const handlers = {
|
|
8
|
+
downloads: exportToDownloadsDef(engine).handler,
|
|
9
|
+
clipboard: copyFileToClipboardDef(engine).handler,
|
|
10
|
+
finder: showFolderDef(engine).handler,
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
name: "export_asset_file",
|
|
14
|
+
description: "Hand an asset's original media file to macOS: copy it to Downloads, put it on the pasteboard as a file, or reveal its folder in Finder.",
|
|
15
|
+
schema: {
|
|
16
|
+
book_id: z.string().describe("Book id"),
|
|
17
|
+
asset_id: z.string().describe("Artifact id"),
|
|
18
|
+
to: z.enum(DESTINATIONS),
|
|
19
|
+
},
|
|
20
|
+
handler: async (args) => handlers[args.to](args),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { v7 as uuidv7 } from "uuid";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { transferAssetTags } from "../asset-tags.js";
|
|
4
|
+
import { resolveBookReference } from "../media-library.js";
|
|
5
|
+
import { queue } from "../queue.js";
|
|
6
|
+
export function exportAssetDef(engine, books) {
|
|
7
|
+
return {
|
|
8
|
+
name: "export_asset_to_project",
|
|
9
|
+
description: "Export an asset from one project to a different project",
|
|
10
|
+
...(books ? { scope: "global" } : {}),
|
|
11
|
+
schema: {
|
|
12
|
+
asset_id: z.string().describe("Artifact id to export"),
|
|
13
|
+
book_id: z.string().describe("Source book UUID"),
|
|
14
|
+
target_project: z.string().describe("Target book name or UUID"),
|
|
15
|
+
new_name: z.string().optional().describe("Optional custom name for the exported asset"),
|
|
16
|
+
},
|
|
17
|
+
handler: async (args) => {
|
|
18
|
+
const { asset_id, book_id, target_project, new_name } = args;
|
|
19
|
+
if (book_id === target_project) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
content: [{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: "Source and target are the same project. Use duplicate_asset to copy within the same project.",
|
|
25
|
+
}],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (books) {
|
|
29
|
+
try {
|
|
30
|
+
const sourceBook = await resolveBookReference(books, book_id);
|
|
31
|
+
const targetBook = await resolveBookReference(books, target_project);
|
|
32
|
+
if (sourceBook.bookId === targetBook.bookId) {
|
|
33
|
+
throw new Error("Source and target are the same project. Use duplicate_asset to copy within the same project.");
|
|
34
|
+
}
|
|
35
|
+
const sourceArtifact = sourceBook.engine.artifacts.get(asset_id);
|
|
36
|
+
if (!sourceArtifact.ok) {
|
|
37
|
+
throw new Error(sourceArtifact.error.message);
|
|
38
|
+
}
|
|
39
|
+
const result = await queue.enqueueAndWait(`copy-library-asset-${uuidv7()}`, {
|
|
40
|
+
type: "copy_library_asset",
|
|
41
|
+
bookId: targetBook.bookId,
|
|
42
|
+
sourceBookId: sourceBook.bookId,
|
|
43
|
+
sourceArtifactId: sourceArtifact.value.artifactId,
|
|
44
|
+
targetBookId: targetBook.bookId,
|
|
45
|
+
...(new_name ? { newName: new_name } : {}),
|
|
46
|
+
}, 15 * 60_000);
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.stringify({
|
|
51
|
+
source_asset_id: asset_id,
|
|
52
|
+
new_asset_id: result.targetArtifactId,
|
|
53
|
+
source_project: book_id,
|
|
54
|
+
target_project,
|
|
55
|
+
source_artifact_id: result.sourceArtifactId,
|
|
56
|
+
target_artifact_id: result.targetArtifactId,
|
|
57
|
+
source_book_id: result.sourceBookId,
|
|
58
|
+
target_book_id: result.targetBookId,
|
|
59
|
+
}),
|
|
60
|
+
}],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
return {
|
|
65
|
+
isError: true,
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: cause instanceof Error ? cause.message : String(cause),
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const sourceArtifact = engine.artifacts.get(asset_id);
|
|
74
|
+
if (!sourceArtifact.ok) {
|
|
75
|
+
return {
|
|
76
|
+
isError: true,
|
|
77
|
+
content: [{ type: "text", text: sourceArtifact.error.message }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
81
|
+
if (!manifestResult.ok) {
|
|
82
|
+
return {
|
|
83
|
+
isError: true,
|
|
84
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const name = new_name ?? sourceArtifact.value.label;
|
|
88
|
+
const createResult = await engine.artifacts.create({
|
|
89
|
+
kind: sourceArtifact.value.kind,
|
|
90
|
+
...(name ? { label: name } : {}),
|
|
91
|
+
});
|
|
92
|
+
if (!createResult.ok) {
|
|
93
|
+
return {
|
|
94
|
+
isError: true,
|
|
95
|
+
content: [{ type: "text", text: createResult.error.message }],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const newAssetId = createResult.value.artifactId;
|
|
99
|
+
for (const file of manifestResult.value.files) {
|
|
100
|
+
const readResult = await engine.files.read(asset_id, file.name);
|
|
101
|
+
if (!readResult.ok) {
|
|
102
|
+
return {
|
|
103
|
+
isError: true,
|
|
104
|
+
content: [{ type: "text", text: readResult.error.message }],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const writeResult = await engine.files.write(newAssetId, file.name, readResult.value);
|
|
108
|
+
if (!writeResult.ok) {
|
|
109
|
+
return {
|
|
110
|
+
isError: true,
|
|
111
|
+
content: [{ type: "text", text: writeResult.error.message }],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
await transferAssetTags({ engine, artifactId: asset_id }, { engine, artifactId: newAssetId });
|
|
117
|
+
}
|
|
118
|
+
catch (cause) {
|
|
119
|
+
return {
|
|
120
|
+
isError: true,
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: cause instanceof Error ? cause.message : String(cause),
|
|
124
|
+
}],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
content: [{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: JSON.stringify({
|
|
131
|
+
source_asset_id: asset_id,
|
|
132
|
+
new_asset_id: newAssetId,
|
|
133
|
+
source_project: book_id,
|
|
134
|
+
target_project,
|
|
135
|
+
}),
|
|
136
|
+
}],
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|