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,438 @@
|
|
|
1
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
2
|
+
import { copyFile, mkdir, mkdtemp, readFile as fsReadFile, rm, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { enqueueAssetTagging } from "./tag-asset.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
import { registerJobHandler, queue } from "../queue.js";
|
|
7
|
+
import { withLocalLease } from "./_lease.js";
|
|
8
|
+
import { enqueueLazyAnalysisIfSlotted } from "../lazy-render.js";
|
|
9
|
+
import { probeVideo, runFfmpeg, extractWaveform, isThumbnailBlank, H264_MEDIUM, AUDIO_OPTS, } from "../ffmpeg.js";
|
|
10
|
+
import { formatTimestamp, generateTimestamps } from "../frame-utils.js";
|
|
11
|
+
import { ingestWorkspaceOutputs } from "./_persist-output.js";
|
|
12
|
+
import { getApiKey } from "../api-key.js";
|
|
13
|
+
import { withTransientStorageRetry } from "./_storage-retry.js";
|
|
14
|
+
import { videoProxyMetadata, videoProxyValidationError, } from "../video-proxy.js";
|
|
15
|
+
export function planVideoTranscriptJobs(input) {
|
|
16
|
+
if (input.skipDownstreamJobs || !input.hasAudio)
|
|
17
|
+
return [];
|
|
18
|
+
const jobs = [];
|
|
19
|
+
if (input.hasOpenAiKey) {
|
|
20
|
+
jobs.push({
|
|
21
|
+
jobId: `transcribe-openai-${input.assetId}-${input.identity}`,
|
|
22
|
+
payload: {
|
|
23
|
+
type: "transcribe_video_audio",
|
|
24
|
+
videoPath: join(input.assetDir, input.videoFilename),
|
|
25
|
+
assetId: input.assetId,
|
|
26
|
+
assetDir: input.assetDir,
|
|
27
|
+
bookId: input.bookId,
|
|
28
|
+
sourceFilename: "original.mp3",
|
|
29
|
+
mediaRevision: input.identity,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (input.hasElevenLabsKey) {
|
|
34
|
+
jobs.push({
|
|
35
|
+
jobId: `transcribe-elevenlabs-${input.assetId}-${input.identity}`,
|
|
36
|
+
payload: {
|
|
37
|
+
type: "transcribe",
|
|
38
|
+
audioPath: join(input.assetDir, "original.mp3"),
|
|
39
|
+
audioFilename: "original.mp3",
|
|
40
|
+
tagAudioEvents: true,
|
|
41
|
+
diarize: true,
|
|
42
|
+
assetId: input.assetId,
|
|
43
|
+
assetDir: input.assetDir,
|
|
44
|
+
bookId: input.bookId,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return jobs;
|
|
49
|
+
}
|
|
50
|
+
async function enqueueVideoTranscriptJobs(jobs) {
|
|
51
|
+
for (const job of jobs) {
|
|
52
|
+
try {
|
|
53
|
+
await queue.enqueue(job.jobId, job.payload);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
logger.warn("process-video-enqueue-failed", { jobType: job.payload.type, error: err });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function videoTranscriptIdentity(engine, assetId, videoFilename) {
|
|
61
|
+
const manifest = await engine.files.manifest(assetId);
|
|
62
|
+
if (!manifest.ok)
|
|
63
|
+
return String(Date.now());
|
|
64
|
+
const file = manifest.value.files.find((entry) => entry.name === videoFilename)
|
|
65
|
+
?? manifest.value.files.find((entry) => entry.name === "original.mp4")
|
|
66
|
+
?? manifest.value.files.find((entry) => entry.name === "original.mp3");
|
|
67
|
+
return file?.objectHash ?? String(Date.now());
|
|
68
|
+
}
|
|
69
|
+
// Write a derived binary file, skipping the write when the bytes are unchanged.
|
|
70
|
+
//
|
|
71
|
+
// The engine commits each write scoped to that file's path. A scoped
|
|
72
|
+
// `git commit -- <path>` with no diff exits non-zero ("nothing to commit" on
|
|
73
|
+
// stdout, empty stderr) whenever the rest of the tree is dirty — which it is
|
|
74
|
+
// on a force re-process, since we just deleted original.jpg + frames. Audio is
|
|
75
|
+
// stream-copied during watermarking, so re-extracting original.mp3 yields bytes
|
|
76
|
+
// identical to the already-committed file; the scoped commit then finds nothing
|
|
77
|
+
// and the engine surfaces a bare "Failed to commit file write:". Skipping the
|
|
78
|
+
// no-op write lets idempotent re-processing succeed. Mirrors
|
|
79
|
+
// writePlanFileIfChanged in plan-notebook.ts.
|
|
80
|
+
async function writeBinaryIfChanged(engine, assetId, filename, data) {
|
|
81
|
+
const existing = await engine.files.read(assetId, filename);
|
|
82
|
+
if (existing.ok && existing.value.equals(data))
|
|
83
|
+
return;
|
|
84
|
+
const written = await engine.files.write(assetId, filename, data);
|
|
85
|
+
if (!written.ok) {
|
|
86
|
+
throw new Error(`writeFile ${filename} failed for ${assetId}: ${written.error.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function registerMediaStreams(engine, assetId, sourcePath, probe) {
|
|
90
|
+
const manifest = await engine.files.manifest(assetId);
|
|
91
|
+
if (!manifest.ok) {
|
|
92
|
+
throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
|
|
93
|
+
}
|
|
94
|
+
const objectHash = manifest.value.files.find((file) => file.name === sourcePath)?.objectHash;
|
|
95
|
+
if (!objectHash)
|
|
96
|
+
throw new Error(`Media source is missing from the manifest: ${sourcePath}`);
|
|
97
|
+
for (const stream of [probe.videoStream, probe.audioStream]) {
|
|
98
|
+
if (!stream)
|
|
99
|
+
continue;
|
|
100
|
+
const registered = await engine.streams.register({
|
|
101
|
+
artifactId: assetId,
|
|
102
|
+
sourcePath,
|
|
103
|
+
objectHash,
|
|
104
|
+
streamIndex: stream.index,
|
|
105
|
+
kind: stream.video ? "video" : "audio",
|
|
106
|
+
timeBase: stream.timeBase,
|
|
107
|
+
durationTicks: stream.durationTicks,
|
|
108
|
+
codec: stream.codec,
|
|
109
|
+
...(stream.video ? { video: stream.video } : {}),
|
|
110
|
+
...(stream.audio ? { audio: stream.audio } : {}),
|
|
111
|
+
});
|
|
112
|
+
if (!registered.ok) {
|
|
113
|
+
throw new Error(`Failed to register media stream for ${assetId}: ${registered.error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function snapshotAssetFile(engine, assetId, filename, targetPath) {
|
|
118
|
+
const manifest = await engine.files.manifest(assetId);
|
|
119
|
+
if (!manifest.ok) {
|
|
120
|
+
throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
|
|
121
|
+
}
|
|
122
|
+
if (!manifest.value.files.some((file) => file.name === filename))
|
|
123
|
+
return false;
|
|
124
|
+
const snapshot = await engine.files.readAtRevision(manifest.value.artifactId, filename, engine.head);
|
|
125
|
+
if (!snapshot.ok) {
|
|
126
|
+
throw new Error(`Failed to read immutable ${filename} for ${assetId}: ${snapshot.error.message}`);
|
|
127
|
+
}
|
|
128
|
+
await fsWriteFile(targetPath, snapshot.value);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
async function persistProxyMetadata(engine, assetId, sourceFilename, proxy) {
|
|
132
|
+
const manifest = await engine.files.manifest(assetId);
|
|
133
|
+
if (!manifest.ok) {
|
|
134
|
+
throw new Error(`Failed to read media manifest for ${assetId}: ${manifest.error.message}`);
|
|
135
|
+
}
|
|
136
|
+
const sourceFile = manifest.value.files.find((file) => file.name === sourceFilename);
|
|
137
|
+
const proxyFile = manifest.value.files.find((file) => file.name === "proxy.mp4");
|
|
138
|
+
if (!sourceFile || !proxyFile) {
|
|
139
|
+
throw new Error(`Failed to record video proxy metadata for ${assetId}`);
|
|
140
|
+
}
|
|
141
|
+
const written = await engine.metadata.artifacts.write(assetId, "proxy", videoProxyMetadata(sourceFile, proxyFile, proxy));
|
|
142
|
+
if (!written.ok) {
|
|
143
|
+
throw new Error(`Failed to record video proxy metadata for ${assetId}: ${written.error.message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export async function processVideoFiles(engine, payload, signal) {
|
|
147
|
+
const sourceWorkspace = await mkdtemp(join(tmpdir(), "videobook-source-"));
|
|
148
|
+
const sourceFilename = basename(payload.videoPath);
|
|
149
|
+
const sourcePath = join(sourceWorkspace, sourceFilename);
|
|
150
|
+
try {
|
|
151
|
+
if (!await snapshotAssetFile(engine, payload.assetId, sourceFilename, sourcePath)) {
|
|
152
|
+
await copyFile(payload.videoPath, sourcePath);
|
|
153
|
+
}
|
|
154
|
+
return await processVideoSnapshot(engine, { ...payload, videoPath: sourcePath }, signal);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await rm(sourceWorkspace, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function processVideoSnapshot(engine, payload, signal) {
|
|
161
|
+
const { videoPath, assetId, assetDir, bookId, provenance, force, skipDownstreamJobs, preserveAudioArtifacts, reviveVisualAnalysis, } = payload;
|
|
162
|
+
signal?.throwIfAborted();
|
|
163
|
+
const probe = await probeVideo(videoPath, { signal });
|
|
164
|
+
if (probe.videoCodec === null || probe.width <= 0 || probe.height <= 0) {
|
|
165
|
+
throw new Error(`process_video requires a video stream: ${assetId}`);
|
|
166
|
+
}
|
|
167
|
+
const { width, height, duration } = probe;
|
|
168
|
+
const sourceFilename = basename(videoPath);
|
|
169
|
+
const importedSource = await engine.files.writeFromPath(assetId, sourceFilename, videoPath);
|
|
170
|
+
if (!importedSource.ok) {
|
|
171
|
+
throw new Error(`Failed to persist source video ${sourceFilename}: ${importedSource.error.message}`);
|
|
172
|
+
}
|
|
173
|
+
// Generate original.mp4 alongside non-mp4 uploads so downstream consumers
|
|
174
|
+
// (browser <video>, ffmpeg jobs, transcribe) get a canonical container.
|
|
175
|
+
// The source file (original.mkv / .mov / etc.) stays on disk as the
|
|
176
|
+
// archival truth — only the derived mp4 is fresh work.
|
|
177
|
+
let activeVideoPath = videoPath;
|
|
178
|
+
const sourceExt = extname(videoPath).toLowerCase();
|
|
179
|
+
if (sourceExt !== ".mp4") {
|
|
180
|
+
const tempPath = join(dirname(videoPath), "canonical.mp4");
|
|
181
|
+
// Explicit -map drops subtitle/data/attachment streams that the mp4
|
|
182
|
+
// muxer can't handle (e.g. subrip from MKV rips), which would otherwise
|
|
183
|
+
// fail the whole transcode. Also pin to the first audio track so files
|
|
184
|
+
// with multiple language tracks pick a deterministic stream.
|
|
185
|
+
//
|
|
186
|
+
// Stream-copy when the source codec is already mp4-compatible (h264
|
|
187
|
+
// video, aac audio). For a 10-min h264 mkv this turns a multi-minute
|
|
188
|
+
// re-encode into a few-second remux. Fall back to libx264 / aac for
|
|
189
|
+
// anything else (hevc, vp9, dts, opus, etc).
|
|
190
|
+
const canCopyVideo = probe.videoCodec === "h264";
|
|
191
|
+
const canCopyAudio = probe.audioCodec === "aac";
|
|
192
|
+
const videoArgs = canCopyVideo ? ["-c:v", "copy"] : H264_MEDIUM;
|
|
193
|
+
const audioArgs = !probe.hasAudio
|
|
194
|
+
? ["-an"]
|
|
195
|
+
: canCopyAudio
|
|
196
|
+
? ["-c:a", "copy"]
|
|
197
|
+
: AUDIO_OPTS;
|
|
198
|
+
const transcodeArgs = [
|
|
199
|
+
"-y",
|
|
200
|
+
"-i", videoPath,
|
|
201
|
+
"-map", "0:v:0",
|
|
202
|
+
...(probe.hasAudio ? ["-map", "0:a:0"] : []),
|
|
203
|
+
...videoArgs,
|
|
204
|
+
...audioArgs,
|
|
205
|
+
"-movflags", "+faststart",
|
|
206
|
+
"-f", "mp4",
|
|
207
|
+
tempPath,
|
|
208
|
+
];
|
|
209
|
+
await runFfmpeg(transcodeArgs, { signal });
|
|
210
|
+
const mp4Buf = await fsReadFile(tempPath);
|
|
211
|
+
await writeBinaryIfChanged(engine, assetId, "original.mp4", mp4Buf);
|
|
212
|
+
activeVideoPath = tempPath;
|
|
213
|
+
}
|
|
214
|
+
const activeSourcePath = sourceExt === ".mp4" ? sourceFilename : "original.mp4";
|
|
215
|
+
const activeProbe = sourceExt === ".mp4" ? probe : await probeVideo(activeVideoPath, { signal });
|
|
216
|
+
await registerMediaStreams(engine, assetId, activeSourcePath, activeProbe);
|
|
217
|
+
let frameCount = 0;
|
|
218
|
+
{
|
|
219
|
+
await engine.metadata.artifacts.write(assetId, "original", { ...provenance, width, height, duration, fps: probe.fps, hasAudio: probe.hasAudio });
|
|
220
|
+
if (duration <= 0)
|
|
221
|
+
return { success: true, width, height, duration, frame_count: 0 };
|
|
222
|
+
if (force) {
|
|
223
|
+
const currentManifest = await engine.files.manifest(assetId);
|
|
224
|
+
if (currentManifest.ok) {
|
|
225
|
+
const staleFiles = currentManifest.value.files
|
|
226
|
+
.map((file) => file.name)
|
|
227
|
+
.filter((filename) => filename === "original.jpg" ||
|
|
228
|
+
filename === "proxy.mp4" ||
|
|
229
|
+
filename.startsWith("original_frames/") ||
|
|
230
|
+
["landscape", "portrait", "square"].some((orientation) => filename === `${orientation}.mp4` ||
|
|
231
|
+
filename === `${orientation}.jpg` ||
|
|
232
|
+
filename.startsWith(`${orientation}_frames/`)));
|
|
233
|
+
for (const filename of staleFiles) {
|
|
234
|
+
await engine.files.delete(assetId, filename);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
await rm(join(assetDir, "original_frames"), { recursive: true, force: true });
|
|
238
|
+
await rm(join(assetDir, "original.jpg"), { force: true }).catch(() => { });
|
|
239
|
+
// Renders are derived from the old source video — drop them so the UI
|
|
240
|
+
// falls back to original.mp4 and prompts a re-render.
|
|
241
|
+
for (const orient of ["landscape", "portrait", "square"]) {
|
|
242
|
+
await rm(join(assetDir, `${orient}.mp4`), { force: true }).catch(() => { });
|
|
243
|
+
await rm(join(assetDir, `${orient}.jpg`), { force: true }).catch(() => { });
|
|
244
|
+
await rm(join(assetDir, `${orient}_frames`), { recursive: true, force: true }).catch(() => { });
|
|
245
|
+
await engine.metadata.artifacts.delete(assetId, `${orient}.render`);
|
|
246
|
+
}
|
|
247
|
+
await engine.metadata.artifacts.delete(assetId, "proxy");
|
|
248
|
+
}
|
|
249
|
+
const proxyWorkspace = await mkdtemp(join(tmpdir(), "videobook-proxy-"));
|
|
250
|
+
const proxyPath = join(proxyWorkspace, "proxy.mp4");
|
|
251
|
+
try {
|
|
252
|
+
let proxyProbe = null;
|
|
253
|
+
if (await snapshotAssetFile(engine, assetId, "proxy.mp4", proxyPath)) {
|
|
254
|
+
try {
|
|
255
|
+
const candidate = await probeVideo(proxyPath, { signal });
|
|
256
|
+
if (videoProxyValidationError(activeProbe, candidate) === null) {
|
|
257
|
+
proxyProbe = candidate;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
proxyProbe = null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!proxyProbe) {
|
|
265
|
+
const currentManifest = await engine.files.manifest(assetId);
|
|
266
|
+
if (!currentManifest.ok) {
|
|
267
|
+
throw new Error(`Failed to read media manifest for ${assetId}: ${currentManifest.error.message}`);
|
|
268
|
+
}
|
|
269
|
+
if (currentManifest.value.files.some((file) => file.name === "proxy.mp4")) {
|
|
270
|
+
const deleted = await engine.files.delete(assetId, "proxy.mp4");
|
|
271
|
+
if (!deleted.ok)
|
|
272
|
+
throw new Error(deleted.error.message);
|
|
273
|
+
}
|
|
274
|
+
await engine.metadata.artifacts.delete(assetId, "proxy");
|
|
275
|
+
const proxySourcePath = join(proxyWorkspace, `source${extname(activeSourcePath)}`);
|
|
276
|
+
if (!await snapshotAssetFile(engine, assetId, activeSourcePath, proxySourcePath)) {
|
|
277
|
+
throw new Error(`Media source is missing from the manifest: ${activeSourcePath}`);
|
|
278
|
+
}
|
|
279
|
+
const proxyArgs = (fpsMode) => [
|
|
280
|
+
"-y",
|
|
281
|
+
"-i", proxySourcePath,
|
|
282
|
+
...fpsMode,
|
|
283
|
+
"-map", "0:v:0",
|
|
284
|
+
...(activeProbe.hasAudio ? ["-map", "0:a:0"] : []),
|
|
285
|
+
"-vf",
|
|
286
|
+
"scale=1280:720:force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
|
287
|
+
"-c:v", "libx264",
|
|
288
|
+
"-preset", "veryfast",
|
|
289
|
+
"-crf", "28",
|
|
290
|
+
"-pix_fmt", "yuv420p",
|
|
291
|
+
...(activeProbe.hasAudio ? AUDIO_OPTS : ["-an"]),
|
|
292
|
+
"-movflags", "+faststart",
|
|
293
|
+
proxyPath,
|
|
294
|
+
];
|
|
295
|
+
// Passthrough keeps the source's exact frame timing — retimed
|
|
296
|
+
// (change_video_speed) and VFR sources would otherwise be resampled
|
|
297
|
+
// back onto the nominal frame grid, dropping frames and drifting the
|
|
298
|
+
// proxy away from the canonical duration. Fall back to the default
|
|
299
|
+
// sync mode for sources whose timestamps the muxer rejects.
|
|
300
|
+
try {
|
|
301
|
+
await runFfmpeg(proxyArgs(["-fps_mode:v", "passthrough"]), { signal });
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
await runFfmpeg(proxyArgs([]), { signal });
|
|
305
|
+
}
|
|
306
|
+
const candidate = await probeVideo(proxyPath, { signal });
|
|
307
|
+
const validationError = videoProxyValidationError(activeProbe, candidate);
|
|
308
|
+
if (validationError) {
|
|
309
|
+
throw new Error(`Invalid video proxy for ${assetId}: ${validationError}`);
|
|
310
|
+
}
|
|
311
|
+
const proxy = await engine.files.writeFromPath(assetId, "proxy.mp4", proxyPath);
|
|
312
|
+
if (!proxy.ok)
|
|
313
|
+
throw new Error(proxy.error.message);
|
|
314
|
+
proxyProbe = candidate;
|
|
315
|
+
}
|
|
316
|
+
await persistProxyMetadata(engine, assetId, activeSourcePath, proxyProbe);
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
await rm(proxyWorkspace, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
const timestamps = generateTimestamps(duration);
|
|
322
|
+
frameCount = timestamps.length;
|
|
323
|
+
const framesDir = join(assetDir, "original_frames");
|
|
324
|
+
await mkdir(framesDir, { recursive: true });
|
|
325
|
+
for (const ts of timestamps) {
|
|
326
|
+
const framePath = join(framesDir, `${formatTimestamp(ts)}.jpg`);
|
|
327
|
+
try {
|
|
328
|
+
await runFfmpeg([
|
|
329
|
+
"-y",
|
|
330
|
+
"-ss", String(ts),
|
|
331
|
+
"-i", activeVideoPath,
|
|
332
|
+
"-frames:v", "1",
|
|
333
|
+
"-q:v", "2",
|
|
334
|
+
"-strict", "unofficial",
|
|
335
|
+
framePath,
|
|
336
|
+
], { signal });
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// frame extraction failure is non-fatal
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Extract original.mp3 from the video (kept in sync with original.mp4 so
|
|
343
|
+
// standalone audio reads — timeline, transcription — match the source).
|
|
344
|
+
const existingMp3 = preserveAudioArtifacts
|
|
345
|
+
? await engine.files.read(assetId, "original.mp3")
|
|
346
|
+
: null;
|
|
347
|
+
if (probe.hasAudio && (!existingMp3 || !existingMp3.ok)) {
|
|
348
|
+
const mp3TempPath = join(assetDir, "original.mp3.tmp");
|
|
349
|
+
await runFfmpeg(["-y", "-i", activeVideoPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", "-f", "mp3", mp3TempPath], { signal });
|
|
350
|
+
try {
|
|
351
|
+
const mp3Buf = await fsReadFile(mp3TempPath);
|
|
352
|
+
await writeBinaryIfChanged(engine, assetId, "original.mp3", mp3Buf);
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
await rm(mp3TempPath, { force: true });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const existingWaveform = preserveAudioArtifacts
|
|
359
|
+
? await engine.metadata.waveforms.read(assetId)
|
|
360
|
+
: null;
|
|
361
|
+
if (!existingWaveform || !existingWaveform.ok) {
|
|
362
|
+
const waveform = await extractWaveform(activeVideoPath);
|
|
363
|
+
if (waveform.length > 0) {
|
|
364
|
+
const wavResult = await engine.metadata.waveforms.write(assetId, waveform);
|
|
365
|
+
if (!wavResult.ok) {
|
|
366
|
+
logger.warn("waveform-write-failed", { assetId, error: wavResult.error });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Create original.jpg from first frame if not already set (e.g., from OG image)
|
|
371
|
+
const thumbResult = await engine.files.read(assetId, "original.jpg");
|
|
372
|
+
if (!thumbResult.ok) {
|
|
373
|
+
try {
|
|
374
|
+
const firstFrameBuf = await fsReadFile(join(framesDir, "0.00.jpg"));
|
|
375
|
+
await engine.files.write(assetId, "original.jpg", firstFrameBuf);
|
|
376
|
+
}
|
|
377
|
+
catch { /* frame not available */ }
|
|
378
|
+
}
|
|
379
|
+
// If thumbnail is blank (all black/grey), replace with a mid-video frame
|
|
380
|
+
const thumbPath = join(assetDir, "original.jpg");
|
|
381
|
+
if (await isThumbnailBlank(thumbPath) && timestamps.length > 1) {
|
|
382
|
+
const midTs = timestamps[Math.floor(timestamps.length / 2)];
|
|
383
|
+
try {
|
|
384
|
+
const midFrameBuf = await fsReadFile(join(framesDir, `${formatTimestamp(midTs)}.jpg`));
|
|
385
|
+
await engine.files.write(assetId, "original.jpg", midFrameBuf);
|
|
386
|
+
}
|
|
387
|
+
catch { /* mid-frame not available */ }
|
|
388
|
+
}
|
|
389
|
+
await ingestWorkspaceOutputs(engine, assetId, ["original_frames"], "process_video", { width, height, duration, frame_count: frameCount });
|
|
390
|
+
}
|
|
391
|
+
// Speech-to-text stays eager: cheap and useful as
|
|
392
|
+
// a standalone artifact even if the asset never lands in a slot. Vision
|
|
393
|
+
// analysis (Gemini) is gated to slot drop in tools/assign-slot.ts — plus
|
|
394
|
+
// the slot-gated revival below for re-processed (trimmed/re-downloaded)
|
|
395
|
+
// videos, whose analysis sidecar was just invalidated.
|
|
396
|
+
const transcriptJobs = planVideoTranscriptJobs({
|
|
397
|
+
skipDownstreamJobs: Boolean(skipDownstreamJobs),
|
|
398
|
+
hasAudio: probe.hasAudio,
|
|
399
|
+
assetId,
|
|
400
|
+
assetDir,
|
|
401
|
+
bookId,
|
|
402
|
+
videoFilename: activeSourcePath,
|
|
403
|
+
identity: await videoTranscriptIdentity(engine, assetId, activeSourcePath),
|
|
404
|
+
hasOpenAiKey: Boolean(getApiKey("OPENAI_API_KEY")),
|
|
405
|
+
hasElevenLabsKey: Boolean(getApiKey("ELEVENLABS_API_KEY")),
|
|
406
|
+
});
|
|
407
|
+
if (process.env.NODE_ENV !== "test") {
|
|
408
|
+
await queue.enqueue(`semantic-index-${assetId}-${Date.now()}`, {
|
|
409
|
+
type: "semantic_index",
|
|
410
|
+
assetId,
|
|
411
|
+
bookId,
|
|
412
|
+
});
|
|
413
|
+
// Analysis revival below still respects slotting; tagging does not,
|
|
414
|
+
// because a Library clip has to be findable without being placed first.
|
|
415
|
+
await enqueueAssetTagging(engine, assetId);
|
|
416
|
+
if (!skipDownstreamJobs && probe.hasAudio) {
|
|
417
|
+
await enqueueVideoTranscriptJobs(transcriptJobs);
|
|
418
|
+
}
|
|
419
|
+
else if (!skipDownstreamJobs || reviveVisualAnalysis) {
|
|
420
|
+
// Silent video: no transcribe job will run, so revive the slot-drop
|
|
421
|
+
// vision analysis here; transcribe_video_audio's tail owns it for
|
|
422
|
+
// videos with audio (after the fresh transcript lands).
|
|
423
|
+
try {
|
|
424
|
+
await enqueueLazyAnalysisIfSlotted(engine, bookId, assetId);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// best-effort — analysis revival must not fail processing
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return { success: true, width, height, duration, frame_count: frameCount };
|
|
432
|
+
}
|
|
433
|
+
export function registerProcessVideoJob(engine) {
|
|
434
|
+
registerJobHandler("process_video", async (payload, ctx) => {
|
|
435
|
+
const processPayload = payload;
|
|
436
|
+
return withLocalLease(engine, processPayload.assetId, ctx, "process", async () => withTransientStorageRetry(() => processVideoFiles(engine, processPayload, ctx.signal), ctx.signal));
|
|
437
|
+
});
|
|
438
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { registerPostRecordJobs } from "./post-record.js";
|
|
2
|
+
import { registerColdOpenJobs } from "./cold-open.js";
|
|
3
|
+
import { bookManager } from "../engine.js";
|
|
4
|
+
import { registerCopyLibraryAssetJob } from "./copy-library-asset.js";
|
|
5
|
+
import { registerTranscribeJob } from "./transcribe.js";
|
|
6
|
+
import { registerTranslateTranscriptJob } from "./translate-transcript.js";
|
|
7
|
+
import { registerDownloadVideoJob } from "./download-video.js";
|
|
8
|
+
import { registerArchiveClipJob } from "./archive-clip.js";
|
|
9
|
+
import { registerRestoreArchiveClipJob } from "./restore-archive-clip.js";
|
|
10
|
+
import { registerDoctorFixJob } from "./doctor-fix.js";
|
|
11
|
+
import { registerTrimVideoJob } from "./trim-video.js";
|
|
12
|
+
import { registerTrimAudioJob } from "./trim-audio.js";
|
|
13
|
+
import { registerSplitVideoJob } from "./split-video.js";
|
|
14
|
+
import { registerExtractVideoClipJob } from "./extract-video-clip.js";
|
|
15
|
+
import { registerEvenSplitJob } from "./even-split.js";
|
|
16
|
+
import { registerPadVideoJob } from "./pad-video.js";
|
|
17
|
+
import { registerCropVideoJob } from "./crop-video.js";
|
|
18
|
+
import { registerCropImageJob } from "./crop-image.js";
|
|
19
|
+
import { registerApplyCutsJob } from "./apply-cuts.js";
|
|
20
|
+
import { registerApplySfxJob } from "./apply-sfx.js";
|
|
21
|
+
import { registerChangeVideoSpeedJob } from "./change-video-speed.js";
|
|
22
|
+
import { registerTimewarpVideoJob } from "./timewarp-video.js";
|
|
23
|
+
import { registerSyncVideoToAudioJob } from "./sync-video-to-audio.js";
|
|
24
|
+
import { registerAudioBoostJob } from "./audio-boost.js";
|
|
25
|
+
import { registerAudioMixJob } from "./audio-mix.js";
|
|
26
|
+
import { registerReverseVideoJob } from "./reverse-video.js";
|
|
27
|
+
import { registerInjectFirstFrameJob } from "./inject-first-frame.js";
|
|
28
|
+
import { registerExtractFrameJob } from "./extract-frame.js";
|
|
29
|
+
import { registerExtractNotebookFrameJob } from "./extract-notebook-frame.js";
|
|
30
|
+
import { registerExtractNotebookAudioJob } from "./extract-notebook-audio.js";
|
|
31
|
+
import { registerExtractAudioJob } from "./extract-audio.js";
|
|
32
|
+
import { registerAnalyzeMusicJob } from "./analyze-music.js";
|
|
33
|
+
import { registerPlanGoalJob } from "./plan-goal.js";
|
|
34
|
+
import { registerAssembleGoalJob } from "./assemble-goal.js";
|
|
35
|
+
import { registerCombineGoalJob } from "./combine-goal.js";
|
|
36
|
+
import { registerFinishGoalJob } from "./finish-goal.js";
|
|
37
|
+
import { registerLabelMusicSectionsJob } from "./label-music-sections.js";
|
|
38
|
+
import { registerEditingMomentsJob } from "./analyze-editing-moments.js";
|
|
39
|
+
import { registerExtractWaveformJob } from "./extract-waveform.js";
|
|
40
|
+
import { registerExtractVideoPosterJob } from "./extract-video-poster.js";
|
|
41
|
+
import { registerReplaceVideoAudioJob } from "./replace-video-audio.js";
|
|
42
|
+
import { registerSpliceVideoJob } from "./splice-video.js";
|
|
43
|
+
import { registerSmoothSceneConcatJob } from "./smooth-scene-concat.js";
|
|
44
|
+
import { registerCompleteProviderTaskJob } from "./complete-provider-task.js";
|
|
45
|
+
import { registerRenderProjectJob } from "./render-project.js";
|
|
46
|
+
import { registerRenderQrOutroJob } from "./render-qr-outro.js";
|
|
47
|
+
import { registerProcessVideoJob } from "./process-video.js";
|
|
48
|
+
import { registerProcessAudioJob } from "./process-audio.js";
|
|
49
|
+
import { registerTranscribeVideoAudioJob } from "./transcribe-video-audio.js";
|
|
50
|
+
import { registerAnalyzeVideoJob } from "./analyze-video.js";
|
|
51
|
+
import { registerAnalyzeKeywordPlacementJob } from "./analyze-keyword-placement.js";
|
|
52
|
+
import { registerAnalyzeAliveCaptionsJob } from "./analyze-alive-captions.js";
|
|
53
|
+
import { registerOsgPlacementJob } from "./osg-placement.js";
|
|
54
|
+
import { registerBurnWatermarkJob } from "./burn-watermark.js";
|
|
55
|
+
import { registerSuggestCutsJob } from "./suggest-cuts.js";
|
|
56
|
+
import { registerSuggestSfxJob } from "./suggest-sfx.js";
|
|
57
|
+
import { registerAnalyzeJokesJob } from "./analyze-jokes.js";
|
|
58
|
+
import { registerDetectScenesJob } from "./detect-scenes.js";
|
|
59
|
+
import { registerDescribeImageJob } from "./describe-image.js";
|
|
60
|
+
import { registerRewriteScriptJob } from "./rewrite-script.js";
|
|
61
|
+
import { registerRenderTimelineJob } from "./render-timeline.js";
|
|
62
|
+
import { registerExportSequenceJob } from "./export-sequence.js";
|
|
63
|
+
import { registerProcessImageJob } from "./process-image.js";
|
|
64
|
+
import { registerIsolateVocalsJob } from "./isolate-vocals.js";
|
|
65
|
+
import { registerCleanVideoAudioJob } from "./clean-video-audio.js";
|
|
66
|
+
import { registerPostToXJob } from "./post-to-x.js";
|
|
67
|
+
import { registerAcestepCoverJob } from "./generate-acestep-cover.js";
|
|
68
|
+
import { registerExtendAudioJob } from "./extend-audio.js";
|
|
69
|
+
import { registerRemoveSoundJob } from "./remove-sound.js";
|
|
70
|
+
import { registerRunNotebookJob } from "./run-notebook.js";
|
|
71
|
+
import { registerRelayoutNotebookJob } from "./relayout-notebook.js";
|
|
72
|
+
import { registerRepairSearchIndexesJob } from "./repair-search-indexes.js";
|
|
73
|
+
import { registerSemanticIndexJob } from "./semantic-index.js";
|
|
74
|
+
import { registerBackfillAssetTagsJob } from "./backfill-asset-tags.js";
|
|
75
|
+
import { registerForgetAssetJob } from "./forget-asset.js";
|
|
76
|
+
import { registerTagAssetJob } from "./tag-asset.js";
|
|
77
|
+
import { registerPrepareSearchReferenceJob } from "./prepare-search-reference.js";
|
|
78
|
+
import { registerPrepareTemporalSearchJob } from "./prepare-temporal-search.js";
|
|
79
|
+
import { registerImageSubjectJobs } from "./image-subjects.js";
|
|
80
|
+
import { registerGenerateMediaJob } from "./generate-media.js";
|
|
81
|
+
export function registerAllJobHandlers(engine, books = bookManager) {
|
|
82
|
+
registerTranscribeJob(engine);
|
|
83
|
+
registerTranslateTranscriptJob(engine);
|
|
84
|
+
registerDownloadVideoJob(engine);
|
|
85
|
+
registerArchiveClipJob(engine);
|
|
86
|
+
registerRestoreArchiveClipJob(engine);
|
|
87
|
+
registerDoctorFixJob(engine);
|
|
88
|
+
registerTrimVideoJob(engine);
|
|
89
|
+
registerTrimAudioJob(engine);
|
|
90
|
+
registerSplitVideoJob(engine);
|
|
91
|
+
registerExtractVideoClipJob(engine);
|
|
92
|
+
registerColdOpenJobs(engine);
|
|
93
|
+
registerEvenSplitJob(engine);
|
|
94
|
+
registerPadVideoJob(engine);
|
|
95
|
+
registerCropVideoJob(engine);
|
|
96
|
+
registerCropImageJob(engine);
|
|
97
|
+
registerApplyCutsJob(engine);
|
|
98
|
+
registerApplySfxJob(engine);
|
|
99
|
+
registerChangeVideoSpeedJob(engine);
|
|
100
|
+
registerTimewarpVideoJob(engine);
|
|
101
|
+
registerSyncVideoToAudioJob(engine);
|
|
102
|
+
registerAudioBoostJob(engine);
|
|
103
|
+
registerAudioMixJob(engine);
|
|
104
|
+
registerReverseVideoJob(engine);
|
|
105
|
+
registerInjectFirstFrameJob(engine);
|
|
106
|
+
registerExtractFrameJob(engine);
|
|
107
|
+
registerExtractNotebookFrameJob(engine);
|
|
108
|
+
registerExtractNotebookAudioJob(engine);
|
|
109
|
+
registerExtractAudioJob(engine);
|
|
110
|
+
registerExtractWaveformJob(engine);
|
|
111
|
+
registerExtractVideoPosterJob(engine);
|
|
112
|
+
registerAnalyzeMusicJob(engine);
|
|
113
|
+
registerPlanGoalJob(engine);
|
|
114
|
+
registerAssembleGoalJob(engine);
|
|
115
|
+
registerCombineGoalJob(engine);
|
|
116
|
+
registerFinishGoalJob(engine);
|
|
117
|
+
registerLabelMusicSectionsJob(engine);
|
|
118
|
+
registerEditingMomentsJob(engine);
|
|
119
|
+
registerReplaceVideoAudioJob(engine);
|
|
120
|
+
registerSpliceVideoJob(engine);
|
|
121
|
+
registerSmoothSceneConcatJob(engine);
|
|
122
|
+
registerCompleteProviderTaskJob(engine);
|
|
123
|
+
registerRenderProjectJob(engine);
|
|
124
|
+
registerRenderQrOutroJob(engine);
|
|
125
|
+
registerProcessVideoJob(engine);
|
|
126
|
+
registerProcessAudioJob(engine);
|
|
127
|
+
registerTranscribeVideoAudioJob(engine);
|
|
128
|
+
registerAnalyzeVideoJob(engine);
|
|
129
|
+
registerAnalyzeKeywordPlacementJob(engine);
|
|
130
|
+
registerAnalyzeAliveCaptionsJob(engine);
|
|
131
|
+
registerOsgPlacementJob(engine);
|
|
132
|
+
registerBurnWatermarkJob(engine);
|
|
133
|
+
registerSuggestCutsJob(engine);
|
|
134
|
+
registerSuggestSfxJob(engine);
|
|
135
|
+
registerAnalyzeJokesJob(engine);
|
|
136
|
+
registerDetectScenesJob(engine);
|
|
137
|
+
registerDescribeImageJob(engine);
|
|
138
|
+
registerRewriteScriptJob(engine);
|
|
139
|
+
registerRenderTimelineJob(engine);
|
|
140
|
+
registerExportSequenceJob(engine);
|
|
141
|
+
registerProcessImageJob(engine);
|
|
142
|
+
registerIsolateVocalsJob(engine);
|
|
143
|
+
registerCleanVideoAudioJob(engine);
|
|
144
|
+
registerGenerateMediaJob(engine);
|
|
145
|
+
registerAcestepCoverJob(engine);
|
|
146
|
+
registerExtendAudioJob(engine);
|
|
147
|
+
registerRemoveSoundJob(engine);
|
|
148
|
+
registerPostToXJob(engine);
|
|
149
|
+
registerPostRecordJobs(engine);
|
|
150
|
+
registerRunNotebookJob(engine);
|
|
151
|
+
registerRelayoutNotebookJob(engine);
|
|
152
|
+
registerSemanticIndexJob(engine);
|
|
153
|
+
registerTagAssetJob(engine);
|
|
154
|
+
registerBackfillAssetTagsJob(engine);
|
|
155
|
+
registerForgetAssetJob(engine);
|
|
156
|
+
registerRepairSearchIndexesJob(engine);
|
|
157
|
+
registerPrepareSearchReferenceJob(engine);
|
|
158
|
+
registerPrepareTemporalSearchJob(engine);
|
|
159
|
+
registerImageSubjectJobs(engine);
|
|
160
|
+
registerCopyLibraryAssetJob(books);
|
|
161
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { notebookGridTag } from "videobook-engine/notebook-grid";
|
|
2
|
+
import { notebookDocumentsEqual, relayoutNotebookDocument, } from "../notebook-relayout.js";
|
|
3
|
+
import { withNotebookMutation } from "../notebook-mutations.js";
|
|
4
|
+
import { registerJobHandler, resolveBookEngine } from "../queue.js";
|
|
5
|
+
export async function executeRelayoutNotebook(engine, payload) {
|
|
6
|
+
return await withNotebookMutation(payload.notebookId, async () => {
|
|
7
|
+
if (payload.expectedRevision && payload.expectedRevision !== engine.head) {
|
|
8
|
+
throw new Error(`Stale notebook revision: expected ${payload.expectedRevision}`);
|
|
9
|
+
}
|
|
10
|
+
return await relayoutNotebook(engine, payload);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function relayoutNotebook(engine, payload) {
|
|
14
|
+
const before = engine.notebooks.read(payload.notebookId);
|
|
15
|
+
if (!before.ok)
|
|
16
|
+
throw new Error(before.error.message);
|
|
17
|
+
const next = relayoutNotebookDocument(engine, before.value);
|
|
18
|
+
if (notebookDocumentsEqual(before.value, next)) {
|
|
19
|
+
return {
|
|
20
|
+
changed: false,
|
|
21
|
+
notebook_id: payload.notebookId,
|
|
22
|
+
cell_count: next.cells.length,
|
|
23
|
+
edge_count: next.edges.length,
|
|
24
|
+
revision: engine.head,
|
|
25
|
+
cells: next.cells.map((cell) => ({
|
|
26
|
+
id: cell.id,
|
|
27
|
+
type: cell.type,
|
|
28
|
+
address: notebookGridTag(cell.slot),
|
|
29
|
+
slot: cell.slot,
|
|
30
|
+
output_artifact_id: cell.outputArtifactId ?? null,
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const write = await engine.notebooks.write(next, {
|
|
35
|
+
operation: "relayout_notebook",
|
|
36
|
+
details: { notebookId: payload.notebookId },
|
|
37
|
+
});
|
|
38
|
+
if (!write.ok)
|
|
39
|
+
throw new Error(write.error.message);
|
|
40
|
+
const after = engine.notebooks.read(payload.notebookId);
|
|
41
|
+
if (!after.ok)
|
|
42
|
+
throw new Error(after.error.message);
|
|
43
|
+
return {
|
|
44
|
+
changed: true,
|
|
45
|
+
notebook_id: payload.notebookId,
|
|
46
|
+
cell_count: after.value.cells.length,
|
|
47
|
+
edge_count: after.value.edges.length,
|
|
48
|
+
revision: write.value.hash,
|
|
49
|
+
cells: after.value.cells.map((cell) => ({
|
|
50
|
+
id: cell.id,
|
|
51
|
+
type: cell.type,
|
|
52
|
+
address: notebookGridTag(cell.slot),
|
|
53
|
+
slot: cell.slot,
|
|
54
|
+
output_artifact_id: cell.outputArtifactId ?? null,
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function registerRelayoutNotebookJob(_fs) {
|
|
59
|
+
registerJobHandler("relayout_notebook", async (payload) => {
|
|
60
|
+
const notebookId = typeof payload.notebookId === "string"
|
|
61
|
+
? payload.notebookId
|
|
62
|
+
: "";
|
|
63
|
+
const bookId = typeof payload.bookId === "string" ? payload.bookId : "";
|
|
64
|
+
if (!notebookId)
|
|
65
|
+
throw new Error("relayout_notebook requires notebookId");
|
|
66
|
+
if (!bookId)
|
|
67
|
+
throw new Error("relayout_notebook requires bookId");
|
|
68
|
+
const engine = await resolveBookEngine(bookId);
|
|
69
|
+
return executeRelayoutNotebook(engine, {
|
|
70
|
+
type: "relayout_notebook",
|
|
71
|
+
bookId,
|
|
72
|
+
notebookId,
|
|
73
|
+
...(typeof payload.expectedRevision === "string"
|
|
74
|
+
? { expectedRevision: payload.expectedRevision }
|
|
75
|
+
: {}),
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|