videobook 0.0.0-stage → 0.1.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 +18 -0
- package/LICENSE +21 -0
- package/README.md +133 -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 +192 -0
- package/dist/cli/doctor-command.js +255 -0
- package/dist/cli/entry.js +2 -0
- package/dist/cli/main.js +91 -0
- package/dist/cli/mcp-command.js +82 -0
- package/dist/cli/mcp-relay.js +51 -0
- package/dist/cli/open-browser.js +14 -0
- package/dist/cli/run-command.js +187 -0
- package/dist/cli/serve-command.js +57 -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 +446 -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-secrets.js +184 -0
- package/dist/ffmpeg-binary.js +12 -0
- package/dist/ffmpeg.js +425 -0
- package/dist/ffprobe-binary.js +55 -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/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 +457 -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 +426 -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 +9 -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/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 +806 -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 +150 -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 +2006 -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/zod-slots.js +334 -0
- package/package.json +92 -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,153 @@
|
|
|
1
|
+
import { resolveNotebookCellArtifactId } from "videobook-engine";
|
|
2
|
+
import { notebookGridTag } from "videobook-engine/notebook-grid";
|
|
3
|
+
import { getChainRun } from "./chain-executor.js";
|
|
4
|
+
import { compileFrameSteps, compileStoryboardSteps, goalStillPrompt } from "./goal-plan.js";
|
|
5
|
+
import { goalReferenceHashes } from "./goal-reference-images.js";
|
|
6
|
+
function goalStepIndex(stage, stepId) {
|
|
7
|
+
if (stage === "frame" || stepId === "frame" || stepId === "scene" || stepId === "video")
|
|
8
|
+
return 0;
|
|
9
|
+
return Number(/\d+$/u.exec(stepId)?.[0]) - 1;
|
|
10
|
+
}
|
|
11
|
+
export async function goalV3Fingerprint(engine, plan, stage, sceneArtifactId) {
|
|
12
|
+
const ids = stage === "frame"
|
|
13
|
+
? plan.scene.imageRefs.map(reference => reference.artifactId)
|
|
14
|
+
: [...plan.video.imageRefs, ...plan.video.videoRefs].map(reference => reference.artifactId);
|
|
15
|
+
return JSON.stringify({
|
|
16
|
+
prompt: stage === "frame" ? plan.scene.prompt : plan.video.prompt,
|
|
17
|
+
settings: stage === "frame"
|
|
18
|
+
? { model: plan.scene.model, aspectRatio: plan.scene.aspectRatio, resolution: plan.scene.resolution, outputFormat: plan.scene.outputFormat }
|
|
19
|
+
: { aspectRatio: plan.video.aspectRatio, resolution: plan.video.resolution, duration: plan.video.durationSeconds, audio: plan.video.generateAudio, outputFormat: plan.video.outputFormat },
|
|
20
|
+
references: await goalReferenceHashes(engine, ids),
|
|
21
|
+
...(stage === "videos" ? { sceneArtifactId } : {}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function goalStageFingerprint(engine, goal, plan, characters, stage, index) {
|
|
25
|
+
const ids = characters.map(character => character.artifactId);
|
|
26
|
+
if (stage !== "frame")
|
|
27
|
+
ids.push(goal.stages.frame?.outputs[0]?.artifactId ?? "");
|
|
28
|
+
if (stage === "videos") {
|
|
29
|
+
ids.push(goal.stages.storyboards?.outputs.find(output => output.index === index)?.artifactId ?? "");
|
|
30
|
+
if (plan.frameContinuity)
|
|
31
|
+
ids.push(goal.stages.storyboards?.outputs.find(output => output.index === index + plan.shots.length)?.artifactId ?? "");
|
|
32
|
+
}
|
|
33
|
+
return JSON.stringify({
|
|
34
|
+
prompt: stage === "videos" ? plan.shots[index]?.videoPrompt : goalStillPrompt(plan, stage, index),
|
|
35
|
+
aspectRatio: plan.aspectRatio, style: { id: plan.style.id, options: Object.entries(plan.style.options).sort() },
|
|
36
|
+
referenceRoles: plan.referenceRoles ?? [], references: await goalReferenceHashes(engine, ids),
|
|
37
|
+
...(stage === "videos" ? { duration: plan.shots[index]?.durationSeconds, resolution: plan.videoResolution, audio: true }
|
|
38
|
+
: { model: plan.imageModel, resolution: plan.imageResolution, ...(stage === "frame" ? { continuity: plan.continuity } : { layout: plan.storyboardLayout ?? "four-panel" }) }),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function goalAttemptFingerprints(engine, goal, plan, characters, stage, steps) {
|
|
42
|
+
return Object.fromEntries(await Promise.all(steps.filter(step => step.id !== "assemble").map(async (step) => [step.id, await goalStageFingerprint(engine, goal, plan, characters, stage, goalStepIndex(stage, step.id))])));
|
|
43
|
+
}
|
|
44
|
+
export function goalStageAttempts(engine, goal, stage) {
|
|
45
|
+
const run = goal.stages[stage];
|
|
46
|
+
const attempts = [...(run?.attempts ?? [])];
|
|
47
|
+
if (!run || stage !== "videos")
|
|
48
|
+
return attempts;
|
|
49
|
+
for (const chainId of [...run.previousChainIds, run.chainId]) {
|
|
50
|
+
for (const step of getChainRun(engine, chainId)?.steps ?? []) {
|
|
51
|
+
const artifactId = step.outputs?.asset_id ?? step.outputs?.vid_asset_id;
|
|
52
|
+
if (!/^shot\d+$/u.test(step.id) || step.status !== "completed" || typeof artifactId !== "string" || attempts.some(attempt => attempt.chainId === chainId && attempt.stepId === step.id))
|
|
53
|
+
continue;
|
|
54
|
+
attempts.push({
|
|
55
|
+
stage, index: goalStepIndex(stage, step.id), chainId, stepId: step.id, artifactId,
|
|
56
|
+
cellId: typeof step.outputs?.cell_id === "string" ? step.outputs.cell_id : null,
|
|
57
|
+
basePrompt: typeof step.params.prompt === "string" ? step.params.prompt.split("\n\n")[0] : "",
|
|
58
|
+
feedback: null, parentArtifactId: null, frameArtifactId: null, createdAt: run.approvedAt,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return attempts;
|
|
63
|
+
}
|
|
64
|
+
export function goalStillAttempts(goal, plan, stage, chainId, steps, createdAt, feedback = null, fingerprints, history = goal.stages[stage]?.attempts ?? []) {
|
|
65
|
+
return [...history, ...steps.filter(step => step.id !== "assemble").map(step => {
|
|
66
|
+
const index = goalStepIndex(stage, step.id);
|
|
67
|
+
return {
|
|
68
|
+
stage, index, chainId, stepId: step.id, artifactId: null, cellId: null,
|
|
69
|
+
basePrompt: stage === "videos" ? plan.shots[index].videoPrompt : goalStillPrompt(plan, stage, index), feedback,
|
|
70
|
+
...(fingerprints?.[step.id] ? { fingerprint: fingerprints[step.id] } : {}),
|
|
71
|
+
...(stage === "storyboards" && index < plan.shots.length ? { storyboardLayout: plan.storyboardLayout ?? "four-panel" } : {}),
|
|
72
|
+
parentArtifactId: feedback ? goal.stages[stage]?.outputs.find(output => output.index === index)?.artifactId ?? null : null,
|
|
73
|
+
frameArtifactId: stage === "storyboards" ? goal.stages.frame?.outputs[0]?.artifactId ?? null : null,
|
|
74
|
+
createdAt,
|
|
75
|
+
};
|
|
76
|
+
})];
|
|
77
|
+
}
|
|
78
|
+
function legacyStillPrompt(plan, characters, stage, index) {
|
|
79
|
+
const references = characters.map((character, position) => `@image${position + (stage === "storyboards" ? 2 : 1)} is Character ${position + 1}: ${character.label.replace(/@[\p{L}\p{N}_-]+/gu, "").slice(0, 160)}`);
|
|
80
|
+
if (stage === "frame")
|
|
81
|
+
return `${plan.continuity}\n\n${plan.frame.prompt}\n\nReferences: ${references.join("; ")}.`;
|
|
82
|
+
references.unshift("@image1 is the approved full frame; preserve its setting and visual style");
|
|
83
|
+
const shotIndex = index % plan.shots.length;
|
|
84
|
+
const shot = plan.shots[shotIndex];
|
|
85
|
+
const next = plan.shots[shotIndex + 1];
|
|
86
|
+
const ending = `Create one still image of the final moment of this shot, with the characters in their ending poses. ${shot.endingPrompt ?? shot.videoPrompt}${next ? `\nPrepare a natural transition into the next shot's opening composition: ${next.storyboardPrompt}` : "\nShow the final payoff with a clear, settled composition."}`;
|
|
87
|
+
return `${plan.continuity}\n\nShot ${shotIndex + 1}: ${shot.title}\n${index < plan.shots.length ? shot.storyboardPrompt : ending}\n\nReferences: ${references.join("; ")}.`;
|
|
88
|
+
}
|
|
89
|
+
function preStyleStillPrompt(plan, characters, stage, index) {
|
|
90
|
+
const references = characters.map((_, position) => `@image${position + (stage === "storyboards" ? 2 : 1)} is Character ${position + 1}`);
|
|
91
|
+
if (stage === "frame")
|
|
92
|
+
return `${plan.continuity}\n\n${plan.frame.prompt}\n\nReferences: ${references.join("; ")}.`;
|
|
93
|
+
references.unshift("@image1 is the full frame; keep its characters, setting and style");
|
|
94
|
+
const shot = plan.shots[index % plan.shots.length];
|
|
95
|
+
return `One ending frame. ${shot.endingPrompt ?? `Final moment: ${shot.videoPrompt}`}\n\nReferences: ${references.join("; ")}.`;
|
|
96
|
+
}
|
|
97
|
+
export async function goalStillStaleReason(engine, goal, document, plan, characters, stage, output) {
|
|
98
|
+
const cell = document.cells.find(candidate => candidate.id === output.cellId);
|
|
99
|
+
if (!cell || resolveNotebookCellArtifactId(document, cell) !== output.artifactId)
|
|
100
|
+
return "The selected output changed or was removed. Choose another version or regenerate it.";
|
|
101
|
+
if (characters.some(character => !document.cells.some(candidate => candidate.id === character.cellId && resolveNotebookCellArtifactId(document, candidate) === character.artifactId)))
|
|
102
|
+
return "A source reference changed or was removed.";
|
|
103
|
+
const attempt = goalStageAttempts(engine, goal, stage).find(item => item.index === output.index && item.artifactId === output.artifactId);
|
|
104
|
+
if (attempt?.fingerprint && attempt.fingerprint !== await goalStageFingerprint(engine, goal, plan, characters, stage, output.index))
|
|
105
|
+
return `${stage === "frame" ? "Scene" : stage === "storyboards" ? "Storyboard" : "Video"} uses older settings, prompts or references. Revise or regenerate it.`;
|
|
106
|
+
if (stage !== "frame") {
|
|
107
|
+
const frame = goal.stages.frame?.outputs[0];
|
|
108
|
+
if (!frame || await goalStillStaleReason(engine, goal, document, plan, characters, "frame", frame))
|
|
109
|
+
return "The scene has changed. Refresh it before continuing.";
|
|
110
|
+
}
|
|
111
|
+
if (stage === "videos") {
|
|
112
|
+
const indexes = [output.index, ...(plan.frameContinuity ? [output.index + plan.shots.length] : [])];
|
|
113
|
+
for (const index of indexes) {
|
|
114
|
+
const board = goal.stages.storyboards?.outputs.find(item => item.index === index);
|
|
115
|
+
if (!board || await goalStillStaleReason(engine, goal, document, plan, characters, "storyboards", board))
|
|
116
|
+
return "The storyboard has changed. Refresh it before generating video.";
|
|
117
|
+
}
|
|
118
|
+
if (attempt && !attempt.fingerprint) {
|
|
119
|
+
const step = getChainRun(engine, attempt.chainId)?.steps.find(item => item.id === attempt.stepId);
|
|
120
|
+
const expected = indexes.map(index => goal.stages.storyboards?.outputs.find(item => item.index === index)?.artifactId);
|
|
121
|
+
if (expected.some((id, index) => id !== step?.pins[index]?.artifactId))
|
|
122
|
+
return "Video uses an older storyboard. Revise or regenerate it.";
|
|
123
|
+
}
|
|
124
|
+
return attempt && attempt.basePrompt !== plan.shots[output.index]?.videoPrompt ? "Video uses older directions. Revise or regenerate it." : null;
|
|
125
|
+
}
|
|
126
|
+
if (attempt?.fingerprint)
|
|
127
|
+
return null;
|
|
128
|
+
const name = stage === "frame" ? "Full frame" : `${output.index >= plan.shots.length ? "Ending frame" : "Storyboard"} ${output.index % plan.shots.length + 1}`;
|
|
129
|
+
const run = goal.stages[stage];
|
|
130
|
+
if (stage === "storyboards" && output.index < plan.shots.length && attempt?.storyboardLayout !== (plan.storyboardLayout ?? "four-panel")) {
|
|
131
|
+
return `${name} is a legacy single-frame preview. Regenerate or revise it to create a four-panel storyboard.`;
|
|
132
|
+
}
|
|
133
|
+
const promptError = `${name} was made from an older prompt. Regenerate or revise it, or restore the prompt.`;
|
|
134
|
+
const frameError = `${name} was made from an older full frame. Regenerate the storyboards.`;
|
|
135
|
+
if (attempt) {
|
|
136
|
+
if (attempt.basePrompt !== goalStillPrompt(plan, stage, output.index))
|
|
137
|
+
return promptError;
|
|
138
|
+
if (stage === "storyboards" && attempt.frameArtifactId !== goal.stages.frame?.outputs[0]?.artifactId)
|
|
139
|
+
return frameError;
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const source = run && [...run.previousChainIds, run.chainId].flatMap(id => getChainRun(engine, id)?.steps ?? [])
|
|
143
|
+
.find(step => step.id === (stage === "frame" ? "frame" : `storyboard${output.index + 1}`) && (step.outputs?.asset_id ?? step.outputs?.img_asset_id) === output.artifactId);
|
|
144
|
+
const frameOutput = goal.stages.frame?.outputs[0];
|
|
145
|
+
if (stage === "storyboards" && source?.pins[0]?.artifactId !== frameOutput?.artifactId)
|
|
146
|
+
return frameError;
|
|
147
|
+
const frameCell = document.cells.find(cell => cell.id === frameOutput?.cellId);
|
|
148
|
+
const compiled = stage === "frame" ? compileFrameSteps(plan, characters)[0]
|
|
149
|
+
: compileStoryboardSteps(plan, characters, frameCell ? notebookGridTag(frameCell.slot) : "", [output.index % plan.shots.length])
|
|
150
|
+
.find(step => step.id === `storyboard${output.index + 1}`);
|
|
151
|
+
return source?.params.prompt === compiled?.params.prompt || plan.style.id === "cinematic" && (source?.params.prompt === legacyStillPrompt(plan, characters, stage, output.index)
|
|
152
|
+
|| source?.params.prompt === preStyleStillPrompt(plan, characters, stage, output.index)) ? null : promptError;
|
|
153
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { firstEmptyNotebookGridSlot, notebookOccupiedSlots } from "videobook-engine/notebook-grid";
|
|
3
|
+
import { goalMediaRefSchema, goalPlanSchema, goalPlanV3Schema, goalStyleSchema, legacyGoalPlanSchema } from "./goal-plan.js";
|
|
4
|
+
import { mutateNotebook } from "./notebook-mutations.js";
|
|
5
|
+
import { arrangeGoalNotebook } from "./notebook-goal-layout.js";
|
|
6
|
+
const KEY = "application.goals.v2";
|
|
7
|
+
const LEGACY_KEY = "application.goals.v1";
|
|
8
|
+
const GOAL_TILE_LABEL = "Video goal";
|
|
9
|
+
const REMOVED_TILE_ERROR = "This goal's tile was removed from the notebook";
|
|
10
|
+
function goalStore(engine) {
|
|
11
|
+
const legacy = engine.settings.get(LEGACY_KEY) ?? {};
|
|
12
|
+
return {
|
|
13
|
+
...Object.fromEntries(Object.entries(legacy).map(([id, goal]) => [id, { ...goal, planVersion: 1, stage: "videos", stages: {} }])),
|
|
14
|
+
...(engine.settings.get(KEY) ?? {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function hasTile(goal) {
|
|
18
|
+
return typeof goal.cellId === "string";
|
|
19
|
+
}
|
|
20
|
+
export function listGoals(engine, notebookId) {
|
|
21
|
+
return Object.values(goalStore(engine))
|
|
22
|
+
.filter((goal) => goal.notebookId === notebookId && hasTile(goal))
|
|
23
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
24
|
+
}
|
|
25
|
+
export function readGoal(engine, goalId) {
|
|
26
|
+
const goal = goalStore(engine)[goalId];
|
|
27
|
+
if (!goal || !hasTile(goal) || goal.bookId !== engine.book.get().bookId)
|
|
28
|
+
throw new Error("Goal not found in this book");
|
|
29
|
+
return goal;
|
|
30
|
+
}
|
|
31
|
+
export function saveGoal(engine, goal) {
|
|
32
|
+
const next = { ...goal, updatedAt: new Date().toISOString() };
|
|
33
|
+
const result = engine.settings.set(KEY, { ...(engine.settings.get(KEY) ?? {}), [goal.goalId]: next });
|
|
34
|
+
if (!result.ok)
|
|
35
|
+
throw new Error(result.error.message);
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
function estimateFrom(value) {
|
|
39
|
+
const record = value && typeof value === "object" ? value : {};
|
|
40
|
+
return {
|
|
41
|
+
usd: typeof record.usd === "number" && Number.isFinite(record.usd) ? record.usd : null,
|
|
42
|
+
warnings: Array.isArray(record.warnings) ? record.warnings.filter((entry) => typeof entry === "string") : [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function emptyGoalEstimates() {
|
|
46
|
+
return { frame: { usd: null, warnings: [] }, storyboards: { usd: null, warnings: [] }, videos: { usd: null, warnings: [] }, total: { usd: null, warnings: [] } };
|
|
47
|
+
}
|
|
48
|
+
function estimatesFrom(value) {
|
|
49
|
+
const record = value && typeof value === "object" ? value : {};
|
|
50
|
+
if (!("total" in record))
|
|
51
|
+
return { ...emptyGoalEstimates(), videos: estimateFrom(value), total: estimateFrom(value) };
|
|
52
|
+
return { frame: estimateFrom(record.frame), storyboards: estimateFrom(record.storyboards), videos: estimateFrom(record.videos), total: estimateFrom(record.total) };
|
|
53
|
+
}
|
|
54
|
+
function charactersFrom(value) {
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
return [];
|
|
57
|
+
return value.filter((entry) => entry && typeof entry === "object"
|
|
58
|
+
&& ["artifactId", "cellId", "address", "label"].every(key => typeof entry[key] === "string"));
|
|
59
|
+
}
|
|
60
|
+
function mediaFrom(value) {
|
|
61
|
+
const parsed = z.array(z.unknown()).safeParse(value);
|
|
62
|
+
if (!parsed.success)
|
|
63
|
+
return [];
|
|
64
|
+
return parsed.data.flatMap(entry => {
|
|
65
|
+
const media = goalMediaRefSchema.safeParse(entry);
|
|
66
|
+
return media.success ? [media.data] : [];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function goalContent(cell) {
|
|
70
|
+
const inputs = cell.inputs ?? {};
|
|
71
|
+
const plan = z.union([goalPlanV3Schema, goalPlanSchema, legacyGoalPlanSchema]).safeParse(inputs.plan);
|
|
72
|
+
const requestedStyle = goalStyleSchema.safeParse(inputs.requestedStyle);
|
|
73
|
+
const media = mediaFrom(inputs.media);
|
|
74
|
+
return {
|
|
75
|
+
brief: cell.prompt ?? "",
|
|
76
|
+
characters: charactersFrom(inputs.characters),
|
|
77
|
+
...(media.length > 0 ? { media } : {}),
|
|
78
|
+
revision: typeof inputs.revision === "number" && Number.isInteger(inputs.revision) ? inputs.revision : 0,
|
|
79
|
+
plan: plan.success ? plan.data : null,
|
|
80
|
+
...(!plan.success && requestedStyle.success ? { requestedStyle: requestedStyle.data } : {}),
|
|
81
|
+
estimate: estimatesFrom(inputs.estimate),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function goalCellInputs(goalId, content) {
|
|
85
|
+
return { goalId, revision: content.revision, plan: content.plan, characters: content.characters, estimate: content.estimate,
|
|
86
|
+
...(content.media ? { media: content.media } : {}),
|
|
87
|
+
...(!content.plan && content.requestedStyle ? { requestedStyle: content.requestedStyle } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function goalCellText(content) {
|
|
91
|
+
return { label: content.plan?.title ?? GOAL_TILE_LABEL, prompt: content.brief };
|
|
92
|
+
}
|
|
93
|
+
export function findGoalCell(document, goal) {
|
|
94
|
+
return document.cells.find((cell) => cell.id === goal.cellId && cell.type === "goal") ?? null;
|
|
95
|
+
}
|
|
96
|
+
export function readGoalCell(engine, goal) {
|
|
97
|
+
const notebook = engine.notebooks.read(goal.notebookId);
|
|
98
|
+
if (!notebook.ok)
|
|
99
|
+
throw new Error(notebook.error.message);
|
|
100
|
+
return findGoalCell(notebook.value, goal);
|
|
101
|
+
}
|
|
102
|
+
export function requireGoalContent(engine, goal) {
|
|
103
|
+
const cell = readGoalCell(engine, goal);
|
|
104
|
+
if (!cell)
|
|
105
|
+
throw new Error(REMOVED_TILE_ERROR);
|
|
106
|
+
return goalContent(cell);
|
|
107
|
+
}
|
|
108
|
+
export async function createGoalCell(engine, notebookId, goalId, content) {
|
|
109
|
+
const created = { cell: null };
|
|
110
|
+
const result = await mutateNotebook(engine, notebookId, "goal_create", (document) => {
|
|
111
|
+
const cell = engine.notebooks.createCell({
|
|
112
|
+
type: "goal",
|
|
113
|
+
...goalCellText(content),
|
|
114
|
+
slot: firstEmptyNotebookGridSlot(notebookOccupiedSlots(document.cells)),
|
|
115
|
+
inputs: goalCellInputs(goalId, content),
|
|
116
|
+
});
|
|
117
|
+
created.cell = cell;
|
|
118
|
+
const edges = content.characters.map((character, index) => engine.notebooks.createEdge({
|
|
119
|
+
source: character.cellId, target: cell.id, targetInput: index === 0 ? "character" : `character_${index + 1}`,
|
|
120
|
+
}));
|
|
121
|
+
const arranged = arrangeGoalNotebook({ ...document, cells: [...document.cells, cell], edges: [...document.edges, ...edges] });
|
|
122
|
+
created.cell = arranged.cells.find(candidate => candidate.id === cell.id);
|
|
123
|
+
return arranged;
|
|
124
|
+
}, { goalId });
|
|
125
|
+
if (!result.ok)
|
|
126
|
+
throw new Error(result.error.message);
|
|
127
|
+
if (!created.cell)
|
|
128
|
+
throw new Error("The goal tile could not be placed");
|
|
129
|
+
return created.cell;
|
|
130
|
+
}
|
|
131
|
+
export async function writeGoalContent(engine, goal, content) {
|
|
132
|
+
const result = await mutateNotebook(engine, goal.notebookId, "goal_plan", (document) => {
|
|
133
|
+
if (!findGoalCell(document, goal))
|
|
134
|
+
throw new Error(REMOVED_TILE_ERROR);
|
|
135
|
+
return {
|
|
136
|
+
...document,
|
|
137
|
+
cells: document.cells.map((cell) => cell.id === goal.cellId
|
|
138
|
+
? { ...cell, ...goalCellText(content), inputs: goalCellInputs(goal.goalId, content) }
|
|
139
|
+
: cell),
|
|
140
|
+
};
|
|
141
|
+
}, { goalId: goal.goalId });
|
|
142
|
+
if (!result.ok)
|
|
143
|
+
throw new Error(result.error.message);
|
|
144
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const GOAL_STYLE_IDS = ["source", "cinematic", "ig-influencer"];
|
|
2
|
+
const styles = {
|
|
3
|
+
source: {
|
|
4
|
+
id: "source", label: "Match source", summary: "Keep the look, camera and lighting of the source screenshot.",
|
|
5
|
+
briefKeywords: [], defaults: { imageModel: "nano-banana-2-1", imageResolution: "2K", videoResolution: "720p" },
|
|
6
|
+
planner: "Infer the visual style, framing, lighting and camera from the scene reference. Preserve that look. Identify the scene screenshot, character references and prop references from the actual images and brief. Reference images are not necessarily people. Remove social-media interface overlays from generated pictures.",
|
|
7
|
+
fragments: {
|
|
8
|
+
frame: "Match the source scene's framing, lighting and visual style; remove interface overlays",
|
|
9
|
+
storyboard: "Keep the approved scene's visual style, people and props",
|
|
10
|
+
ending: "Keep the approved scene's visual style",
|
|
11
|
+
video: "Match the approved scene's camera and lighting; natural ambient sound",
|
|
12
|
+
revise: "Preserve the selected image's visual style",
|
|
13
|
+
},
|
|
14
|
+
banned: [], options: [],
|
|
15
|
+
},
|
|
16
|
+
cinematic: {
|
|
17
|
+
id: "cinematic", label: "Cinematic", summary: "Photographic live-action with natural proportions, textures and motion.",
|
|
18
|
+
briefKeywords: ["cinematic"], defaults: {},
|
|
19
|
+
planner: "Photographic live-action. Natural proportions, real skin and material textures, practical light. Restrained expressions and plausible motion. Comedy comes from situations and timing; avoid cartoon rendering, caricature, rubbery movement and exaggerated features.",
|
|
20
|
+
fragments: {
|
|
21
|
+
frame: "Photographic live-action, natural proportions, real textures",
|
|
22
|
+
storyboard: "Photographic live-action, natural proportions, real textures",
|
|
23
|
+
ending: "Photographic live-action, natural proportions, real textures",
|
|
24
|
+
video: "Live-action, natural motion, restrained expressions",
|
|
25
|
+
revise: "Photographic live-action, natural proportions, real textures",
|
|
26
|
+
},
|
|
27
|
+
banned: [], options: [],
|
|
28
|
+
},
|
|
29
|
+
"ig-influencer": {
|
|
30
|
+
id: "ig-influencer", label: "IG Influencer", summary: "Hyper-realistic, candid iPhone footage with natural skin and light.",
|
|
31
|
+
briefKeywords: ["ig influencer", "instagram influencer", "influencer", "insta reel", "instagram reel"],
|
|
32
|
+
defaults: { aspectRatio: "9:16", imageModel: "sunburst", imageResolution: "2K", videoResolution: "720p" },
|
|
33
|
+
planner: "Use a hyper-realistic Instagram phone-camera register: real-world activity in present tense, one location per shot, candid framing with slight imperfection, light grain, unretouched skin with visible pores and texture. Use the selected iPhone camera and lighting; natural daylight or soft indoor light by default. No HDR crunch or beautify. No captions or on-screen text inside pictures or video; storyboard notes go below pictures. Avoid cinematic, masterpiece, editorial, Portra, Cinestill and all film-stock vocabulary. Match the delivery: address the camera for selfies, be caught mid-action when filmed by a friend, hold framing steady on a tripod, or show the reflection for a mirror shot. Make the selected content format explicit in the four beats. Sound is ambient room or street sound with an optional short spoken line.",
|
|
34
|
+
fragments: {
|
|
35
|
+
frame: "hyper-realistic, skin pores, light grain",
|
|
36
|
+
storyboard: "hyper-realistic, skin pores, light grain",
|
|
37
|
+
ending: "hyper-realistic, skin pores",
|
|
38
|
+
video: "hyper-realistic skin pores, natural motion, handheld micro-shake, ambient sound",
|
|
39
|
+
revise: "hyper-realistic, skin pores",
|
|
40
|
+
},
|
|
41
|
+
banned: ["cinematic", "masterpiece", "editorial", "Portra", "Cinestill"],
|
|
42
|
+
options: [
|
|
43
|
+
{
|
|
44
|
+
key: "format", label: "Content format", default: "day-in-the-life",
|
|
45
|
+
choices: [
|
|
46
|
+
{ value: "grwm", label: "GRWM", prompt: "GRWM", briefKeywords: ["grwm", "get ready with me"], beats: "skincare, outfit, final look, mirror check" },
|
|
47
|
+
{ value: "ootd", label: "OOTD", prompt: "OOTD", briefKeywords: ["ootd", "outfit of the day"], beats: "reveal, details, walk, turn" },
|
|
48
|
+
{ value: "day-in-the-life", label: "Day in the life", prompt: "day-in-the-life", briefKeywords: ["day in the life", "vlog"], beats: "arrive, begin activity, react, finish" },
|
|
49
|
+
{ value: "travel-reel", label: "Travel reel", prompt: "travel", briefKeywords: ["travel reel"], beats: "arrive, explore, discover, farewell" },
|
|
50
|
+
{ value: "cafe", label: "Café", prompt: "café", briefKeywords: ["brunch", "cafe", "café"], beats: "order, receive drink, taste, smile" },
|
|
51
|
+
{ value: "gym", label: "Gym", prompt: "gym", briefKeywords: ["gym"], beats: "warm up, exercise, effort, recover" },
|
|
52
|
+
{ value: "skincare", label: "Skincare", prompt: "skincare", briefKeywords: ["skincare"], beats: "cleanse, apply, massage, reveal" },
|
|
53
|
+
{ value: "haul", label: "Haul", prompt: "haul", briefKeywords: ["haul"], beats: "unpack, show item, try on, react" },
|
|
54
|
+
{ value: "storytime", label: "Storytime", prompt: "storytime", briefKeywords: ["storytime"], beats: "hook, explain, react, conclude" },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "delivery", label: "Delivery", default: "selfie-to-camera",
|
|
59
|
+
choices: [
|
|
60
|
+
{ value: "selfie-to-camera", label: "Selfie to camera", prompt: "selfie", briefKeywords: ["selfie", "front camera"] },
|
|
61
|
+
{ value: "filmed-by-friend", label: "Filmed by a friend", prompt: "friend-filmed", briefKeywords: ["filmed by"] },
|
|
62
|
+
{ value: "tripod", label: "Tripod", prompt: "tripod", briefKeywords: ["tripod"] },
|
|
63
|
+
{ value: "mirror", label: "Mirror", prompt: "mirror", briefKeywords: ["mirror"] },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "camera", label: "Camera", default: "iphone-front",
|
|
68
|
+
choices: [
|
|
69
|
+
{ value: "iphone-front", label: "iPhone front camera", prompt: "iPhone front", briefKeywords: ["front camera", "iphone front"] },
|
|
70
|
+
{ value: "iphone-main", label: "iPhone main camera", prompt: "iPhone main", briefKeywords: ["main camera", "iphone main"] },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "light", label: "Light", default: "soft-daylight",
|
|
75
|
+
choices: [
|
|
76
|
+
{ value: "soft-daylight", label: "Soft daylight", prompt: "soft daylight", briefKeywords: ["soft daylight", "natural daylight"] },
|
|
77
|
+
{ value: "golden-hour", label: "Golden hour", prompt: "golden hour", briefKeywords: ["golden hour"] },
|
|
78
|
+
{ value: "indoor-warm", label: "Warm indoor light", prompt: "warm indoor", briefKeywords: ["warm indoor", "indoor warm"] },
|
|
79
|
+
{ value: "night-flash", label: "Night flash", prompt: "night flash", briefKeywords: ["night out", "flash"] },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export function goalStyle(id) {
|
|
86
|
+
return styles[id];
|
|
87
|
+
}
|
|
88
|
+
export function resolveGoalStyleOptions(id, partial = {}) {
|
|
89
|
+
const style = goalStyle(id);
|
|
90
|
+
for (const key of Object.keys(partial)) {
|
|
91
|
+
if (!style.options.some(option => option.key === key))
|
|
92
|
+
throw new Error(`Unknown ${style.label} option: ${key}`);
|
|
93
|
+
}
|
|
94
|
+
const resolved = {};
|
|
95
|
+
for (const option of style.options) {
|
|
96
|
+
const fallback = option.key === "camera" && resolved.delivery !== "selfie-to-camera" ? "iphone-main" : option.default;
|
|
97
|
+
const value = partial[option.key] ?? fallback;
|
|
98
|
+
if (!option.choices.some(choice => choice.value === value)) {
|
|
99
|
+
throw new Error(`Invalid ${style.label} ${option.label}: ${value}. Choose ${option.choices.map(choice => choice.value).join(", ")}.`);
|
|
100
|
+
}
|
|
101
|
+
resolved[option.key] = value;
|
|
102
|
+
}
|
|
103
|
+
return resolved;
|
|
104
|
+
}
|
|
105
|
+
export function goalStyleFragment(selection, stage) {
|
|
106
|
+
const style = goalStyle(selection.id);
|
|
107
|
+
const options = resolveGoalStyleOptions(selection.id, selection.options);
|
|
108
|
+
let fragment = style.fragments[stage];
|
|
109
|
+
if (!fragment)
|
|
110
|
+
return "";
|
|
111
|
+
if (stage === "video" && options.delivery === "tripod")
|
|
112
|
+
fragment = fragment.replace("handheld micro-shake", "steady framing");
|
|
113
|
+
const pieces = style.options.map(option => option.choices.find(choice => choice.value === options[option.key]).prompt);
|
|
114
|
+
return `${pieces.length ? `${pieces.join(", ")}: ` : ""}${fragment}.`;
|
|
115
|
+
}
|
|
116
|
+
export function goalStyleSummary(selection) {
|
|
117
|
+
const style = goalStyle(selection.id);
|
|
118
|
+
const options = resolveGoalStyleOptions(selection.id, selection.options);
|
|
119
|
+
return [style.label, ...style.options.map(option => option.choices.find(choice => choice.value === options[option.key]).label)].join(" · ");
|
|
120
|
+
}
|
|
121
|
+
export function goalStylesEqual(left, right) {
|
|
122
|
+
if (left.id !== right.id)
|
|
123
|
+
return false;
|
|
124
|
+
const a = resolveGoalStyleOptions(left.id, left.options);
|
|
125
|
+
const b = resolveGoalStyleOptions(right.id, right.options);
|
|
126
|
+
return Object.keys(a).every(key => a[key] === b[key]);
|
|
127
|
+
}
|
|
128
|
+
function firstKeyword(brief, keywords) {
|
|
129
|
+
return Math.min(...keywords.map(keyword => {
|
|
130
|
+
const pattern = keyword.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&").replace(/[ -]+/gu, "[\\s-]+");
|
|
131
|
+
const match = new RegExp(`(^|[^\\p{L}\\p{N}_-])${pattern}(?=$|[^\\p{L}\\p{N}_-])`, "iu").exec(brief);
|
|
132
|
+
return match ? match.index + match[1].length : Infinity;
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
export function goalBriefStyle(brief) {
|
|
136
|
+
const style = GOAL_STYLE_IDS.map(id => ({ style: goalStyle(id), index: firstKeyword(brief, goalStyle(id).briefKeywords) }))
|
|
137
|
+
.filter(match => Number.isFinite(match.index)).sort((left, right) => left.index - right.index)[0]?.style ?? goalStyle("source");
|
|
138
|
+
const partial = Object.fromEntries(style.options.flatMap(option => {
|
|
139
|
+
const choice = option.choices.map(candidate => ({ value: candidate.value, index: firstKeyword(brief, candidate.briefKeywords) }))
|
|
140
|
+
.filter(match => Number.isFinite(match.index)).sort((left, right) => left.index - right.index)[0];
|
|
141
|
+
return choice ? [[option.key, choice.value]] : [];
|
|
142
|
+
}));
|
|
143
|
+
return { id: style.id, options: resolveGoalStyleOptions(style.id, partial) };
|
|
144
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { v7 as uuidv7 } from "uuid";
|
|
3
|
+
import { findPrimaryMediaFile, nextHorizontalSlotFrom } from "videobook-engine";
|
|
4
|
+
import { notebookGridTag, notebookOccupiedSlots } from "videobook-engine/notebook-grid";
|
|
5
|
+
import { scanNotebookMentions } from "videobook-engine/notebook-mentions";
|
|
6
|
+
import { probeVideo } from "./ffmpeg.js";
|
|
7
|
+
import { closestGoalAspectRatio, compileGoalV3Steps } from "./goal-plan-v3.js";
|
|
8
|
+
import { captureFrame } from "./notebook-media-ops.js";
|
|
9
|
+
import { EXTRACT_FRAME_OUTPUT_ROLE } from "./notebook-cell-outputs.js";
|
|
10
|
+
import { mutateNotebook } from "./notebook-mutations.js";
|
|
11
|
+
import { slotRight } from "./notebook-placement.js";
|
|
12
|
+
import { notebookCellFingerprint } from "./jobs/run-notebook.js";
|
|
13
|
+
import { queue } from "./queue.js";
|
|
14
|
+
import { resolveNotebookPromptReferences } from "./notebook-reference-resolver.js";
|
|
15
|
+
import { assessToolCallEstimate } from "./jobs/cost-estimate-assessment.js";
|
|
16
|
+
const GOAL_MEDIA_HELP = "Mention the reference images or videos in the brief, for example /goal @a1 @a2 as ...";
|
|
17
|
+
function mediaKind(kind, cellType) {
|
|
18
|
+
if (kind === "image" || cellType === "image" || cellType === "generate_image")
|
|
19
|
+
return "image";
|
|
20
|
+
if (kind === "video" || kind === "final" || ["video", "generate_video", "concat", "splice", "split_video"].includes(cellType))
|
|
21
|
+
return "video";
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export async function resolveGoalMediaReferences(engine, document, brief) {
|
|
25
|
+
if (scanNotebookMentions(brief).length === 0)
|
|
26
|
+
throw new Error(GOAL_MEDIA_HELP);
|
|
27
|
+
const resolved = resolveNotebookPromptReferences({ engine, document, prompt: brief, requireMedia: true });
|
|
28
|
+
if (resolved.unresolved.length > 0)
|
|
29
|
+
throw new Error(resolved.unresolved[0].message);
|
|
30
|
+
const references = [];
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
for (const reference of resolved.resolved) {
|
|
33
|
+
if (seen.has(reference.artifactId))
|
|
34
|
+
continue;
|
|
35
|
+
const artifact = engine.artifacts.get(reference.artifactId);
|
|
36
|
+
const cell = document.cells.find(candidate => candidate.id === reference.cellId);
|
|
37
|
+
const kind = artifact.ok && cell ? mediaKind(artifact.value.kind, cell.type) : null;
|
|
38
|
+
if (!artifact.ok || !cell || !kind)
|
|
39
|
+
throw new Error(`${reference.mention} is not an image or video`);
|
|
40
|
+
const manifest = await engine.files.manifest(reference.artifactId);
|
|
41
|
+
const file = manifest.ok ? findPrimaryMediaFile(manifest.value.files, kind === "image" ? "image" : "video") : null;
|
|
42
|
+
if (!manifest.ok || !file)
|
|
43
|
+
throw new Error(`${reference.mention} has no readable media`);
|
|
44
|
+
seen.add(reference.artifactId);
|
|
45
|
+
references.push({
|
|
46
|
+
kind,
|
|
47
|
+
artifactId: reference.artifactId,
|
|
48
|
+
cellId: cell.id,
|
|
49
|
+
address: notebookGridTag(cell.slot),
|
|
50
|
+
label: artifact.value.label || cell.label || reference.mention,
|
|
51
|
+
objectHash: file.objectHash,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (references.length === 0)
|
|
55
|
+
throw new Error(GOAL_MEDIA_HELP);
|
|
56
|
+
return references;
|
|
57
|
+
}
|
|
58
|
+
async function frameImageFile(engine, artifactId) {
|
|
59
|
+
const manifest = await engine.files.manifest(artifactId);
|
|
60
|
+
if (!manifest.ok)
|
|
61
|
+
throw new Error(manifest.error.message);
|
|
62
|
+
const file = findPrimaryMediaFile(manifest.value.files, "image");
|
|
63
|
+
if (!file)
|
|
64
|
+
throw new Error("The scene frame has no image file");
|
|
65
|
+
return { path: join(manifest.value.path, file.name), objectHash: file.objectHash };
|
|
66
|
+
}
|
|
67
|
+
async function insertSceneFrameCell(engine, notebookId, video, artifactId, atSeconds) {
|
|
68
|
+
const placed = { cell: null };
|
|
69
|
+
const runId = uuidv7();
|
|
70
|
+
const result = await mutateNotebook(engine, notebookId, "goal_scene_frame", (notebook) => {
|
|
71
|
+
const anchor = notebook.cells.find(cell => cell.id === video.cellId);
|
|
72
|
+
if (!anchor)
|
|
73
|
+
throw new Error(`${video.address} was removed`);
|
|
74
|
+
const cell = engine.notebooks.createCell({
|
|
75
|
+
type: "image",
|
|
76
|
+
label: `Frame at ${atSeconds}s`,
|
|
77
|
+
slot: nextHorizontalSlotFrom(slotRight(anchor.slot), notebookOccupiedSlots(notebook.cells)),
|
|
78
|
+
outputArtifactId: artifactId,
|
|
79
|
+
inputs: { mediaRole: EXTRACT_FRAME_OUTPUT_ROLE, frameMode: "time", timestampSeconds: atSeconds, atSeconds, sourceCellId: anchor.id, state: "completed" },
|
|
80
|
+
});
|
|
81
|
+
placed.cell = cell;
|
|
82
|
+
const completedAt = new Date().toISOString();
|
|
83
|
+
return {
|
|
84
|
+
...notebook,
|
|
85
|
+
cells: [...notebook.cells, cell],
|
|
86
|
+
edges: [...notebook.edges, engine.notebooks.createEdge({ source: anchor.id, target: cell.id, targetInput: "segment" })],
|
|
87
|
+
execution: {
|
|
88
|
+
...(notebook.execution ?? {}),
|
|
89
|
+
[cell.id]: { fingerprint: notebookCellFingerprint(notebook, cell), status: "completed", runId, outputArtifactId: artifactId, completedAt, updatedAt: completedAt, tool: "ffmpeg" },
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}, { sourceCellId: video.cellId, runId }, { artifactId });
|
|
93
|
+
if (!result.ok)
|
|
94
|
+
throw new Error(result.error.message);
|
|
95
|
+
if (!placed.cell)
|
|
96
|
+
throw new Error("The scene frame could not be placed");
|
|
97
|
+
return placed.cell;
|
|
98
|
+
}
|
|
99
|
+
export async function captureGoalSceneFrame(engine, notebookId, references) {
|
|
100
|
+
const video = references.find(reference => reference.kind === "video");
|
|
101
|
+
if (!video)
|
|
102
|
+
return null;
|
|
103
|
+
const captured = await captureFrame(engine, video.artifactId, { role: "time", timestampSeconds: 1, title: `${video.address} · scene frame` });
|
|
104
|
+
const file = await frameImageFile(engine, captured.artifactId);
|
|
105
|
+
const probe = await probeVideo(file.path);
|
|
106
|
+
await queue.enqueue(`process-image-${captured.artifactId}`, {
|
|
107
|
+
type: "process_image", assetId: captured.artifactId, bookId: engine.book.get().bookId, extension: ".jpg",
|
|
108
|
+
provenance: { origin: "extract_frame", sourceArtifactId: video.artifactId, frameMode: "time", atSeconds: captured.atSeconds },
|
|
109
|
+
});
|
|
110
|
+
const cell = await insertSceneFrameCell(engine, notebookId, video, captured.artifactId, captured.atSeconds);
|
|
111
|
+
return {
|
|
112
|
+
reference: {
|
|
113
|
+
kind: "image", artifactId: captured.artifactId, cellId: cell.id, address: notebookGridTag(cell.slot),
|
|
114
|
+
label: cell.label ?? "Scene frame", objectHash: file.objectHash,
|
|
115
|
+
},
|
|
116
|
+
seconds: captured.atSeconds,
|
|
117
|
+
aspectRatio: closestGoalAspectRatio(probe.width, probe.height),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function estimateInput(engine, plan, step) {
|
|
121
|
+
const images = (step.id === "video" ? plan.video.imageRefs : plan.scene.imageRefs).map(reference => reference.artifactId);
|
|
122
|
+
const videos = step.id === "video" ? plan.video.videoRefs.map(reference => reference.artifactId) : [];
|
|
123
|
+
return {
|
|
124
|
+
book_id: engine.book.get().bookId,
|
|
125
|
+
...step.params,
|
|
126
|
+
refs: { image: images, video: videos },
|
|
127
|
+
...(step.id === "video" && images.length + videos.length > 0 ? { mode: "r2v" } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export async function estimateGoalV3(engine, plan) {
|
|
131
|
+
const [scene, video] = compileGoalV3Steps(plan);
|
|
132
|
+
const priced = await Promise.all([scene, video].map(async (step) => {
|
|
133
|
+
const estimate = await assessToolCallEstimate(engine, step.tool, estimateInput(engine, plan, step));
|
|
134
|
+
return { usd: estimate.available ? estimate.usd ?? null : null, warnings: estimate.warnings };
|
|
135
|
+
}));
|
|
136
|
+
const frame = priced[0];
|
|
137
|
+
const videos = priced[1];
|
|
138
|
+
const storyboards = { usd: 0, warnings: [] };
|
|
139
|
+
const parts = [frame, storyboards, videos];
|
|
140
|
+
return {
|
|
141
|
+
frame,
|
|
142
|
+
storyboards,
|
|
143
|
+
videos,
|
|
144
|
+
total: {
|
|
145
|
+
usd: parts.every(part => part.usd !== null) ? parts.reduce((sum, part) => sum + (part.usd ?? 0), 0) : null,
|
|
146
|
+
warnings: [...new Set(parts.flatMap(part => part.warnings))],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function goalMediaCharacters(references) {
|
|
151
|
+
return references.filter(reference => reference.kind === "image").map(reference => ({
|
|
152
|
+
artifactId: reference.artifactId,
|
|
153
|
+
cellId: reference.cellId,
|
|
154
|
+
address: reference.address,
|
|
155
|
+
label: reference.label,
|
|
156
|
+
}));
|
|
157
|
+
}
|