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,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { queue } from "../queue.js";
|
|
3
|
+
function error(text) {
|
|
4
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
5
|
+
}
|
|
6
|
+
function hasSpokenWords(eleven) {
|
|
7
|
+
return Array.isArray(eleven.words) && eleven.words.some((w) => w.type === "word");
|
|
8
|
+
}
|
|
9
|
+
export function analyzeJokesDef(engine) {
|
|
10
|
+
return {
|
|
11
|
+
name: "analyze_jokes",
|
|
12
|
+
description: "Find and classify the jokes in a clip from its ElevenLabs transcript (verbatim text, start and end times, why each works) in a background job. Run transcribe first when there is no transcript.",
|
|
13
|
+
schema: {
|
|
14
|
+
asset_id: z.string().describe("Video asset ID (vid-* or @vid-slug)"),
|
|
15
|
+
book_id: z.string().describe("Book id"),
|
|
16
|
+
},
|
|
17
|
+
handler: async (args) => {
|
|
18
|
+
const { asset_id, book_id } = args;
|
|
19
|
+
const assetId = asset_id.replace(/^@/, "");
|
|
20
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
21
|
+
return error("OPENAI_API_KEY environment variable is not set");
|
|
22
|
+
}
|
|
23
|
+
const manifestResult = await engine.files.manifest(assetId);
|
|
24
|
+
if (!manifestResult.ok) {
|
|
25
|
+
return error(manifestResult.error.message);
|
|
26
|
+
}
|
|
27
|
+
const read = await engine.files.read(assetId, "elevenlabs.json");
|
|
28
|
+
if (!read.ok) {
|
|
29
|
+
return error(`No ElevenLabs transcript found for ${assetId} — run /transcribe @${assetId} first.`);
|
|
30
|
+
}
|
|
31
|
+
let eleven;
|
|
32
|
+
try {
|
|
33
|
+
eleven = JSON.parse(read.value.toString());
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return error(`elevenlabs.json for ${assetId} is not valid JSON`);
|
|
37
|
+
}
|
|
38
|
+
if (!hasSpokenWords(eleven)) {
|
|
39
|
+
return error(`No ElevenLabs transcript found for ${assetId} — run /transcribe @${assetId} first.`);
|
|
40
|
+
}
|
|
41
|
+
const jobId = `analyze-jokes-${assetId}-${Date.now()}`;
|
|
42
|
+
await queue.enqueue(jobId, {
|
|
43
|
+
type: "analyze_jokes",
|
|
44
|
+
assetId,
|
|
45
|
+
assetDir: manifestResult.value.path,
|
|
46
|
+
bookId: book_id,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
content: [{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: JSON.stringify({ asset_id: assetId, job_id: jobId, status: "enqueued" }),
|
|
52
|
+
}],
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { findVideoFile } from "videobook-engine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { PLAIN_TRANSCRIPT_FILE, TIMED_TRANSCRIPT_FILE, } from "../transcript-projection.js";
|
|
5
|
+
import { isAnalyzableVideo, VIDEO_ANALYSIS_FILE, } from "../video-analysis-projection.js";
|
|
6
|
+
function error(text) {
|
|
7
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
8
|
+
}
|
|
9
|
+
function ok(payload) {
|
|
10
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
11
|
+
}
|
|
12
|
+
export async function analyzeVideoJobPayload(engine, assetId, bookId, purpose) {
|
|
13
|
+
const manifest = await engine.files.manifest(assetId);
|
|
14
|
+
if (!manifest.ok)
|
|
15
|
+
throw new Error(manifest.error.message);
|
|
16
|
+
const [timed, plain] = await Promise.all([
|
|
17
|
+
engine.files.read(assetId, TIMED_TRANSCRIPT_FILE),
|
|
18
|
+
engine.files.read(assetId, PLAIN_TRANSCRIPT_FILE),
|
|
19
|
+
]);
|
|
20
|
+
return {
|
|
21
|
+
type: "analyze_video",
|
|
22
|
+
assetId,
|
|
23
|
+
assetDir: manifest.value.path,
|
|
24
|
+
bookId,
|
|
25
|
+
hasTranscription: timed.ok || plain.ok,
|
|
26
|
+
...(purpose ? { purpose } : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function analyzeVideoDef(engine) {
|
|
30
|
+
return {
|
|
31
|
+
name: "analyze_video",
|
|
32
|
+
description: "Analyze a video with vision and its transcript in a background job: summary, scenes, tags, subjects, and burned-in subtitles or watermarks. The analysis appears on the asset when the job finishes.",
|
|
33
|
+
schema: {
|
|
34
|
+
asset_id: z.string().describe("Video artifact id"),
|
|
35
|
+
book_id: z.string().describe("Book UUID"),
|
|
36
|
+
force: z.boolean().optional().default(false)
|
|
37
|
+
.describe("Re-analyze even when the asset already has an analysis"),
|
|
38
|
+
},
|
|
39
|
+
handler: async (args) => {
|
|
40
|
+
const { asset_id, book_id, force } = args;
|
|
41
|
+
if (!process.env.OPENAI_API_KEY)
|
|
42
|
+
return error("OPENAI_API_KEY is not set");
|
|
43
|
+
const artifact = engine.artifacts.get(asset_id);
|
|
44
|
+
if (!artifact.ok)
|
|
45
|
+
return error(artifact.error.message);
|
|
46
|
+
if (!isAnalyzableVideo(artifact.value.kind)) {
|
|
47
|
+
return error(`analyze_video only accepts video artifacts, got ${artifact.value.kind} artifact ${asset_id}`);
|
|
48
|
+
}
|
|
49
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
50
|
+
if (!manifestResult.ok)
|
|
51
|
+
return error(manifestResult.error.message);
|
|
52
|
+
if (!findVideoFile(manifestResult.value.files)) {
|
|
53
|
+
return error(`${asset_id} has no video file yet; wait for its generation or processing to finish before analyzing it`);
|
|
54
|
+
}
|
|
55
|
+
if (!force && (await engine.files.read(asset_id, VIDEO_ANALYSIS_FILE)).ok) {
|
|
56
|
+
return ok({ asset_id, status: "already_analyzed" });
|
|
57
|
+
}
|
|
58
|
+
// Stable dedupe key: repeated clicks collapse onto one queued job, and the
|
|
59
|
+
// partial unique index only covers active states so a later re-analysis
|
|
60
|
+
// still enqueues.
|
|
61
|
+
const jobId = `analyze-video-${asset_id}`;
|
|
62
|
+
await queue.enqueue(jobId, await analyzeVideoJobPayload(engine, asset_id, book_id));
|
|
63
|
+
return ok({ asset_id, job_id: jobId, status: "enqueued" });
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { findVideoFile } from "videobook-engine";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getApiKey } from "../api-key.js";
|
|
5
|
+
import { isSubtitleStyle } from "../remotion/subtitles/styles.js";
|
|
6
|
+
import { isKeywordPlacementCacheValid, keywordPlacementFingerprint, parseKeywordPlacementAnalysis, readKeywordPlacementAnalysis, } from "../keyword-placement-cache.js";
|
|
7
|
+
import { queue } from "../queue.js";
|
|
8
|
+
import { artifactKindOf } from "./_shared.js";
|
|
9
|
+
import { DEFAULT_KEYWORD_GRAPHICS_LINGER_SECONDS, DEFAULT_KEYWORD_GRAPHICS_MAX_COUNT, parseKeywordTranscript, selectKeywordGraphics, selectRequestedKeywordGraphics, } from "../remotion/keyword-graphics.js";
|
|
10
|
+
import { KEYWORD_FRAME_CACHE_FILENAME, resolveKeywordPlacements, } from "../remotion/keyword-placement.js";
|
|
11
|
+
import { RENDER_ORIENTATIONS } from "./reconcile-project-work.js";
|
|
12
|
+
async function persistSettings(engine, assetId, enabled, maxCount, lingerSeconds, keywords) {
|
|
13
|
+
for (const orientation of RENDER_ORIENTATIONS) {
|
|
14
|
+
const existing = await engine.metadata.artifacts.read(assetId, `${orientation}.render`);
|
|
15
|
+
const write = await engine.metadata.artifacts.write(assetId, `${orientation}.render`, {
|
|
16
|
+
...(existing.ok ? existing.value : {}),
|
|
17
|
+
keywordGraphicsEnabled: enabled,
|
|
18
|
+
keywordGraphicsMaxCount: maxCount,
|
|
19
|
+
keywordGraphicsLingerSeconds: lingerSeconds,
|
|
20
|
+
keywordGraphicsKeywords: keywords,
|
|
21
|
+
});
|
|
22
|
+
if (!write.ok)
|
|
23
|
+
throw new Error(`Failed to persist ${orientation} keyword settings: ${write.error.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isSubtitlePosition(value) {
|
|
27
|
+
return value === "top" || value === "middle" || value === "bottom";
|
|
28
|
+
}
|
|
29
|
+
function isFontSize(value) {
|
|
30
|
+
return value === "small" || value === "medium" || value === "large";
|
|
31
|
+
}
|
|
32
|
+
async function placementSummary(engine, assetId, graphics, analysis) {
|
|
33
|
+
const summaries = {};
|
|
34
|
+
for (const orientation of RENDER_ORIENTATIONS) {
|
|
35
|
+
const storedResult = await engine.metadata.artifacts.read(assetId, `${orientation}.render`);
|
|
36
|
+
const stored = storedResult.ok ? storedResult.value : {};
|
|
37
|
+
const settings = {
|
|
38
|
+
subtitlesEnabled: stored.subtitlesEnabled === true,
|
|
39
|
+
subtitlePosition: isSubtitlePosition(stored.position) ? stored.position : "bottom",
|
|
40
|
+
subtitleStyle: isSubtitleStyle(stored.highlightStyle) ? stored.highlightStyle : "impact",
|
|
41
|
+
subtitleFontSize: isFontSize(stored.fontSize) ? stored.fontSize : "medium",
|
|
42
|
+
};
|
|
43
|
+
const resolved = resolveKeywordPlacements(graphics, analysis, orientation, settings);
|
|
44
|
+
summaries[orientation] = {
|
|
45
|
+
keywords: resolved.graphics.map((graphic) => ({
|
|
46
|
+
text: graphic.text,
|
|
47
|
+
anchor: graphic.anchor,
|
|
48
|
+
scale: graphic.scale,
|
|
49
|
+
frame_time: graphic.frameTime,
|
|
50
|
+
})),
|
|
51
|
+
skipped: resolved.skipped,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return summaries;
|
|
55
|
+
}
|
|
56
|
+
function errorResult(status, message, details = {}) {
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [{ type: "text", text: JSON.stringify({ status, message, ...details }) }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function animateKeywordsDef(engine) {
|
|
63
|
+
return {
|
|
64
|
+
name: "animate_keywords",
|
|
65
|
+
description: "Turn transcript-timed keyword titles on or off for a video's renders, placed by vision away from faces, subtitles and on-screen text. Pass keywords for exact transcript words, or omit them for automatic picks.",
|
|
66
|
+
schema: {
|
|
67
|
+
asset_id: z.string().describe("Video artifact id"),
|
|
68
|
+
book_id: z.string().describe("Book id"),
|
|
69
|
+
enabled: z.boolean().optional().describe("Enable keyword graphics (default true)"),
|
|
70
|
+
max_keywords: z
|
|
71
|
+
.number()
|
|
72
|
+
.int()
|
|
73
|
+
.min(1)
|
|
74
|
+
.max(20)
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Maximum keyword graphics selected from the transcript (default 8)"),
|
|
77
|
+
linger_seconds: z
|
|
78
|
+
.number()
|
|
79
|
+
.min(0.8)
|
|
80
|
+
.max(5)
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("How long each keyword lingers after it is spoken (default 2.4 seconds)"),
|
|
83
|
+
keywords: z
|
|
84
|
+
.array(z.string().min(1))
|
|
85
|
+
.min(1)
|
|
86
|
+
.max(20)
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Exact transcript words to animate, matched case-insensitively"),
|
|
89
|
+
},
|
|
90
|
+
handler: async (args) => {
|
|
91
|
+
const { asset_id, book_id, enabled = true, max_keywords = DEFAULT_KEYWORD_GRAPHICS_MAX_COUNT, linger_seconds = DEFAULT_KEYWORD_GRAPHICS_LINGER_SECONDS, keywords, } = args;
|
|
92
|
+
// Kept on the structured errorResult contract instead of
|
|
93
|
+
// requireArtifactKind so callers keep parsing a status code. A missing
|
|
94
|
+
// artifact falls through to the manifest read below, which reports
|
|
95
|
+
// asset_not_found.
|
|
96
|
+
const kind = artifactKindOf(engine, asset_id);
|
|
97
|
+
if (kind !== null && kind !== "video") {
|
|
98
|
+
return errorResult("invalid_asset", `animate_keywords requires a video artifact; ${asset_id} is ${kind}`);
|
|
99
|
+
}
|
|
100
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
101
|
+
if (!manifestResult.ok) {
|
|
102
|
+
return errorResult("asset_not_found", manifestResult.error.message);
|
|
103
|
+
}
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
try {
|
|
106
|
+
await persistSettings(engine, asset_id, false, max_keywords, linger_seconds, null);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return errorResult("settings_write_failed", error instanceof Error ? error.message : String(error));
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify({ asset_id, status: "configured", enabled: false }),
|
|
115
|
+
}],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const transcriptResult = await engine.files.read(asset_id, "elevenlabs.json");
|
|
119
|
+
if (!transcriptResult.ok) {
|
|
120
|
+
return errorResult("missing_transcript", `No timed transcript found for ${asset_id}. Run /transcribe @${asset_id} first.`);
|
|
121
|
+
}
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
raw = JSON.parse(transcriptResult.value.toString());
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return errorResult("invalid_transcript", `The timed transcript for ${asset_id} is not valid JSON.`);
|
|
128
|
+
}
|
|
129
|
+
const segments = parseKeywordTranscript(raw);
|
|
130
|
+
const requested = keywords?.length
|
|
131
|
+
? selectRequestedKeywordGraphics(segments, keywords, { lingerSeconds: linger_seconds })
|
|
132
|
+
: null;
|
|
133
|
+
if (requested && requested.missing.length > 0) {
|
|
134
|
+
return errorResult("keywords_not_found", `Some requested keywords were not found in the timed transcript for ${asset_id}.`, { missing_keywords: requested.missing });
|
|
135
|
+
}
|
|
136
|
+
const graphics = requested?.graphics ?? selectKeywordGraphics(segments, {
|
|
137
|
+
maxCount: max_keywords,
|
|
138
|
+
lingerSeconds: linger_seconds,
|
|
139
|
+
});
|
|
140
|
+
if (graphics.length === 0) {
|
|
141
|
+
return errorResult("no_timed_words", `The transcript for ${asset_id} has no usable word-level timing. Run /transcribe @${asset_id} again.`);
|
|
142
|
+
}
|
|
143
|
+
const videoFile = findVideoFile(manifestResult.value.files);
|
|
144
|
+
if (!videoFile) {
|
|
145
|
+
return errorResult("missing_video", `No video file found in ${asset_id}.`);
|
|
146
|
+
}
|
|
147
|
+
const fingerprint = keywordPlacementFingerprint(videoFile, transcriptResult.value, graphics);
|
|
148
|
+
let analysis = await readKeywordPlacementAnalysis(engine, asset_id);
|
|
149
|
+
let cached = await isKeywordPlacementCacheValid(engine, asset_id, analysis, fingerprint, graphics);
|
|
150
|
+
let jobId = null;
|
|
151
|
+
if (!cached) {
|
|
152
|
+
if (!getApiKey("OPENAI_API_KEY")) {
|
|
153
|
+
return errorResult("missing_vision_provider", "OPENAI_API_KEY is required to verify face-safe keyword placement.");
|
|
154
|
+
}
|
|
155
|
+
const clear = await engine.jobs.failures.clear(asset_id);
|
|
156
|
+
if (!clear.ok) {
|
|
157
|
+
return errorResult("placement_analysis_failed", clear.error.message);
|
|
158
|
+
}
|
|
159
|
+
jobId = `keyword-placement-${asset_id}-${Date.now()}`;
|
|
160
|
+
try {
|
|
161
|
+
const result = await queue.enqueueAndWait(jobId, {
|
|
162
|
+
type: "analyze_keyword_placement",
|
|
163
|
+
assetId: asset_id,
|
|
164
|
+
assetDir: manifestResult.value.path,
|
|
165
|
+
bookId: book_id,
|
|
166
|
+
videoPath: join(manifestResult.value.path, videoFile.name),
|
|
167
|
+
fingerprint,
|
|
168
|
+
graphics,
|
|
169
|
+
}, 300_000);
|
|
170
|
+
analysis = parseKeywordPlacementAnalysis(result.analysis);
|
|
171
|
+
cached = false;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
return errorResult("placement_analysis_failed", error instanceof Error ? error.message : String(error));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!analysis) {
|
|
178
|
+
return errorResult("placement_analysis_failed", "Keyword placement analysis was not persisted.");
|
|
179
|
+
}
|
|
180
|
+
let placements;
|
|
181
|
+
try {
|
|
182
|
+
placements = await placementSummary(engine, asset_id, graphics, analysis);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return errorResult("placement_analysis_failed", error instanceof Error ? error.message : String(error));
|
|
186
|
+
}
|
|
187
|
+
const resolvedKeywords = requested ? graphics.map((graphic) => graphic.text) : null;
|
|
188
|
+
try {
|
|
189
|
+
await persistSettings(engine, asset_id, true, max_keywords, linger_seconds, resolvedKeywords);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return errorResult("settings_write_failed", error instanceof Error ? error.message : String(error));
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
content: [{
|
|
196
|
+
type: "text",
|
|
197
|
+
text: JSON.stringify({
|
|
198
|
+
asset_id,
|
|
199
|
+
status: "configured",
|
|
200
|
+
enabled: true,
|
|
201
|
+
style: "cinematic-serif",
|
|
202
|
+
placement_status: "completed",
|
|
203
|
+
cached,
|
|
204
|
+
frame_cache: KEYWORD_FRAME_CACHE_FILENAME,
|
|
205
|
+
job_id: jobId,
|
|
206
|
+
mode: requested ? "explicit" : "automatic",
|
|
207
|
+
max_keywords,
|
|
208
|
+
linger_seconds,
|
|
209
|
+
requested_keywords: resolvedKeywords,
|
|
210
|
+
keywords: graphics,
|
|
211
|
+
placements,
|
|
212
|
+
}),
|
|
213
|
+
}],
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createKimiCoding } from "@apicity/kimicoding";
|
|
2
|
+
import { isPlaceholderApiKey } from "../api-key.js";
|
|
3
|
+
export function apiStatusDef() {
|
|
4
|
+
return {
|
|
5
|
+
name: "check_kimi_coding_api",
|
|
6
|
+
description: "Check whether the Kimi Coding API is reachable and its configured API key is valid",
|
|
7
|
+
schema: {},
|
|
8
|
+
handler: async () => {
|
|
9
|
+
const apiKey = process.env.KIMI_CODING_API_KEY;
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
return {
|
|
12
|
+
isError: true,
|
|
13
|
+
content: [{ type: "text", text: "KIMI_CODING_API_KEY environment variable is not set" }],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (isPlaceholderApiKey(apiKey)) {
|
|
17
|
+
return {
|
|
18
|
+
isError: true,
|
|
19
|
+
content: [{ type: "text", text: "KIMI_CODING_API_KEY is a placeholder; refusing to call the real API" }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const provider = createKimiCoding({ apiKey });
|
|
24
|
+
await provider.post.coding.v1.messages({
|
|
25
|
+
model: "k2p5",
|
|
26
|
+
max_tokens: 1,
|
|
27
|
+
messages: [{ role: "user", content: "hi" }],
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: "200 OK" }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: [{ type: "text", text: message }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const text = (name, description, required = true) => ({ name, description, required, type: "string" });
|
|
2
|
+
const number = (name, description, required = true) => ({ name, description, required, type: "number" });
|
|
3
|
+
const clip = text("clip_id", "Sequence clip; choose from the current sequence");
|
|
4
|
+
const track = text("track_id", "Sequence track");
|
|
5
|
+
const media = text("asset_id", "Source media or notebook address, such as @a1");
|
|
6
|
+
const start = number("start_frame", "Start frame in the sequence, beginning at zero");
|
|
7
|
+
const duration = number("duration_frames", "Duration in frames");
|
|
8
|
+
function command(name, description, params = [], example) {
|
|
9
|
+
return {
|
|
10
|
+
command: name, description, params, example: example ?? name, category: "action",
|
|
11
|
+
icon: "Play", size: "default", variant: "default", chevron: "right", exposure: "composite",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const APPLICATION_COMMANDS = [
|
|
15
|
+
command("/caption", "Preview a sequence caption with timing and style", [start, duration, text("style", "clean, bold or high-contrast"), text("text", "Caption text")], "/caption 0 90 clean Opening scene"),
|
|
16
|
+
command("/duck", "Lower background audio beneath a narration clip", [text("narration_clip_id", "Narration clip"), number("reduction_db", "Reduction from -36 to -1 dB; default -12", false)]),
|
|
17
|
+
command("/fit", "Fit, fill or crop a visual sequence clip", [clip, text("mode", "fit, fill or crop")]),
|
|
18
|
+
command("/gap", "Preview inserting a gap that shifts later clips forward", [start, duration, { ...track, required: false }]),
|
|
19
|
+
command("/generate-cell", "Review generation for a configured notebook cell", [text("cell_id", "Generation cell, such as @a1")], "/generate-cell @a1"),
|
|
20
|
+
command("/insert", "Preview inserting media into the sequence", [media, start, { ...duration, required: false }], "/insert @a1 0 90"),
|
|
21
|
+
command("/move", "Preview moving a sequence clip to a track and frame", [clip, track, start]),
|
|
22
|
+
command("/overwrite", "Preview overwriting sequence content with media", [media, start, { ...duration, required: false }], "/overwrite @a1 0 90"),
|
|
23
|
+
command("/reframe", "Preview a crop transform for a visual sequence clip", [clip]),
|
|
24
|
+
command("/rough-cut", "Review a rough cut built from notebook media"),
|
|
25
|
+
command("/sound-effects", "Review suggested sound effects for the sequence"),
|
|
26
|
+
command("/track-add", "Add a video, audio or caption track", [text("kind", "video, audio or caption"), text("name", "Display name", false)], "/track-add audio Music"),
|
|
27
|
+
command("/track-remove", "Remove an empty sequence track", [track]),
|
|
28
|
+
command("/volume", "Preview a sequence clip's audio gain", [clip, number("gain_db", "Audio gain from -96 to 24 dB")]),
|
|
29
|
+
command("/clip-audio", "Edit a sequence audio clip's gain, mute, or fades", [clip, text("field", "gain, mute, fade-in, or fade-out"), text("value", "Gain from -96 to 24 dB, on or off, or fade length in frames")], "/clip-audio @clip-id gain -6"),
|
|
30
|
+
command("/clip-delete", "Preview deleting a sequence clip while retaining its source media", [clip, text("ripple", "Use --ripple to close the gap", false)]),
|
|
31
|
+
command("/clip-reverse", "Preview reversing a sequence clip while retaining its source media", [clip]),
|
|
32
|
+
command("/clip-trim", "Preview trimming a sequence clip in frames", [clip, start, duration]),
|
|
33
|
+
command("/export-sequence", "Review and export the current sequence", [text("aspect", "16:9, 9:16 or 1:1", false), text("captions", "burned, sidecar or omit", false)], "/export-sequence --aspect 16:9 --captions burned"),
|
|
34
|
+
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { findVideoFile } from "videobook-engine";
|
|
5
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
6
|
+
export async function resolveHasAudioForCuts(metaHasAudio, inputPath) {
|
|
7
|
+
if (typeof metaHasAudio === "boolean")
|
|
8
|
+
return metaHasAudio;
|
|
9
|
+
return (await probeVideo(inputPath)).hasAudio;
|
|
10
|
+
}
|
|
11
|
+
function validateCuts(cuts, duration) {
|
|
12
|
+
if (cuts.length === 0)
|
|
13
|
+
return { ok: false, error: "cuts must contain at least one entry" };
|
|
14
|
+
const sorted = [...cuts].sort((a, b) => a.cut_start - b.cut_start);
|
|
15
|
+
let lastEnd = -Infinity;
|
|
16
|
+
for (const c of sorted) {
|
|
17
|
+
if (c.cut_start < 0 || c.cut_end < 0) {
|
|
18
|
+
return { ok: false, error: "cut_start and cut_end must be >= 0" };
|
|
19
|
+
}
|
|
20
|
+
if (c.cut_start >= c.cut_end) {
|
|
21
|
+
return { ok: false, error: `cut_start must be < cut_end (got ${c.cut_start} >= ${c.cut_end})` };
|
|
22
|
+
}
|
|
23
|
+
if (c.cut_start < lastEnd) {
|
|
24
|
+
return { ok: false, error: `cuts overlap: ${c.cut_start.toFixed(2)} < previous end ${lastEnd.toFixed(2)}` };
|
|
25
|
+
}
|
|
26
|
+
if (duration !== null && c.cut_end > duration + 0.01) {
|
|
27
|
+
return { ok: false, error: `cut_end ${c.cut_end.toFixed(2)} exceeds video duration ${duration.toFixed(2)}` };
|
|
28
|
+
}
|
|
29
|
+
lastEnd = c.cut_end;
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, sorted };
|
|
32
|
+
}
|
|
33
|
+
export function applyCutsDef(engine) {
|
|
34
|
+
return {
|
|
35
|
+
name: "apply_cuts",
|
|
36
|
+
description: "Remove one or more time ranges from a video in place, then reprocess its frames, transcript and analysis. Take ranges from suggest_cuts or choose them.",
|
|
37
|
+
schema: {
|
|
38
|
+
asset_id: z.string().describe("Video asset ID (vid-*)"),
|
|
39
|
+
book_id: z.string().describe("Book id"),
|
|
40
|
+
cuts: z
|
|
41
|
+
.array(z.object({
|
|
42
|
+
cut_start: z.number().min(0).describe("Start of range to remove (seconds)"),
|
|
43
|
+
cut_end: z.number().min(0).describe("End of range to remove (seconds)"),
|
|
44
|
+
}))
|
|
45
|
+
.min(1)
|
|
46
|
+
.describe("Ranges to remove. Sorted, non-overlapping, within [0, duration]."),
|
|
47
|
+
},
|
|
48
|
+
handler: async (args) => {
|
|
49
|
+
const { asset_id, book_id, cuts } = args;
|
|
50
|
+
const assetId = asset_id.replace(/^@/, "");
|
|
51
|
+
const manifestResult = await engine.files.manifest(assetId);
|
|
52
|
+
if (!manifestResult.ok) {
|
|
53
|
+
return {
|
|
54
|
+
isError: true,
|
|
55
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const manifest = manifestResult.value;
|
|
59
|
+
const videoFile = findVideoFile(manifest.files);
|
|
60
|
+
if (!videoFile) {
|
|
61
|
+
return {
|
|
62
|
+
isError: true,
|
|
63
|
+
content: [{ type: "text", text: `No video file found in asset ${assetId}` }],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const metaResult = await engine.metadata.artifacts.read(assetId, "original");
|
|
67
|
+
const duration = metaResult.ok && typeof metaResult.value.duration === "number"
|
|
68
|
+
? metaResult.value.duration
|
|
69
|
+
: null;
|
|
70
|
+
const metaHasAudio = metaResult.ok && typeof metaResult.value.hasAudio === "boolean"
|
|
71
|
+
? metaResult.value.hasAudio
|
|
72
|
+
: undefined;
|
|
73
|
+
const validated = validateCuts(cuts, duration);
|
|
74
|
+
if (!validated.ok) {
|
|
75
|
+
return {
|
|
76
|
+
isError: true,
|
|
77
|
+
content: [{ type: "text", text: validated.error }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const assetDir = manifest.path;
|
|
81
|
+
const inputPath = join(assetDir, videoFile.name);
|
|
82
|
+
const hasAudio = await resolveHasAudioForCuts(metaHasAudio, inputPath);
|
|
83
|
+
const outputPath = join(assetDir, "original_cuts.mp4");
|
|
84
|
+
const jobId = `apply-cuts-${assetId}-${Date.now()}`;
|
|
85
|
+
try {
|
|
86
|
+
await queue.enqueueAndWait(jobId, {
|
|
87
|
+
type: "apply_cuts",
|
|
88
|
+
inputPath,
|
|
89
|
+
outputPath,
|
|
90
|
+
cuts: validated.sorted,
|
|
91
|
+
duration: duration ?? 0,
|
|
92
|
+
hasAudio,
|
|
93
|
+
assetId,
|
|
94
|
+
assetDir,
|
|
95
|
+
bookId: book_id,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return {
|
|
100
|
+
isError: true,
|
|
101
|
+
content: [{ type: "text", text: `Apply cuts failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({ asset_id: assetId, job_id: jobId, status: "completed" }),
|
|
108
|
+
}],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|