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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `videobook` npm package are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and the package uses [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-10-19
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `videobook` command-line interface: `serve` runs the MCP server with the
|
|
14
|
+
bundled web UI (or `--headless` for MCP and API only), `mcp` serves MCP over
|
|
15
|
+
stdio for agent hosts, `run` and `tools` call MCP tools from the shell,
|
|
16
|
+
`gc-books` reclaims catalog storage, and `doctor` checks the local
|
|
17
|
+
environment.
|
|
18
|
+
- Installed data defaults to `~/.videobook`, overridable with `VIDEOBOOK_HOME`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Justin Tanner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,134 @@
|
|
|
1
|
-
#
|
|
1
|
+
# videobook
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Videobook is a local-first notebook studio for generative media: images,
|
|
4
|
+
videos, audio, prompts, characters and scenes live as cells in an executable
|
|
5
|
+
graph, and every change is recorded as a revision. This package installs one
|
|
6
|
+
`videobook` command that runs the Videobook MCP server three ways:
|
|
7
|
+
|
|
8
|
+
- with the web UI at `http://127.0.0.1:3001/`
|
|
9
|
+
- headless, serving MCP and the HTTP API only
|
|
10
|
+
- over stdio, for agent hosts such as Claude Code
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 22 or newer.
|
|
15
|
+
- macOS (Apple Silicon or Intel) or Linux (x64 or arm64). Windows x64 has
|
|
16
|
+
every native binary but is untested.
|
|
17
|
+
- About 1.2 GB of disk. Installing downloads the ffmpeg and yt-dlp binaries
|
|
18
|
+
from GitHub, and the first video render downloads Chrome Headless Shell
|
|
19
|
+
(about 150 MB) into `~/.cache/videobook/remotion`.
|
|
20
|
+
- `ffmpeg` and `ffprobe` on `PATH` are recommended. Apple Silicon and Linux
|
|
21
|
+
arm64 always use the system `ffprobe`, and every platform falls back to the
|
|
22
|
+
system binaries when the bundled ones are missing.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g videobook
|
|
28
|
+
videobook doctor
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
npm 12 and newer skip dependency install scripts unless you approve them.
|
|
32
|
+
Approve the six that videobook needs, either per install or once for your
|
|
33
|
+
user:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g videobook --allow-scripts=ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild
|
|
37
|
+
npm config set allow-scripts=ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild --location=user
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `ffmpeg-static` downloads ffmpeg, which every media operation needs.
|
|
41
|
+
- `ytdlp-nodejs` downloads yt-dlp, which downloading videos from URLs needs.
|
|
42
|
+
- `@dolthub/doltlite`, `usearch` and `onnxruntime-node` use prebuilt native
|
|
43
|
+
binaries and only compile or fetch extras on unusual platforms.
|
|
44
|
+
- `esbuild` checks its platform binary for the Remotion renderer.
|
|
45
|
+
|
|
46
|
+
`videobook doctor` reports anything missing and prints the fix.
|
|
47
|
+
|
|
48
|
+
## Run
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
videobook # same as videobook serve
|
|
52
|
+
videobook serve --open # serve MCP and the web UI, open a browser
|
|
53
|
+
videobook serve --headless # MCP and HTTP API only
|
|
54
|
+
videobook mcp # MCP over stdio for agent hosts
|
|
55
|
+
videobook run list_books # call one MCP tool and print its result
|
|
56
|
+
videobook run create_book name=demo
|
|
57
|
+
videobook tools # list tools and their parameters
|
|
58
|
+
videobook gc-books # reclaim catalog storage in every book
|
|
59
|
+
videobook doctor # check Node, binaries, native modules and keys
|
|
60
|
+
videobook --version
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Every command accepts `--port <n>` (default 3001), `--library <dir>`,
|
|
64
|
+
`--home <dir>` and `--log-level <silent|error|warn|info|debug>`. Tool
|
|
65
|
+
arguments are `key=value`; values that parse as JSON are passed as JSON. Exit
|
|
66
|
+
codes are 0 for success, 1 for a failure and 2 for a usage error.
|
|
67
|
+
|
|
68
|
+
## Agent hosts
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
claude mcp add videobook -- videobook mcp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Other hosts take the same command, for example
|
|
75
|
+
`{ "command": "videobook", "args": ["mcp"] }`. stdio accepts clients that
|
|
76
|
+
start with the 2025-era `initialize` and clients that use MCP 2026-07-28.
|
|
77
|
+
While `videobook serve` runs, HTTP clients can connect to
|
|
78
|
+
`http://127.0.0.1:3001/mcp`, which speaks MCP 2026-07-28 only.
|
|
79
|
+
|
|
80
|
+
## One server per library
|
|
81
|
+
|
|
82
|
+
A library is owned by one videobook process at a time, recorded in
|
|
83
|
+
`<library>/.videobook-server.json`. A second `videobook serve` for the same
|
|
84
|
+
library exits and names the running URL. `videobook mcp` and `videobook run`
|
|
85
|
+
relay to the running server when there is one; otherwise they start one in
|
|
86
|
+
their own process, which `videobook mcp` keeps serving HTTP and the web UI
|
|
87
|
+
for as long as the agent host keeps it open. `videobook gc-books` refuses to
|
|
88
|
+
run while a server owns the library.
|
|
89
|
+
|
|
90
|
+
## Data and configuration
|
|
91
|
+
|
|
92
|
+
Installed data lives in `~/.videobook`:
|
|
93
|
+
|
|
94
|
+
- `books/`, the library of books
|
|
95
|
+
- `app-state.json`, connected X and Dropbox accounts
|
|
96
|
+
- `.env`, provider keys, also editable from Settings in the web UI
|
|
97
|
+
|
|
98
|
+
`VIDEOBOOK_HOME` or `--home` moves all three. `VIDEOBOOK_LIBRARY_DIR`,
|
|
99
|
+
`VIDEOBOOK_APP_STATE_PATH` and `VIDEOBOOK_ENV_PATH` move one each; `--home`
|
|
100
|
+
overrides any of them inherited from your shell. Set `VIDEOBOOK_HOME` and
|
|
101
|
+
`VIDEOBOOK_ENV_PATH` in the environment, not in `.env`, because they decide
|
|
102
|
+
which `.env` is read.
|
|
103
|
+
|
|
104
|
+
Provider keys: `OPENAI_API_KEY`, `KIE_API_KEY`, `XAI_API_KEY`, `FAL_API_KEY`,
|
|
105
|
+
`DASHSCOPE_API_KEY`, `ELEVENLABS_API_KEY`, `KIMI_CODING_API_KEY`,
|
|
106
|
+
`X_CLIENT_ID` and `X_CLIENT_SECRET`, `DROPBOX_CLIENT_ID` and
|
|
107
|
+
`DROPBOX_CLIENT_SECRET`, and the `B2_*` storage settings. The server starts
|
|
108
|
+
without any of them; tools that need one report which.
|
|
109
|
+
|
|
110
|
+
Caches live in `~/.cache/videobook`. Other settings: `PORT`,
|
|
111
|
+
`VIDEOBOOK_FFMPEG_PATH`, `VIDEOBOOK_FFPROBE_PATH`,
|
|
112
|
+
`VIDEOBOOK_REMOTION_CACHE_DIR`, `VIDEOBOOK_MODEL_CACHE_DIR`,
|
|
113
|
+
`VIDEOBOOK_DOWNLOADS_DIR` and `VIDEOBOOK_LOG_LEVEL`.
|
|
114
|
+
|
|
115
|
+
## Platform notes
|
|
116
|
+
|
|
117
|
+
- macOS on Apple Silicon is the primary platform. The bundled ffprobe is an
|
|
118
|
+
Intel build, so install `ffprobe` with `brew install ffmpeg`.
|
|
119
|
+
- macOS on Intel works, but ONNX Runtime has no Intel Mac build, so semantic
|
|
120
|
+
search is unavailable there.
|
|
121
|
+
- Linux x64 is supported. Linux arm64 needs `ffprobe` from the system package
|
|
122
|
+
manager.
|
|
123
|
+
- Windows x64 is untested.
|
|
124
|
+
|
|
125
|
+
## Security
|
|
126
|
+
|
|
127
|
+
Videobook is a local, single-user service. It listens on `127.0.0.1` only and
|
|
128
|
+
accepts requests from loopback hosts and origins. It has no user
|
|
129
|
+
authentication, so do not expose it to other machines or share it between
|
|
130
|
+
users.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
package/bin/videobook.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
3
|
+
if (major < 22) {
|
|
4
|
+
process.stderr.write(`videobook needs Node.js 22 or newer; this is ${process.version}.\nInstall Node.js 22+ from https://nodejs.org or your version manager, then run videobook again.\n`);
|
|
5
|
+
process.exit(1);
|
|
6
|
+
}
|
|
7
|
+
await import(new URL("../dist/cli/entry.js", import.meta.url).href);
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { AceStepError } from "./errors.js";
|
|
3
|
+
// The live ace-server job flow (tools/ace-server.cpp, verified against version
|
|
4
|
+
// 9761469): POST /synth returns {"id"} immediately, a FIFO worker thread runs
|
|
5
|
+
// the GPU job, GET /job?id reports running|done|failed|cancelled, GET
|
|
6
|
+
// /job?id&result=1 serves the multipart/mixed body (fixed boundary
|
|
7
|
+
// "ace-batch-boundary": one audio part plus one latent part per track), and
|
|
8
|
+
// POST /job?id&cancel=1 cancels a running job. Completed jobs are evicted FIFO
|
|
9
|
+
// past 32, so the result is fetched immediately after the poll reports done.
|
|
10
|
+
const DEFAULT_SYNTH_TIMEOUT_MS = 30 * 60_000;
|
|
11
|
+
const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
12
|
+
const SUBMIT_TIMEOUT_MS = 60_000;
|
|
13
|
+
const POLL_TIMEOUT_MS = 5_000;
|
|
14
|
+
const RESULT_TIMEOUT_MS = 120_000;
|
|
15
|
+
const ERROR_BODY_PREVIEW_CHARS = 500;
|
|
16
|
+
export async function isHealthy(baseUrl, timeoutMs = 500) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
19
|
+
return response.ok;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function wireRequest(request) {
|
|
26
|
+
return {
|
|
27
|
+
caption: request.caption,
|
|
28
|
+
lyrics: request.lyrics,
|
|
29
|
+
task_type: request.taskType,
|
|
30
|
+
output_format: request.outputFormat,
|
|
31
|
+
seed: request.seed,
|
|
32
|
+
inference_steps: request.inferenceSteps,
|
|
33
|
+
audio_cover_strength: request.audioCoverStrength,
|
|
34
|
+
synth_batch_size: request.synthBatchSize,
|
|
35
|
+
synth_model: request.synthModel,
|
|
36
|
+
duration: request.duration,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function headerRecord(headerBlock) {
|
|
40
|
+
const headers = {};
|
|
41
|
+
for (const line of headerBlock.split("\r\n")) {
|
|
42
|
+
const colon = line.indexOf(":");
|
|
43
|
+
if (colon === -1)
|
|
44
|
+
continue;
|
|
45
|
+
headers[line.slice(0, colon).trim().toLowerCase()] = line.slice(colon + 1).trim();
|
|
46
|
+
}
|
|
47
|
+
return headers;
|
|
48
|
+
}
|
|
49
|
+
export function parseMultipartMixed(body, contentType) {
|
|
50
|
+
const boundaryMatch = /boundary="?([^";]+)"?/u.exec(contentType);
|
|
51
|
+
if (boundaryMatch === null) {
|
|
52
|
+
throw new AceStepError("malformed_response", `ace-server response has no multipart boundary (content-type: "${contentType}")`);
|
|
53
|
+
}
|
|
54
|
+
const delimiter = Buffer.from(`--${boundaryMatch[1]}`);
|
|
55
|
+
const parts = [];
|
|
56
|
+
let offset = body.indexOf(delimiter);
|
|
57
|
+
while (offset !== -1) {
|
|
58
|
+
const afterDelimiter = offset + delimiter.length;
|
|
59
|
+
if (body.subarray(afterDelimiter, afterDelimiter + 2).toString("latin1") === "--")
|
|
60
|
+
break;
|
|
61
|
+
const boundaryLineEnd = body.indexOf("\r\n", afterDelimiter);
|
|
62
|
+
const headerEnd = body.indexOf("\r\n\r\n", afterDelimiter);
|
|
63
|
+
const next = headerEnd === -1 ? -1 : body.indexOf(delimiter, headerEnd + 4);
|
|
64
|
+
if (boundaryLineEnd === -1 || headerEnd === -1 || next === -1) {
|
|
65
|
+
throw new AceStepError("malformed_response", "ace-server multipart response is truncated");
|
|
66
|
+
}
|
|
67
|
+
const headerBlock = body.subarray(boundaryLineEnd + 2, headerEnd).toString("utf8");
|
|
68
|
+
let dataEnd = next;
|
|
69
|
+
if (body.subarray(dataEnd - 2, dataEnd).toString("latin1") === "\r\n")
|
|
70
|
+
dataEnd -= 2;
|
|
71
|
+
parts.push({ headers: headerRecord(headerBlock), data: body.subarray(headerEnd + 4, dataEnd) });
|
|
72
|
+
offset = next;
|
|
73
|
+
}
|
|
74
|
+
if (parts.length === 0) {
|
|
75
|
+
throw new AceStepError("malformed_response", "ace-server multipart response contained no parts");
|
|
76
|
+
}
|
|
77
|
+
return parts;
|
|
78
|
+
}
|
|
79
|
+
function describeFetchError(error) {
|
|
80
|
+
if (error instanceof Error) {
|
|
81
|
+
const cause = error.cause instanceof Error ? `: ${error.cause.message}` : "";
|
|
82
|
+
return `${error.message}${cause}`;
|
|
83
|
+
}
|
|
84
|
+
return String(error);
|
|
85
|
+
}
|
|
86
|
+
async function readErrorPreview(response) {
|
|
87
|
+
const bodyText = await response.text().catch(() => "");
|
|
88
|
+
return bodyText.slice(0, ERROR_BODY_PREVIEW_CHARS);
|
|
89
|
+
}
|
|
90
|
+
async function submitSynthJob(baseUrl, srcWavPath, request, signal) {
|
|
91
|
+
const sourceBytes = await readFile(srcWavPath);
|
|
92
|
+
const form = new FormData();
|
|
93
|
+
form.set("request", new Blob([JSON.stringify(wireRequest(request))], { type: "application/json" }), "request.json");
|
|
94
|
+
form.set("audio", new Blob([sourceBytes], { type: "audio/wav" }), "source.wav");
|
|
95
|
+
const response = await fetch(`${baseUrl}/synth`, { method: "POST", body: form, signal });
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new AceStepError("synth_http_error", `ace-server /synth returned ${response.status}: ${await readErrorPreview(response)}`);
|
|
98
|
+
}
|
|
99
|
+
const body = (await response.json().catch(() => null));
|
|
100
|
+
if (typeof body?.id !== "string" || body.id.length === 0) {
|
|
101
|
+
throw new AceStepError("malformed_response", "ace-server /synth returned no job id");
|
|
102
|
+
}
|
|
103
|
+
return body.id;
|
|
104
|
+
}
|
|
105
|
+
async function readJobStatus(baseUrl, jobId) {
|
|
106
|
+
const response = await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}`, {
|
|
107
|
+
signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new AceStepError("synth_http_error", `ace-server /job returned ${response.status}: ${await readErrorPreview(response)}`);
|
|
111
|
+
}
|
|
112
|
+
const body = (await response.json().catch(() => null));
|
|
113
|
+
if (typeof body?.status !== "string") {
|
|
114
|
+
throw new AceStepError("malformed_response", "ace-server /job returned no status");
|
|
115
|
+
}
|
|
116
|
+
return body.status;
|
|
117
|
+
}
|
|
118
|
+
async function cancelSynthJob(baseUrl, jobId) {
|
|
119
|
+
try {
|
|
120
|
+
await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}&cancel=1`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Best-effort: the job also dies with the server, and an orphaned
|
|
127
|
+
// generation is discarded when its result is never fetched.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function fetchSynthResult(baseUrl, jobId) {
|
|
131
|
+
const response = await fetch(`${baseUrl}/job?id=${encodeURIComponent(jobId)}&result=1`, {
|
|
132
|
+
signal: AbortSignal.timeout(RESULT_TIMEOUT_MS),
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new AceStepError("synth_http_error", `ace-server /job result returned ${response.status}: ${await readErrorPreview(response)}`);
|
|
136
|
+
}
|
|
137
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
138
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
139
|
+
if (contentType.startsWith("audio/")) {
|
|
140
|
+
return { audio: body, audioContentType: contentType, latents: null };
|
|
141
|
+
}
|
|
142
|
+
const parts = parseMultipartMixed(body, contentType);
|
|
143
|
+
const audioPart = parts.find((part) => (part.headers["content-type"] ?? "").startsWith("audio/"));
|
|
144
|
+
if (audioPart === undefined) {
|
|
145
|
+
throw new AceStepError("malformed_response", "ace-server synth result contained no audio part");
|
|
146
|
+
}
|
|
147
|
+
const latentPart = parts.find((part) => (part.headers["content-type"] ?? "").startsWith("application/octet-stream"));
|
|
148
|
+
return {
|
|
149
|
+
audio: audioPart.data,
|
|
150
|
+
audioContentType: audioPart.headers["content-type"] ?? "audio/wav",
|
|
151
|
+
latents: latentPart?.data ?? null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function delay(ms, signal) {
|
|
155
|
+
await new Promise((resolve, reject) => {
|
|
156
|
+
const timer = setTimeout(() => {
|
|
157
|
+
signal.removeEventListener("abort", onAbort);
|
|
158
|
+
resolve();
|
|
159
|
+
}, ms);
|
|
160
|
+
const onAbort = () => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
163
|
+
};
|
|
164
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
export async function synthCover(baseUrl, srcWavPath, request, options = {}) {
|
|
168
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SYNTH_TIMEOUT_MS;
|
|
169
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
170
|
+
const deadlineSignal = AbortSignal.timeout(timeoutMs);
|
|
171
|
+
const signal = options.signal
|
|
172
|
+
? AbortSignal.any([options.signal, deadlineSignal])
|
|
173
|
+
: deadlineSignal;
|
|
174
|
+
let jobId = null;
|
|
175
|
+
try {
|
|
176
|
+
jobId = await submitSynthJob(baseUrl, srcWavPath, request, AbortSignal.any([signal, AbortSignal.timeout(SUBMIT_TIMEOUT_MS)]));
|
|
177
|
+
for (;;) {
|
|
178
|
+
const status = await readJobStatus(baseUrl, jobId);
|
|
179
|
+
if (status === "done")
|
|
180
|
+
return await fetchSynthResult(baseUrl, jobId);
|
|
181
|
+
if (status === "failed") {
|
|
182
|
+
throw new AceStepError("synth_failed", "ace-server reported the cover synthesis job as failed; the reason is in the [ace-step] logs above");
|
|
183
|
+
}
|
|
184
|
+
if (status === "cancelled") {
|
|
185
|
+
throw new AceStepError("synth_failed", "ace-server cancelled the cover synthesis job");
|
|
186
|
+
}
|
|
187
|
+
signal.throwIfAborted();
|
|
188
|
+
await delay(pollIntervalMs, signal);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error instanceof AceStepError)
|
|
193
|
+
throw error;
|
|
194
|
+
if (jobId !== null && signal.aborted)
|
|
195
|
+
await cancelSynthJob(baseUrl, jobId);
|
|
196
|
+
if (options.signal?.aborted)
|
|
197
|
+
throw error;
|
|
198
|
+
if (deadlineSignal.aborted) {
|
|
199
|
+
throw new AceStepError("synth_timeout", `ace-server cover synthesis did not finish within ${timeoutMs}ms`);
|
|
200
|
+
}
|
|
201
|
+
throw new AceStepError("server_exited", `ace-server request failed: ${describeFetchError(error)}. The server may have crashed or been stopped; check [ace-step] logs.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { isHealthy } from "./client.js";
|
|
5
|
+
const DEFAULT_PORT = 8317;
|
|
6
|
+
const DEFAULT_MODEL = "acestep-v15-turbo-Q4_K_M.gguf";
|
|
7
|
+
const TEXT_ENCODER_MODEL = "Qwen3-Embedding-0.6B-Q8_0.gguf";
|
|
8
|
+
const VAE_MODEL = "vae-BF16.gguf";
|
|
9
|
+
const GATE_HEALTH_TIMEOUT_MS = 500;
|
|
10
|
+
function acestepRoot() {
|
|
11
|
+
return join(homedir(), ".cache", "videobook", "acestep");
|
|
12
|
+
}
|
|
13
|
+
export function aceStepConfig() {
|
|
14
|
+
const baseUrl = process.env.VIDEOBOOK_ACESTEP_BASE_URL?.trim();
|
|
15
|
+
const parsedPort = Number.parseInt(process.env.VIDEOBOOK_ACESTEP_PORT ?? "", 10);
|
|
16
|
+
return {
|
|
17
|
+
baseUrl: baseUrl ? baseUrl.replace(/\/+$/u, "") : null,
|
|
18
|
+
serverPath: process.env.VIDEOBOOK_ACESTEP_SERVER_PATH?.trim() || join(acestepRoot(), "bin", "ace-server"),
|
|
19
|
+
modelsDir: process.env.VIDEOBOOK_ACESTEP_MODELS_DIR?.trim() || join(acestepRoot(), "models"),
|
|
20
|
+
port: Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : DEFAULT_PORT,
|
|
21
|
+
model: process.env.VIDEOBOOK_ACESTEP_MODEL?.trim() || DEFAULT_MODEL,
|
|
22
|
+
keepLoaded: process.env.VIDEOBOOK_ACESTEP_KEEP_LOADED === "1",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function requiredModelFiles(config = aceStepConfig()) {
|
|
26
|
+
return [config.model, TEXT_ENCODER_MODEL, VAE_MODEL];
|
|
27
|
+
}
|
|
28
|
+
export function checkLocalAvailability(config = aceStepConfig()) {
|
|
29
|
+
if (!existsSync(config.serverPath)) {
|
|
30
|
+
return {
|
|
31
|
+
available: false,
|
|
32
|
+
kind: "binary_missing",
|
|
33
|
+
reason: `acestep.cpp runtime is not installed: ace-server not found at ${config.serverPath}. ` +
|
|
34
|
+
`Run "npm run install:acestep", or set VIDEOBOOK_ACESTEP_SERVER_PATH / VIDEOBOOK_ACESTEP_BASE_URL.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const missing = requiredModelFiles(config).filter((name) => !existsSync(join(config.modelsDir, name)));
|
|
38
|
+
if (missing.length > 0) {
|
|
39
|
+
return {
|
|
40
|
+
available: false,
|
|
41
|
+
kind: "models_missing",
|
|
42
|
+
reason: `ACE-Step models missing from ${config.modelsDir}: ${missing.join(", ")}. Run "npm run install:acestep".`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { available: true };
|
|
46
|
+
}
|
|
47
|
+
export async function checkAceStepAvailability() {
|
|
48
|
+
const config = aceStepConfig();
|
|
49
|
+
if (config.baseUrl !== null) {
|
|
50
|
+
const healthy = await isHealthy(config.baseUrl, GATE_HEALTH_TIMEOUT_MS);
|
|
51
|
+
if (!healthy) {
|
|
52
|
+
return {
|
|
53
|
+
available: false,
|
|
54
|
+
kind: "unreachable",
|
|
55
|
+
reason: `ace-server at ${config.baseUrl} did not answer /health within ${GATE_HEALTH_TIMEOUT_MS}ms ` +
|
|
56
|
+
`(VIDEOBOOK_ACESTEP_BASE_URL). Start it or unset the variable.`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { available: true };
|
|
60
|
+
}
|
|
61
|
+
return checkLocalAvailability(config);
|
|
62
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { isHealthy } from "./client.js";
|
|
3
|
+
import { aceStepConfig, checkLocalAvailability } from "./config.js";
|
|
4
|
+
import { logger } from "../logger.js";
|
|
5
|
+
import { AceStepError } from "./errors.js";
|
|
6
|
+
const state = {
|
|
7
|
+
child: null,
|
|
8
|
+
handle: null,
|
|
9
|
+
starting: null,
|
|
10
|
+
spawnFailure: null,
|
|
11
|
+
lock: Promise.resolve(),
|
|
12
|
+
};
|
|
13
|
+
const HEALTH_POLL_INTERVAL_MS = 500;
|
|
14
|
+
const HEALTH_POLL_TIMEOUT_MS = 30_000;
|
|
15
|
+
const PROBE_TIMEOUT_MS = 700;
|
|
16
|
+
const EXTERNAL_HEALTH_ATTEMPTS = 3;
|
|
17
|
+
const STOP_GRACE_MS = 2_000;
|
|
18
|
+
async function delay(ms) {
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
}
|
|
21
|
+
function drainOutput(stream) {
|
|
22
|
+
if (stream === null)
|
|
23
|
+
return;
|
|
24
|
+
stream.resume();
|
|
25
|
+
}
|
|
26
|
+
// Identity check before adopting a listener on our port: /health must return the
|
|
27
|
+
// documented {"status":"ok"} shape and /props must serve JSON, so we never treat
|
|
28
|
+
// an unrelated local service as ace-server.
|
|
29
|
+
async function probeAceServer(baseUrl) {
|
|
30
|
+
try {
|
|
31
|
+
const health = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
|
|
32
|
+
if (!health.ok)
|
|
33
|
+
return false;
|
|
34
|
+
const healthBody = (await health.json());
|
|
35
|
+
if (healthBody?.status !== "ok")
|
|
36
|
+
return false;
|
|
37
|
+
const props = await fetch(`${baseUrl}/props`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
|
|
38
|
+
if (!props.ok)
|
|
39
|
+
return false;
|
|
40
|
+
const propsBody = await props.json();
|
|
41
|
+
return typeof propsBody === "object" && propsBody !== null;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function startServer(config, signal) {
|
|
48
|
+
const baseUrl = `http://127.0.0.1:${config.port}`;
|
|
49
|
+
if (await probeAceServer(baseUrl)) {
|
|
50
|
+
const adopted = { baseUrl, owned: false };
|
|
51
|
+
state.handle = adopted;
|
|
52
|
+
return adopted;
|
|
53
|
+
}
|
|
54
|
+
const availability = checkLocalAvailability(config);
|
|
55
|
+
if (!availability.available) {
|
|
56
|
+
throw new AceStepError(availability.kind === "models_missing" ? "models_missing" : "binary_missing", availability.reason);
|
|
57
|
+
}
|
|
58
|
+
const serverArgs = [
|
|
59
|
+
"--models",
|
|
60
|
+
config.modelsDir,
|
|
61
|
+
"--host",
|
|
62
|
+
"127.0.0.1",
|
|
63
|
+
"--port",
|
|
64
|
+
String(config.port),
|
|
65
|
+
];
|
|
66
|
+
if (config.keepLoaded)
|
|
67
|
+
serverArgs.push("--keep-loaded");
|
|
68
|
+
const child = spawn(config.serverPath, serverArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
69
|
+
drainOutput(child.stdout);
|
|
70
|
+
drainOutput(child.stderr);
|
|
71
|
+
state.child = child;
|
|
72
|
+
state.spawnFailure = null;
|
|
73
|
+
child.on("error", (error) => {
|
|
74
|
+
state.spawnFailure = new AceStepError("spawn_failed", `Failed to start ace-server at ${config.serverPath}: ${error.message}`);
|
|
75
|
+
});
|
|
76
|
+
child.on("exit", (code, exitSignal) => {
|
|
77
|
+
logger.error("ace-server-exited", { exitCode: code, signal: exitSignal });
|
|
78
|
+
if (state.child === child) {
|
|
79
|
+
state.child = null;
|
|
80
|
+
state.handle = null;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const deadline = Date.now() + HEALTH_POLL_TIMEOUT_MS;
|
|
84
|
+
while (Date.now() < deadline) {
|
|
85
|
+
if (signal?.aborted) {
|
|
86
|
+
child.kill("SIGKILL");
|
|
87
|
+
state.child = null;
|
|
88
|
+
signal.throwIfAborted();
|
|
89
|
+
}
|
|
90
|
+
if (state.spawnFailure !== null)
|
|
91
|
+
throw state.spawnFailure;
|
|
92
|
+
if (child.exitCode !== null) {
|
|
93
|
+
throw new AceStepError("spawn_failed", `ace-server exited during startup (code ${child.exitCode}); run the configured server directly to inspect its output`);
|
|
94
|
+
}
|
|
95
|
+
if (await isHealthy(baseUrl, PROBE_TIMEOUT_MS)) {
|
|
96
|
+
const owned = { baseUrl, owned: true };
|
|
97
|
+
state.handle = owned;
|
|
98
|
+
return owned;
|
|
99
|
+
}
|
|
100
|
+
await delay(HEALTH_POLL_INTERVAL_MS);
|
|
101
|
+
}
|
|
102
|
+
child.kill("SIGKILL");
|
|
103
|
+
state.child = null;
|
|
104
|
+
throw new AceStepError("health_timeout", `ace-server did not become healthy on ${baseUrl} within ${HEALTH_POLL_TIMEOUT_MS}ms`);
|
|
105
|
+
}
|
|
106
|
+
export async function ensureAceServer(options = {}) {
|
|
107
|
+
const config = aceStepConfig();
|
|
108
|
+
if (config.baseUrl !== null) {
|
|
109
|
+
for (let attempt = 0; attempt < EXTERNAL_HEALTH_ATTEMPTS; attempt += 1) {
|
|
110
|
+
if (await probeAceServer(config.baseUrl))
|
|
111
|
+
return { baseUrl: config.baseUrl, owned: false };
|
|
112
|
+
if (attempt < EXTERNAL_HEALTH_ATTEMPTS - 1)
|
|
113
|
+
await delay(PROBE_TIMEOUT_MS);
|
|
114
|
+
}
|
|
115
|
+
throw new AceStepError("unreachable", `ace-server at ${config.baseUrl} is not responding (VIDEOBOOK_ACESTEP_BASE_URL). Start it or unset the variable.`);
|
|
116
|
+
}
|
|
117
|
+
if (state.handle !== null && (await isHealthy(state.handle.baseUrl, PROBE_TIMEOUT_MS))) {
|
|
118
|
+
return state.handle;
|
|
119
|
+
}
|
|
120
|
+
if (state.starting !== null)
|
|
121
|
+
return state.starting;
|
|
122
|
+
state.starting = startServer(config, options.signal);
|
|
123
|
+
try {
|
|
124
|
+
return await state.starting;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
state.starting = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ace-server runs one GPU job at a time on a FIFO worker thread and evicts
|
|
131
|
+
// completed jobs past a small cap while holding every result in memory, so we
|
|
132
|
+
// keep at most one videobook synthesis in flight: queued covers wait here
|
|
133
|
+
// instead of piling result buffers onto the server.
|
|
134
|
+
export async function withSynthLock(fn) {
|
|
135
|
+
const previous = state.lock;
|
|
136
|
+
let release;
|
|
137
|
+
state.lock = new Promise((resolve) => {
|
|
138
|
+
release = resolve;
|
|
139
|
+
});
|
|
140
|
+
await previous;
|
|
141
|
+
try {
|
|
142
|
+
return await fn();
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
release?.();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export async function stopAceServer() {
|
|
149
|
+
const child = state.child;
|
|
150
|
+
state.child = null;
|
|
151
|
+
state.handle = null;
|
|
152
|
+
state.starting = null;
|
|
153
|
+
if (child === null || child.exitCode !== null)
|
|
154
|
+
return;
|
|
155
|
+
const exited = new Promise((resolve) => {
|
|
156
|
+
child.once("exit", () => resolve());
|
|
157
|
+
});
|
|
158
|
+
child.kill("SIGTERM");
|
|
159
|
+
const outcome = await Promise.race([
|
|
160
|
+
exited.then(() => "exited"),
|
|
161
|
+
delay(STOP_GRACE_MS).then(() => "timeout"),
|
|
162
|
+
]);
|
|
163
|
+
if (outcome === "timeout") {
|
|
164
|
+
child.kill("SIGKILL");
|
|
165
|
+
await exited;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function resetAceStepForTests() {
|
|
169
|
+
state.child?.kill("SIGKILL");
|
|
170
|
+
state.child = null;
|
|
171
|
+
state.handle = null;
|
|
172
|
+
state.starting = null;
|
|
173
|
+
state.spawnFailure = null;
|
|
174
|
+
state.lock = Promise.resolve();
|
|
175
|
+
}
|