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,182 @@
|
|
|
1
|
+
import { LANDSCAPE_HEIGHT, LANDSCAPE_WIDTH, PORTRAIT_HEIGHT, PORTRAIT_WIDTH, SQUARE_HEIGHT, SQUARE_WIDTH, getHighlightFontSize, getImpactFontSize, getYPosition, } from "./constants.js";
|
|
2
|
+
export const KEYWORD_PLACEMENT_VERSION = 1;
|
|
3
|
+
export const KEYWORD_FRAME_CACHE_FILENAME = ".keyword-frames.jpg";
|
|
4
|
+
export const KEYWORD_PLACEMENT_METADATA_KEY = "keyword-graphics.placement";
|
|
5
|
+
const PLACEMENT_SCALES = [1, 0.85, 0.7];
|
|
6
|
+
const ANCHORS = [
|
|
7
|
+
"upper-left",
|
|
8
|
+
"upper-right",
|
|
9
|
+
"middle-left",
|
|
10
|
+
"middle-right",
|
|
11
|
+
];
|
|
12
|
+
const OCCUPIED_MARGIN = 0.04;
|
|
13
|
+
export function keywordCueId(graphic) {
|
|
14
|
+
const frameTime = graphic.frameTime ?? graphic.start;
|
|
15
|
+
return `${graphic.text.toLocaleLowerCase()}@${frameTime.toFixed(3)}`;
|
|
16
|
+
}
|
|
17
|
+
export function orientationDimensions(orientation) {
|
|
18
|
+
if (orientation === "portrait")
|
|
19
|
+
return { width: PORTRAIT_WIDTH, height: PORTRAIT_HEIGHT };
|
|
20
|
+
if (orientation === "square")
|
|
21
|
+
return { width: SQUARE_WIDTH, height: SQUARE_HEIGHT };
|
|
22
|
+
return { width: LANDSCAPE_WIDTH, height: LANDSCAPE_HEIGHT };
|
|
23
|
+
}
|
|
24
|
+
export function keywordFontSize(text, width, height, scale = 1) {
|
|
25
|
+
const portrait = height > width;
|
|
26
|
+
const square = height === width;
|
|
27
|
+
const base = width * (portrait ? 0.095 : square ? 0.078 : 0.055);
|
|
28
|
+
const length = Array.from(text).length;
|
|
29
|
+
const lengthScale = length > 16 ? 0.64 : length > 11 ? 0.78 : 1;
|
|
30
|
+
return Math.round(base * lengthScale * scale);
|
|
31
|
+
}
|
|
32
|
+
function keywordAnchorSide(anchor) {
|
|
33
|
+
return anchor.endsWith("right") ? "right" : "left";
|
|
34
|
+
}
|
|
35
|
+
export function keywordLayout(text, anchor, width, height, scale = 1) {
|
|
36
|
+
const portrait = height > width;
|
|
37
|
+
const top = height * (anchor.startsWith("upper") ? 0.18 : 0.38);
|
|
38
|
+
const sideInset = width * (portrait ? 0.065 : 0.07);
|
|
39
|
+
const containerWidth = width * (portrait ? 0.72 : 0.43);
|
|
40
|
+
const fontSize = keywordFontSize(text, width, height, scale);
|
|
41
|
+
const textWidth = Math.min(containerWidth, Array.from(text).length * fontSize * 0.68);
|
|
42
|
+
const underlineWidth = Math.min(width * 0.14, fontSize * 2.25);
|
|
43
|
+
const rectWidth = Math.max(textWidth, underlineWidth);
|
|
44
|
+
const rectHeight = fontSize * 1.22 + Math.max(2, width * 0.002);
|
|
45
|
+
const side = keywordAnchorSide(anchor);
|
|
46
|
+
const x = side === "left" ? sideInset : width - sideInset - rectWidth;
|
|
47
|
+
return {
|
|
48
|
+
top,
|
|
49
|
+
sideInset,
|
|
50
|
+
containerWidth,
|
|
51
|
+
fontSize,
|
|
52
|
+
side,
|
|
53
|
+
rect: {
|
|
54
|
+
x: x / width,
|
|
55
|
+
y: top / height,
|
|
56
|
+
width: rectWidth / width,
|
|
57
|
+
height: rectHeight / height,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function clampRect(rect) {
|
|
62
|
+
const x = Math.max(0, Math.min(1, rect.x));
|
|
63
|
+
const y = Math.max(0, Math.min(1, rect.y));
|
|
64
|
+
return {
|
|
65
|
+
x,
|
|
66
|
+
y,
|
|
67
|
+
width: Math.max(0, Math.min(1 - x, rect.width)),
|
|
68
|
+
height: Math.max(0, Math.min(1 - y, rect.height)),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function expandRect(rect, margin) {
|
|
72
|
+
return clampRect({
|
|
73
|
+
x: rect.x - margin,
|
|
74
|
+
y: rect.y - margin,
|
|
75
|
+
width: rect.width + margin * 2,
|
|
76
|
+
height: rect.height + margin * 2,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function intersects(a, b) {
|
|
80
|
+
return a.x < b.x + b.width
|
|
81
|
+
&& a.x + a.width > b.x
|
|
82
|
+
&& a.y < b.y + b.height
|
|
83
|
+
&& a.y + a.height > b.y;
|
|
84
|
+
}
|
|
85
|
+
function rectGap(a, b) {
|
|
86
|
+
const dx = Math.max(a.x - (b.x + b.width), b.x - (a.x + a.width), 0);
|
|
87
|
+
const dy = Math.max(a.y - (b.y + b.height), b.y - (a.y + a.height), 0);
|
|
88
|
+
return Math.hypot(dx, dy);
|
|
89
|
+
}
|
|
90
|
+
export function intersectionArea(a, b) {
|
|
91
|
+
const width = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
92
|
+
const height = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
93
|
+
return width > 0 && height > 0 ? width * height : 0;
|
|
94
|
+
}
|
|
95
|
+
export function mapSourceRectToTarget(rect, sourceWidth, sourceHeight, targetWidth, targetHeight) {
|
|
96
|
+
const sourceAspect = sourceWidth / sourceHeight;
|
|
97
|
+
const targetAspect = targetWidth / targetHeight;
|
|
98
|
+
let foregroundWidth;
|
|
99
|
+
let foregroundHeight;
|
|
100
|
+
if (sourceAspect > targetAspect) {
|
|
101
|
+
foregroundWidth = targetWidth;
|
|
102
|
+
foregroundHeight = targetWidth / sourceAspect;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
foregroundHeight = targetHeight;
|
|
106
|
+
foregroundWidth = targetHeight * sourceAspect;
|
|
107
|
+
}
|
|
108
|
+
const offsetX = (targetWidth - foregroundWidth) / 2;
|
|
109
|
+
const offsetY = (targetHeight - foregroundHeight) / 2;
|
|
110
|
+
return clampRect({
|
|
111
|
+
x: (offsetX + rect.x * foregroundWidth) / targetWidth,
|
|
112
|
+
y: (offsetY + rect.y * foregroundHeight) / targetHeight,
|
|
113
|
+
width: rect.width * foregroundWidth / targetWidth,
|
|
114
|
+
height: rect.height * foregroundHeight / targetHeight,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function subtitleBand(orientation, targetHeight, position, style, fontSize) {
|
|
118
|
+
const y = getYPosition(position, orientation);
|
|
119
|
+
const fontPx = style !== "highlight"
|
|
120
|
+
? getImpactFontSize(fontSize, orientation)
|
|
121
|
+
: getHighlightFontSize(fontSize, orientation);
|
|
122
|
+
const height = Math.max(targetHeight * 0.1, fontPx * 1.9) / targetHeight;
|
|
123
|
+
const top = position === "top"
|
|
124
|
+
? y - 0.025
|
|
125
|
+
: position === "bottom"
|
|
126
|
+
? y - height + 0.025
|
|
127
|
+
: y - height / 2;
|
|
128
|
+
return clampRect({ x: 0.02, y: top, width: 0.96, height });
|
|
129
|
+
}
|
|
130
|
+
function preferredAnchorOrder(index) {
|
|
131
|
+
const preferRight = index % 2 === 0;
|
|
132
|
+
return preferRight
|
|
133
|
+
? ["upper-right", "middle-right", "upper-left", "middle-left"]
|
|
134
|
+
: ["upper-left", "middle-left", "upper-right", "middle-right"];
|
|
135
|
+
}
|
|
136
|
+
function choosePlacement(graphic, index, targetWidth, targetHeight, occupied) {
|
|
137
|
+
const order = preferredAnchorOrder(index);
|
|
138
|
+
for (const scale of PLACEMENT_SCALES) {
|
|
139
|
+
const safe = ANCHORS.flatMap((anchor) => {
|
|
140
|
+
const rect = keywordLayout(graphic.text, anchor, targetWidth, targetHeight, scale).rect;
|
|
141
|
+
if (occupied.some((region) => intersects(rect, region)))
|
|
142
|
+
return [];
|
|
143
|
+
const distance = occupied.length === 0
|
|
144
|
+
? 1
|
|
145
|
+
: Math.min(...occupied.map((region) => rectGap(rect, region)));
|
|
146
|
+
const preference = (order.length - order.indexOf(anchor)) * 0.0001;
|
|
147
|
+
return [{ anchor, scale, score: distance + preference }];
|
|
148
|
+
});
|
|
149
|
+
safe.sort((a, b) => b.score - a.score);
|
|
150
|
+
if (safe[0])
|
|
151
|
+
return { anchor: safe[0].anchor, scale: safe[0].scale };
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
export function resolveKeywordPlacements(graphics, analysis, orientation, settings) {
|
|
156
|
+
const { width, height } = orientationDimensions(orientation);
|
|
157
|
+
const cues = new Map(analysis.cues.map((cue) => [cue.cueId, cue]));
|
|
158
|
+
const placed = [];
|
|
159
|
+
const skipped = [];
|
|
160
|
+
for (let index = 0; index < graphics.length; index++) {
|
|
161
|
+
const graphic = graphics[index];
|
|
162
|
+
const cue = cues.get(keywordCueId(graphic));
|
|
163
|
+
if (!cue)
|
|
164
|
+
throw new Error(`Missing placement analysis for keyword ${graphic.text}`);
|
|
165
|
+
const occupied = cue.regions.map((region) => expandRect(mapSourceRectToTarget(region, analysis.sourceWidth, analysis.sourceHeight, width, height), OCCUPIED_MARGIN));
|
|
166
|
+
if (settings.subtitlesEnabled) {
|
|
167
|
+
occupied.push(expandRect(subtitleBand(orientation, height, settings.subtitlePosition, settings.subtitleStyle, settings.subtitleFontSize), OCCUPIED_MARGIN));
|
|
168
|
+
}
|
|
169
|
+
const decision = choosePlacement(graphic, index, width, height, occupied);
|
|
170
|
+
if (!decision) {
|
|
171
|
+
skipped.push(graphic.text);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
placed.push({
|
|
175
|
+
...graphic,
|
|
176
|
+
anchor: decision.anchor,
|
|
177
|
+
scale: decision.scale,
|
|
178
|
+
side: keywordAnchorSide(decision.anchor),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return { graphics: placed, skipped };
|
|
182
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { expandRect, intersectionArea, orientationDimensions, } from "./keyword-placement.js";
|
|
2
|
+
// Max edge of the graphic as a fraction of the video width. Sized for a
|
|
3
|
+
// visible on-screen graphic (TV-style logo bug): `medium` ≈ 14% of width,
|
|
4
|
+
// `small` half that, `large` 1.5×. The previous 5%/2.5%/7.5% presets rendered
|
|
5
|
+
// a near-invisible corner thumbnail that users read as "OSG didn't apply".
|
|
6
|
+
const SIZE_FACTOR = {
|
|
7
|
+
small: 0.07,
|
|
8
|
+
medium: 0.14,
|
|
9
|
+
large: 0.21,
|
|
10
|
+
};
|
|
11
|
+
// The OSG always eases in/out over this window so it never pops abruptly.
|
|
12
|
+
export const FADE_SECONDS = 0.4;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the visibility window [startFrame, endFrame). Precedence: an explicit
|
|
15
|
+
* absolute window (startSeconds/endSeconds) wins; else a duration-relative tail;
|
|
16
|
+
* else the full clip.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveOsgWindow(opts) {
|
|
19
|
+
const { startSeconds, endSeconds, tailSeconds, durationInFrames, fps } = opts;
|
|
20
|
+
const hasAbsoluteWindow = startSeconds != null || endSeconds != null;
|
|
21
|
+
let startFrame;
|
|
22
|
+
let endFrame;
|
|
23
|
+
if (hasAbsoluteWindow) {
|
|
24
|
+
startFrame = startSeconds != null ? Math.round(startSeconds * fps) : 0;
|
|
25
|
+
endFrame = endSeconds != null ? Math.round(endSeconds * fps) : durationInFrames;
|
|
26
|
+
}
|
|
27
|
+
else if (tailSeconds != null) {
|
|
28
|
+
startFrame = Math.max(0, durationInFrames - Math.round(tailSeconds * fps));
|
|
29
|
+
endFrame = durationInFrames;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
startFrame = 0;
|
|
33
|
+
endFrame = durationInFrames;
|
|
34
|
+
}
|
|
35
|
+
startFrame = Math.min(Math.max(0, startFrame), durationInFrames);
|
|
36
|
+
endFrame = Math.min(Math.max(startFrame, endFrame), durationInFrames);
|
|
37
|
+
return { startFrame, endFrame, hasExplicitEnd: endFrame < durationInFrames };
|
|
38
|
+
}
|
|
39
|
+
function ramp(frame, x0, x1, y0, y1) {
|
|
40
|
+
if (frame <= x0)
|
|
41
|
+
return y0;
|
|
42
|
+
if (frame >= x1)
|
|
43
|
+
return y1;
|
|
44
|
+
return y0 + ((frame - x0) / (x1 - x0)) * (y1 - y0);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Opacity envelope for a frame within a window: eases in over FADE_SECONDS and
|
|
48
|
+
* symmetrically eases out toward the window end — including the implicit
|
|
49
|
+
* end-of-clip, so a whole-video watermark fades at both edges. Frames outside
|
|
50
|
+
* [startFrame, endFrame) are hard-gated to 0 so a collapsed fade on a
|
|
51
|
+
* sub-second span can't leave the graphic showing past its window.
|
|
52
|
+
*/
|
|
53
|
+
export function osgFadeEnvelope(frame, window, fps) {
|
|
54
|
+
const { startFrame, endFrame } = window;
|
|
55
|
+
if (frame < startFrame || frame >= endFrame)
|
|
56
|
+
return 0;
|
|
57
|
+
const span = Math.max(1, endFrame - startFrame);
|
|
58
|
+
const maxFade = Math.max(1, Math.round(FADE_SECONDS * fps));
|
|
59
|
+
const fade = Math.max(1, Math.min(maxFade, Math.floor(span / 2)));
|
|
60
|
+
const inEnd = startFrame + fade;
|
|
61
|
+
const outStart = endFrame - fade;
|
|
62
|
+
if (outStart > inEnd) {
|
|
63
|
+
if (frame >= outStart)
|
|
64
|
+
return ramp(frame, outStart, endFrame, 1, 0);
|
|
65
|
+
return ramp(frame, startFrame, inEnd, 0, 1);
|
|
66
|
+
}
|
|
67
|
+
return ramp(frame, startFrame, inEnd, 0, 1);
|
|
68
|
+
}
|
|
69
|
+
export function osgMaxSize(width, size, scale) {
|
|
70
|
+
return Math.round(width * SIZE_FACTOR[size] * scale);
|
|
71
|
+
}
|
|
72
|
+
export function osgPadding(width) {
|
|
73
|
+
return Math.round(width * 0.02);
|
|
74
|
+
}
|
|
75
|
+
export function osgAnchorPlacement(position, padding) {
|
|
76
|
+
const [vertical, horizontal] = position.split("-");
|
|
77
|
+
const placement = { transforms: [] };
|
|
78
|
+
if (vertical === "top")
|
|
79
|
+
placement.top = padding;
|
|
80
|
+
else if (vertical === "bottom")
|
|
81
|
+
placement.bottom = padding;
|
|
82
|
+
else {
|
|
83
|
+
placement.top = "50%";
|
|
84
|
+
placement.transforms.push("translateY(-50%)");
|
|
85
|
+
}
|
|
86
|
+
if (horizontal === "left")
|
|
87
|
+
placement.left = padding;
|
|
88
|
+
else if (horizontal === "right")
|
|
89
|
+
placement.right = padding;
|
|
90
|
+
else {
|
|
91
|
+
placement.left = "50%";
|
|
92
|
+
placement.transforms.push("translateX(-50%)");
|
|
93
|
+
}
|
|
94
|
+
return placement;
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Floating watermark: a faint QR + @handle badge shown for the whole clip.
|
|
98
|
+
// Sized off the canvas's min dimension (via a per-orientation osgScale) so the
|
|
99
|
+
// badge is the same physical size on landscape, portrait, and square renders.
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Badge height as a fraction of min(width, height) — ≈173px on a 1080 min dim,
|
|
102
|
+
// keeping the QR (~2/3 of badge height) scannable while staying unobtrusive.
|
|
103
|
+
export const WATERMARK_MIN_DIM_FRACTION = 0.16;
|
|
104
|
+
export const WATERMARK_OSG_SIZE = "medium";
|
|
105
|
+
export const WATERMARK_OPACITY = 0.5;
|
|
106
|
+
// Candidate anchors in tie-break preference order.
|
|
107
|
+
export const WATERMARK_CORNERS = [
|
|
108
|
+
"bottom-right",
|
|
109
|
+
"bottom-left",
|
|
110
|
+
"top-right",
|
|
111
|
+
"top-left",
|
|
112
|
+
];
|
|
113
|
+
const WATERMARK_OCCUPIED_MARGIN = 0.04;
|
|
114
|
+
const REGION_KIND_WEIGHT = {
|
|
115
|
+
face: 3,
|
|
116
|
+
text: 2,
|
|
117
|
+
graphic: 1,
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* OsgOverlay sizes off canvas width; translate the min-dim target into the
|
|
121
|
+
* equivalent per-orientation osgScale for the `medium` preset, clamped to the
|
|
122
|
+
* set_video_overlay schema range.
|
|
123
|
+
*/
|
|
124
|
+
export function watermarkOsgScale(orientation, userScale = 1) {
|
|
125
|
+
const { width, height } = orientationDimensions(orientation);
|
|
126
|
+
const minDim = Math.min(width, height);
|
|
127
|
+
const raw = (WATERMARK_MIN_DIM_FRACTION / SIZE_FACTOR[WATERMARK_OSG_SIZE]) * (minDim / width) * userScale;
|
|
128
|
+
return Math.min(2, Math.max(0.1, Number(raw.toFixed(4))));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* On-canvas rect of the contain-fitted badge at a given anchor, normalized to
|
|
132
|
+
* an arbitrary canvas (a render orientation or the source frame itself, for
|
|
133
|
+
* burned-in watermarks). `aspect` is the badge's width/height (< 1: the badge
|
|
134
|
+
* is taller than wide, so height binds at maxSize).
|
|
135
|
+
*/
|
|
136
|
+
function watermarkRectForCanvas(width, height, corner, aspect, userScale = 1) {
|
|
137
|
+
const maxSize = Math.round(Math.min(width, height) * WATERMARK_MIN_DIM_FRACTION * userScale);
|
|
138
|
+
const badgeHeight = aspect < 1 ? maxSize : maxSize / aspect;
|
|
139
|
+
const badgeWidth = badgeHeight * aspect;
|
|
140
|
+
const padding = osgPadding(width);
|
|
141
|
+
const [vertical, horizontal] = corner.split("-");
|
|
142
|
+
const x = horizontal === "left"
|
|
143
|
+
? padding
|
|
144
|
+
: horizontal === "right"
|
|
145
|
+
? width - padding - badgeWidth
|
|
146
|
+
: (width - badgeWidth) / 2;
|
|
147
|
+
const y = vertical === "top"
|
|
148
|
+
? padding
|
|
149
|
+
: vertical === "bottom"
|
|
150
|
+
? height - padding - badgeHeight
|
|
151
|
+
: (height - badgeHeight) / 2;
|
|
152
|
+
return {
|
|
153
|
+
x: x / width,
|
|
154
|
+
y: y / height,
|
|
155
|
+
width: badgeWidth / width,
|
|
156
|
+
height: badgeHeight / height,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function watermarkRectForCorner(orientation, corner, aspect, userScale = 1) {
|
|
160
|
+
const { width, height } = orientationDimensions(orientation);
|
|
161
|
+
return watermarkRectForCanvas(width, height, corner, aspect, userScale);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Pick the least-occupied corner for the watermark. Score per corner is the
|
|
165
|
+
* summed overlap area with each occupied region (expanded by a margin),
|
|
166
|
+
* weighted by region kind and confidence; the subtitle band counts as a
|
|
167
|
+
* full-confidence text region. Ties resolve by WATERMARK_CORNERS order.
|
|
168
|
+
*/
|
|
169
|
+
export function scoreWatermarkCornersForCanvas(width, height, occupied, opts) {
|
|
170
|
+
const { aspect, userScale = 1, subtitleBand = null } = opts;
|
|
171
|
+
const scores = {};
|
|
172
|
+
let best = WATERMARK_CORNERS[0];
|
|
173
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
174
|
+
for (const corner of WATERMARK_CORNERS) {
|
|
175
|
+
const rect = watermarkRectForCanvas(width, height, corner, aspect, userScale);
|
|
176
|
+
let score = 0;
|
|
177
|
+
for (const region of occupied) {
|
|
178
|
+
const expanded = expandRect(region, WATERMARK_OCCUPIED_MARGIN);
|
|
179
|
+
score += intersectionArea(rect, expanded) * REGION_KIND_WEIGHT[region.kind] * region.confidence;
|
|
180
|
+
}
|
|
181
|
+
if (subtitleBand) {
|
|
182
|
+
const expanded = expandRect(subtitleBand, WATERMARK_OCCUPIED_MARGIN);
|
|
183
|
+
score += intersectionArea(rect, expanded) * REGION_KIND_WEIGHT.text;
|
|
184
|
+
}
|
|
185
|
+
scores[corner] = score;
|
|
186
|
+
// Epsilon so float noise between mathematically-equal corner areas can't
|
|
187
|
+
// steal a tie from the preference order.
|
|
188
|
+
if (score < bestScore - 1e-9) {
|
|
189
|
+
bestScore = score;
|
|
190
|
+
best = corner;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { position: best, scores };
|
|
194
|
+
}
|
|
195
|
+
export function scoreWatermarkCorners(orientation, occupied, opts) {
|
|
196
|
+
const { width, height } = orientationDimensions(orientation);
|
|
197
|
+
return scoreWatermarkCornersForCanvas(width, height, occupied, opts);
|
|
198
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_QR_OUTRO_SOUND_FILENAME = "qr-outro-impact.wav";
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const FPS = 30;
|
|
2
|
+
const IMPACT_INTERVAL_S = 0.4;
|
|
3
|
+
const IMPACT_INTERVAL_FRAMES = Math.round(IMPACT_INTERVAL_S * FPS);
|
|
4
|
+
const FIRST_IMPACT_S = 0.4;
|
|
5
|
+
const FIRST_IMPACT_FRAME = Math.round(FIRST_IMPACT_S * FPS);
|
|
6
|
+
const MIN_DURATION_S = 3.0;
|
|
7
|
+
const TAIL_AFTER_LAST_IMPACT_S = 1.6;
|
|
8
|
+
const ENTRY_FRAMES = IMPACT_INTERVAL_FRAMES;
|
|
9
|
+
const ENTRY_OVERSCAN_FRACTION = 0.25;
|
|
10
|
+
const MAX_FINAL_CUE_S = 0.6;
|
|
11
|
+
const BACKGROUND_COLOR = "#0A0A0A";
|
|
12
|
+
const LAYOUT_PAD = 24;
|
|
13
|
+
const CARD_GAP = 16;
|
|
14
|
+
export { FPS, IMPACT_INTERVAL_FRAMES, FIRST_IMPACT_FRAME, ENTRY_FRAMES, MAX_FINAL_CUE_S, BACKGROUND_COLOR, LAYOUT_PAD, };
|
|
15
|
+
export function qrLayout(ratio) {
|
|
16
|
+
switch (ratio) {
|
|
17
|
+
case "16:9": {
|
|
18
|
+
const w = 1920;
|
|
19
|
+
const h = 1080;
|
|
20
|
+
return {
|
|
21
|
+
width: w,
|
|
22
|
+
height: h,
|
|
23
|
+
contentRect: { x: 0, y: 0, w, h },
|
|
24
|
+
entryEdge: "right",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
case "9:16": {
|
|
28
|
+
const w = 1080;
|
|
29
|
+
const h = 1920;
|
|
30
|
+
return {
|
|
31
|
+
width: w,
|
|
32
|
+
height: h,
|
|
33
|
+
contentRect: { x: 0, y: 0, w, h },
|
|
34
|
+
entryEdge: "bottom",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
case "1:1": {
|
|
38
|
+
const w = 1080;
|
|
39
|
+
const h = 1080;
|
|
40
|
+
return {
|
|
41
|
+
width: w,
|
|
42
|
+
height: h,
|
|
43
|
+
contentRect: { x: 0, y: 0, w, h },
|
|
44
|
+
entryEdge: "right",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function orientationForAspectRatio(ratio) {
|
|
50
|
+
if (ratio === "9:16")
|
|
51
|
+
return "portrait";
|
|
52
|
+
if (ratio === "1:1")
|
|
53
|
+
return "square";
|
|
54
|
+
return "landscape";
|
|
55
|
+
}
|
|
56
|
+
export function cardPlacements(qrCount, contentRect) {
|
|
57
|
+
if (qrCount < 1 || qrCount > 4) {
|
|
58
|
+
throw new Error(`qrCount must be 1-4, got ${qrCount}`);
|
|
59
|
+
}
|
|
60
|
+
const insetX = contentRect.x + LAYOUT_PAD;
|
|
61
|
+
const insetY = contentRect.y + LAYOUT_PAD;
|
|
62
|
+
const availW = contentRect.w - LAYOUT_PAD * 2;
|
|
63
|
+
const availH = contentRect.h - LAYOUT_PAD * 2;
|
|
64
|
+
if (qrCount === 1) {
|
|
65
|
+
const side = Math.min(availW, availH);
|
|
66
|
+
return [{
|
|
67
|
+
x: insetX + Math.floor((availW - side) / 2),
|
|
68
|
+
y: insetY + Math.floor((availH - side) / 2),
|
|
69
|
+
w: side,
|
|
70
|
+
h: side,
|
|
71
|
+
index: 0,
|
|
72
|
+
}];
|
|
73
|
+
}
|
|
74
|
+
if (qrCount === 2) {
|
|
75
|
+
if (availW >= availH) {
|
|
76
|
+
const cardW = Math.floor((availW - CARD_GAP) / 2);
|
|
77
|
+
const side = Math.min(cardW, availH);
|
|
78
|
+
const cy = insetY + Math.floor((availH - side) / 2);
|
|
79
|
+
return [
|
|
80
|
+
{ x: insetX + Math.floor((cardW - side) / 2), y: cy, w: side, h: side, index: 0 },
|
|
81
|
+
{ x: insetX + cardW + CARD_GAP + Math.floor((cardW - side) / 2), y: cy, w: side, h: side, index: 1 },
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
const cardH = Math.floor((availH - CARD_GAP) / 2);
|
|
85
|
+
const side = Math.min(availW, cardH);
|
|
86
|
+
const cx = insetX + Math.floor((availW - side) / 2);
|
|
87
|
+
return [
|
|
88
|
+
{ x: cx, y: insetY + Math.floor((cardH - side) / 2), w: side, h: side, index: 0 },
|
|
89
|
+
{ x: cx, y: insetY + cardH + CARD_GAP + Math.floor((cardH - side) / 2), w: side, h: side, index: 1 },
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
const cols = 2;
|
|
93
|
+
const rows = 2;
|
|
94
|
+
const cellW = Math.floor((availW - CARD_GAP * (cols - 1)) / cols);
|
|
95
|
+
const cellH = Math.floor((availH - CARD_GAP * (rows - 1)) / rows);
|
|
96
|
+
const side = Math.min(cellW, cellH);
|
|
97
|
+
const placements = [];
|
|
98
|
+
for (let i = 0; i < qrCount; i++) {
|
|
99
|
+
const col = i % cols;
|
|
100
|
+
const row = Math.floor(i / cols);
|
|
101
|
+
const cellX = insetX + col * (cellW + CARD_GAP);
|
|
102
|
+
const cellY = insetY + row * (cellH + CARD_GAP);
|
|
103
|
+
placements.push({
|
|
104
|
+
x: cellX + Math.floor((cellW - side) / 2),
|
|
105
|
+
y: cellY + Math.floor((cellH - side) / 2),
|
|
106
|
+
w: side,
|
|
107
|
+
h: side,
|
|
108
|
+
index: i,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return placements;
|
|
112
|
+
}
|
|
113
|
+
export function cardEntryOffset(placement, layout) {
|
|
114
|
+
const driftDirection = placement.index % 2 === 0 ? -1 : 1;
|
|
115
|
+
const overscan = Math.ceil(Math.max(placement.w, placement.h) * ENTRY_OVERSCAN_FRACTION);
|
|
116
|
+
if (layout.entryEdge === "right") {
|
|
117
|
+
return {
|
|
118
|
+
x: layout.width - placement.x + overscan,
|
|
119
|
+
y: driftDirection * placement.h * 0.12,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
x: driftDirection * placement.w * 0.12,
|
|
124
|
+
y: layout.height - placement.y + overscan,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function impactFrames(qrCount) {
|
|
128
|
+
const impacts = [];
|
|
129
|
+
for (let i = 0; i < qrCount; i++) {
|
|
130
|
+
impacts.push({
|
|
131
|
+
frame: FIRST_IMPACT_FRAME + i * IMPACT_INTERVAL_FRAMES,
|
|
132
|
+
cardIndex: i,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return impacts;
|
|
136
|
+
}
|
|
137
|
+
export function totalDuration(qrCount, requestedSeconds) {
|
|
138
|
+
const lastImpactS = FIRST_IMPACT_S + (qrCount - 1) * IMPACT_INTERVAL_S;
|
|
139
|
+
const minimumDurationS = Math.max(MIN_DURATION_S, lastImpactS + TAIL_AFTER_LAST_IMPACT_S);
|
|
140
|
+
const durationS = Math.max(minimumDurationS, requestedSeconds ?? 0);
|
|
141
|
+
const frames = Math.round(durationS * FPS);
|
|
142
|
+
return { frames, seconds: frames / FPS };
|
|
143
|
+
}
|
|
144
|
+
export function buildOutroSpec(ratio, qrCount, requestedSeconds) {
|
|
145
|
+
const layout = qrLayout(ratio);
|
|
146
|
+
const cards = cardPlacements(qrCount, layout.contentRect);
|
|
147
|
+
const impacts = impactFrames(qrCount);
|
|
148
|
+
const dur = totalDuration(qrCount, requestedSeconds);
|
|
149
|
+
return {
|
|
150
|
+
layout,
|
|
151
|
+
cards,
|
|
152
|
+
impacts,
|
|
153
|
+
totalDurationFrames: dur.frames,
|
|
154
|
+
totalDurationSeconds: dur.seconds,
|
|
155
|
+
fps: FPS,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function cardAnimationProgress(currentFrame, impactFrame) {
|
|
159
|
+
const entryStart = impactFrame - ENTRY_FRAMES;
|
|
160
|
+
if (currentFrame < entryStart) {
|
|
161
|
+
return {
|
|
162
|
+
visible: false,
|
|
163
|
+
opacity: 0,
|
|
164
|
+
translateFraction: 1,
|
|
165
|
+
rotation: 8,
|
|
166
|
+
scale: 1.2,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (currentFrame < impactFrame) {
|
|
170
|
+
const elapsed = currentFrame - entryStart;
|
|
171
|
+
const t = elapsed / ENTRY_FRAMES;
|
|
172
|
+
const eased = 1 - Math.pow(1 - t, 2);
|
|
173
|
+
return {
|
|
174
|
+
visible: true,
|
|
175
|
+
opacity: eased,
|
|
176
|
+
translateFraction: 1 - eased,
|
|
177
|
+
rotation: (1 - eased) * 8,
|
|
178
|
+
scale: 1.2 - eased * 0.26,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const settleElapsed = currentFrame - impactFrame;
|
|
182
|
+
const settleDuration = 6;
|
|
183
|
+
if (settleElapsed < settleDuration) {
|
|
184
|
+
const t = settleElapsed / settleDuration;
|
|
185
|
+
const spring = 0.94 + 0.06 * t + 0.045 * Math.sin(t * Math.PI * 2) * (1 - t);
|
|
186
|
+
return {
|
|
187
|
+
visible: true,
|
|
188
|
+
opacity: 1,
|
|
189
|
+
translateFraction: 0,
|
|
190
|
+
rotation: 0,
|
|
191
|
+
scale: spring,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { visible: true, opacity: 1, translateFraction: 0, rotation: 0, scale: 1 };
|
|
195
|
+
}
|
|
196
|
+
export function cueWindows(qrCount, totalFrames) {
|
|
197
|
+
const impacts = impactFrames(qrCount);
|
|
198
|
+
const windows = [];
|
|
199
|
+
for (let i = 0; i < impacts.length; i++) {
|
|
200
|
+
const start = impacts[i].frame;
|
|
201
|
+
let end;
|
|
202
|
+
if (i < impacts.length - 1) {
|
|
203
|
+
end = impacts[i + 1].frame;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
end = Math.min(start + Math.round(MAX_FINAL_CUE_S * FPS), totalFrames);
|
|
207
|
+
}
|
|
208
|
+
windows.push({ startFrame: start, endFrame: end, impactIndex: i });
|
|
209
|
+
}
|
|
210
|
+
return windows;
|
|
211
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { bundle } from "@remotion/bundler";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, extname, join } from "node:path";
|
|
4
|
+
const MODULE_PATH = fileURLToPath(import.meta.url);
|
|
5
|
+
const REMOTION_DIR = dirname(MODULE_PATH);
|
|
6
|
+
const REMOTION_ENTRY = join(REMOTION_DIR, extname(MODULE_PATH) === ".js" ? "index.js" : "index.tsx");
|
|
7
|
+
const REMOTION_PUBLIC_DIR = join(REMOTION_DIR, "public");
|
|
8
|
+
let bundleLocation = null;
|
|
9
|
+
let bundlePromise = null;
|
|
10
|
+
export async function getSharedBundleLocation() {
|
|
11
|
+
if (bundleLocation)
|
|
12
|
+
return bundleLocation;
|
|
13
|
+
if (bundlePromise)
|
|
14
|
+
return bundlePromise;
|
|
15
|
+
bundlePromise = bundle({
|
|
16
|
+
entryPoint: REMOTION_ENTRY,
|
|
17
|
+
publicDir: REMOTION_PUBLIC_DIR,
|
|
18
|
+
webpackOverride: (config) => ({
|
|
19
|
+
...config,
|
|
20
|
+
resolve: {
|
|
21
|
+
...config.resolve,
|
|
22
|
+
extensions: [".tsx", ".ts", ".js", ".jsx", ".mjs", ".cjs", ".json"],
|
|
23
|
+
extensionAlias: {
|
|
24
|
+
".js": [".tsx", ".ts", ".js"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
}).then((location) => {
|
|
29
|
+
bundleLocation = location;
|
|
30
|
+
bundlePromise = null;
|
|
31
|
+
return location;
|
|
32
|
+
}).catch((err) => {
|
|
33
|
+
bundlePromise = null;
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
return bundlePromise;
|
|
37
|
+
}
|