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,18 @@
|
|
|
1
|
+
export function isValidHex(hex) {
|
|
2
|
+
return /^#[0-9a-fA-F]{6}$/.test(hex);
|
|
3
|
+
}
|
|
4
|
+
export function getCurrentWordIndex(words, currentTime, lingerDuration = 0) {
|
|
5
|
+
for (let i = 0; i < words.length; i++) {
|
|
6
|
+
let wordEnd = words[i].end;
|
|
7
|
+
if (lingerDuration > 0 && i + 1 < words.length) {
|
|
8
|
+
wordEnd = Math.min(wordEnd + lingerDuration, words[i + 1].start - 0.01);
|
|
9
|
+
}
|
|
10
|
+
else if (lingerDuration > 0) {
|
|
11
|
+
wordEnd = wordEnd + lingerDuration;
|
|
12
|
+
}
|
|
13
|
+
if (words[i].start <= currentTime && currentTime <= wordEnd) {
|
|
14
|
+
return i;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return -1;
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { makeCancelSignal } from "@remotion/renderer";
|
|
2
|
+
export async function withRemotionCancellation(signal, render) {
|
|
3
|
+
signal.throwIfAborted();
|
|
4
|
+
const { cancelSignal, cancel } = makeCancelSignal();
|
|
5
|
+
const abort = () => cancel();
|
|
6
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
7
|
+
try {
|
|
8
|
+
const result = await render(cancelSignal);
|
|
9
|
+
signal.throwIfAborted();
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
signal.removeEventListener("abort", abort);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Asset kinds eligible for rename-all (matches the preview scope). */
|
|
2
|
+
export const RENAME_KINDS = ["video", "image"];
|
|
3
|
+
export function isRenamableKind(kind) {
|
|
4
|
+
return RENAME_KINDS.some((candidate) => candidate === kind);
|
|
5
|
+
}
|
|
6
|
+
function rowKind(kind) {
|
|
7
|
+
return kind === "video" ? "video" : "image";
|
|
8
|
+
}
|
|
9
|
+
function displayName(label, artifactId) {
|
|
10
|
+
return label ?? artifactId;
|
|
11
|
+
}
|
|
12
|
+
/** Build the user-visible rows from the AI suggestion map, marking skipped and
|
|
13
|
+
* unchanged rows. `suggestions` maps asset_id → proposed label. Labels are
|
|
14
|
+
* free text with no uniqueness constraint, so there are no collisions to
|
|
15
|
+
* resolve. */
|
|
16
|
+
export function buildRenameRows(assets, suggestions) {
|
|
17
|
+
const rows = [];
|
|
18
|
+
for (const asset of assets) {
|
|
19
|
+
const currentName = displayName(asset.label, asset.asset_id);
|
|
20
|
+
const proposed = suggestions[asset.asset_id]?.trim();
|
|
21
|
+
if (!proposed) {
|
|
22
|
+
rows.push({
|
|
23
|
+
asset_id: asset.asset_id,
|
|
24
|
+
type: rowKind(asset.type),
|
|
25
|
+
current_name: currentName,
|
|
26
|
+
suggested_name: currentName,
|
|
27
|
+
status: "skipped",
|
|
28
|
+
reason: "No suggestion produced",
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (proposed === asset.label) {
|
|
33
|
+
rows.push({
|
|
34
|
+
asset_id: asset.asset_id,
|
|
35
|
+
type: rowKind(asset.type),
|
|
36
|
+
current_name: currentName,
|
|
37
|
+
suggested_name: proposed,
|
|
38
|
+
status: "unchanged",
|
|
39
|
+
reason: "Label already matches",
|
|
40
|
+
});
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
rows.push({
|
|
44
|
+
asset_id: asset.asset_id,
|
|
45
|
+
type: rowKind(asset.type),
|
|
46
|
+
current_name: currentName,
|
|
47
|
+
suggested_name: proposed,
|
|
48
|
+
status: "renamable",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
/** Revalidate a user-edited set of renames against the live asset list. This
|
|
54
|
+
* is the dry-run-with-edits path: assets may have been deleted or relabeled
|
|
55
|
+
* since the suggestion pass, so every row is rechecked. */
|
|
56
|
+
export function validateEditedRenames(edits, candidates) {
|
|
57
|
+
const byId = new Map(candidates.map((candidate) => [candidate.artifactId, candidate]));
|
|
58
|
+
const rows = [];
|
|
59
|
+
for (const edit of edits) {
|
|
60
|
+
const candidate = byId.get(edit.asset_id);
|
|
61
|
+
const trimmed = edit.suggested_name.trim();
|
|
62
|
+
if (!candidate) {
|
|
63
|
+
rows.push({
|
|
64
|
+
asset_id: edit.asset_id,
|
|
65
|
+
type: "image",
|
|
66
|
+
current_name: edit.asset_id,
|
|
67
|
+
suggested_name: edit.suggested_name,
|
|
68
|
+
status: "conflict",
|
|
69
|
+
reason: "Asset no longer exists",
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const currentName = displayName(candidate.label, candidate.artifactId);
|
|
74
|
+
if (!isRenamableKind(candidate.kind)) {
|
|
75
|
+
rows.push({
|
|
76
|
+
asset_id: edit.asset_id,
|
|
77
|
+
type: rowKind(candidate.kind),
|
|
78
|
+
current_name: currentName,
|
|
79
|
+
suggested_name: edit.suggested_name,
|
|
80
|
+
status: "conflict",
|
|
81
|
+
reason: `Only video and image assets are renamable here (got ${candidate.kind})`,
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!trimmed) {
|
|
86
|
+
rows.push({
|
|
87
|
+
asset_id: edit.asset_id,
|
|
88
|
+
type: rowKind(candidate.kind),
|
|
89
|
+
current_name: currentName,
|
|
90
|
+
suggested_name: edit.suggested_name,
|
|
91
|
+
status: "conflict",
|
|
92
|
+
reason: "Empty name",
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (trimmed === candidate.label) {
|
|
97
|
+
rows.push({
|
|
98
|
+
asset_id: edit.asset_id,
|
|
99
|
+
type: rowKind(candidate.kind),
|
|
100
|
+
current_name: currentName,
|
|
101
|
+
suggested_name: trimmed,
|
|
102
|
+
status: "unchanged",
|
|
103
|
+
reason: "Label already matches",
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
rows.push({
|
|
108
|
+
asset_id: edit.asset_id,
|
|
109
|
+
type: rowKind(candidate.kind),
|
|
110
|
+
current_name: currentName,
|
|
111
|
+
suggested_name: trimmed,
|
|
112
|
+
status: "renamable",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return rows;
|
|
116
|
+
}
|
|
117
|
+
/** Rows that will actually be relabeled at apply time. */
|
|
118
|
+
export function applyableRows(rows) {
|
|
119
|
+
return rows.filter((r) => r.status === "renamable");
|
|
120
|
+
}
|
|
121
|
+
// --- GPT prompt construction (pure) -----------------------------------------
|
|
122
|
+
/** Origins that describe how an image was produced, not what it shows. */
|
|
123
|
+
const PROCESS_ORIGINS = new Set([
|
|
124
|
+
"first_frame",
|
|
125
|
+
"last_frame",
|
|
126
|
+
"custom_frame",
|
|
127
|
+
"external",
|
|
128
|
+
"upload",
|
|
129
|
+
"apply_cuts",
|
|
130
|
+
]);
|
|
131
|
+
/** Whether a metadata origin is a process label (useless alone for naming). */
|
|
132
|
+
export function isProcessOrigin(origin) {
|
|
133
|
+
return PROCESS_ORIGINS.has(origin.trim().toLowerCase());
|
|
134
|
+
}
|
|
135
|
+
/** Build the single GPT-5.5 user message requesting a JSON map of
|
|
136
|
+
* asset_id → short descriptive label. Kept pure for unit testing.
|
|
137
|
+
* When `hasImages` is true, the message notes that preview frames follow. */
|
|
138
|
+
export function buildSuggestionPrompt(assets, opts = {}) {
|
|
139
|
+
const entries = assets.map((a) => {
|
|
140
|
+
const current = a.label ? `; current label: ${a.label}` : "";
|
|
141
|
+
const heading = `${a.asset_id} (${a.type}${current})`;
|
|
142
|
+
const snippet = a.context_text.slice(0, 700).replace(/\s+/g, " ").trim();
|
|
143
|
+
return snippet
|
|
144
|
+
? `- ${heading}: ${snippet}`
|
|
145
|
+
: `- ${heading}: (no text context — use the preview image)`;
|
|
146
|
+
});
|
|
147
|
+
const imageNote = opts.hasImages
|
|
148
|
+
? [
|
|
149
|
+
"Preview images follow this list: for each asset_id, one or more stills",
|
|
150
|
+
"(videos: first frame then last frame; images: the image itself).",
|
|
151
|
+
"Prefer what you SEE in the previews over process labels in the text.",
|
|
152
|
+
"",
|
|
153
|
+
]
|
|
154
|
+
: [];
|
|
155
|
+
return [
|
|
156
|
+
"You label video and image assets in a creative project so their display",
|
|
157
|
+
"labels describe their VISUAL content. For each asset, propose a VERY SHORT",
|
|
158
|
+
"label: prefer 1–2 content words (3 max). Think folder tags, not sentences.",
|
|
159
|
+
"",
|
|
160
|
+
"Rules:",
|
|
161
|
+
"- Name the main subject or prop only (e.g. \"gadget pitch\", \"green device\").",
|
|
162
|
+
"- Prefer concrete nouns over adjectives, settings, or abstract process words.",
|
|
163
|
+
"- Do NOT invent details absent from the context text and preview images.",
|
|
164
|
+
"- Do NOT name after pipeline/meta labels: last frame, first frame, update,",
|
|
165
|
+
" device image, locked off, fixed camera, external, upload, edit, regenerate.",
|
|
166
|
+
"- Do NOT copy typo-ridden edit prompts (\"updat ethe device...\") — describe",
|
|
167
|
+
" the resulting image instead.",
|
|
168
|
+
"- Prefer analysis/summary/visual cues over raw transcript; use spoken words",
|
|
169
|
+
" only when they name a distinctive prop or topic (e.g. a product brand).",
|
|
170
|
+
"- Make labels distinct within this batch when assets look different.",
|
|
171
|
+
"- Keep labels lowercase plain words (no punctuation, no artifact ids).",
|
|
172
|
+
"",
|
|
173
|
+
...imageNote,
|
|
174
|
+
"Return ONLY a JSON object mapping each asset_id to the proposed label.",
|
|
175
|
+
"Example: {\"0198f9a2-…\": \"gadget pitch\", \"0198f9a3-…\": \"green device\"}",
|
|
176
|
+
"",
|
|
177
|
+
"Assets:",
|
|
178
|
+
...entries,
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
/** Parse the GPT JSON response into an asset_id → label map, tolerating a
|
|
182
|
+
* leading code-fence or surrounding prose. Returns empty map on failure. */
|
|
183
|
+
export function parseSuggestionResponse(text) {
|
|
184
|
+
const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
185
|
+
const candidate = fenceMatch ? fenceMatch[1] : text;
|
|
186
|
+
const objStart = candidate.indexOf("{");
|
|
187
|
+
const objEnd = candidate.lastIndexOf("}");
|
|
188
|
+
if (objStart === -1 || objEnd === -1 || objEnd <= objStart)
|
|
189
|
+
return {};
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(candidate.slice(objStart, objEnd + 1));
|
|
192
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
193
|
+
return {};
|
|
194
|
+
const out = {};
|
|
195
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
196
|
+
if (typeof v === "string")
|
|
197
|
+
out[k] = v;
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createOnsetAccumulator } from "./onset.js";
|
|
3
|
+
import { trackBeats } from "./track.js";
|
|
4
|
+
import { RHYTHM_SAMPLE_RATE, } from "./contract.js";
|
|
5
|
+
const YIELD_EVERY_HOPS = 96;
|
|
6
|
+
async function yieldToEventLoop() {
|
|
7
|
+
await new Promise((resolve) => {
|
|
8
|
+
setImmediate(resolve);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function startEventLoopProbe(intervalMs = 20) {
|
|
12
|
+
let maxLag = 0;
|
|
13
|
+
let previous = Date.now();
|
|
14
|
+
const timer = setInterval(() => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
maxLag = Math.max(maxLag, now - previous - intervalMs);
|
|
17
|
+
previous = now;
|
|
18
|
+
}, intervalMs);
|
|
19
|
+
return {
|
|
20
|
+
stop() {
|
|
21
|
+
clearInterval(timer);
|
|
22
|
+
return Math.max(0, maxLag, Date.now() - previous - intervalMs);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function analyzePcmStream(chunks, options = {}) {
|
|
27
|
+
const sampleRate = options.sampleRate ?? RHYTHM_SAMPLE_RATE;
|
|
28
|
+
const accumulator = createOnsetAccumulator(sampleRate);
|
|
29
|
+
const probe = startEventLoopProbe();
|
|
30
|
+
let peakRss = process.memoryUsage().rss;
|
|
31
|
+
let yieldedAt = 0;
|
|
32
|
+
let samples = 0;
|
|
33
|
+
try {
|
|
34
|
+
for await (const chunk of chunks) {
|
|
35
|
+
options.signal?.throwIfAborted();
|
|
36
|
+
accumulator.push(chunk);
|
|
37
|
+
samples += chunk.length;
|
|
38
|
+
if (samples > sampleRate * 60 * 60)
|
|
39
|
+
throw new Error("Music analysis supports up to 60 minutes");
|
|
40
|
+
peakRss = Math.max(peakRss, process.memoryUsage().rss);
|
|
41
|
+
const hops = accumulator.hopCount();
|
|
42
|
+
if (hops - yieldedAt >= YIELD_EVERY_HOPS) {
|
|
43
|
+
yieldedAt = hops;
|
|
44
|
+
await yieldToEventLoop();
|
|
45
|
+
peakRss = Math.max(peakRss, process.memoryUsage().rss);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
options.signal?.throwIfAborted();
|
|
49
|
+
const analysis = trackBeats(accumulator.finish());
|
|
50
|
+
analysis.resources = resources(Math.max(peakRss, process.memoryUsage().rss), probe.stop(), accumulator.retainedSamples(), samples / sampleRate);
|
|
51
|
+
return analysis;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
probe.stop();
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function analyzePcm(samples, sampleRate = RHYTHM_SAMPLE_RATE, signal) {
|
|
59
|
+
async function* once() {
|
|
60
|
+
const slice = Math.max(sampleRate, 1);
|
|
61
|
+
for (let offset = 0; offset < samples.length; offset += slice) {
|
|
62
|
+
yield samples.subarray(offset, Math.min(samples.length, offset + slice));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return analyzePcmStream(once(), { sampleRate, signal });
|
|
66
|
+
}
|
|
67
|
+
async function* decodeAudioToPcm(ffmpegPath, inputPath, signal, streamIndex) {
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const child = spawn(ffmpegPath, [
|
|
70
|
+
"-v", "error",
|
|
71
|
+
"-i", inputPath,
|
|
72
|
+
"-map", streamIndex === undefined ? "0:a:0" : `0:${streamIndex}`, "-vn", "-ac", "1",
|
|
73
|
+
"-ar", String(RHYTHM_SAMPLE_RATE),
|
|
74
|
+
"-f", "f32le",
|
|
75
|
+
"pipe:1",
|
|
76
|
+
], { stdio: ["ignore", "pipe", "pipe"] });
|
|
77
|
+
const stderr = [];
|
|
78
|
+
let errorBytes = 0;
|
|
79
|
+
child.stderr?.on("data", (chunk) => {
|
|
80
|
+
if (errorBytes < 8192)
|
|
81
|
+
stderr.push(chunk.subarray(0, 8192 - errorBytes));
|
|
82
|
+
errorBytes += chunk.length;
|
|
83
|
+
});
|
|
84
|
+
const closed = new Promise((resolve, reject) => {
|
|
85
|
+
child.once("error", reject);
|
|
86
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
87
|
+
});
|
|
88
|
+
void closed.catch(() => undefined);
|
|
89
|
+
let pending = Buffer.alloc(0);
|
|
90
|
+
const abort = () => {
|
|
91
|
+
child.kill("SIGTERM");
|
|
92
|
+
};
|
|
93
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
94
|
+
let drained = false;
|
|
95
|
+
try {
|
|
96
|
+
for await (const chunk of child.stdout ?? []) {
|
|
97
|
+
if (signal?.aborted)
|
|
98
|
+
break;
|
|
99
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
100
|
+
const combined = pending.length === 0 ? bytes : Buffer.concat([pending, bytes]);
|
|
101
|
+
const sampleCount = Math.floor(combined.length / 4);
|
|
102
|
+
const pcm = new Float32Array(sampleCount);
|
|
103
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
104
|
+
pcm[index] = combined.readFloatLE(index * 4);
|
|
105
|
+
}
|
|
106
|
+
pending = Buffer.from(combined.subarray(sampleCount * 4));
|
|
107
|
+
if (pcm.length > 0)
|
|
108
|
+
yield pcm;
|
|
109
|
+
}
|
|
110
|
+
drained = true;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
signal?.removeEventListener("abort", abort);
|
|
114
|
+
if (!drained && child.exitCode === null)
|
|
115
|
+
child.kill("SIGTERM");
|
|
116
|
+
}
|
|
117
|
+
const exitCode = await closed;
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
if (exitCode !== 0) {
|
|
120
|
+
throw new Error(`ffmpeg decode failed (${exitCode}): ${Buffer.concat(stderr).toString("utf8")}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export async function analyzeAudioFile(ffmpegPath, inputPath, signal, streamIndex) {
|
|
124
|
+
return analyzePcmStream(decodeAudioToPcm(ffmpegPath, inputPath, signal, streamIndex), { signal });
|
|
125
|
+
}
|
|
126
|
+
function resources(peakRssBytes, maxEventLoopLagMs, pcmRetainedSamples, durationSeconds) {
|
|
127
|
+
return { peakRssBytes, maxEventLoopLagMs, pcmRetainedSamples, durationSeconds };
|
|
128
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const RHYTHM_ANALYZER_ID = "videobook-spectral-flux-dp";
|
|
2
|
+
export const RHYTHM_ANALYZER_VERSION = "4";
|
|
3
|
+
export const RHYTHM_SCHEMA_VERSION = 1;
|
|
4
|
+
export const RHYTHM_SAMPLE_RATE = 22_050;
|
|
5
|
+
export const RHYTHM_WINDOW_SAMPLES = 1_024;
|
|
6
|
+
export const RHYTHM_HOP_SAMPLES = 256;
|
|
7
|
+
export const RHYTHM_LATENCY_SAMPLES = RHYTHM_WINDOW_SAMPLES / 2;
|
|
8
|
+
export const RHYTHM_MAX_RETAINED_SAMPLES = RHYTHM_WINDOW_SAMPLES + RHYTHM_HOP_SAMPLES;
|
|
9
|
+
export const ENERGY_ALGORITHM_ID = "videobook-rms-energy";
|
|
10
|
+
export const ENERGY_ALGORITHM_VERSION = "1";
|
|
11
|
+
export const SECTION_DETECTOR_ID = "videobook-energy-sections";
|
|
12
|
+
export const SECTION_DETECTOR_VERSION = "1";
|
|
13
|
+
export const SECTION_LABELER_ID = "videobook-section-labeler";
|
|
14
|
+
export const SECTION_LABELER_VERSION = "1";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const musicDraftOptionsSchema = z.object({
|
|
3
|
+
track_id: z.string().uuid(),
|
|
4
|
+
start_frame: z.number().int().nonnegative(),
|
|
5
|
+
end_frame: z.number().int().positive(),
|
|
6
|
+
sources: z.array(z.object({ artifact_id: z.string().uuid(), start_seconds: z.number().nonnegative().optional(), end_seconds: z.number().positive().optional() })).min(1).max(100),
|
|
7
|
+
interval: z.union([z.literal(1), z.literal(2), z.literal(4), z.literal(8)]),
|
|
8
|
+
unit: z.enum(["beats", "bars"]),
|
|
9
|
+
minimum_shot_frames: z.number().int().positive(),
|
|
10
|
+
preserve_existing: z.boolean().default(true),
|
|
11
|
+
preserve_clip_ids: z.array(z.string().uuid()).default([]),
|
|
12
|
+
reuse_sources: z.boolean().default(false),
|
|
13
|
+
allow_retiming: z.boolean().default(false),
|
|
14
|
+
strategy: z.enum(["beat_interval", "follow_song_energy"]).default("beat_interval"),
|
|
15
|
+
analysis: z.object({
|
|
16
|
+
music_analysis_id: z.string().min(1),
|
|
17
|
+
moments: z.array(z.object({ artifact_id: z.string().uuid(), moments_id: z.string().min(1) })).max(24).optional(),
|
|
18
|
+
}).optional(),
|
|
19
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ENERGY_ALGORITHM_ID, ENERGY_ALGORITHM_VERSION, SECTION_DETECTOR_ID, SECTION_DETECTOR_VERSION, } from "./contract.js";
|
|
2
|
+
const ENERGY_BIN_SECONDS = 0.25;
|
|
3
|
+
const SECTION_SMOOTH_SECONDS = 2;
|
|
4
|
+
const SECTION_RADIUS_SECONDS = 1.5;
|
|
5
|
+
const SECTION_MIN_GAP_SECONDS = 8;
|
|
6
|
+
const SECTION_MIN_NOVELTY = 0.12;
|
|
7
|
+
const TIER_SPREAD = 0.15;
|
|
8
|
+
const round = (value) => Math.round(value * 10000) / 10000;
|
|
9
|
+
export function energyCurve(envelope) {
|
|
10
|
+
const frameSeconds = envelope.hopSamples / envelope.sampleRate;
|
|
11
|
+
const binFrames = Math.max(1, Math.round(ENERGY_BIN_SECONDS / frameSeconds));
|
|
12
|
+
const raw = [];
|
|
13
|
+
let peak = 0;
|
|
14
|
+
for (let start = 0; start < envelope.frameCount; start += binFrames) {
|
|
15
|
+
const end = Math.min(envelope.frameCount, start + binFrames);
|
|
16
|
+
let sum = 0;
|
|
17
|
+
for (let index = start; index < end; index += 1)
|
|
18
|
+
sum += envelope.rms[index] ?? 0;
|
|
19
|
+
const mean = sum / (end - start);
|
|
20
|
+
raw.push(mean);
|
|
21
|
+
if (mean > peak)
|
|
22
|
+
peak = mean;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
algorithmId: ENERGY_ALGORITHM_ID,
|
|
26
|
+
algorithmVersion: ENERGY_ALGORITHM_VERSION,
|
|
27
|
+
hopSeconds: round(binFrames * frameSeconds),
|
|
28
|
+
values: raw.map((value) => round(peak > 0 ? value / peak : 0)),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function candidateSections(energy, durationSeconds) {
|
|
32
|
+
const binSeconds = energy.hopSeconds;
|
|
33
|
+
const smoothWidth = Math.max(1, 2 * Math.round(SECTION_SMOOTH_SECONDS / binSeconds / 2) + 1);
|
|
34
|
+
const radius = Math.max(1, Math.round(SECTION_RADIUS_SECONDS / binSeconds));
|
|
35
|
+
const smooth = movingAverage(energy.values, smoothWidth);
|
|
36
|
+
const novelty = new Array(energy.values.length).fill(0);
|
|
37
|
+
for (let index = radius; index < energy.values.length - radius; index += 1) {
|
|
38
|
+
novelty[index] = Math.abs(smooth[index + radius] - smooth[index - radius]);
|
|
39
|
+
}
|
|
40
|
+
const threshold = Math.max(SECTION_MIN_NOVELTY, 0.6 * quantile(novelty, 0.9));
|
|
41
|
+
const peaks = [];
|
|
42
|
+
for (let index = 1; index < novelty.length - 1; index += 1) {
|
|
43
|
+
const value = novelty[index];
|
|
44
|
+
if (value < threshold)
|
|
45
|
+
continue;
|
|
46
|
+
if (value < novelty[index - 1] || value < novelty[index + 1])
|
|
47
|
+
continue;
|
|
48
|
+
const timeSeconds = index * binSeconds;
|
|
49
|
+
if (timeSeconds < SECTION_RADIUS_SECONDS || timeSeconds > durationSeconds - SECTION_RADIUS_SECONDS)
|
|
50
|
+
continue;
|
|
51
|
+
peaks.push({ timeSeconds, novelty: value });
|
|
52
|
+
}
|
|
53
|
+
peaks.sort((left, right) => right.novelty - left.novelty || left.timeSeconds - right.timeSeconds);
|
|
54
|
+
const kept = [];
|
|
55
|
+
for (const peak of peaks) {
|
|
56
|
+
if (kept.every((entry) => Math.abs(entry.timeSeconds - peak.timeSeconds) >= SECTION_MIN_GAP_SECONDS)) {
|
|
57
|
+
kept.push(peak);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
kept.sort((left, right) => left.timeSeconds - right.timeSeconds);
|
|
61
|
+
return {
|
|
62
|
+
algorithmId: SECTION_DETECTOR_ID,
|
|
63
|
+
algorithmVersion: SECTION_DETECTOR_VERSION,
|
|
64
|
+
candidates: kept.map((entry, index) => ({
|
|
65
|
+
id: `section-${index + 1}`,
|
|
66
|
+
timeSeconds: round(entry.timeSeconds),
|
|
67
|
+
quality: round(Math.min(1, entry.novelty / (2 * threshold))),
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function sampleEnergy(energy, timeSeconds) {
|
|
72
|
+
if (!energy.values.length)
|
|
73
|
+
return 0;
|
|
74
|
+
const index = Math.min(energy.values.length - 1, Math.max(0, Math.floor(timeSeconds / energy.hopSeconds)));
|
|
75
|
+
return energy.values[index] ?? 0;
|
|
76
|
+
}
|
|
77
|
+
export function energySegments(energy, sections, durationSeconds) {
|
|
78
|
+
if (!(durationSeconds > 0))
|
|
79
|
+
return [];
|
|
80
|
+
const bounds = [0, ...(sections?.candidates ?? []).map((candidate) => candidate.timeSeconds), durationSeconds]
|
|
81
|
+
.filter((value, index, all) => value >= 0 && value <= durationSeconds && (index === 0 || value > all[index - 1]));
|
|
82
|
+
if (bounds.length < 2)
|
|
83
|
+
return [];
|
|
84
|
+
const segments = bounds.slice(0, -1).map((start, index) => {
|
|
85
|
+
const end = bounds[index + 1];
|
|
86
|
+
return { startSeconds: start, endSeconds: end, meanEnergy: meanEnergy(energy, start, end), tier: "mid" };
|
|
87
|
+
});
|
|
88
|
+
const baseline = median(segments.map((segment) => segment.meanEnergy));
|
|
89
|
+
if (baseline > 0) {
|
|
90
|
+
for (const segment of segments) {
|
|
91
|
+
segment.tier = segment.meanEnergy >= baseline * (1 + TIER_SPREAD) ? "high"
|
|
92
|
+
: segment.meanEnergy <= baseline * (1 - TIER_SPREAD) ? "low" : "mid";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return segments;
|
|
96
|
+
}
|
|
97
|
+
function meanEnergy(energy, startSeconds, endSeconds) {
|
|
98
|
+
const first = Math.max(0, Math.floor(startSeconds / energy.hopSeconds));
|
|
99
|
+
const last = Math.min(energy.values.length, Math.ceil(endSeconds / energy.hopSeconds));
|
|
100
|
+
if (last <= first)
|
|
101
|
+
return sampleEnergy(energy, (startSeconds + endSeconds) / 2);
|
|
102
|
+
let sum = 0;
|
|
103
|
+
for (let index = first; index < last; index += 1)
|
|
104
|
+
sum += energy.values[index] ?? 0;
|
|
105
|
+
return round(sum / (last - first));
|
|
106
|
+
}
|
|
107
|
+
function movingAverage(values, width) {
|
|
108
|
+
const half = Math.floor(width / 2);
|
|
109
|
+
const result = new Array(values.length).fill(0);
|
|
110
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
111
|
+
const start = Math.max(0, index - half);
|
|
112
|
+
const end = Math.min(values.length, index + half + 1);
|
|
113
|
+
let sum = 0;
|
|
114
|
+
for (let cursor = start; cursor < end; cursor += 1)
|
|
115
|
+
sum += values[cursor] ?? 0;
|
|
116
|
+
result[index] = sum / (end - start);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
function quantile(values, share) {
|
|
121
|
+
if (!values.length)
|
|
122
|
+
return 0;
|
|
123
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
124
|
+
const position = Math.min(sorted.length - 1, Math.max(0, Math.floor(share * sorted.length)));
|
|
125
|
+
return sorted[position] ?? 0;
|
|
126
|
+
}
|
|
127
|
+
function median(values) {
|
|
128
|
+
if (!values.length)
|
|
129
|
+
return 0;
|
|
130
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
131
|
+
const middle = Math.floor(sorted.length / 2);
|
|
132
|
+
return sorted.length % 2 === 0 ? ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2 : sorted[middle] ?? 0;
|
|
133
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function fftInPlace(real, imag) {
|
|
2
|
+
const n = real.length;
|
|
3
|
+
if (n < 2 || imag.length !== n || (n & (n - 1)) !== 0) {
|
|
4
|
+
throw new Error("FFT length must be a power of two");
|
|
5
|
+
}
|
|
6
|
+
for (let i = 1, j = 0; i < n; i += 1) {
|
|
7
|
+
let bit = n >> 1;
|
|
8
|
+
for (; (j & bit) !== 0; bit >>= 1)
|
|
9
|
+
j ^= bit;
|
|
10
|
+
j ^= bit;
|
|
11
|
+
if (i < j) {
|
|
12
|
+
const realSwap = real[i];
|
|
13
|
+
real[i] = real[j];
|
|
14
|
+
real[j] = realSwap;
|
|
15
|
+
const imagSwap = imag[i];
|
|
16
|
+
imag[i] = imag[j];
|
|
17
|
+
imag[j] = imagSwap;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (let length = 2; length <= n; length <<= 1) {
|
|
21
|
+
const angle = (-2 * Math.PI) / length;
|
|
22
|
+
const stepReal = Math.cos(angle);
|
|
23
|
+
const stepImag = Math.sin(angle);
|
|
24
|
+
for (let start = 0; start < n; start += length) {
|
|
25
|
+
let twiddleReal = 1;
|
|
26
|
+
let twiddleImag = 0;
|
|
27
|
+
const half = length / 2;
|
|
28
|
+
for (let offset = 0; offset < half; offset += 1) {
|
|
29
|
+
const evenReal = real[start + offset];
|
|
30
|
+
const evenImag = imag[start + offset];
|
|
31
|
+
const oddIndex = start + offset + half;
|
|
32
|
+
const oddReal = real[oddIndex] * twiddleReal - imag[oddIndex] * twiddleImag;
|
|
33
|
+
const oddImag = real[oddIndex] * twiddleImag + imag[oddIndex] * twiddleReal;
|
|
34
|
+
real[start + offset] = evenReal + oddReal;
|
|
35
|
+
imag[start + offset] = evenImag + oddImag;
|
|
36
|
+
real[oddIndex] = evenReal - oddReal;
|
|
37
|
+
imag[oddIndex] = evenImag - oddImag;
|
|
38
|
+
const nextReal = twiddleReal * stepReal - twiddleImag * stepImag;
|
|
39
|
+
twiddleImag = twiddleReal * stepImag + twiddleImag * stepReal;
|
|
40
|
+
twiddleReal = nextReal;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const beatSchema = z.object({ timeSeconds: z.number().finite().nonnegative(), confidence: z.number().min(0).max(1).nullable() });
|
|
3
|
+
const musicEnergySchema = z.object({
|
|
4
|
+
algorithmId: z.string().min(1),
|
|
5
|
+
algorithmVersion: z.string().min(1),
|
|
6
|
+
hopSeconds: z.number().finite().positive(),
|
|
7
|
+
values: z.array(z.number().finite().min(0).max(1)).max(20000),
|
|
8
|
+
});
|
|
9
|
+
const sectionCandidateSchema = z.object({
|
|
10
|
+
id: z.string().min(1).max(100),
|
|
11
|
+
timeSeconds: z.number().finite().nonnegative().max(3600),
|
|
12
|
+
quality: z.number().min(0).max(1),
|
|
13
|
+
});
|
|
14
|
+
const sectionDetectionSchema = z.object({
|
|
15
|
+
algorithmId: z.string().min(1),
|
|
16
|
+
algorithmVersion: z.string().min(1),
|
|
17
|
+
candidates: z.array(sectionCandidateSchema).max(200),
|
|
18
|
+
});
|
|
19
|
+
export const musicAnalysisSchema = z.object({
|
|
20
|
+
analysisId: z.string(),
|
|
21
|
+
artifactId: z.string().uuid(),
|
|
22
|
+
objectHash: z.string(),
|
|
23
|
+
streamId: z.string().uuid(),
|
|
24
|
+
durationSeconds: z.number().finite().positive().max(3600),
|
|
25
|
+
analyzerVersion: z.string(),
|
|
26
|
+
beats: z.array(beatSchema).max(24000),
|
|
27
|
+
bpm: z.number().finite().positive().nullable(),
|
|
28
|
+
confidence: z.number().min(0).max(1).nullable(),
|
|
29
|
+
bars: z.array(beatSchema).max(24000).nullable(),
|
|
30
|
+
beatsPerBar: z.number().int().min(1).max(16).nullable(),
|
|
31
|
+
barSource: z.enum(["detected", "estimated"]).nullable().default(null),
|
|
32
|
+
waveform: z.array(z.number().finite().min(0).max(1)).max(65536),
|
|
33
|
+
lowConfidence: z.boolean(),
|
|
34
|
+
energy: musicEnergySchema.nullable().default(null),
|
|
35
|
+
sections: sectionDetectionSchema.nullable().default(null),
|
|
36
|
+
});
|
|
37
|
+
const SECTION_LABEL_VALUES = ["intro", "verse", "chorus", "drop", "break", "bridge", "outro"];
|
|
38
|
+
export const sectionLabelsSchema = z.object({
|
|
39
|
+
schemaVersion: z.literal(1),
|
|
40
|
+
analysisId: z.string(),
|
|
41
|
+
artifactId: z.string().uuid(),
|
|
42
|
+
objectHash: z.string(),
|
|
43
|
+
streamId: z.string().uuid(),
|
|
44
|
+
labeler: z.object({ id: z.string(), version: z.string(), model: z.string() }),
|
|
45
|
+
boundaries: z.object({ algorithmId: z.string(), algorithmVersion: z.string() }),
|
|
46
|
+
status: z.enum(["ready", "unavailable"]),
|
|
47
|
+
reason: z.string().nullable().default(null),
|
|
48
|
+
createdAt: z.number().finite().positive(),
|
|
49
|
+
labels: z.array(z.object({
|
|
50
|
+
candidateId: z.string().min(1).max(100),
|
|
51
|
+
label: z.enum(SECTION_LABEL_VALUES),
|
|
52
|
+
confidence: z.number().min(0).max(1),
|
|
53
|
+
rationale: z.string().max(500),
|
|
54
|
+
})).max(200).default([]),
|
|
55
|
+
});
|
|
56
|
+
export const rhythmMarkerSchema = z.object({
|
|
57
|
+
id: z.string().min(1).max(100),
|
|
58
|
+
timeSeconds: z.number().finite().nonnegative().max(3600),
|
|
59
|
+
bar: z.boolean(),
|
|
60
|
+
manual: z.boolean(),
|
|
61
|
+
});
|
|
62
|
+
export const rhythmSettingsSchema = z.object({
|
|
63
|
+
masterClipId: z.string().uuid().nullable(),
|
|
64
|
+
correction: z.object({
|
|
65
|
+
objectHash: z.string(),
|
|
66
|
+
analysisId: z.string().nullable(),
|
|
67
|
+
markers: z.array(rhythmMarkerSchema).max(24000),
|
|
68
|
+
bpm: z.number().finite().min(20).max(400).nullable(),
|
|
69
|
+
beatsPerBar: z.number().int().min(1).max(16).nullable(),
|
|
70
|
+
}).nullable(),
|
|
71
|
+
});
|