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,97 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LIGHTNING_WATERMARK_PROFILE } from "../lightning-defaults.js";
|
|
3
|
+
import { WATERMARK_PLATFORMS } from "../watermark-platform.js";
|
|
4
|
+
import { normalizeHandle, readWatermarkConfig, recordHandle } from "../watermark-config.js";
|
|
5
|
+
import { createWatermarkAsset, DEFAULT_HANDLE_PADDING, DEFAULT_HANDLE_ROUNDING, DEFAULT_HANDLE, defaultUrl, MAX_HANDLE_PADDING, MAX_HANDLE_ROUNDING, MIN_HANDLE_PADDING, MIN_HANDLE_ROUNDING, } from "../watermark-badge.js";
|
|
6
|
+
export function createWatermarkCardDef(engine) {
|
|
7
|
+
return {
|
|
8
|
+
name: "create_watermark_card",
|
|
9
|
+
description: "Make a transparent QR plus X or TikTok handle badge as an image asset, with optional platform icon, border, padding and rounding. Burn it in with apply_watermark.",
|
|
10
|
+
schema: {
|
|
11
|
+
book_id: z.string().describe("Book id"),
|
|
12
|
+
platform: z.enum(WATERMARK_PLATFORMS).optional().describe("Alias platform (default tiktok)"),
|
|
13
|
+
handle: z.string().optional().describe(`X or TikTok alias without @ (X default: ${DEFAULT_HANDLE}; TikTok default: ${LIGHTNING_WATERMARK_PROFILE.handle})`),
|
|
14
|
+
url: z.string().optional().describe("URL the QR code resolves to (default: the selected X or TikTok profile URL)"),
|
|
15
|
+
save_default: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Remember this handle as the default for future watermarks"),
|
|
19
|
+
include_platform_icon: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Show the selected platform icon before the plain alias (default true)"),
|
|
23
|
+
include_handle_border: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Draw a border around the handle pill (default true)"),
|
|
27
|
+
handle_padding: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(MIN_HANDLE_PADDING)
|
|
31
|
+
.max(MAX_HANDLE_PADDING)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(`Horizontal handle padding in reference pixels (default ${DEFAULT_HANDLE_PADDING}; range ${MIN_HANDLE_PADDING}-${MAX_HANDLE_PADDING})`),
|
|
34
|
+
handle_padding_y: z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.min(MIN_HANDLE_PADDING)
|
|
38
|
+
.max(MAX_HANDLE_PADDING)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe(`Vertical handle padding in reference pixels (default ${DEFAULT_HANDLE_PADDING}; range ${MIN_HANDLE_PADDING}-${MAX_HANDLE_PADDING})`),
|
|
41
|
+
handle_rounding: z
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.min(MIN_HANDLE_ROUNDING)
|
|
45
|
+
.max(MAX_HANDLE_ROUNDING)
|
|
46
|
+
.optional()
|
|
47
|
+
.describe(`Handle corner rounding percent (default ${DEFAULT_HANDLE_ROUNDING}; range ${MIN_HANDLE_ROUNDING}-${MAX_HANDLE_ROUNDING})`),
|
|
48
|
+
},
|
|
49
|
+
handler: async (args) => {
|
|
50
|
+
const { handle, url, save_default, platform = "tiktok", include_platform_icon = true, include_handle_border = true, handle_padding = platform === "tiktok" ? LIGHTNING_WATERMARK_PROFILE.handlePaddingX : DEFAULT_HANDLE_PADDING, handle_padding_y = platform === "tiktok" ? LIGHTNING_WATERMARK_PROFILE.handlePaddingY : DEFAULT_HANDLE_PADDING, handle_rounding = platform === "tiktok" ? LIGHTNING_WATERMARK_PROFILE.handleRounding : DEFAULT_HANDLE_ROUNDING, } = args;
|
|
51
|
+
const config = await readWatermarkConfig(engine, platform);
|
|
52
|
+
const fallbackHandle = platform === "x"
|
|
53
|
+
? DEFAULT_HANDLE
|
|
54
|
+
: config.savedHandle ?? config.recent[0] ?? (platform === "tiktok" ? LIGHTNING_WATERMARK_PROFILE.handle : "");
|
|
55
|
+
const resolvedHandle = normalizeHandle(handle ?? fallbackHandle);
|
|
56
|
+
if (!resolvedHandle) {
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [{ type: "text", text: "handle must be a non-empty string" }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const resolvedUrl = url ?? defaultUrl(resolvedHandle, platform);
|
|
63
|
+
const created = await createWatermarkAsset(engine, resolvedHandle, resolvedUrl, {
|
|
64
|
+
platform,
|
|
65
|
+
includePlatformIcon: include_platform_icon,
|
|
66
|
+
includeHandleBorder: include_handle_border,
|
|
67
|
+
handlePadding: handle_padding,
|
|
68
|
+
handlePaddingY: handle_padding_y,
|
|
69
|
+
handleRounding: handle_rounding,
|
|
70
|
+
});
|
|
71
|
+
if (!created.ok) {
|
|
72
|
+
return {
|
|
73
|
+
isError: true,
|
|
74
|
+
content: [{ type: "text", text: created.error }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Remember the handle so the next watermark review defaults to it.
|
|
78
|
+
await recordHandle(resolvedHandle, { saveDefault: save_default, platform }, engine).catch(() => undefined);
|
|
79
|
+
return {
|
|
80
|
+
content: [{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: JSON.stringify({
|
|
83
|
+
asset_id: created.assetId,
|
|
84
|
+
handle: resolvedHandle,
|
|
85
|
+
url: resolvedUrl,
|
|
86
|
+
platform,
|
|
87
|
+
include_platform_icon,
|
|
88
|
+
include_handle_border,
|
|
89
|
+
handle_padding,
|
|
90
|
+
handle_padding_y,
|
|
91
|
+
handle_rounding,
|
|
92
|
+
}),
|
|
93
|
+
}],
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { cropImageDef } from "./crop-image.js";
|
|
3
|
+
import { cropVideoDef } from "./crop-video.js";
|
|
4
|
+
export function cropAssetDef(engine) {
|
|
5
|
+
const image = cropImageDef(engine);
|
|
6
|
+
const video = cropVideoDef(engine);
|
|
7
|
+
return {
|
|
8
|
+
name: "crop_asset",
|
|
9
|
+
description: "Crop an image or video in place to a 16:9, 9:16 or 1:1 region given as 0-1 fractions (x, y, w, h), resized to 1920x1080, 1080x1920 or 1080x1080.",
|
|
10
|
+
schema: { ...image.schema, asset_id: z.string().describe("Image or video artifact id") },
|
|
11
|
+
handler: async (args) => {
|
|
12
|
+
const artifact = engine.artifacts.get(String(args.asset_id));
|
|
13
|
+
if (!artifact.ok)
|
|
14
|
+
return { isError: true, content: [{ type: "text", text: artifact.error.message }] };
|
|
15
|
+
return artifact.value.kind === "image" ? image.handler(args) : video.handler(args);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { SKIP_CROP_EXTENSIONS } from "../jobs/process-image.js";
|
|
5
|
+
import { cropGeometry } from "../crop-geometry.js";
|
|
6
|
+
import { probeImage } from "../image.js";
|
|
7
|
+
const RECT_EPSILON = 0.001;
|
|
8
|
+
const IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "webp"]);
|
|
9
|
+
function validateRect(x, y, w, h) {
|
|
10
|
+
if (w <= 0 || h <= 0)
|
|
11
|
+
return { error: "Crop width and height must be > 0." };
|
|
12
|
+
if (x < 0 || y < 0)
|
|
13
|
+
return { error: "Crop x and y must be >= 0." };
|
|
14
|
+
if (x + w > 1 + RECT_EPSILON)
|
|
15
|
+
return { error: `Crop x + w must be <= 1 (got ${x + w}).` };
|
|
16
|
+
if (y + h > 1 + RECT_EPSILON)
|
|
17
|
+
return { error: `Crop y + h must be <= 1 (got ${y + h}).` };
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function findOriginalImage(files) {
|
|
21
|
+
const images = files.filter((f) => {
|
|
22
|
+
const ext = (f.extension ?? extname(f.name)).replace(/^\./, "").toLowerCase();
|
|
23
|
+
return IMAGE_EXTENSIONS.has(ext);
|
|
24
|
+
});
|
|
25
|
+
// Prefer lossless/true originals when both png and jpg exist (repair cases).
|
|
26
|
+
const byName = (name) => images.find((f) => f.name === name);
|
|
27
|
+
return (byName("original.png")
|
|
28
|
+
?? byName("original.webp")
|
|
29
|
+
?? byName("original.jpg")
|
|
30
|
+
?? byName("original.jpeg")
|
|
31
|
+
?? images.find((f) => f.name.startsWith("original"))
|
|
32
|
+
?? null);
|
|
33
|
+
}
|
|
34
|
+
export function cropImageDef(engine) {
|
|
35
|
+
return {
|
|
36
|
+
name: "crop_image",
|
|
37
|
+
description: "Crop an image in place to a 16:9, 9:16 or 1:1 region given as 0-1 fractions (x, y, w, h), resized to 1920x1080, 1080x1920 or 1080x1080.",
|
|
38
|
+
schema: {
|
|
39
|
+
asset_id: z.string().describe("Asset ID (e.g. img-my-photo)"),
|
|
40
|
+
book_id: z.string().describe("Book id"),
|
|
41
|
+
ratio: z.enum(["16:9", "9:16", "1:1"]).describe("Target aspect ratio"),
|
|
42
|
+
x: z.number().min(0).max(1).describe("Crop origin X as fraction of source width (0..1)"),
|
|
43
|
+
y: z.number().min(0).max(1).describe("Crop origin Y as fraction of source height (0..1)"),
|
|
44
|
+
w: z.number().min(0).max(1).describe("Crop width as fraction of source width (0..1)"),
|
|
45
|
+
h: z.number().min(0).max(1).describe("Crop height as fraction of source height (0..1)"),
|
|
46
|
+
},
|
|
47
|
+
handler: async (args) => {
|
|
48
|
+
const { asset_id, book_id, ratio, x, y, w, h } = args;
|
|
49
|
+
const rectErr = validateRect(x, y, w, h);
|
|
50
|
+
if (rectErr) {
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [{ type: "text", text: rectErr.error }],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
57
|
+
if (!manifestResult.ok) {
|
|
58
|
+
return {
|
|
59
|
+
isError: true,
|
|
60
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const manifest = manifestResult.value;
|
|
64
|
+
const imageFile = findOriginalImage(manifest.files);
|
|
65
|
+
if (!imageFile) {
|
|
66
|
+
return {
|
|
67
|
+
isError: true,
|
|
68
|
+
content: [{ type: "text", text: `No original image file found in asset ${asset_id}` }],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const ext = (imageFile.extension ?? extname(imageFile.name)).toLowerCase();
|
|
72
|
+
const extWithDot = ext.startsWith(".") ? ext : `.${ext}`;
|
|
73
|
+
if (SKIP_CROP_EXTENSIONS.has(extWithDot)) {
|
|
74
|
+
return {
|
|
75
|
+
isError: true,
|
|
76
|
+
content: [{ type: "text", text: `Cannot crop images with extension ${extWithDot}` }],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const assetDir = manifest.path;
|
|
80
|
+
try {
|
|
81
|
+
const original = await engine.files.read(asset_id, imageFile.name);
|
|
82
|
+
if (!original.ok)
|
|
83
|
+
throw new Error(original.error.message);
|
|
84
|
+
const source = await probeImage(original.value);
|
|
85
|
+
cropGeometry({ x, y, w, h }, source.width, source.height, ratio, 1);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
|
|
89
|
+
}
|
|
90
|
+
const jobId = `crop-${asset_id}-${Date.now()}`;
|
|
91
|
+
const jobPayload = {
|
|
92
|
+
type: "crop_image",
|
|
93
|
+
inputFilename: imageFile.name,
|
|
94
|
+
ratio,
|
|
95
|
+
rect: { x, y, w, h },
|
|
96
|
+
assetId: asset_id,
|
|
97
|
+
assetDir,
|
|
98
|
+
bookId: book_id,
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
await queue.enqueueAndWait(jobId, jobPayload);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return {
|
|
105
|
+
isError: true,
|
|
106
|
+
content: [{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: `Crop failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
+
}],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify({ asset_id, job_id: jobId, ratio, status: "completed" }),
|
|
116
|
+
}],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { cropGeometry } from "../crop-geometry.js";
|
|
6
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
7
|
+
const RECT_EPSILON = 0.001;
|
|
8
|
+
function validateRect(x, y, w, h) {
|
|
9
|
+
if (w <= 0 || h <= 0)
|
|
10
|
+
return { error: "Crop width and height must be > 0." };
|
|
11
|
+
if (x < 0 || y < 0)
|
|
12
|
+
return { error: "Crop x and y must be >= 0." };
|
|
13
|
+
if (x + w > 1 + RECT_EPSILON)
|
|
14
|
+
return { error: `Crop x + w must be <= 1 (got ${x + w}).` };
|
|
15
|
+
if (y + h > 1 + RECT_EPSILON)
|
|
16
|
+
return { error: `Crop y + h must be <= 1 (got ${y + h}).` };
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function cropVideoDef(engine) {
|
|
20
|
+
return {
|
|
21
|
+
name: "crop_video",
|
|
22
|
+
description: "Crop a video asset to a 16:9, 9:16, or 1:1 region. Coordinates are normalized fractions of the source frame (0..1). Re-renders in place to canonical dims (1920x1080 / 1080x1920 / 1080x1080) with git versioning.",
|
|
23
|
+
schema: {
|
|
24
|
+
asset_id: z.string().describe("Asset ID (e.g. vid-my-video)"),
|
|
25
|
+
book_id: z.string().describe("Book id"),
|
|
26
|
+
ratio: z.enum(["16:9", "9:16", "1:1"]).describe("Target aspect ratio"),
|
|
27
|
+
x: z.number().min(0).max(1).describe("Crop origin X as fraction of source width (0..1)"),
|
|
28
|
+
y: z.number().min(0).max(1).describe("Crop origin Y as fraction of source height (0..1)"),
|
|
29
|
+
w: z.number().min(0).max(1).describe("Crop width as fraction of source width (0..1)"),
|
|
30
|
+
h: z.number().min(0).max(1).describe("Crop height as fraction of source height (0..1)"),
|
|
31
|
+
},
|
|
32
|
+
handler: async (args) => {
|
|
33
|
+
const { asset_id, book_id, ratio, x, y, w, h } = args;
|
|
34
|
+
const rectErr = validateRect(x, y, w, h);
|
|
35
|
+
if (rectErr) {
|
|
36
|
+
return {
|
|
37
|
+
isError: true,
|
|
38
|
+
content: [{ type: "text", text: rectErr.error }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
42
|
+
if (!manifestResult.ok) {
|
|
43
|
+
return {
|
|
44
|
+
isError: true,
|
|
45
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const manifest = manifestResult.value;
|
|
49
|
+
const videoFile = findVideoFile(manifest.files);
|
|
50
|
+
if (!videoFile) {
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [{ type: "text", text: `No video file found in asset ${asset_id}` }],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const assetDir = manifest.path;
|
|
57
|
+
const videoPath = join(assetDir, videoFile.name);
|
|
58
|
+
try {
|
|
59
|
+
const source = await probeVideo(videoPath);
|
|
60
|
+
cropGeometry({ x, y, w, h }, source.width, source.height, ratio, 2);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
|
|
64
|
+
}
|
|
65
|
+
const tempPath = join(assetDir, "original_cropped.mp4");
|
|
66
|
+
const jobId = `crop-${asset_id}-${Date.now()}`;
|
|
67
|
+
const jobPayload = {
|
|
68
|
+
type: "crop_video",
|
|
69
|
+
inputPath: videoPath,
|
|
70
|
+
outputPath: tempPath,
|
|
71
|
+
ratio,
|
|
72
|
+
rect: { x, y, w, h },
|
|
73
|
+
assetId: asset_id,
|
|
74
|
+
assetDir,
|
|
75
|
+
bookId: book_id,
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
await queue.enqueueAndWait(jobId, jobPayload);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
isError: true,
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `Crop failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
86
|
+
}],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
content: [{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: JSON.stringify({ asset_id, job_id: jobId, ratio, status: "completed" }),
|
|
93
|
+
}],
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { queue } from "../queue.js";
|
|
3
|
+
import { readTimeline, timelineFailure, timelineInput, writeTimeline, } from "../timeline-helpers.js";
|
|
4
|
+
const FORGET_TIMEOUT_MS = 5 * 60_000;
|
|
5
|
+
/** Drops every primary-sequence clip and audio clip that plays the artifact. */
|
|
6
|
+
export async function removeArtifactFromTimeline(engine, artifactId, options = {}) {
|
|
7
|
+
const timeline = readTimeline(engine);
|
|
8
|
+
const slots = timeline.slots.filter((slot) => slot.artifactId !== artifactId);
|
|
9
|
+
const audio = timeline.audio.filter((clip) => clip.artifactId !== artifactId);
|
|
10
|
+
if (!engine.sequences.getPrimary().clips.some((clip) => clip.source.artifactId === artifactId)) {
|
|
11
|
+
return { updated: false };
|
|
12
|
+
}
|
|
13
|
+
const result = await writeTimeline(engine, {
|
|
14
|
+
...timelineInput(timeline),
|
|
15
|
+
slots: slots.map((slot) => ({
|
|
16
|
+
id: slot.id,
|
|
17
|
+
artifactId: slot.artifactId,
|
|
18
|
+
...(slot.volume === undefined ? {} : { volume: slot.volume }),
|
|
19
|
+
...(slot.audioFadeIn === undefined ? {} : { audioFadeIn: slot.audioFadeIn }),
|
|
20
|
+
...(slot.audioFadeOut === undefined ? {} : { audioFadeOut: slot.audioFadeOut }),
|
|
21
|
+
})),
|
|
22
|
+
audio: audio.map((clip) => ({
|
|
23
|
+
id: clip.id,
|
|
24
|
+
artifactId: clip.artifactId,
|
|
25
|
+
startFrame: clip.startFrame,
|
|
26
|
+
durationFrames: clip.durationFrames,
|
|
27
|
+
...(clip.volume === undefined ? {} : { volume: clip.volume }),
|
|
28
|
+
...(clip.fadeIn === undefined ? {} : { fadeIn: clip.fadeIn }),
|
|
29
|
+
...(clip.fadeOut === undefined ? {} : { fadeOut: clip.fadeOut }),
|
|
30
|
+
})),
|
|
31
|
+
}, { removeArtifactId: artifactId, ...options });
|
|
32
|
+
return result.ok ? { updated: true } : { updated: false, error: result.error.message };
|
|
33
|
+
}
|
|
34
|
+
function jsonResult(value) {
|
|
35
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
36
|
+
}
|
|
37
|
+
async function deleteArtifact(engine, artifact) {
|
|
38
|
+
const assetId = artifact.artifactId;
|
|
39
|
+
const references = engine.artifacts.deletionReferences(assetId, { deleteOwnedMedia: true });
|
|
40
|
+
if (!references.ok)
|
|
41
|
+
return timelineFailure(references.error.message);
|
|
42
|
+
const removable = new Set(engine.sequences.getPrimary().clips
|
|
43
|
+
.filter((clip) => clip.source.artifactId === assetId).map((clip) => clip.clipId));
|
|
44
|
+
const blockers = references.value.filter((reference) => reference.kind !== "sequenceClip" || !removable.has(reference.id));
|
|
45
|
+
if (blockers.length > 0) {
|
|
46
|
+
return timelineFailure(`Artifact is still referenced: ${assetId} (${blockers.map((reference) => reference.kind).join(", ")}); delete it with forget and clear_references to clear them`);
|
|
47
|
+
}
|
|
48
|
+
const timeline = await removeArtifactFromTimeline(engine, assetId);
|
|
49
|
+
if (timeline.error)
|
|
50
|
+
return timelineFailure(timeline.error);
|
|
51
|
+
const result = await engine.artifacts.delete(assetId, { deleteOwnedMedia: true });
|
|
52
|
+
if (!result.ok)
|
|
53
|
+
return timelineFailure(result.error.message);
|
|
54
|
+
return jsonResult({
|
|
55
|
+
status: "deleted",
|
|
56
|
+
artifact_id: assetId,
|
|
57
|
+
...(artifact.label ? { label: artifact.label } : {}),
|
|
58
|
+
deleted_at: new Date().toISOString(),
|
|
59
|
+
timeline,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function forgetArtifact(bookId, artifactId, clearReferences) {
|
|
63
|
+
try {
|
|
64
|
+
const outcome = await queue.enqueueAndWait(`forget-asset-${artifactId}`, { type: "forget_asset", bookId, forgetArtifactId: artifactId, clearReferences }, FORGET_TIMEOUT_MS);
|
|
65
|
+
return jsonResult({
|
|
66
|
+
status: outcome.status,
|
|
67
|
+
artifact_id: outcome.artifactId,
|
|
68
|
+
...(outcome.label ? { label: outcome.label } : {}),
|
|
69
|
+
references: outcome.references,
|
|
70
|
+
cleared_cells: outcome.clearedCells,
|
|
71
|
+
forgotten_objects: outcome.forgottenObjects,
|
|
72
|
+
shared_objects: outcome.sharedObjects,
|
|
73
|
+
reclaimed_bytes: outcome.reclaimedBytes,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
return timelineFailure(cause instanceof Error ? cause.message : String(cause));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function deleteAssetDef(engine) {
|
|
81
|
+
return {
|
|
82
|
+
name: "delete_asset",
|
|
83
|
+
description: "Delete an asset, dropping its sequence clips. forget also destroys its stored bytes (bytes another asset shares are kept)",
|
|
84
|
+
annotations: { destructiveHint: true, idempotentHint: false },
|
|
85
|
+
schema: {
|
|
86
|
+
asset_id: z.string(),
|
|
87
|
+
book_id: z.string().uuid(),
|
|
88
|
+
forget: z.boolean().optional().describe("Permanently destroy the stored content too"),
|
|
89
|
+
clear_references: z.boolean().optional()
|
|
90
|
+
.describe("With forget: clear notebook cells and sequence clips holding the asset first"),
|
|
91
|
+
},
|
|
92
|
+
handler: async (args) => {
|
|
93
|
+
const artifact = engine.artifacts.get(String(args.asset_id));
|
|
94
|
+
if (!artifact.ok)
|
|
95
|
+
return timelineFailure(artifact.error.message);
|
|
96
|
+
if (args.forget === true) {
|
|
97
|
+
return forgetArtifact(String(args.book_id), artifact.value.artifactId, args.clear_references === true);
|
|
98
|
+
}
|
|
99
|
+
if (args.clear_references === true)
|
|
100
|
+
return timelineFailure("clear_references applies only with forget");
|
|
101
|
+
return deleteArtifact(engine, artifact.value);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
const IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "webp", "gif"]);
|
|
5
|
+
function findImageFile(files) {
|
|
6
|
+
const images = files.filter((f) => {
|
|
7
|
+
const ext = (f.extension ?? extname(f.name)).replace(/^\./, "").toLowerCase();
|
|
8
|
+
return IMAGE_EXTENSIONS.has(ext);
|
|
9
|
+
});
|
|
10
|
+
return images.find((f) => f.name.startsWith("original")) ?? images[0] ?? null;
|
|
11
|
+
}
|
|
12
|
+
function error(text) {
|
|
13
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
14
|
+
}
|
|
15
|
+
export function describeImageDef(engine) {
|
|
16
|
+
return {
|
|
17
|
+
name: "describe_image",
|
|
18
|
+
description: "Write a reproduction-ready prompt for an image asset into a new script asset using vision (asynchronous).",
|
|
19
|
+
schema: {
|
|
20
|
+
asset_id: z.string().describe("Image artifact id"),
|
|
21
|
+
book_id: z.string().describe("Book UUID"),
|
|
22
|
+
},
|
|
23
|
+
handler: async (args) => {
|
|
24
|
+
const { asset_id, book_id } = args;
|
|
25
|
+
const sourceArtifact = engine.artifacts.get(asset_id);
|
|
26
|
+
if (!sourceArtifact.ok)
|
|
27
|
+
return error(sourceArtifact.error.message);
|
|
28
|
+
if (sourceArtifact.value.kind !== "image") {
|
|
29
|
+
return error(`describe_image only accepts image artifacts, got ${sourceArtifact.value.kind} artifact ${asset_id}`);
|
|
30
|
+
}
|
|
31
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
32
|
+
return error("OPENAI_API_KEY is not set");
|
|
33
|
+
}
|
|
34
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
35
|
+
if (!manifestResult.ok) {
|
|
36
|
+
return error(manifestResult.error.message);
|
|
37
|
+
}
|
|
38
|
+
const imageFile = findImageFile(manifestResult.value.files);
|
|
39
|
+
if (!imageFile) {
|
|
40
|
+
return error(`No image file found in artifact ${asset_id}`);
|
|
41
|
+
}
|
|
42
|
+
const scriptName = `desc ${sourceArtifact.value.label ?? asset_id}`;
|
|
43
|
+
const createResult = await engine.artifacts.create("script", scriptName);
|
|
44
|
+
if (!createResult.ok) {
|
|
45
|
+
return error(`Failed to create script asset: ${createResult.error.message}`);
|
|
46
|
+
}
|
|
47
|
+
const { artifactId: scriptAssetId } = createResult.value;
|
|
48
|
+
const jobId = `describe-image-${asset_id}-${Date.now()}`;
|
|
49
|
+
await queue.enqueue(jobId, {
|
|
50
|
+
type: "describe_image",
|
|
51
|
+
sourceAssetId: asset_id,
|
|
52
|
+
sourceImageFilename: imageFile.name,
|
|
53
|
+
assetId: scriptAssetId,
|
|
54
|
+
bookId: book_id,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
content: [{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: JSON.stringify({
|
|
60
|
+
source_asset_id: asset_id,
|
|
61
|
+
script_asset_id: scriptAssetId,
|
|
62
|
+
job_id: jobId,
|
|
63
|
+
status: "enqueued",
|
|
64
|
+
}),
|
|
65
|
+
}],
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { findVideoFile } from "videobook-engine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
export function detectScenesDef(engine) {
|
|
5
|
+
return {
|
|
6
|
+
name: "detect_scenes",
|
|
7
|
+
description: "Find natural visual breaks in a video in a background job: shot cuts and motion-settle moments inside steady shots, each with a time and score. Read-only.",
|
|
8
|
+
schema: {
|
|
9
|
+
source_asset_id: z.string().describe("Video asset id (vid-*) inside the project"),
|
|
10
|
+
book_id: z.string().describe("Book id"),
|
|
11
|
+
},
|
|
12
|
+
handler: async (args) => {
|
|
13
|
+
const { source_asset_id, book_id } = args;
|
|
14
|
+
const sourceAssetId = source_asset_id.replace(/^@/, "");
|
|
15
|
+
const manifestResult = await engine.files.manifest(sourceAssetId);
|
|
16
|
+
if (!manifestResult.ok) {
|
|
17
|
+
return {
|
|
18
|
+
isError: true,
|
|
19
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const videoFile = findVideoFile(manifestResult.value.files);
|
|
23
|
+
if (!videoFile) {
|
|
24
|
+
return {
|
|
25
|
+
isError: true,
|
|
26
|
+
content: [{ type: "text", text: `No video file found in asset ${sourceAssetId}` }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const jobId = `detect-scenes-${sourceAssetId}-${Date.now()}`;
|
|
30
|
+
await queue.enqueue(jobId, {
|
|
31
|
+
type: "detect_scenes",
|
|
32
|
+
assetId: sourceAssetId,
|
|
33
|
+
assetDir: manifestResult.value.path,
|
|
34
|
+
bookId: book_id,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
content: [{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: JSON.stringify({ asset_id: sourceAssetId, job_id: jobId, status: "enqueued" }),
|
|
40
|
+
}],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|