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,168 @@
|
|
|
1
|
+
import { INTELLIGENCE_MODEL, buildIntelligenceResponses, firstResponseText, intelligenceRequest, } from "../openai-intelligence.js";
|
|
2
|
+
import { getApiKey } from "../api-key.js";
|
|
3
|
+
import { assessToolCallEstimate, logUnavailableCostEstimate } from "../jobs/cost-estimate-assessment.js";
|
|
4
|
+
import { energySegments } from "./energy.js";
|
|
5
|
+
import { sectionLabelsSchema, } from "./model.js";
|
|
6
|
+
import { cachedMusicAnalysis, musicSource, readSectionLabels, sectionLabelsCurrent } from "./music-analysis.js";
|
|
7
|
+
import { SECTION_DETECTOR_ID, SECTION_DETECTOR_VERSION, SECTION_LABELER_ID, SECTION_LABELER_VERSION } from "./contract.js";
|
|
8
|
+
const LABEL_PROMPT = [
|
|
9
|
+
"You label pre-detected section boundaries in a song for a video editor.",
|
|
10
|
+
"Each candidate boundary was detected from the song's energy curve; its id, position and detection quality are given.",
|
|
11
|
+
"Assign exactly one label per candidate id, choosing from: intro, verse, chorus, drop, break, bridge, outro.",
|
|
12
|
+
"You cannot move, add, or remove boundaries: label only the listed candidate ids.",
|
|
13
|
+
"Labels are editorial suggestions, not facts. Judge from the energy arc around each boundary, not from lyrics you imagine.",
|
|
14
|
+
'Answer with JSON {"labels": [{candidate_id, label, confidence, rationale}]} covering every candidate id exactly once.',
|
|
15
|
+
"confidence is 0-1. rationale is one short sentence grounded in the reported energy data.",
|
|
16
|
+
].join(" ");
|
|
17
|
+
const LABELS_JSON_SCHEMA = {
|
|
18
|
+
type: "object",
|
|
19
|
+
required: ["labels"],
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: {
|
|
22
|
+
labels: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: {
|
|
25
|
+
type: "object",
|
|
26
|
+
required: ["candidate_id", "label", "confidence", "rationale"],
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
properties: {
|
|
29
|
+
candidate_id: { type: "string" },
|
|
30
|
+
label: { type: "string", enum: ["intro", "verse", "chorus", "drop", "break", "bridge", "outro"] },
|
|
31
|
+
confidence: { type: "number" },
|
|
32
|
+
rationale: { type: "string" },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export async function labelMusicSections(engine, artifactId, analysisId, streamId, signal) {
|
|
39
|
+
const source = await musicSource(engine, artifactId, streamId);
|
|
40
|
+
if (source.analysisId !== analysisId)
|
|
41
|
+
throw new Error("The audio source changed; analyze it again");
|
|
42
|
+
const analysis = await cachedMusicAnalysis(engine, source);
|
|
43
|
+
if (!analysis || analysis.analysisId !== analysisId)
|
|
44
|
+
throw new Error("Run analyze_music before requesting section labels");
|
|
45
|
+
if (!analysis.energy || !analysis.sections)
|
|
46
|
+
throw new Error("The analysis has no energy curve; analyze the current audio again");
|
|
47
|
+
const stored = await readSectionLabels(engine, artifactId, analysisId);
|
|
48
|
+
if (stored && stored.status === "ready" && sectionLabelsCurrent(stored, analysis))
|
|
49
|
+
return stored;
|
|
50
|
+
const next = await buildLabels(engine, analysis, signal);
|
|
51
|
+
const saved = await engine.metadata.artifacts.write(artifactId, `music.sections.${analysisId}`, next);
|
|
52
|
+
if (!saved.ok)
|
|
53
|
+
throw new Error(saved.error.message);
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
async function buildLabels(engine, analysis, signal) {
|
|
57
|
+
const header = {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
analysisId: analysis.analysisId,
|
|
60
|
+
artifactId: analysis.artifactId,
|
|
61
|
+
objectHash: analysis.objectHash,
|
|
62
|
+
streamId: analysis.streamId,
|
|
63
|
+
labeler: { id: SECTION_LABELER_ID, version: SECTION_LABELER_VERSION, model: "" },
|
|
64
|
+
boundaries: {
|
|
65
|
+
algorithmId: analysis.sections?.algorithmId ?? SECTION_DETECTOR_ID,
|
|
66
|
+
algorithmVersion: analysis.sections?.algorithmVersion ?? SECTION_DETECTOR_VERSION,
|
|
67
|
+
},
|
|
68
|
+
createdAt: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
const apiKey = getApiKey("OPENAI_API_KEY");
|
|
71
|
+
if (!apiKey) {
|
|
72
|
+
return sectionLabelsSchema.parse({
|
|
73
|
+
...header,
|
|
74
|
+
status: "unavailable",
|
|
75
|
+
reason: "OPENAI_API_KEY is not set; section labels stay optional",
|
|
76
|
+
labels: [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
await adviseCostEstimate(engine, analysis);
|
|
81
|
+
const response = await requestLabels(apiKey, analysis, signal);
|
|
82
|
+
return sectionLabelsSchema.parse({
|
|
83
|
+
...header,
|
|
84
|
+
labeler: { id: SECTION_LABELER_ID, version: SECTION_LABELER_VERSION, model: INTELLIGENCE_MODEL },
|
|
85
|
+
status: "ready",
|
|
86
|
+
reason: null,
|
|
87
|
+
labels: response,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function requestLabels(apiKey, analysis, signal) {
|
|
91
|
+
if (!analysis.energy || !analysis.sections)
|
|
92
|
+
throw new Error("The analysis has no energy curve");
|
|
93
|
+
const segments = energySegments(analysis.energy, analysis.sections, analysis.durationSeconds);
|
|
94
|
+
const tierFor = (timeSeconds) => segments.find((segment) => timeSeconds >= segment.startSeconds && timeSeconds < segment.endSeconds);
|
|
95
|
+
const lines = analysis.sections.candidates.map((candidate) => {
|
|
96
|
+
const segment = tierFor(candidate.timeSeconds);
|
|
97
|
+
const mean = segment ? `segment ${segment.startSeconds.toFixed(1)}-${segment.endSeconds.toFixed(1)}s energy ${segment.tier} (mean ${segment.meanEnergy.toFixed(2)})` : "segment energy unknown";
|
|
98
|
+
return `${candidate.id}: ${candidate.timeSeconds.toFixed(1)}s, detection quality ${candidate.quality.toFixed(2)}, ${mean}`;
|
|
99
|
+
});
|
|
100
|
+
const content = [{
|
|
101
|
+
type: "input_text",
|
|
102
|
+
text: [
|
|
103
|
+
`Song duration ${analysis.durationSeconds.toFixed(1)}s${analysis.bpm ? `, tempo about ${analysis.bpm.toFixed(0)} BPM` : ""}.`,
|
|
104
|
+
`Detected section boundary candidates:`,
|
|
105
|
+
...lines,
|
|
106
|
+
].join("\n"),
|
|
107
|
+
}];
|
|
108
|
+
const responses = buildIntelligenceResponses(apiKey, { timeout: 120_000 });
|
|
109
|
+
const response = await responses(intelligenceRequest({
|
|
110
|
+
instructions: LABEL_PROMPT,
|
|
111
|
+
input: [{ role: "user", content }],
|
|
112
|
+
text: { format: { type: "json_schema", name: "section_labels", schema: LABELS_JSON_SCHEMA, strict: true } },
|
|
113
|
+
max_output_tokens: 2048,
|
|
114
|
+
}), signal);
|
|
115
|
+
const wanted = new Map(analysis.sections.candidates.map((candidate, index) => [candidate.id, index]));
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const labels = [];
|
|
118
|
+
for (const entry of parseLabelResponse(firstResponseText(response))) {
|
|
119
|
+
if (typeof entry !== "object" || entry === null)
|
|
120
|
+
continue;
|
|
121
|
+
const row = entry;
|
|
122
|
+
const id = typeof row.candidate_id === "string" ? row.candidate_id : "";
|
|
123
|
+
const label = row.label;
|
|
124
|
+
if (!wanted.has(id) || seen.has(id))
|
|
125
|
+
continue;
|
|
126
|
+
if (typeof label !== "string" || !["intro", "verse", "chorus", "drop", "break", "bridge", "outro"].includes(label))
|
|
127
|
+
continue;
|
|
128
|
+
seen.add(id);
|
|
129
|
+
labels.push({
|
|
130
|
+
candidateId: id,
|
|
131
|
+
label: label,
|
|
132
|
+
confidence: Math.min(1, Math.max(0, typeof row.confidence === "number" && Number.isFinite(row.confidence) ? row.confidence : 0)),
|
|
133
|
+
rationale: typeof row.rationale === "string" ? row.rationale.slice(0, 500) : "",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (labels.length !== wanted.size)
|
|
137
|
+
throw new Error("The section labeler did not cover every boundary; retry labeling");
|
|
138
|
+
return labels;
|
|
139
|
+
}
|
|
140
|
+
function parseLabelResponse(text) {
|
|
141
|
+
const parsed = JSON.parse(text);
|
|
142
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
143
|
+
return [];
|
|
144
|
+
const labels = parsed.labels;
|
|
145
|
+
return Array.isArray(labels) ? labels : [];
|
|
146
|
+
}
|
|
147
|
+
async function adviseCostEstimate(engine, analysis) {
|
|
148
|
+
try {
|
|
149
|
+
const assessment = await assessToolCallEstimate(engine, "analyze_video", { asset_id: analysis.artifactId });
|
|
150
|
+
if (!assessment.available) {
|
|
151
|
+
await logUnavailableCostEstimate({
|
|
152
|
+
assessment,
|
|
153
|
+
engine,
|
|
154
|
+
correlation: {
|
|
155
|
+
bookId: engine.book.get().bookId,
|
|
156
|
+
artifactId: analysis.artifactId,
|
|
157
|
+
tool: "label_music_sections",
|
|
158
|
+
provider: "openai",
|
|
159
|
+
model: INTELLIGENCE_MODEL,
|
|
160
|
+
stage: "label-music-sections",
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { withNotebookMutation } from "../notebook-mutations.js";
|
|
4
|
+
import { rhythmSettingsSchema } from "./model.js";
|
|
5
|
+
import { soundtrackClip } from "./timing.js";
|
|
6
|
+
import { musicSource } from "./music-analysis.js";
|
|
7
|
+
const documentSchema = z.object({
|
|
8
|
+
revision: z.string(), settings: rhythmSettingsSchema,
|
|
9
|
+
past: z.array(rhythmSettingsSchema).max(20), future: z.array(rhythmSettingsSchema).max(20),
|
|
10
|
+
});
|
|
11
|
+
async function rhythmDocument(engine, sequenceId) {
|
|
12
|
+
if (!engine.sequences.get(sequenceId).ok)
|
|
13
|
+
throw new Error("The sequence no longer exists");
|
|
14
|
+
const saved = await engine.metadata.book.read(`music.rhythm.${sequenceId}`);
|
|
15
|
+
if (saved.ok)
|
|
16
|
+
return documentSchema.parse(saved.value);
|
|
17
|
+
return { revision: "none", settings: { masterClipId: null, correction: null }, past: [], future: [] };
|
|
18
|
+
}
|
|
19
|
+
function state(document) {
|
|
20
|
+
return { revision: document.revision, settings: document.settings, canUndo: document.past.length > 0, canRedo: document.future.length > 0 };
|
|
21
|
+
}
|
|
22
|
+
export async function readSequenceRhythm(engine, sequenceId) {
|
|
23
|
+
return state(await rhythmDocument(engine, sequenceId));
|
|
24
|
+
}
|
|
25
|
+
export async function updateSequenceRhythm(engine, sequenceId, expectedRevision, update) {
|
|
26
|
+
return withNotebookMutation(`rhythm:${engine.book.get().bookId}:${sequenceId}`, async () => {
|
|
27
|
+
const current = await rhythmDocument(engine, sequenceId);
|
|
28
|
+
if (current.revision !== expectedRevision)
|
|
29
|
+
throw new Error("The beat map changed. Reload it before editing.");
|
|
30
|
+
const sequence = engine.sequences.get(sequenceId);
|
|
31
|
+
if (!sequence.ok)
|
|
32
|
+
throw new Error(sequence.error.message);
|
|
33
|
+
const settings = update === "undo" ? current.past.at(-1) : update === "redo" ? current.future.at(-1) : rhythmSettingsSchema.parse(update);
|
|
34
|
+
if (!settings)
|
|
35
|
+
throw new Error(`Nothing to ${update}`);
|
|
36
|
+
const master = soundtrackClip(sequence.value, settings);
|
|
37
|
+
if (settings.masterClipId && !master)
|
|
38
|
+
throw new Error("The selected soundtrack clip is no longer available");
|
|
39
|
+
if (settings.correction) {
|
|
40
|
+
if (!master || settings.correction.objectHash !== master.source.range.objectHash)
|
|
41
|
+
throw new Error("The soundtrack changed. Reset its beat map before editing.");
|
|
42
|
+
const source = await musicSource(engine, master.source.artifactId, master.source.range.streamId);
|
|
43
|
+
if (source.objectHash !== settings.correction.objectHash)
|
|
44
|
+
throw new Error("The audio source changed");
|
|
45
|
+
if (sequence.value.tracks.find((track) => track.trackId === master.trackId)?.locked)
|
|
46
|
+
throw new Error("Unlock the soundtrack track before correcting its beat map");
|
|
47
|
+
const streams = engine.streams.get(master.source.range.streamId);
|
|
48
|
+
if (!streams.ok || streams.value.objectHash !== settings.correction.objectHash)
|
|
49
|
+
throw new Error("The audio source changed");
|
|
50
|
+
const duration = streams.value.durationTicks * streams.value.timeBase.numerator / streams.value.timeBase.denominator;
|
|
51
|
+
if (settings.correction.markers.some((marker) => marker.timeSeconds >= duration))
|
|
52
|
+
throw new Error("Beat markers must be inside the source audio");
|
|
53
|
+
const ids = new Set(settings.correction.markers.map((marker) => marker.id));
|
|
54
|
+
if (ids.size !== settings.correction.markers.length)
|
|
55
|
+
throw new Error("Beat marker IDs must be unique");
|
|
56
|
+
settings.correction.markers.sort((left, right) => left.timeSeconds - right.timeSeconds);
|
|
57
|
+
}
|
|
58
|
+
const next = { revision: randomUUID(), settings,
|
|
59
|
+
past: update === "undo" ? current.past.slice(0, -1) : [...current.past, current.settings].slice(-20),
|
|
60
|
+
future: update === "redo" ? current.future.slice(0, -1) : update === "undo" ? [...current.future, current.settings].slice(-20) : [],
|
|
61
|
+
};
|
|
62
|
+
const saved = await engine.metadata.book.write(`music.rhythm.${sequenceId}`, next);
|
|
63
|
+
if (!saved.ok)
|
|
64
|
+
throw new Error(saved.error.message);
|
|
65
|
+
return state(next);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { RHYTHM_SAMPLE_RATE } from "./contract.js";
|
|
2
|
+
export function synthesizeClicks(options) {
|
|
3
|
+
const sampleRate = options.sampleRate ?? RHYTHM_SAMPLE_RATE;
|
|
4
|
+
const lead = options.leadSilenceSeconds ?? 0;
|
|
5
|
+
const samples = new Float32Array(Math.ceil(options.seconds * sampleRate));
|
|
6
|
+
const beatSeconds = [];
|
|
7
|
+
const interval = 60 / options.bpm;
|
|
8
|
+
for (let time = lead; time < options.seconds - 0.05; time += interval) {
|
|
9
|
+
beatSeconds.push(time);
|
|
10
|
+
addClick(samples, sampleRate, time, 1);
|
|
11
|
+
}
|
|
12
|
+
return { sampleRate, samples, beatSeconds, barSeconds: null };
|
|
13
|
+
}
|
|
14
|
+
export function synthesizePattern(options) {
|
|
15
|
+
const sampleRate = options.sampleRate ?? RHYTHM_SAMPLE_RATE;
|
|
16
|
+
const samples = new Float32Array(Math.ceil(options.seconds * sampleRate));
|
|
17
|
+
const beatSeconds = [];
|
|
18
|
+
const barSeconds = [];
|
|
19
|
+
const interval = 60 / options.bpm;
|
|
20
|
+
let index = 0;
|
|
21
|
+
for (let time = 0.1; time < options.seconds - 0.05; time += interval) {
|
|
22
|
+
beatSeconds.push(time);
|
|
23
|
+
if (index % options.beatsPerBar === 0)
|
|
24
|
+
barSeconds.push(time);
|
|
25
|
+
const accent = index % options.accentEvery === 0 ? 1 : 0.55;
|
|
26
|
+
addClick(samples, sampleRate, time, accent);
|
|
27
|
+
if (options.syncopated)
|
|
28
|
+
addClick(samples, sampleRate, time + interval * 0.5, 0.35);
|
|
29
|
+
index += 1;
|
|
30
|
+
}
|
|
31
|
+
if (options.harmonic)
|
|
32
|
+
addFormant(samples, sampleRate);
|
|
33
|
+
return {
|
|
34
|
+
sampleRate,
|
|
35
|
+
samples,
|
|
36
|
+
beatSeconds,
|
|
37
|
+
barSeconds,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function synthesizeTempoChange(options) {
|
|
41
|
+
const sampleRate = options.sampleRate ?? RHYTHM_SAMPLE_RATE;
|
|
42
|
+
const samples = new Float32Array(Math.ceil(options.seconds * sampleRate));
|
|
43
|
+
const beatSeconds = [];
|
|
44
|
+
let time = 0.1;
|
|
45
|
+
let bpm = options.firstBpm;
|
|
46
|
+
while (time < options.seconds - 0.05) {
|
|
47
|
+
if (time >= options.changeAtSeconds)
|
|
48
|
+
bpm = options.secondBpm;
|
|
49
|
+
beatSeconds.push(time);
|
|
50
|
+
addClick(samples, sampleRate, time, 1);
|
|
51
|
+
time += 60 / bpm;
|
|
52
|
+
}
|
|
53
|
+
return { sampleRate, samples, beatSeconds, barSeconds: null };
|
|
54
|
+
}
|
|
55
|
+
export function synthesizeSpeechLike(seconds, sampleRate = RHYTHM_SAMPLE_RATE) {
|
|
56
|
+
const samples = new Float32Array(Math.ceil(seconds * sampleRate));
|
|
57
|
+
let cursor = 0.05;
|
|
58
|
+
let salt = 17;
|
|
59
|
+
while (cursor < seconds - 0.1) {
|
|
60
|
+
salt = (salt * 1_103_515_245 + 12_345) % 2_147_483_648;
|
|
61
|
+
const gap = 0.07 + (salt % 430) / 1_000;
|
|
62
|
+
const length = 0.03 + (salt % 90) / 1_000;
|
|
63
|
+
addNoiseBurst(samples, sampleRate, cursor, length);
|
|
64
|
+
cursor += gap;
|
|
65
|
+
}
|
|
66
|
+
return { sampleRate, samples, beatSeconds: [], barSeconds: null };
|
|
67
|
+
}
|
|
68
|
+
export function synthesizeSilence(seconds, sampleRate = RHYTHM_SAMPLE_RATE) {
|
|
69
|
+
return {
|
|
70
|
+
sampleRate,
|
|
71
|
+
samples: new Float32Array(Math.ceil(seconds * sampleRate)),
|
|
72
|
+
beatSeconds: [],
|
|
73
|
+
barSeconds: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function addClick(samples, sampleRate, timeSeconds, gain) {
|
|
77
|
+
const start = Math.max(0, Math.floor(timeSeconds * sampleRate));
|
|
78
|
+
const length = Math.floor(0.012 * sampleRate);
|
|
79
|
+
for (let index = 0; index < length && start + index < samples.length; index += 1) {
|
|
80
|
+
const envelope = Math.exp(-index / (0.0022 * sampleRate));
|
|
81
|
+
const tone = Math.sin((2 * Math.PI * 1_400 * index) / sampleRate);
|
|
82
|
+
const noise = ((index * 17) % 11) / 11 - 0.5;
|
|
83
|
+
samples[start + index] = Math.max(-1, Math.min(1, samples[start + index] + gain * envelope * (0.7 * tone + 0.6 * noise)));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function addFormant(samples, sampleRate) {
|
|
87
|
+
for (let index = 0; index < samples.length; index += 1) {
|
|
88
|
+
const time = index / sampleRate;
|
|
89
|
+
const vowel = 0.18 * (Math.sin(2 * Math.PI * 220 * time)
|
|
90
|
+
+ 0.5 * Math.sin(2 * Math.PI * 440 * time)
|
|
91
|
+
+ 0.25 * Math.sin(2 * Math.PI * 880 * time));
|
|
92
|
+
samples[index] = Math.max(-1, Math.min(1, samples[index] * 0.75 + vowel));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function addNoiseBurst(samples, sampleRate, timeSeconds, lengthSeconds) {
|
|
96
|
+
const start = Math.floor(timeSeconds * sampleRate);
|
|
97
|
+
const length = Math.floor(lengthSeconds * sampleRate);
|
|
98
|
+
for (let index = 0; index < length && start + index < samples.length; index += 1) {
|
|
99
|
+
const envelope = Math.sin((Math.PI * index) / length);
|
|
100
|
+
const noise = Math.sin(index * 12.9898) * 43_758.5453;
|
|
101
|
+
samples[start + index] = (noise - Math.floor(noise) - 0.5) * envelope;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export function soundtrackClip(sequence, settings) {
|
|
2
|
+
const audioTracks = new Set(sequence.tracks.filter((track) => track.kind === "audio").map((track) => track.trackId));
|
|
3
|
+
const clips = sequence.clips.filter((clip) => audioTracks.has(clip.trackId) && clip.source.kind === "timed");
|
|
4
|
+
return (settings.masterClipId ? clips.find((clip) => clip.clipId === settings.masterClipId) : clips.length === 1 ? clips[0] : null) ?? null;
|
|
5
|
+
}
|
|
6
|
+
export function effectiveMarkers(clip, settings, analysis) {
|
|
7
|
+
if (!clip)
|
|
8
|
+
return [];
|
|
9
|
+
if (settings.correction)
|
|
10
|
+
return settings.correction.objectHash === clip.source.range.objectHash ? settings.correction.markers : [];
|
|
11
|
+
if (!analysis || analysis.objectHash !== clip.source.range.objectHash || analysis.streamId !== clip.source.range.streamId)
|
|
12
|
+
return [];
|
|
13
|
+
const bars = new Set(analysis.bars?.map((bar) => Math.round(bar.timeSeconds * 1000000)));
|
|
14
|
+
return analysis.beats.map((beat, index) => ({
|
|
15
|
+
id: `${analysis.analysisId.slice(0, 12)}-${index}`, timeSeconds: beat.timeSeconds,
|
|
16
|
+
bar: bars.has(Math.round(beat.timeSeconds * 1000000)), manual: false,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
export function beatPositions(beats, bpm) {
|
|
20
|
+
if (!beats.length)
|
|
21
|
+
return [];
|
|
22
|
+
const period = bpm !== null && Number.isFinite(bpm) && bpm > 0 ? 60 / bpm : null;
|
|
23
|
+
const positions = [0];
|
|
24
|
+
for (let index = 1; index < beats.length; index += 1) {
|
|
25
|
+
const gap = beats[index].timeSeconds - beats[index - 1].timeSeconds;
|
|
26
|
+
positions.push(positions[index - 1] + (period === null ? 1 : Math.max(1, Math.round(gap / period))));
|
|
27
|
+
}
|
|
28
|
+
return positions;
|
|
29
|
+
}
|
|
30
|
+
export function groupBeatBars(markers, anchorId, beatsPerBar, bpm) {
|
|
31
|
+
const ordered = [...markers].sort((left, right) => left.timeSeconds - right.timeSeconds);
|
|
32
|
+
const anchor = ordered.findIndex(marker => marker.id === anchorId);
|
|
33
|
+
if (anchor < 0 || !Number.isInteger(beatsPerBar) || beatsPerBar < 1 || beatsPerBar > 16)
|
|
34
|
+
throw new Error("Choose a first bar beat and 1–16 beats per bar");
|
|
35
|
+
const positions = beatPositions(ordered, bpm);
|
|
36
|
+
return ordered.map((marker, index) => ({
|
|
37
|
+
...marker, bar: (positions[index] - positions[anchor]) % beatsPerBar === 0, manual: true,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
export function sourceTimeAtFrame(clip, frame, fps) {
|
|
41
|
+
const range = clip.source.range;
|
|
42
|
+
const base = range.timeBase.numerator / range.timeBase.denominator;
|
|
43
|
+
const elapsed = (frame - clip.timelineStartFrame) / fps * clip.speed.numerator / clip.speed.denominator;
|
|
44
|
+
return clip.reverse ? (range.startTick + range.durationTicks) * base - elapsed : range.startTick * base + elapsed;
|
|
45
|
+
}
|
|
46
|
+
export function frameAtSourceTime(clip, seconds, fps) {
|
|
47
|
+
const range = clip.source.range;
|
|
48
|
+
const base = range.timeBase.numerator / range.timeBase.denominator;
|
|
49
|
+
const elapsed = clip.reverse ? (range.startTick + range.durationTicks) * base - seconds : seconds - range.startTick * base;
|
|
50
|
+
return Math.round(clip.timelineStartFrame + elapsed * fps * clip.speed.denominator / clip.speed.numerator);
|
|
51
|
+
}
|
|
52
|
+
export function timelineMarkers(clip, markers, sequence) {
|
|
53
|
+
if (!clip || !clip.enabled)
|
|
54
|
+
return [];
|
|
55
|
+
const fps = sequence.frameRate.numerator / sequence.frameRate.denominator;
|
|
56
|
+
const range = clip.source.range;
|
|
57
|
+
const base = range.timeBase.numerator / range.timeBase.denominator;
|
|
58
|
+
const startSeconds = range.startTick * base;
|
|
59
|
+
const endSeconds = (range.startTick + range.durationTicks) * base;
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
return markers.filter((marker) => marker.timeSeconds >= startSeconds && marker.timeSeconds < endSeconds)
|
|
62
|
+
.map((marker) => ({ ...marker, frame: frameAtSourceTime(clip, marker.timeSeconds, fps) }))
|
|
63
|
+
.filter((marker) => marker.frame >= clip.timelineStartFrame && marker.frame < clip.timelineStartFrame + clip.durationFrames)
|
|
64
|
+
.sort((left, right) => left.frame - right.frame || Number(right.bar) - Number(left.bar))
|
|
65
|
+
.filter((marker) => { if (seen.has(marker.frame))
|
|
66
|
+
return false; seen.add(marker.frame); return true; });
|
|
67
|
+
}
|
|
68
|
+
export function nearestBeat(frame, markers, mode, threshold = Infinity) {
|
|
69
|
+
if (mode === "off")
|
|
70
|
+
return Math.round(frame);
|
|
71
|
+
let best = Math.round(frame);
|
|
72
|
+
let distance = threshold;
|
|
73
|
+
for (const marker of markers) {
|
|
74
|
+
if (mode === "bars" && !marker.bar)
|
|
75
|
+
continue;
|
|
76
|
+
const delta = Math.abs(marker.frame - frame);
|
|
77
|
+
if (delta < distance || delta === distance && marker.frame < best) {
|
|
78
|
+
best = marker.frame;
|
|
79
|
+
distance = delta;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return best;
|
|
83
|
+
}
|
|
84
|
+
export function clipWaveform(clip, waveform, sourceDuration, fps, samples = 256) {
|
|
85
|
+
if (clip.source.kind !== "timed" || !sourceDuration || !waveform.length)
|
|
86
|
+
return [];
|
|
87
|
+
const timed = clip;
|
|
88
|
+
return Array.from({ length: samples }, (_, index) => {
|
|
89
|
+
const start = sourceTimeAtFrame(timed, clip.timelineStartFrame + index / samples * clip.durationFrames, fps);
|
|
90
|
+
const end = sourceTimeAtFrame(timed, clip.timelineStartFrame + (index + 1) / samples * clip.durationFrames, fps);
|
|
91
|
+
const first = Math.max(0, Math.floor(Math.min(start, end) / sourceDuration * waveform.length));
|
|
92
|
+
const last = Math.min(waveform.length, Math.max(first + 1, Math.ceil(Math.max(start, end) / sourceDuration * waveform.length)));
|
|
93
|
+
let peak = 0;
|
|
94
|
+
for (let bucket = first; bucket < last; bucket++)
|
|
95
|
+
peak = Math.max(peak, waveform[bucket] ?? 0);
|
|
96
|
+
return peak;
|
|
97
|
+
});
|
|
98
|
+
}
|