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,126 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gcEngineCatalog } from "../catalog-gc.js";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
function error(text) {
|
|
5
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
6
|
+
}
|
|
7
|
+
function ok(payload) {
|
|
8
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
9
|
+
}
|
|
10
|
+
// assets-table statuses that count as "in progress" — a row sitting in one of
|
|
11
|
+
// these past its deadline is wedged, not legitimately working.
|
|
12
|
+
const STUCK_WORK_STATUSES = new Set(["working", "pending"]);
|
|
13
|
+
/**
|
|
14
|
+
* Read-only health scan. Type-agnostic: it iterates every asset returned by
|
|
15
|
+
* listAssets and asks the engine's own computeArtifactStatus (via getAssetStatus)
|
|
16
|
+
* for each, so all seven asset types are covered without special-casing.
|
|
17
|
+
*/
|
|
18
|
+
async function diagnose(engine, bookId, onlyAssetId) {
|
|
19
|
+
const all = await engine.artifacts.list();
|
|
20
|
+
const assets = onlyAssetId ? all.filter((a) => a.artifactId === onlyAssetId) : all;
|
|
21
|
+
const errorsRes = await engine.jobs.failures.findAll();
|
|
22
|
+
const errorByAsset = new Map();
|
|
23
|
+
if (errorsRes.ok)
|
|
24
|
+
for (const e of errorsRes.value)
|
|
25
|
+
errorByAsset.set(e.artifactId, e);
|
|
26
|
+
const workRes = await engine.jobs.artifactWork.list();
|
|
27
|
+
const workByAsset = new Map();
|
|
28
|
+
if (workRes.ok)
|
|
29
|
+
for (const w of workRes.value)
|
|
30
|
+
workByAsset.set(w.artifactId, w);
|
|
31
|
+
const nowMs = Date.now();
|
|
32
|
+
const broken = [];
|
|
33
|
+
const stuck = [];
|
|
34
|
+
let healthy = 0;
|
|
35
|
+
await Promise.all(assets.map(async (a) => {
|
|
36
|
+
const statusRes = await engine.status.get(a.artifactId);
|
|
37
|
+
const status = statusRes.ok ? statusRes.value : "error";
|
|
38
|
+
const work = workByAsset.get(a.artifactId);
|
|
39
|
+
if (status === "error") {
|
|
40
|
+
const ge = errorByAsset.get(a.artifactId);
|
|
41
|
+
broken.push({
|
|
42
|
+
asset_id: a.artifactId,
|
|
43
|
+
type: a.kind,
|
|
44
|
+
error: ge
|
|
45
|
+
? { message: ge.message, fail_code: ge.failCode ?? null, failed_at: ge.failedAt }
|
|
46
|
+
: null,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else if (work &&
|
|
50
|
+
STUCK_WORK_STATUSES.has(work.status) &&
|
|
51
|
+
work.deadlineAt != null &&
|
|
52
|
+
work.deadlineAt < nowMs) {
|
|
53
|
+
stuck.push({
|
|
54
|
+
asset_id: a.artifactId,
|
|
55
|
+
type: a.kind,
|
|
56
|
+
work_status: work.status,
|
|
57
|
+
deadline_at: work.deadlineAt,
|
|
58
|
+
minutes_overdue: Math.round((nowMs - work.deadlineAt) / 60000),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
healthy += 1;
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
const report = {
|
|
66
|
+
book_id: bookId,
|
|
67
|
+
total: assets.length,
|
|
68
|
+
healthy,
|
|
69
|
+
broken,
|
|
70
|
+
stuck,
|
|
71
|
+
};
|
|
72
|
+
if (broken.length > 0 || stuck.length > 0) {
|
|
73
|
+
report.hint =
|
|
74
|
+
"Re-run with { asset_id, fix: true } to restore a specific asset to its last usable state.";
|
|
75
|
+
}
|
|
76
|
+
return report;
|
|
77
|
+
}
|
|
78
|
+
export function doctorDef(engine) {
|
|
79
|
+
return {
|
|
80
|
+
name: "doctor",
|
|
81
|
+
description: "Report assets stuck in an error state or past their work deadline. fix with an asset_id restores that one asset (clears a stale error or lease, otherwise restores it from history); fix alone only reports. gc reclaims catalog storage for this book.",
|
|
82
|
+
schema: {
|
|
83
|
+
book_id: z.string().describe("Book id"),
|
|
84
|
+
asset_id: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Limit diagnosis to one asset by artifact id; REQUIRED when fix is true"),
|
|
88
|
+
fix: z
|
|
89
|
+
.boolean()
|
|
90
|
+
.optional()
|
|
91
|
+
.default(false)
|
|
92
|
+
.describe("Restore the given asset_id to its last usable state. Requires asset_id."),
|
|
93
|
+
gc: z
|
|
94
|
+
.boolean()
|
|
95
|
+
.optional()
|
|
96
|
+
.default(false)
|
|
97
|
+
.describe("Run catalog GC on this book and report bytes/chunks reclaimed."),
|
|
98
|
+
},
|
|
99
|
+
handler: async (args) => {
|
|
100
|
+
const { book_id, asset_id, fix, gc } = args;
|
|
101
|
+
const assetId = asset_id?.replace(/^@/, "");
|
|
102
|
+
if (fix) {
|
|
103
|
+
if (!assetId) {
|
|
104
|
+
return error("doctor fix requires an explicit asset_id — it never bulk-fixes a project. " +
|
|
105
|
+
"Run doctor without fix to see which assets need attention.");
|
|
106
|
+
}
|
|
107
|
+
const jobId = `doctor-fix-${assetId}-${Date.now()}`;
|
|
108
|
+
try {
|
|
109
|
+
const result = await queue.enqueueAndWait(jobId, {
|
|
110
|
+
type: "doctor_fix",
|
|
111
|
+
bookId: book_id,
|
|
112
|
+
assetId,
|
|
113
|
+
});
|
|
114
|
+
return ok(result);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return error(`doctor fix failed for ${assetId}: ${e instanceof Error ? e.message : String(e)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const report = await diagnose(engine, book_id, assetId);
|
|
121
|
+
if (gc)
|
|
122
|
+
report.catalog_gc = gcEngineCatalog(engine);
|
|
123
|
+
return ok(report);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { parseTime } from "../time-parse.js";
|
|
5
|
+
const SOURCE_PREFIX_MAP = {
|
|
6
|
+
"youtube.com": "yt",
|
|
7
|
+
"youtu.be": "yt",
|
|
8
|
+
"twitter.com": "x",
|
|
9
|
+
"x.com": "x",
|
|
10
|
+
"instagram.com": "ig",
|
|
11
|
+
"tiktok.com": "tt",
|
|
12
|
+
"vimeo.com": "vm",
|
|
13
|
+
"reddit.com": "rd",
|
|
14
|
+
"facebook.com": "fb",
|
|
15
|
+
"fb.watch": "fb",
|
|
16
|
+
"twitch.tv": "tw",
|
|
17
|
+
"dailymotion.com": "dm",
|
|
18
|
+
};
|
|
19
|
+
function sourcePrefix(url) {
|
|
20
|
+
try {
|
|
21
|
+
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
22
|
+
if (SOURCE_PREFIX_MAP[hostname])
|
|
23
|
+
return SOURCE_PREFIX_MAP[hostname];
|
|
24
|
+
const base = hostname.split(".").slice(-2, -1)[0];
|
|
25
|
+
return base ? base.slice(0, 3) : "dl";
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "dl";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function downloadVideoDef(engine) {
|
|
32
|
+
return {
|
|
33
|
+
name: "download_video",
|
|
34
|
+
description: "Download a video or requested time range from a URL (YouTube, Twitter/X, etc.) into a Videobook as original.mp4. Supports auto-exports in 16:9 and 9:16.",
|
|
35
|
+
schema: {
|
|
36
|
+
url: z.string().describe("Video URL (YouTube, Twitter/X, Instagram, TikTok, etc.)"),
|
|
37
|
+
start_time: z.union([z.string(), z.number()]).optional().describe("Range start in seconds or timestamp notation (e.g. '30', '1:30', '2m20s')"),
|
|
38
|
+
end_time: z.union([z.string(), z.number()]).optional().describe("Range end in seconds or timestamp notation (e.g. '60', '2:00', '3m')"),
|
|
39
|
+
book_id: z.string().describe("Book UUID"),
|
|
40
|
+
export_videos: z.boolean().optional().default(false).describe("Auto-export 16:9 and 9:16 versions (default: off)"),
|
|
41
|
+
full: z.boolean().optional().default(false).describe("Deprecated; omit start_time and end_time to keep the full source"),
|
|
42
|
+
},
|
|
43
|
+
handler: async (args) => {
|
|
44
|
+
const { url, start_time, end_time, book_id, export_videos } = args;
|
|
45
|
+
let startSecs;
|
|
46
|
+
let endSecs;
|
|
47
|
+
try {
|
|
48
|
+
startSecs = parseTime(start_time);
|
|
49
|
+
endSecs = parseTime(end_time);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
53
|
+
return { isError: true, content: [{ type: "text", text: msg }] };
|
|
54
|
+
}
|
|
55
|
+
if ((startSecs !== undefined && startSecs < 0)
|
|
56
|
+
|| (endSecs !== undefined && endSecs <= 0)) {
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [{ type: "text", text: "start_time must be >= 0 and end_time must be > 0." }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (startSecs !== undefined && endSecs !== undefined && startSecs >= endSecs) {
|
|
63
|
+
return {
|
|
64
|
+
isError: true,
|
|
65
|
+
content: [{ type: "text", text: "start_time must be less than end_time." }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const requestedRange = startSecs !== undefined || endSecs !== undefined
|
|
69
|
+
? {
|
|
70
|
+
...(startSecs !== undefined ? { start: startSecs } : {}),
|
|
71
|
+
...(endSecs !== undefined ? { end: endSecs } : {}),
|
|
72
|
+
}
|
|
73
|
+
: undefined;
|
|
74
|
+
const normalizedUrl = /^https?:\/\//i.test(url) ? url : `https://${url}`;
|
|
75
|
+
const prefix = sourcePrefix(normalizedUrl);
|
|
76
|
+
const title = new URL(normalizedUrl).pathname.split("/").filter(Boolean).pop() ?? "video";
|
|
77
|
+
const assetName = `${prefix} ${title}`;
|
|
78
|
+
const assetResult = await engine.artifacts.create("video", assetName);
|
|
79
|
+
if (!assetResult.ok) {
|
|
80
|
+
return {
|
|
81
|
+
isError: true,
|
|
82
|
+
content: [{ type: "text", text: `Error creating asset: ${assetResult.error.message}` }],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const { artifactId, path: assetDir, } = assetResult.value;
|
|
86
|
+
const assetId = artifactId;
|
|
87
|
+
const outputPath = join(assetDir, "original.mp4");
|
|
88
|
+
const jobId = `download-${assetId}-${Date.now()}`;
|
|
89
|
+
await queue.enqueue(jobId, {
|
|
90
|
+
type: "download_video",
|
|
91
|
+
url: normalizedUrl,
|
|
92
|
+
outputPath,
|
|
93
|
+
assetId,
|
|
94
|
+
assetDir,
|
|
95
|
+
bookId: book_id,
|
|
96
|
+
exportVideos: export_videos ?? false,
|
|
97
|
+
full: true,
|
|
98
|
+
sourcePrefix: prefix,
|
|
99
|
+
...(requestedRange ? { requestedRange } : {}),
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
content: [{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify({
|
|
105
|
+
artifact_id: artifactId,
|
|
106
|
+
assetId,
|
|
107
|
+
job_id: jobId,
|
|
108
|
+
status: "enqueued",
|
|
109
|
+
}),
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { NOTEBOOK_GRID_ROW_COUNT, notebookOccupiedSlots, notebookGridTag, parseNotebookGridAddress, } from "videobook-engine/notebook-grid";
|
|
2
|
+
import { NOTEBOOK_GRID_ADDRESS_SOURCE } from "videobook-engine/notebook-mentions";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { EMPTY_TRANSFER_SUMMARY, transferAssetTags } from "../asset-tags.js";
|
|
5
|
+
import { enqueueSearchIndex } from "../jobs/enqueue-search-index.js";
|
|
6
|
+
import { mutateNotebook } from "../notebook-mutations.js";
|
|
7
|
+
import { sourceCellForPostedArtifact } from "../notebook-published-post.js";
|
|
8
|
+
const destinationSchema = z.string()
|
|
9
|
+
.regex(new RegExp(`^@?${NOTEBOOK_GRID_ADDRESS_SOURCE}$`, "iu"))
|
|
10
|
+
.describe("Optional empty destination cell from a1 through bl8");
|
|
11
|
+
function mediaCellType(kind) {
|
|
12
|
+
if (kind === "final")
|
|
13
|
+
return "video";
|
|
14
|
+
return kind === "audio" || kind === "image" || kind === "video"
|
|
15
|
+
? kind
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
function sourceCellForArtifact(notebook, artifactId, cellType) {
|
|
19
|
+
return sourceCellForPostedArtifact(notebook, artifactId, cellType);
|
|
20
|
+
}
|
|
21
|
+
function duplicateDestination(notebook, source, requested) {
|
|
22
|
+
const destination = requested
|
|
23
|
+
? parseNotebookGridAddress(requested)
|
|
24
|
+
: source && source.slot.row + 1 < NOTEBOOK_GRID_ROW_COUNT
|
|
25
|
+
? { row: source.slot.row + 1, column: source.slot.column }
|
|
26
|
+
: null;
|
|
27
|
+
if (!destination) {
|
|
28
|
+
throw new Error(source
|
|
29
|
+
? `${notebookGridTag(source.slot)} has no cell below it; choose a destination`
|
|
30
|
+
: "Choose a destination because the source asset is not in this notebook");
|
|
31
|
+
}
|
|
32
|
+
const occupied = notebookOccupiedSlots(notebook.cells).some((slot) => slot.row === destination.row && slot.column === destination.column);
|
|
33
|
+
if (occupied) {
|
|
34
|
+
throw new Error(`Notebook cell ${notebookGridTag(destination)} is occupied`);
|
|
35
|
+
}
|
|
36
|
+
return destination;
|
|
37
|
+
}
|
|
38
|
+
function failure(message) {
|
|
39
|
+
return {
|
|
40
|
+
isError: true,
|
|
41
|
+
content: [{ type: "text", text: message }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function copyOriginalMetadata(engine, sourceAssetId, destinationAssetId) {
|
|
45
|
+
const sourceMetadata = await engine.metadata.artifacts.read(sourceAssetId, "original");
|
|
46
|
+
if (!sourceMetadata.ok) {
|
|
47
|
+
if (sourceMetadata.error.code === "NOT_FOUND")
|
|
48
|
+
return;
|
|
49
|
+
throw new Error(sourceMetadata.error.message);
|
|
50
|
+
}
|
|
51
|
+
const metadataWrite = await engine.metadata.artifacts.write(destinationAssetId, "original", sourceMetadata.value);
|
|
52
|
+
if (!metadataWrite.ok)
|
|
53
|
+
throw new Error(metadataWrite.error.message);
|
|
54
|
+
}
|
|
55
|
+
export function duplicateAssetDef(engine) {
|
|
56
|
+
return {
|
|
57
|
+
name: "duplicate_asset",
|
|
58
|
+
description: "Duplicate a media asset and place its notebook cell at an optional destination or directly below the source",
|
|
59
|
+
schema: {
|
|
60
|
+
asset_id: z.string().describe("Artifact id to duplicate"),
|
|
61
|
+
book_id: z.string().describe("Book UUID"),
|
|
62
|
+
new_name: z.string().optional().describe("Optional custom name for the copy"),
|
|
63
|
+
notebook_id: z.string().uuid().optional().describe("Open notebook receiving the duplicate cell"),
|
|
64
|
+
destination: destinationSchema.optional(),
|
|
65
|
+
},
|
|
66
|
+
handler: async (args) => {
|
|
67
|
+
const { asset_id, book_id: _book_id, destination, new_name, notebook_id, } = args;
|
|
68
|
+
const sourceArtifact = engine.artifacts.get(asset_id);
|
|
69
|
+
if (!sourceArtifact.ok) {
|
|
70
|
+
return failure(sourceArtifact.error.message);
|
|
71
|
+
}
|
|
72
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
73
|
+
if (!manifestResult.ok) {
|
|
74
|
+
return failure(manifestResult.error.message);
|
|
75
|
+
}
|
|
76
|
+
const name = new_name
|
|
77
|
+
?? (sourceArtifact.value.label ? `${sourceArtifact.value.label} copy` : undefined);
|
|
78
|
+
const cellType = mediaCellType(sourceArtifact.value.kind);
|
|
79
|
+
if (destination && !notebook_id) {
|
|
80
|
+
return failure("A duplicate destination requires notebook_id");
|
|
81
|
+
}
|
|
82
|
+
if (notebook_id) {
|
|
83
|
+
if (!cellType) {
|
|
84
|
+
return failure(`A ${sourceArtifact.value.kind} artifact cannot be placed in a media cell`);
|
|
85
|
+
}
|
|
86
|
+
const notebook = engine.notebooks.read(notebook_id);
|
|
87
|
+
if (!notebook.ok)
|
|
88
|
+
return failure(notebook.error.message);
|
|
89
|
+
try {
|
|
90
|
+
duplicateDestination(notebook.value, sourceCellForArtifact(notebook.value, asset_id, cellType), destination);
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const createResult = await engine.artifacts.create({
|
|
97
|
+
kind: sourceArtifact.value.kind,
|
|
98
|
+
...(name ? { label: name } : {}),
|
|
99
|
+
});
|
|
100
|
+
if (!createResult.ok) {
|
|
101
|
+
return failure(createResult.error.message);
|
|
102
|
+
}
|
|
103
|
+
const newAssetId = createResult.value.artifactId;
|
|
104
|
+
for (const file of manifestResult.value.files) {
|
|
105
|
+
const readResult = await engine.files.read(asset_id, file.name);
|
|
106
|
+
if (!readResult.ok) {
|
|
107
|
+
await engine.artifacts.delete(newAssetId);
|
|
108
|
+
return failure(readResult.error.message);
|
|
109
|
+
}
|
|
110
|
+
const writeResult = await engine.files.write(newAssetId, file.name, readResult.value);
|
|
111
|
+
if (!writeResult.ok) {
|
|
112
|
+
await engine.artifacts.delete(newAssetId);
|
|
113
|
+
return failure(writeResult.error.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Tags transfer after the bytes: the engine only lets automatic
|
|
117
|
+
// evidence cross when the copy actually carries the analyzed content.
|
|
118
|
+
const tagTransfer = { value: EMPTY_TRANSFER_SUMMARY };
|
|
119
|
+
try {
|
|
120
|
+
await copyOriginalMetadata(engine, asset_id, newAssetId);
|
|
121
|
+
tagTransfer.value = await transferAssetTags({ engine, artifactId: asset_id }, { engine, artifactId: newAssetId });
|
|
122
|
+
}
|
|
123
|
+
catch (cause) {
|
|
124
|
+
await engine.artifacts.delete(newAssetId);
|
|
125
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
126
|
+
}
|
|
127
|
+
const placement = { cell: null };
|
|
128
|
+
if (notebook_id && cellType) {
|
|
129
|
+
try {
|
|
130
|
+
const notebookResult = await mutateNotebook(engine, notebook_id, "duplicate_asset", (notebook) => {
|
|
131
|
+
const slot = duplicateDestination(notebook, sourceCellForArtifact(notebook, asset_id, cellType), destination);
|
|
132
|
+
placement.cell = engine.notebooks.createCell({
|
|
133
|
+
type: cellType,
|
|
134
|
+
...(name ? { label: name } : {}),
|
|
135
|
+
slot,
|
|
136
|
+
inputs: {
|
|
137
|
+
sourceOperation: "duplicate_asset",
|
|
138
|
+
sourceArtifactId: asset_id,
|
|
139
|
+
state: "completed",
|
|
140
|
+
},
|
|
141
|
+
outputArtifactId: newAssetId,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
...notebook,
|
|
145
|
+
cells: [...notebook.cells, placement.cell],
|
|
146
|
+
};
|
|
147
|
+
}, {
|
|
148
|
+
sourceArtifactId: asset_id,
|
|
149
|
+
newArtifactId: newAssetId,
|
|
150
|
+
destination: destination ?? null,
|
|
151
|
+
}, { artifactId: newAssetId });
|
|
152
|
+
if (!notebookResult.ok)
|
|
153
|
+
throw new Error(notebookResult.error.message);
|
|
154
|
+
}
|
|
155
|
+
catch (cause) {
|
|
156
|
+
await engine.artifacts.delete(newAssetId);
|
|
157
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
await enqueueSearchIndex(engine, newAssetId);
|
|
161
|
+
const createdCell = placement.cell;
|
|
162
|
+
const address = createdCell ? notebookGridTag(createdCell.slot) : null;
|
|
163
|
+
return {
|
|
164
|
+
content: [{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: JSON.stringify({
|
|
167
|
+
source_asset_id: asset_id,
|
|
168
|
+
new_asset_id: newAssetId,
|
|
169
|
+
kind: sourceArtifact.value.kind,
|
|
170
|
+
tags: tagTransfer.value,
|
|
171
|
+
...(createdCell && notebook_id
|
|
172
|
+
? {
|
|
173
|
+
notebook_id,
|
|
174
|
+
cell_id: createdCell.id,
|
|
175
|
+
address,
|
|
176
|
+
message: `Duplicated as ${address}`,
|
|
177
|
+
}
|
|
178
|
+
: { message: "Asset duplicated" }),
|
|
179
|
+
}),
|
|
180
|
+
}],
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { timelineFailure, timelineSuccess, writeTimeline, } from "../timeline-helpers.js";
|
|
3
|
+
const slotSchema = z.object({
|
|
4
|
+
id: z.string().uuid().optional(),
|
|
5
|
+
artifact_id: z.string().uuid(),
|
|
6
|
+
volume: z.number().min(0).optional(),
|
|
7
|
+
audioFadeIn: z.number().min(0).optional(),
|
|
8
|
+
audioFadeOut: z.number().min(0).optional(),
|
|
9
|
+
});
|
|
10
|
+
const audioSchema = z.object({
|
|
11
|
+
id: z.string().uuid().optional(),
|
|
12
|
+
artifact_id: z.string().uuid(),
|
|
13
|
+
startFrame: z.number().int().min(0),
|
|
14
|
+
durationFrames: z.number().int().positive(),
|
|
15
|
+
volume: z.number().min(0).optional(),
|
|
16
|
+
fadeIn: z.number().min(0).optional(),
|
|
17
|
+
fadeOut: z.number().min(0).optional(),
|
|
18
|
+
});
|
|
19
|
+
export function setTimelineDef(engine) {
|
|
20
|
+
return {
|
|
21
|
+
name: "set_timeline",
|
|
22
|
+
description: "Replace a book's typed v2 timeline using stable artifact UUIDs",
|
|
23
|
+
schema: {
|
|
24
|
+
book_id: z.string().uuid(),
|
|
25
|
+
render: z.enum(["landscape", "portrait", "square"]),
|
|
26
|
+
slots: z.array(slotSchema).optional(),
|
|
27
|
+
audio: z.array(audioSchema).optional(),
|
|
28
|
+
},
|
|
29
|
+
handler: async (args) => {
|
|
30
|
+
const slots = args.slots;
|
|
31
|
+
const audio = args.audio;
|
|
32
|
+
const input = {
|
|
33
|
+
render: args.render,
|
|
34
|
+
...(slots === undefined ? {} : {
|
|
35
|
+
slots: slots.map((slot) => ({
|
|
36
|
+
...(slot.id ? { id: slot.id } : {}),
|
|
37
|
+
artifactId: slot.artifact_id,
|
|
38
|
+
...(slot.volume === undefined ? {} : { volume: slot.volume }),
|
|
39
|
+
...(slot.audioFadeIn === undefined ? {} : { audioFadeIn: slot.audioFadeIn }),
|
|
40
|
+
...(slot.audioFadeOut === undefined ? {} : { audioFadeOut: slot.audioFadeOut }),
|
|
41
|
+
})),
|
|
42
|
+
}),
|
|
43
|
+
...(audio === undefined ? {} : {
|
|
44
|
+
audio: audio.map((clip) => ({
|
|
45
|
+
...(clip.id ? { id: clip.id } : {}),
|
|
46
|
+
artifactId: clip.artifact_id,
|
|
47
|
+
startFrame: clip.startFrame,
|
|
48
|
+
durationFrames: clip.durationFrames,
|
|
49
|
+
...(clip.volume === undefined ? {} : { volume: clip.volume }),
|
|
50
|
+
...(clip.fadeIn === undefined ? {} : { fadeIn: clip.fadeIn }),
|
|
51
|
+
...(clip.fadeOut === undefined ? {} : { fadeOut: clip.fadeOut }),
|
|
52
|
+
})),
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
const result = await writeTimeline(engine, input);
|
|
56
|
+
return result.ok ? timelineSuccess(result.value) : timelineFailure(result.error.message);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { queue } from "../queue.js";
|
|
3
|
+
import { findPrimaryMediaFile } from "videobook-engine";
|
|
4
|
+
import { MOMENTS_MAX_SCAN_SECONDS, MOMENTS_MIN_MOMENT_SECONDS, editingMomentsId, readEditingMoments, } from "../editing-moments.js";
|
|
5
|
+
function json(value) {
|
|
6
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
7
|
+
}
|
|
8
|
+
function failure(error) {
|
|
9
|
+
return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
|
|
10
|
+
}
|
|
11
|
+
const readSchema = z.object({ book_id: z.string().uuid(), asset_id: z.string().uuid() });
|
|
12
|
+
const analyzeSchema = readSchema.extend({
|
|
13
|
+
start_seconds: z.number().nonnegative().optional(),
|
|
14
|
+
end_seconds: z.number().positive().optional(),
|
|
15
|
+
force: z.boolean().optional(),
|
|
16
|
+
});
|
|
17
|
+
export function editingMomentsDefs(engine) {
|
|
18
|
+
const schema = { book_id: z.string().uuid(), asset_id: z.string().uuid() };
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
name: "analyze_editing_moments",
|
|
22
|
+
description: "Analyze footage for editing in a background job: shot boundaries, per-shot motion, subject changes, usable ranges and ranked source moments, reused for unchanged sources. Read the results with get_editing_moments.",
|
|
23
|
+
scope: "book",
|
|
24
|
+
schema: {
|
|
25
|
+
...schema,
|
|
26
|
+
start_seconds: z.number().nonnegative().optional(),
|
|
27
|
+
end_seconds: z.number().positive().optional(),
|
|
28
|
+
force: z.boolean().optional(),
|
|
29
|
+
},
|
|
30
|
+
handler: async (input) => {
|
|
31
|
+
try {
|
|
32
|
+
const args = analyzeSchema.parse(input);
|
|
33
|
+
const source = await momentsSource(engine, args.asset_id, {
|
|
34
|
+
startSeconds: args.start_seconds, endSeconds: args.end_seconds,
|
|
35
|
+
});
|
|
36
|
+
if (!args.force && await readEditingMoments(engine, args.asset_id, source.momentsId)) {
|
|
37
|
+
return json(await readState(engine, args.asset_id, args.start_seconds, args.end_seconds));
|
|
38
|
+
}
|
|
39
|
+
await queue.enqueue(`editing-moments-${source.momentsId}`, {
|
|
40
|
+
type: "analyze_editing_moments", bookId: args.book_id, assetId: args.asset_id,
|
|
41
|
+
startSeconds: source.startSeconds, endSeconds: source.endSeconds,
|
|
42
|
+
});
|
|
43
|
+
return json(await readState(engine, args.asset_id, args.start_seconds, args.end_seconds));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return failure(error);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "get_editing_moments",
|
|
52
|
+
description: "Read cached editing moments (shot boundaries, motion, ranked source moments) for the current video source",
|
|
53
|
+
scope: "book", schema,
|
|
54
|
+
handler: async (args) => {
|
|
55
|
+
try {
|
|
56
|
+
return json(await readState(engine, String(args.asset_id)));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return failure(error);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
async function momentsSource(engine, artifactId, args = {}) {
|
|
66
|
+
const artifact = engine.artifacts.get(artifactId);
|
|
67
|
+
if (!artifact.ok)
|
|
68
|
+
throw new Error(artifact.error.message);
|
|
69
|
+
if (!["video", "final"].includes(artifact.value.kind))
|
|
70
|
+
throw new Error("Choose a video source to analyze");
|
|
71
|
+
const manifest = await engine.files.manifest(artifactId);
|
|
72
|
+
if (!manifest.ok)
|
|
73
|
+
throw new Error(manifest.error.message);
|
|
74
|
+
const file = findPrimaryMediaFile(manifest.value.files, artifact.value.kind);
|
|
75
|
+
const streams = engine.streams.list(artifactId);
|
|
76
|
+
if (!streams.ok)
|
|
77
|
+
throw new Error(streams.error.message);
|
|
78
|
+
const stream = streams.value.find((item) => item.kind === "video" && item.sourcePath === file?.name
|
|
79
|
+
&& item.objectHash === file?.objectHash);
|
|
80
|
+
if (!stream || !file)
|
|
81
|
+
throw new Error("Wait for the current video source to finish processing");
|
|
82
|
+
const durationSeconds = stream.durationTicks * stream.timeBase.numerator / stream.timeBase.denominator;
|
|
83
|
+
if (!(durationSeconds > 0) || durationSeconds > MOMENTS_MAX_SCAN_SECONDS) {
|
|
84
|
+
throw new Error(`Moment analysis supports video up to ${MOMENTS_MAX_SCAN_SECONDS / 60} minutes`);
|
|
85
|
+
}
|
|
86
|
+
const startSeconds = Math.min(Math.max(0, args.startSeconds ?? 0), Math.max(0, durationSeconds - MOMENTS_MIN_MOMENT_SECONDS));
|
|
87
|
+
const endSeconds = args.endSeconds === undefined ? durationSeconds : Math.min(Math.max(startSeconds + MOMENTS_MIN_MOMENT_SECONDS, args.endSeconds), durationSeconds);
|
|
88
|
+
const momentsId = editingMomentsId({ objectHash: stream.objectHash, streamIndex: stream.streamIndex, startSeconds, endSeconds });
|
|
89
|
+
return { momentsId, startSeconds, endSeconds };
|
|
90
|
+
}
|
|
91
|
+
async function readState(engine, artifactId, startSeconds, endSeconds) {
|
|
92
|
+
const source = await momentsSource(engine, artifactId, { startSeconds, endSeconds });
|
|
93
|
+
const cached = await readEditingMoments(engine, artifactId, source.momentsId);
|
|
94
|
+
const jobs = engine.jobs.queue.list({ type: "analyze_editing_moments", limit: 500 });
|
|
95
|
+
const job = jobs.filter((item) => item.payload.assetId === artifactId)
|
|
96
|
+
.sort((left, right) => right.id - left.id)[0];
|
|
97
|
+
const pending = job && ["queued", "running", "completing"].includes(job.state);
|
|
98
|
+
const status = cached ? "ready"
|
|
99
|
+
: pending ? job.state === "queued" ? "queued" : "analyzing"
|
|
100
|
+
: job?.state === "aborted" ? "cancelled" : job?.state === "failed" ? "failed" : "missing";
|
|
101
|
+
return {
|
|
102
|
+
status, momentsId: source.momentsId, jobId: job?.id ?? null,
|
|
103
|
+
error: job?.error?.message ?? null, moments: cached,
|
|
104
|
+
};
|
|
105
|
+
}
|