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,250 @@
|
|
|
1
|
+
import { v7 as uuidv7 } from "uuid";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { sequenceContinuationRevision } from "./command-plan.js";
|
|
5
|
+
const exportSettingsSchema = z.object({
|
|
6
|
+
sequence_revision: z.string().min(1),
|
|
7
|
+
orientation: z.enum(["landscape", "portrait", "square"]),
|
|
8
|
+
captions: z.enum(["burn-in", "sidecar", "omit"]),
|
|
9
|
+
});
|
|
10
|
+
function json(value) {
|
|
11
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
12
|
+
}
|
|
13
|
+
export function sequenceExportDimensions(orientation) {
|
|
14
|
+
if (orientation === "portrait")
|
|
15
|
+
return [1080, 1920];
|
|
16
|
+
if (orientation === "square")
|
|
17
|
+
return [1080, 1080];
|
|
18
|
+
return [1920, 1080];
|
|
19
|
+
}
|
|
20
|
+
function sequenceDuration(sequence) {
|
|
21
|
+
return Math.max(0, ...sequence.clips.map((clip) => clip.timelineStartFrame + clip.durationFrames), ...sequence.captions.map((cue) => cue.timelineStartFrame + cue.durationFrames));
|
|
22
|
+
}
|
|
23
|
+
export async function validateSequenceExport(engine, settings) {
|
|
24
|
+
const sequence = engine.sequences.getPrimary();
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const objectHashes = new Set();
|
|
28
|
+
const [width, height] = sequenceExportDimensions(settings.orientation);
|
|
29
|
+
const durationFrames = sequenceDuration(sequence);
|
|
30
|
+
if (settings.sequence_revision !== sequence.revision
|
|
31
|
+
|| settings.sequence_revision !== engine.head) {
|
|
32
|
+
errors.push({
|
|
33
|
+
code: "STALE_REVISION",
|
|
34
|
+
message: `Requested ${settings.sequence_revision} but the current revision is ${engine.head}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (durationFrames < 1) {
|
|
38
|
+
errors.push({
|
|
39
|
+
code: "EMPTY_SEQUENCE",
|
|
40
|
+
message: "The sequence has no playable duration",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const enabledTracks = new Map(sequence.tracks
|
|
44
|
+
.filter((track) => track.enabled)
|
|
45
|
+
.map((track) => [track.trackId, track]));
|
|
46
|
+
for (const clip of sequence.clips.filter((candidate) => candidate.enabled)) {
|
|
47
|
+
const track = enabledTracks.get(clip.trackId);
|
|
48
|
+
if (!track || track.kind === "caption") {
|
|
49
|
+
errors.push({
|
|
50
|
+
code: "MISSING_TRACK",
|
|
51
|
+
message: "The clip is not on an enabled media track",
|
|
52
|
+
clipId: clip.clipId,
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const transform = clip.transform;
|
|
57
|
+
if (transform
|
|
58
|
+
&& (transform.anchorX !== 0.5
|
|
59
|
+
|| transform.anchorY !== 0.5
|
|
60
|
+
|| transform.cropLeft + transform.cropRight >= 1
|
|
61
|
+
|| transform.cropTop + transform.cropBottom >= 1)) {
|
|
62
|
+
errors.push({
|
|
63
|
+
code: "UNSUPPORTED_EFFECT",
|
|
64
|
+
message: "This export renderer requires a centered anchor and non-empty crop",
|
|
65
|
+
clipId: clip.clipId,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const artifact = engine.artifacts.get(clip.source.artifactId);
|
|
69
|
+
if (!artifact.ok) {
|
|
70
|
+
errors.push({
|
|
71
|
+
code: "MISSING_MEDIA",
|
|
72
|
+
message: artifact.error.message,
|
|
73
|
+
clipId: clip.clipId,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const manifest = await engine.files.manifest(artifact.value.artifactId);
|
|
78
|
+
if (!manifest.ok) {
|
|
79
|
+
errors.push({
|
|
80
|
+
code: "OFFLINE_OBJECT",
|
|
81
|
+
message: manifest.error.message,
|
|
82
|
+
clipId: clip.clipId,
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let expectedHash;
|
|
87
|
+
let sourcePath;
|
|
88
|
+
if (clip.source.kind === "timed") {
|
|
89
|
+
expectedHash = clip.source.range.objectHash;
|
|
90
|
+
const streamId = clip.source.range.streamId;
|
|
91
|
+
const streams = engine.streams.list(artifact.value.artifactId);
|
|
92
|
+
sourcePath = streams.ok
|
|
93
|
+
? streams.value.find((stream) => stream.streamId === streamId)?.sourcePath
|
|
94
|
+
: undefined;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
expectedHash = clip.source.objectHash;
|
|
98
|
+
sourcePath = clip.source.sourcePath;
|
|
99
|
+
}
|
|
100
|
+
const file = manifest.value.files.find((candidate) => candidate.objectHash === expectedHash
|
|
101
|
+
&& (sourcePath === undefined || candidate.name === sourcePath));
|
|
102
|
+
if (!file) {
|
|
103
|
+
errors.push({
|
|
104
|
+
code: "SOURCE_REPLACED",
|
|
105
|
+
message: "The immutable source snapshot is not available locally",
|
|
106
|
+
clipId: clip.clipId,
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
objectHashes.add(file.objectHash);
|
|
111
|
+
}
|
|
112
|
+
if (settings.captions !== "omit") {
|
|
113
|
+
for (const cue of sequence.captions) {
|
|
114
|
+
const seconds = cue.durationFrames
|
|
115
|
+
* sequence.frameRate.denominator
|
|
116
|
+
/ sequence.frameRate.numerator;
|
|
117
|
+
if (cue.text.length > Math.max(42, Math.floor(seconds * 28))) {
|
|
118
|
+
warnings.push({
|
|
119
|
+
code: "CAPTION_OVERFLOW",
|
|
120
|
+
message: "Caption text may not fit its display duration",
|
|
121
|
+
cueId: cue.cueId,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
valid: errors.length === 0,
|
|
128
|
+
sequenceId: sequence.sequenceId,
|
|
129
|
+
revision: sequence.revision,
|
|
130
|
+
durationFrames,
|
|
131
|
+
orientation: settings.orientation,
|
|
132
|
+
width,
|
|
133
|
+
height,
|
|
134
|
+
captions: settings.captions,
|
|
135
|
+
inputObjectHashes: [...objectHashes].sort(),
|
|
136
|
+
errors,
|
|
137
|
+
warnings,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function exportSequenceDefs(engine) {
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
name: "validate_sequence_export",
|
|
144
|
+
description: "Validate a revision-pinned canonical sequence export and resolve immutable inputs",
|
|
145
|
+
scope: "book",
|
|
146
|
+
schema: {
|
|
147
|
+
book_id: z.string().uuid(),
|
|
148
|
+
...exportSettingsSchema.shape,
|
|
149
|
+
},
|
|
150
|
+
handler: async (args) => json(await validateSequenceExport(engine, args)),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "plan_sequence_export",
|
|
154
|
+
description: "Create a queued export CommandPlan for a validated canonical sequence revision",
|
|
155
|
+
scope: "book",
|
|
156
|
+
schema: {
|
|
157
|
+
book_id: z.string().uuid(),
|
|
158
|
+
source_surface: z.enum(["ui", "slash", "chat"]),
|
|
159
|
+
actor: z.string().min(1),
|
|
160
|
+
...exportSettingsSchema.shape,
|
|
161
|
+
},
|
|
162
|
+
handler: async (args) => {
|
|
163
|
+
const settings = args;
|
|
164
|
+
const validation = await validateSequenceExport(engine, settings);
|
|
165
|
+
if (!validation.valid)
|
|
166
|
+
return json({ kind: "invalid-export", validation });
|
|
167
|
+
const planId = uuidv7();
|
|
168
|
+
return json({
|
|
169
|
+
planVersion: 1,
|
|
170
|
+
planId,
|
|
171
|
+
sourceSurface: args.source_surface,
|
|
172
|
+
actor: args.actor,
|
|
173
|
+
bookId: engine.book.get().bookId,
|
|
174
|
+
summary: `Export ${validation.orientation} MP4`,
|
|
175
|
+
normalizedReferences: [{
|
|
176
|
+
kind: "sequence",
|
|
177
|
+
targetId: validation.sequenceId,
|
|
178
|
+
revision: validation.revision,
|
|
179
|
+
}],
|
|
180
|
+
stages: [
|
|
181
|
+
{
|
|
182
|
+
stageId: `${planId}:export:0`,
|
|
183
|
+
kind: "export",
|
|
184
|
+
summary: "Validate final render settings",
|
|
185
|
+
sequenceId: validation.sequenceId,
|
|
186
|
+
revision: validation.revision,
|
|
187
|
+
orientation: validation.orientation,
|
|
188
|
+
captions: validation.captions,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
stageId: `${planId}:queued:1`,
|
|
192
|
+
kind: "queued",
|
|
193
|
+
summary: "Render H.264/AAC MP4",
|
|
194
|
+
jobType: "export",
|
|
195
|
+
tool: "export_sequence",
|
|
196
|
+
input: {
|
|
197
|
+
sequence_revision: validation.revision,
|
|
198
|
+
sequence_content_revision: sequenceContinuationRevision(engine.sequences.getPrimary()),
|
|
199
|
+
orientation: validation.orientation,
|
|
200
|
+
captions: validation.captions,
|
|
201
|
+
width: validation.width,
|
|
202
|
+
height: validation.height,
|
|
203
|
+
input_object_hashes: validation.inputObjectHashes,
|
|
204
|
+
},
|
|
205
|
+
insertsOutput: false,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
confirmation: {
|
|
209
|
+
required: true,
|
|
210
|
+
policy: "always",
|
|
211
|
+
reason: "Export runs as a queued render against this exact revision",
|
|
212
|
+
},
|
|
213
|
+
validation,
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "enqueue_sequence_export",
|
|
219
|
+
description: "Queue a confirmed canonical sequence export after revalidating its exact revision",
|
|
220
|
+
scope: "book",
|
|
221
|
+
schema: {
|
|
222
|
+
book_id: z.string().uuid(),
|
|
223
|
+
confirm: z.literal(true),
|
|
224
|
+
...exportSettingsSchema.shape,
|
|
225
|
+
},
|
|
226
|
+
handler: async (args) => {
|
|
227
|
+
const settings = args;
|
|
228
|
+
const validation = await validateSequenceExport(engine, settings);
|
|
229
|
+
if (!validation.valid)
|
|
230
|
+
return json({ kind: "invalid-export", validation });
|
|
231
|
+
const jobId = `export-sequence-${engine.book.get().bookId}-${uuidv7()}`;
|
|
232
|
+
await queue.enqueue(jobId, {
|
|
233
|
+
type: "export_sequence",
|
|
234
|
+
bookId: engine.book.get().bookId,
|
|
235
|
+
sequenceRevision: validation.revision,
|
|
236
|
+
orientation: validation.orientation,
|
|
237
|
+
captions: validation.captions,
|
|
238
|
+
});
|
|
239
|
+
return json({
|
|
240
|
+
status: "enqueued",
|
|
241
|
+
jobId,
|
|
242
|
+
sequenceId: validation.sequenceId,
|
|
243
|
+
sequenceRevision: validation.revision,
|
|
244
|
+
orientation: validation.orientation,
|
|
245
|
+
captions: validation.captions,
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { extname, join } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { findOriginalMediaFile } from "../original-media-file.js";
|
|
4
|
+
import { copyToDownloads } from "../downloads-dest.js";
|
|
5
|
+
export function exportToDownloadsDef(engine) {
|
|
6
|
+
return {
|
|
7
|
+
name: "export_to_downloads",
|
|
8
|
+
description: "Copy an asset's original media file to the user's Downloads folder",
|
|
9
|
+
schema: {
|
|
10
|
+
asset_id: z.string().describe("Artifact id"),
|
|
11
|
+
book_id: z.string().describe("Book UUID"),
|
|
12
|
+
},
|
|
13
|
+
handler: async (args) => {
|
|
14
|
+
const { asset_id, book_id: _book_id } = args;
|
|
15
|
+
const artifactResult = engine.artifacts.get(asset_id);
|
|
16
|
+
if (!artifactResult.ok) {
|
|
17
|
+
return {
|
|
18
|
+
isError: true,
|
|
19
|
+
content: [{ type: "text", text: artifactResult.error.message }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const manifestResult = await engine.files.manifest(asset_id);
|
|
23
|
+
if (!manifestResult.ok) {
|
|
24
|
+
return {
|
|
25
|
+
isError: true,
|
|
26
|
+
content: [{ type: "text", text: manifestResult.error.message }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const manifest = manifestResult.value;
|
|
30
|
+
const file = findOriginalMediaFile(manifest.files, artifactResult.value.kind);
|
|
31
|
+
if (!file) {
|
|
32
|
+
return {
|
|
33
|
+
isError: true,
|
|
34
|
+
content: [{ type: "text", text: "No original media file found for asset" }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const source = join(manifest.path, file.name);
|
|
38
|
+
const ext = extname(file.name);
|
|
39
|
+
let dest;
|
|
40
|
+
try {
|
|
41
|
+
dest = await copyToDownloads(source, engine.book.get().name, ext);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
isError: true,
|
|
46
|
+
content: [{ type: "text", text: `Failed to copy file: ${err.message}` }],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: JSON.stringify({ status: "exported", path: dest }) }],
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { copyToDownloads } from "../downloads-dest.js";
|
|
6
|
+
import { PLAIN_TRANSCRIPT_FILE, TIMED_TRANSCRIPT_FILE, timedTranscriptFrom, } from "../transcript-projection.js";
|
|
7
|
+
import { transcriptToSrt } from "../transcript-srt.js";
|
|
8
|
+
function parseJson(value) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(value.toString());
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function errorResult(message) {
|
|
17
|
+
return {
|
|
18
|
+
isError: true,
|
|
19
|
+
content: [{ type: "text", text: message }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function exportTranscriptSrtDef(engine) {
|
|
23
|
+
return {
|
|
24
|
+
name: "export_transcript_srt",
|
|
25
|
+
description: "Render an asset's word-timed ElevenLabs transcript as SubRip (.srt) and save it to the user's Downloads folder. Errors when the asset has no elevenlabs.json timings.",
|
|
26
|
+
schema: {
|
|
27
|
+
asset_id: z.string().describe("Artifact id"),
|
|
28
|
+
book_id: z.string().describe("Book UUID"),
|
|
29
|
+
downloads_dir: z.string().optional()
|
|
30
|
+
.describe("Override Downloads directory (tests)"),
|
|
31
|
+
},
|
|
32
|
+
handler: async (args) => {
|
|
33
|
+
const { asset_id, downloads_dir } = args;
|
|
34
|
+
const artifactResult = engine.artifacts.get(asset_id);
|
|
35
|
+
if (!artifactResult.ok) {
|
|
36
|
+
return errorResult(artifactResult.error.message);
|
|
37
|
+
}
|
|
38
|
+
const timedRead = await engine.files.read(asset_id, TIMED_TRANSCRIPT_FILE);
|
|
39
|
+
if (!timedRead.ok) {
|
|
40
|
+
const plainRead = await engine.files.read(asset_id, PLAIN_TRANSCRIPT_FILE);
|
|
41
|
+
if (plainRead.ok) {
|
|
42
|
+
return errorResult("No word-timed ElevenLabs transcript for this asset. A plain gpt-4o transcription cannot be exported as SRT.");
|
|
43
|
+
}
|
|
44
|
+
return errorResult("No word-timed ElevenLabs transcript for this asset. Run /transcribe first.");
|
|
45
|
+
}
|
|
46
|
+
const raw = parseJson(timedRead.value);
|
|
47
|
+
const timed = timedTranscriptFrom(raw);
|
|
48
|
+
if (!timed || timed.kind !== "timed") {
|
|
49
|
+
return errorResult("No word-timed ElevenLabs transcript for this asset. A plain gpt-4o transcription cannot be exported as SRT.");
|
|
50
|
+
}
|
|
51
|
+
let srt;
|
|
52
|
+
try {
|
|
53
|
+
srt = transcriptToSrt({ words: timed.words });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
return errorResult(err.message);
|
|
57
|
+
}
|
|
58
|
+
const tempDir = await mkdtemp(join(tmpdir(), "videobook-srt-"));
|
|
59
|
+
let dest;
|
|
60
|
+
try {
|
|
61
|
+
const tempPath = join(tempDir, "captions.srt");
|
|
62
|
+
await writeFile(tempPath, srt, "utf8");
|
|
63
|
+
dest = await copyToDownloads(tempPath, artifactResult.value.label ?? asset_id, ".srt", downloads_dir);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return errorResult(`Failed to save SRT: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify({ status: "exported", path: dest }),
|
|
75
|
+
}],
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { findAudioFile } from "videobook-engine";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { MAX_AUDIO_CONTINUATION_MS, MAX_CONTINUATION_DIRECTION_CHARS, MAX_SOUND_GENERATION_PROMPT_CHARS, MIN_AUDIO_CONTINUATION_MS, normalizeContinuationPrompt, } from "../elevenlabs-audio-extension.js";
|
|
5
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
6
|
+
import { queue } from "../queue.js";
|
|
7
|
+
import { toolError } from "./_shared.js";
|
|
8
|
+
function defaultExtendedName(source, targetDurationSeconds) {
|
|
9
|
+
const base = source.label ?? source.artifactId;
|
|
10
|
+
const duration = String(targetDurationSeconds).replace(".", "-");
|
|
11
|
+
return `extended-${base}-${duration}s`;
|
|
12
|
+
}
|
|
13
|
+
export function extendAudioDef(engine) {
|
|
14
|
+
return {
|
|
15
|
+
name: "extend_audio_asset",
|
|
16
|
+
description: "Extend an audio asset to a total duration with a matching ElevenLabs continuation that keeps the source at the start, as a new audio asset.",
|
|
17
|
+
schema: {
|
|
18
|
+
asset_id: z.string().describe("Source audio artifact id"),
|
|
19
|
+
target_duration_seconds: z.number().positive().describe("Requested total output duration in seconds"),
|
|
20
|
+
continuation_prompt: z.string().min(1).max(MAX_SOUND_GENERATION_PROMPT_CHARS).optional().describe('Optional direction for what should follow the source audio, e.g. "add outro music"'),
|
|
21
|
+
asset_name: z.string().min(1).optional().describe("Optional name for the new audio asset"),
|
|
22
|
+
book_id: z.string().describe("Book UUID"),
|
|
23
|
+
},
|
|
24
|
+
handler: async (args) => {
|
|
25
|
+
const { asset_id, target_duration_seconds, continuation_prompt, asset_name, book_id, } = args;
|
|
26
|
+
if (!process.env.ELEVENLABS_API_KEY) {
|
|
27
|
+
return toolError("ELEVENLABS_API_KEY environment variable is not set");
|
|
28
|
+
}
|
|
29
|
+
const sourceAssetId = asset_id.replace(/^@/, "");
|
|
30
|
+
const sourceArtifact = engine.artifacts.get(sourceAssetId);
|
|
31
|
+
if (!sourceArtifact.ok)
|
|
32
|
+
return toolError(sourceArtifact.error.message);
|
|
33
|
+
if (sourceArtifact.value.kind !== "audio") {
|
|
34
|
+
return toolError(`extend_audio requires an audio source, got ${sourceArtifact.value.kind} artifact ${sourceAssetId}`);
|
|
35
|
+
}
|
|
36
|
+
const continuationPrompt = normalizeContinuationPrompt(continuation_prompt);
|
|
37
|
+
if (continuationPrompt && continuationPrompt.length > MAX_CONTINUATION_DIRECTION_CHARS) {
|
|
38
|
+
return toolError(`continuation_prompt supports at most ${MAX_CONTINUATION_DIRECTION_CHARS} characters of direction`);
|
|
39
|
+
}
|
|
40
|
+
const manifest = await engine.files.manifest(sourceAssetId);
|
|
41
|
+
if (!manifest.ok)
|
|
42
|
+
return toolError(manifest.error.message);
|
|
43
|
+
const audioFile = findAudioFile(manifest.value.files, sourceArtifact.value.kind);
|
|
44
|
+
if (!audioFile)
|
|
45
|
+
return toolError(`No audio file found in artifact ${sourceAssetId}`);
|
|
46
|
+
let sourceDurationSeconds;
|
|
47
|
+
try {
|
|
48
|
+
const probe = await probeVideo(join(manifest.value.path, audioFile.name));
|
|
49
|
+
sourceDurationSeconds = probe.audioDuration ?? probe.duration;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return toolError(`Failed to probe ${sourceAssetId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
if (!Number.isFinite(sourceDurationSeconds) || sourceDurationSeconds <= 0) {
|
|
55
|
+
return toolError(`Could not determine a valid duration for ${sourceAssetId}`);
|
|
56
|
+
}
|
|
57
|
+
const sourceDurationMs = Math.round(sourceDurationSeconds * 1000);
|
|
58
|
+
const targetDurationMs = Math.round(target_duration_seconds * 1000);
|
|
59
|
+
if (targetDurationMs <= sourceDurationMs) {
|
|
60
|
+
return toolError(`Target duration must exceed the source duration of ${(sourceDurationMs / 1000).toFixed(3)}s`);
|
|
61
|
+
}
|
|
62
|
+
const continuationDurationMs = targetDurationMs - sourceDurationMs;
|
|
63
|
+
if (continuationDurationMs < MIN_AUDIO_CONTINUATION_MS) {
|
|
64
|
+
return toolError(`ElevenLabs requires at least ${(MIN_AUDIO_CONTINUATION_MS / 1000).toFixed(1)}s of new continuation`);
|
|
65
|
+
}
|
|
66
|
+
if (continuationDurationMs > MAX_AUDIO_CONTINUATION_MS) {
|
|
67
|
+
return toolError(`ElevenLabs Sound Generation supports at most ${(MAX_AUDIO_CONTINUATION_MS / 1000).toFixed(1)}s of new continuation per request`);
|
|
68
|
+
}
|
|
69
|
+
const name = asset_name?.trim() || defaultExtendedName(sourceArtifact.value, target_duration_seconds);
|
|
70
|
+
const created = await engine.artifacts.create("audio", name);
|
|
71
|
+
if (!created.ok)
|
|
72
|
+
return toolError(`Failed to create audio asset: ${created.error.message}`);
|
|
73
|
+
const jobId = `extend-audio-${created.value.artifactId}-${Date.now()}`;
|
|
74
|
+
await queue.enqueue(jobId, {
|
|
75
|
+
type: "extend_audio",
|
|
76
|
+
audAssetId: created.value.artifactId,
|
|
77
|
+
sourceAssetId,
|
|
78
|
+
sourceFilename: audioFile.name,
|
|
79
|
+
sourceDurationMs,
|
|
80
|
+
targetDurationMs,
|
|
81
|
+
continuationPrompt,
|
|
82
|
+
bookId: book_id,
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: JSON.stringify({
|
|
88
|
+
aud_asset_id: created.value.artifactId,
|
|
89
|
+
source_audio_asset_id: sourceAssetId,
|
|
90
|
+
source_duration_seconds: sourceDurationMs / 1000,
|
|
91
|
+
target_duration_seconds: targetDurationMs / 1000,
|
|
92
|
+
continuation_duration_seconds: (targetDurationMs - sourceDurationMs) / 1000,
|
|
93
|
+
job_id: jobId,
|
|
94
|
+
status: "enqueued",
|
|
95
|
+
}),
|
|
96
|
+
}],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { queue } from "../queue.js";
|
|
4
|
+
import { findVideoFile } from "videobook-engine";
|
|
5
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
6
|
+
import { validateMediaOutputPaths } from "../media-output-path.js";
|
|
7
|
+
function deriveAudioName(source) {
|
|
8
|
+
return `ext-${source.label ?? source.artifactId}`;
|
|
9
|
+
}
|
|
10
|
+
export function extractAudioDef(engine) {
|
|
11
|
+
return {
|
|
12
|
+
name: "extract_audio",
|
|
13
|
+
description: "Extract a video's audio as MP3 into a new audio asset, or as a file inside an existing asset with destination. range limits it to a time window.",
|
|
14
|
+
schema: {
|
|
15
|
+
asset_id: z.string().describe("Video artifact id"),
|
|
16
|
+
book_id: z.string().describe("Book UUID"),
|
|
17
|
+
destination: z
|
|
18
|
+
.object({
|
|
19
|
+
asset_id: z.string().describe("Existing artifact id to write the mp3 into"),
|
|
20
|
+
filename: z.string().describe("Filename for the mp3 inside the destination asset (e.g. \"s1.mp3\")"),
|
|
21
|
+
})
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("When set, write the mp3 to <destination.asset_id>/<destination.filename> instead of minting a fresh audio asset."),
|
|
24
|
+
range: z
|
|
25
|
+
.object({
|
|
26
|
+
start_s: z.number().min(0).describe("Start offset in seconds (inclusive)"),
|
|
27
|
+
duration_s: z.number().positive().describe("Duration in seconds"),
|
|
28
|
+
})
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("When set, extract only this time window from the source."),
|
|
31
|
+
},
|
|
32
|
+
handler: async (args) => {
|
|
33
|
+
const { asset_id, book_id, destination, range } = args;
|
|
34
|
+
const sourceArtifact = engine.artifacts.get(asset_id);
|
|
35
|
+
if (!sourceArtifact.ok) {
|
|
36
|
+
return {
|
|
37
|
+
isError: true,
|
|
38
|
+
content: [{ type: "text", text: sourceArtifact.error.message }],
|
|
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 videoPath = join(manifest.path, videoFile.name);
|
|
57
|
+
let probe;
|
|
58
|
+
try {
|
|
59
|
+
probe = await probeVideo(videoPath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
63
|
+
return {
|
|
64
|
+
isError: true,
|
|
65
|
+
content: [{ type: "text", text: `Failed to probe video: ${msg}` }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!probe.hasAudio) {
|
|
69
|
+
return {
|
|
70
|
+
isError: true,
|
|
71
|
+
content: [{ type: "text", text: `Video has no audio stream: ${asset_id}` }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const audioDuration = probe.audioDuration ?? probe.duration;
|
|
75
|
+
if (range && (!Number.isFinite(audioDuration) || range.start_s >= audioDuration)) {
|
|
76
|
+
return {
|
|
77
|
+
isError: true,
|
|
78
|
+
content: [{ type: "text", text: "Audio range must start before the source audio ends." }],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
let audioAssetId;
|
|
82
|
+
let audioAssetDir;
|
|
83
|
+
let outputPath;
|
|
84
|
+
const destinationMode = destination !== undefined;
|
|
85
|
+
if (destination !== undefined) {
|
|
86
|
+
try {
|
|
87
|
+
[destination.filename] = await validateMediaOutputPaths(engine, destination.asset_id, [destination.filename], ".mp3");
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
isError: true,
|
|
92
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const destManifest = await engine.files.manifest(destination.asset_id);
|
|
96
|
+
if (!destManifest.ok) {
|
|
97
|
+
return {
|
|
98
|
+
isError: true,
|
|
99
|
+
content: [{ type: "text", text: `Destination asset ${destination.asset_id} not found: ${destManifest.error.message}` }],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
audioAssetId = destination.asset_id;
|
|
103
|
+
audioAssetDir = destManifest.value.path;
|
|
104
|
+
outputPath = join(audioAssetDir, destination.filename);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const audioName = deriveAudioName(sourceArtifact.value);
|
|
108
|
+
const assetResult = await engine.artifacts.create("audio", audioName);
|
|
109
|
+
if (!assetResult.ok) {
|
|
110
|
+
return {
|
|
111
|
+
isError: true,
|
|
112
|
+
content: [{ type: "text", text: `Failed to create audio asset: ${assetResult.error.message}` }],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
audioAssetId = assetResult.value.artifactId;
|
|
116
|
+
audioAssetDir = assetResult.value.path;
|
|
117
|
+
outputPath = join(audioAssetDir, "original.mp3");
|
|
118
|
+
}
|
|
119
|
+
const jobId = `extract-audio-${audioAssetId}-${Date.now()}`;
|
|
120
|
+
const payload = {
|
|
121
|
+
type: "extract_audio",
|
|
122
|
+
inputPath: videoPath,
|
|
123
|
+
outputPath,
|
|
124
|
+
audioAssetId,
|
|
125
|
+
audioAssetDir,
|
|
126
|
+
sourceVideoId: asset_id,
|
|
127
|
+
bookId: book_id,
|
|
128
|
+
destination: destinationMode ? { filename: destination.filename } : undefined,
|
|
129
|
+
range,
|
|
130
|
+
};
|
|
131
|
+
// Destination mode synchronously awaits the ffmpeg + commit so the
|
|
132
|
+
// caller (PlanViewer bootstrap) knows the file is on disk before
|
|
133
|
+
// gating RUN VIDEO. The auto-mint path keeps fire-and-forget so the
|
|
134
|
+
// sidebar shows a pending aud-* tile while the job runs.
|
|
135
|
+
if (destinationMode) {
|
|
136
|
+
try {
|
|
137
|
+
await queue.enqueueAndWait(jobId, payload);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
return {
|
|
141
|
+
isError: true,
|
|
142
|
+
content: [{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `Audio extract failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
145
|
+
}],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
content: [{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: JSON.stringify({
|
|
152
|
+
destination: { asset_id: audioAssetId, filename: destination.filename },
|
|
153
|
+
source_video: asset_id,
|
|
154
|
+
job_id: jobId,
|
|
155
|
+
status: "completed",
|
|
156
|
+
}),
|
|
157
|
+
}],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
await queue.enqueue(jobId, payload);
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
audio_asset_id: audioAssetId,
|
|
166
|
+
source_video: asset_id,
|
|
167
|
+
job_id: jobId,
|
|
168
|
+
status: "enqueued",
|
|
169
|
+
}),
|
|
170
|
+
}],
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|