@gentbajko/slopify 2.4.0 → 3.0.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/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { referenceKey } from "../admission/model.js";
|
|
4
|
+
import { usesReference } from "../admission/rules.js";
|
|
3
5
|
import { outputPath, stagingPath } from "../storage/layout.js";
|
|
4
6
|
import { stagedFileById } from "../storage/repo.js";
|
|
5
7
|
export function bindUpload(content, upload, assetId) {
|
|
@@ -31,7 +33,7 @@ export function validateUploads(deps, edit) {
|
|
|
31
33
|
for (const [index, upload] of (edit.uploads ?? []).entries()) {
|
|
32
34
|
const field = `uploads.${index}`;
|
|
33
35
|
const to = upload.destination;
|
|
34
|
-
if ((to.kind === "provided" && edit.config
|
|
36
|
+
if ((to.kind === "provided" && !providesOwnFile(edit.config, to.stage)) ||
|
|
35
37
|
(to.kind === "narration" && edit.config.sources.audio !== "generate") ||
|
|
36
38
|
(to.kind === "image" && edit.config.sources.images === "off"))
|
|
37
39
|
fields.push({
|
|
@@ -52,7 +54,13 @@ export function validateUploads(deps, edit) {
|
|
|
52
54
|
staged.add(upload.stagedFileId);
|
|
53
55
|
destinations.add(destination);
|
|
54
56
|
const file = stagedFileById(deps.db, upload.stagedFileId);
|
|
55
|
-
const kind = to.kind === "provided"
|
|
57
|
+
const kind = to.kind === "provided"
|
|
58
|
+
? to.stage === "reference"
|
|
59
|
+
? "images"
|
|
60
|
+
: to.stage
|
|
61
|
+
: to.kind === "image"
|
|
62
|
+
? "images"
|
|
63
|
+
: "audio";
|
|
56
64
|
if (file === undefined ||
|
|
57
65
|
file.state !== "staged" ||
|
|
58
66
|
file.stageKind !== kind ||
|
|
@@ -77,7 +85,7 @@ export function validateUploads(deps, edit) {
|
|
|
77
85
|
export function validateAssetReferences(deps, projectId, content, prepared) {
|
|
78
86
|
const fields = [];
|
|
79
87
|
const refs = [];
|
|
80
|
-
for (const key of ["research", "article", "audio", "thumbnail"]) {
|
|
88
|
+
for (const key of ["research", "article", "audio", "thumbnail", "reference"]) {
|
|
81
89
|
const id = content.provided[key];
|
|
82
90
|
if (id !== undefined)
|
|
83
91
|
refs.push([`content.provided.${key}`, id, key, false]);
|
|
@@ -94,7 +102,10 @@ export function validateAssetReferences(deps, projectId, content, prepared) {
|
|
|
94
102
|
audio: ["audio_body", "audio_intro", "audio_outro", "audio_export"],
|
|
95
103
|
images: ["image"],
|
|
96
104
|
thumbnail: ["thumbnail"],
|
|
105
|
+
reference: ["reference"],
|
|
97
106
|
};
|
|
107
|
+
// The establishing image is the Images stage's.
|
|
108
|
+
const stageOf = (kind) => (kind === "reference" ? "images" : kind);
|
|
98
109
|
for (const [field, id, kind, allowPiece] of refs) {
|
|
99
110
|
if (!prepared.some((row) => row.id === id && row.projectId === projectId) &&
|
|
100
111
|
deps.db
|
|
@@ -107,7 +118,7 @@ export function validateAssetReferences(deps, projectId, content, prepared) {
|
|
|
107
118
|
if (!prepared.some((row) => row.id === id) &&
|
|
108
119
|
deps.db
|
|
109
120
|
.prepare("SELECT 1 FROM revision_outputs WHERE project_id=? AND asset_id=? AND json_extract(descriptor,'$.stageKind')=? AND json_extract(descriptor,'$.role') IN (SELECT value FROM json_each(?))")
|
|
110
|
-
.get(projectId, id, kind, JSON.stringify(roles[kind])) === undefined &&
|
|
121
|
+
.get(projectId, id, stageOf(kind), JSON.stringify(roles[kind])) === undefined &&
|
|
111
122
|
(!allowPiece ||
|
|
112
123
|
deps.db
|
|
113
124
|
.prepare("SELECT 1 FROM revision_pieces WHERE project_id=? AND asset_id=? AND stage_kind=? AND json_extract(descriptor,'$.kind') IN (SELECT value FROM json_each(?))")
|
|
@@ -133,6 +144,16 @@ export function validateAssetReferences(deps, projectId, content, prepared) {
|
|
|
133
144
|
field: "content.shortsMusic",
|
|
134
145
|
message: "This music file does not belong to this project. Reload the page and choose the music again.",
|
|
135
146
|
});
|
|
147
|
+
// The ambient bed's own file is only ever the one the project was started with.
|
|
148
|
+
const bed = content.ambientBed;
|
|
149
|
+
if (bed !== undefined &&
|
|
150
|
+
deps.db
|
|
151
|
+
.prepare("SELECT 1 FROM project_revisions WHERE project_id=? AND json_extract(content,'$.ambientBed')=? LIMIT 1")
|
|
152
|
+
.get(projectId, bed) === undefined)
|
|
153
|
+
fields.push({
|
|
154
|
+
field: "content.ambientBed",
|
|
155
|
+
message: "This ambient sound file does not belong to this project. Reload the page and save again.",
|
|
156
|
+
});
|
|
136
157
|
return fields;
|
|
137
158
|
}
|
|
138
159
|
export async function measureAudio(deps, projectId, path) {
|
|
@@ -150,12 +171,25 @@ export function assetPath(deps, projectId, id) {
|
|
|
150
171
|
.prepare("SELECT path FROM project_assets WHERE project_id=? AND id=?")
|
|
151
172
|
.get(projectId, id)).path;
|
|
152
173
|
}
|
|
174
|
+
// A stage whose own file the project uses: Provide for audio and the thumbnail, Upload for the
|
|
175
|
+
// establishing image.
|
|
176
|
+
export function providesOwnFile(config, kind) {
|
|
177
|
+
return kind === "reference"
|
|
178
|
+
? usesReference(config) && config.reference?.source === "provide"
|
|
179
|
+
: config.sources[kind] === "provide";
|
|
180
|
+
}
|
|
181
|
+
// Where each provided file is published: its work key and output role.
|
|
182
|
+
export const providedOutput = {
|
|
183
|
+
audio: { workKey: "audio:provided", role: "audio_body" },
|
|
184
|
+
thumbnail: { workKey: "thumbnail:image", role: "thumbnail" },
|
|
185
|
+
reference: { workKey: referenceKey, role: "reference" },
|
|
186
|
+
};
|
|
153
187
|
export function providedAssetSelected(base, kind, assetId) {
|
|
154
|
-
return (base.revision.config
|
|
188
|
+
return (providesOwnFile(base.revision.config, kind) &&
|
|
155
189
|
base.outputs.some((row) => row.selected &&
|
|
156
190
|
row.assetId === assetId &&
|
|
157
|
-
row.workKey ===
|
|
158
|
-
row.output.role ===
|
|
191
|
+
row.workKey === providedOutput[kind].workKey &&
|
|
192
|
+
row.output.role === providedOutput[kind].role));
|
|
159
193
|
}
|
|
160
194
|
export function validateReplacementAvailability(deps, base, edit) {
|
|
161
195
|
const previous = new Set([
|
|
@@ -163,10 +197,10 @@ export function validateReplacementAvailability(deps, base, edit) {
|
|
|
163
197
|
...Object.values(base.revision.content.imageDefinitions).map((row) => row.assetId),
|
|
164
198
|
...Object.values(base.revision.content.narrationOverrides).flatMap((row) => row.kind === "asset" ? [row.assetId] : []),
|
|
165
199
|
]);
|
|
166
|
-
for (const kind of ["audio", "thumbnail"]) {
|
|
200
|
+
for (const kind of ["audio", "thumbnail", "reference"]) {
|
|
167
201
|
const assetId = edit.content.provided[kind];
|
|
168
202
|
if (assetId !== undefined &&
|
|
169
|
-
edit.config
|
|
203
|
+
providesOwnFile(edit.config, kind) &&
|
|
170
204
|
!providedAssetSelected(base, kind, assetId) &&
|
|
171
205
|
!edit.uploads?.some((row) => row.destination.kind === "provided" && row.destination.stage === kind))
|
|
172
206
|
previous.delete(assetId);
|
|
@@ -1,6 +1,59 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { outputPath } from "../storage/layout.js";
|
|
5
|
+
import { usesVoices } from "../voices/model.js";
|
|
6
|
+
import { cueSpeaker } from "../voices/timing.js";
|
|
2
7
|
import { assetPath, measureAudio } from "./mutation-assets.js";
|
|
3
8
|
import { validateCues } from "./rules.js";
|
|
9
|
+
// Edited captions of a multi-voice run keep their speakers. A cue that comes back without one
|
|
10
|
+
// (the caption editor edits text and times only) takes the speaker it had before, found by the
|
|
11
|
+
// cue's id, and a new cue the speaker of the narration under it, from the word timing. A cue
|
|
12
|
+
// sent with a speaker keeps it. Runs with one voice are left as they are.
|
|
13
|
+
export function withCueSpeakers(deps, base, edit) {
|
|
14
|
+
const edited = edit.content.subtitleCues;
|
|
15
|
+
if (edited === undefined ||
|
|
16
|
+
!usesVoices(edit.config) ||
|
|
17
|
+
!cuesChanged(base, edit) ||
|
|
18
|
+
edited.cues.every((cue) => cue.speaker !== undefined))
|
|
19
|
+
return edit;
|
|
20
|
+
const before = new Map((base.revision.content.subtitleCues?.cues ?? []).flatMap((cue) => cue.speaker === undefined ? [] : [[cue.id, cue.speaker]]));
|
|
21
|
+
let words;
|
|
22
|
+
const timing = () => {
|
|
23
|
+
if (words !== undefined)
|
|
24
|
+
return words;
|
|
25
|
+
const row = base.outputs.find((one) => one.selected &&
|
|
26
|
+
one.available &&
|
|
27
|
+
one.state === "ready" &&
|
|
28
|
+
one.workKey === "subtitles:timing" &&
|
|
29
|
+
one.output.role === "subtitle_words");
|
|
30
|
+
try {
|
|
31
|
+
words =
|
|
32
|
+
row === undefined
|
|
33
|
+
? []
|
|
34
|
+
: timedWords.parse(JSON.parse(readFileSync(outputPath(deps.paths, base.revision.projectId, row.output.path), "utf8"))).words;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Unreadable timing leaves the new cues without a speaker; they still save.
|
|
38
|
+
words = [];
|
|
39
|
+
}
|
|
40
|
+
return words;
|
|
41
|
+
};
|
|
42
|
+
const cues = edited.cues.map((cue) => {
|
|
43
|
+
if (cue.speaker !== undefined)
|
|
44
|
+
return cue;
|
|
45
|
+
const speaker = before.get(cue.id) ?? cueSpeaker(cue, timing() ?? []);
|
|
46
|
+
return speaker === undefined ? cue : { ...cue, speaker };
|
|
47
|
+
});
|
|
48
|
+
return { ...edit, content: { ...edit.content, subtitleCues: { ...edited, cues } } };
|
|
49
|
+
}
|
|
50
|
+
const timedWords = z.object({
|
|
51
|
+
words: z.array(z.object({
|
|
52
|
+
start: z.number(),
|
|
53
|
+
end: z.number(),
|
|
54
|
+
speaker: z.string().optional(),
|
|
55
|
+
})),
|
|
56
|
+
});
|
|
4
57
|
export function cuesChanged(base, edit) {
|
|
5
58
|
return (edit.content.subtitleCues !== undefined &&
|
|
6
59
|
!isDeepStrictEqual(base.revision.content.subtitleCues, edit.content.subtitleCues));
|
|
@@ -54,12 +107,30 @@ export function validateProposedCues(base, edit, plan, prepared, durations) {
|
|
|
54
107
|
return undefined;
|
|
55
108
|
if (recipe.input.kind === "provided" && recipe.input.assetId !== null)
|
|
56
109
|
return durations.get(recipe.input.assetId);
|
|
110
|
+
// Pauses between sentences lengthen the join by what only the join measures.
|
|
111
|
+
if (recipe.input.kind === "local" &&
|
|
112
|
+
Array.isArray(recipe.input.values) &&
|
|
113
|
+
recipe.input.values[2] !== undefined)
|
|
114
|
+
return undefined;
|
|
57
115
|
if (recipe.input.kind === "local" && recipe.input.operation === "concat-narration") {
|
|
58
116
|
const parts = recipe.dependsOn.map(durationFor);
|
|
59
117
|
return parts.some((one) => one === undefined)
|
|
60
118
|
? undefined
|
|
61
119
|
: parts.reduce((sum, one) => sum + (one ?? 0), 0);
|
|
62
120
|
}
|
|
121
|
+
// Speaker turns: each at its pace, with the gap after it (`recipe-voices.ts`).
|
|
122
|
+
if (recipe.input.kind === "local" && recipe.input.operation === "concat-turns-v1") {
|
|
123
|
+
const layout = Array.isArray(recipe.input.values) ? recipe.input.values[1] : undefined;
|
|
124
|
+
const parts = recipe.dependsOn.map(durationFor);
|
|
125
|
+
if (!Array.isArray(layout) || parts.some((one) => one === undefined))
|
|
126
|
+
return undefined;
|
|
127
|
+
return parts.reduce((sum, one, index) => {
|
|
128
|
+
const [pace, gap] = Array.isArray(layout[index]) ? layout[index] : [1, 0];
|
|
129
|
+
return (sum +
|
|
130
|
+
(one ?? 0) / (typeof pace === "number" && pace > 0 ? pace : 1) +
|
|
131
|
+
(typeof gap === "number" ? gap * 1000 : 0));
|
|
132
|
+
}, 0);
|
|
133
|
+
}
|
|
63
134
|
if (plan.baseFingerprints[key] !== plan.fingerprints[key])
|
|
64
135
|
return undefined;
|
|
65
136
|
const prefix = key.replace(/:1$/, ":");
|
|
@@ -5,7 +5,7 @@ import { normalizeArticleIntent } from "../rebuild/recipe-save.js";
|
|
|
5
5
|
import { discardPreparedAssets } from "../storage/assets.js";
|
|
6
6
|
import { prepareStagedFile, prepareText } from "../storage/prepare.js";
|
|
7
7
|
import { outputSchema } from "../storage/schema.js";
|
|
8
|
-
import { bindUpload, measureAudio, providedAssetSelected, } from "./mutation-assets.js";
|
|
8
|
+
import { bindUpload, measureAudio, providedAssetSelected, providedOutput, providesOwnFile, } from "./mutation-assets.js";
|
|
9
9
|
export async function prepareEditAssets(deps, base, edit) {
|
|
10
10
|
const prepared = [];
|
|
11
11
|
const allocated = [];
|
|
@@ -16,8 +16,8 @@ export async function prepareEditAssets(deps, base, edit) {
|
|
|
16
16
|
const to = upload.destination;
|
|
17
17
|
const role = to.kind === "image"
|
|
18
18
|
? "image"
|
|
19
|
-
: to.kind === "provided" && to.stage
|
|
20
|
-
?
|
|
19
|
+
: to.kind === "provided" && to.stage !== "audio"
|
|
20
|
+
? providedOutput[to.stage].role
|
|
21
21
|
: "audio_body";
|
|
22
22
|
const result = prepareStagedFile(deps, {
|
|
23
23
|
projectId,
|
|
@@ -35,9 +35,7 @@ export async function prepareEditAssets(deps, base, edit) {
|
|
|
35
35
|
? to.key
|
|
36
36
|
: to.kind === "shortsMusic"
|
|
37
37
|
? "shorts:music"
|
|
38
|
-
: to.stage
|
|
39
|
-
? "audio:provided"
|
|
40
|
-
: "thumbnail:image";
|
|
38
|
+
: providedOutput[to.stage].workKey;
|
|
41
39
|
const item = {
|
|
42
40
|
...result,
|
|
43
41
|
upload,
|
|
@@ -93,21 +91,21 @@ export async function prepareEditAssets(deps, base, edit) {
|
|
|
93
91
|
slot: workKey,
|
|
94
92
|
});
|
|
95
93
|
}
|
|
96
|
-
for (const kind of ["audio", "thumbnail"]) {
|
|
94
|
+
for (const kind of ["audio", "thumbnail", "reference"]) {
|
|
97
95
|
const assetId = content.provided[kind];
|
|
98
|
-
if (edit.config
|
|
96
|
+
if (!providesOwnFile(edit.config, kind) ||
|
|
99
97
|
assetId === undefined ||
|
|
100
98
|
providedAssetSelected(base, kind, assetId) ||
|
|
101
99
|
prepared.some((row) => row.asset.id === assetId))
|
|
102
100
|
continue;
|
|
103
101
|
const existing = deps.db
|
|
104
102
|
.prepare("SELECT descriptor FROM revision_outputs WHERE project_id=? AND asset_id=? AND json_extract(descriptor,'$.stageKind')=? LIMIT 1")
|
|
105
|
-
.get(projectId, assetId, kind);
|
|
103
|
+
.get(projectId, assetId, kind === "reference" ? "images" : kind);
|
|
106
104
|
if (existing === undefined)
|
|
107
105
|
throw new Error("Validated replacement asset has no descriptor.");
|
|
108
106
|
const descriptor = outputSchema.parse(JSON.parse(z.string().parse(existing.descriptor)));
|
|
109
107
|
const asset = retainedAsset(deps, projectId, assetId);
|
|
110
|
-
const role = kind
|
|
108
|
+
const role = providedOutput[kind].role;
|
|
111
109
|
const item = {
|
|
112
110
|
asset,
|
|
113
111
|
output: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { projectExists } from "../admission/repo.js";
|
|
3
4
|
import { currentRevisionId } from "./repo.js";
|
|
4
5
|
import { getRevisionView } from "./view.js";
|
|
5
6
|
export function requestHash(operation, baseRevisionId, input) {
|
|
@@ -43,7 +44,7 @@ export function checkMutation(deps, input) {
|
|
|
43
44
|
.prepare("SELECT 1 FROM project_recovery_requests WHERE project_id=? AND idempotency_key=?")
|
|
44
45
|
.get(input.projectId, input.idempotencyKey) !== undefined)
|
|
45
46
|
return refusal(deps, input.projectId, "idempotency-conflict");
|
|
46
|
-
if (db
|
|
47
|
+
if (!projectExists(db, input.projectId))
|
|
47
48
|
return refusal(deps, input.projectId, "no-project");
|
|
48
49
|
if (currentRevisionId(db, input.projectId) !== input.baseRevisionId)
|
|
49
50
|
return refusal(deps, input.projectId, "conflict");
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { transact } from "../../kernel/db/tx.js";
|
|
3
|
+
import { rebrandedEdit } from "../channels/rebrand.js";
|
|
4
|
+
import { projectChannelId, resolveChannelId, setProjectChannel } from "../channels/repo.js";
|
|
3
5
|
import { carryCheckpointGates } from "../checkpoints/recovery.js";
|
|
6
|
+
import { imageCountsOf, imageScaleProblem } from "../images/scale.js";
|
|
7
|
+
import { promptByName } from "../library/repo.js";
|
|
4
8
|
import { narrationRegenerationKey } from "../narration/plan.js";
|
|
5
9
|
import { recipeInputSchema } from "../rebuild/recipe-input-schema.js";
|
|
6
10
|
import { planRevision } from "../rebuild/recipe-save.js";
|
|
@@ -9,8 +13,10 @@ import { projectStandings } from "../rebuild/runtime-store.js";
|
|
|
9
13
|
import { discardPreparedAssets } from "../storage/assets.js";
|
|
10
14
|
import { deleteStagedFile } from "../storage/repo.js";
|
|
11
15
|
import { releaseStagedFile } from "../storage/staging-refs.js";
|
|
16
|
+
import { ambientBedProblems } from "../video/ambient-bed.js";
|
|
17
|
+
import { imagePromptFields, replanImagePrompts, sameImagePrompts } from "./image-plan.js";
|
|
12
18
|
import { validateAssetReferences, validateReplacementAvailability, validateUploads, } from "./mutation-assets.js";
|
|
13
|
-
import { cuesChanged, inspectCueAudio, measuredOutputs, validateProposedCues, } from "./mutation-cues.js";
|
|
19
|
+
import { cuesChanged, inspectCueAudio, measuredOutputs, validateProposedCues, withCueSpeakers, } from "./mutation-cues.js";
|
|
14
20
|
import { prepareEditAssets } from "./mutation-prepare.js";
|
|
15
21
|
import { checkMutation, insertReceipt, requestHash, requiredView } from "./mutation-request.js";
|
|
16
22
|
import { preserveDeferredIntent } from "./mutation-work.js";
|
|
@@ -43,8 +49,17 @@ export async function saveRevision(deps, input) {
|
|
|
43
49
|
if (checked !== undefined)
|
|
44
50
|
return checked;
|
|
45
51
|
const base = requiredView(deps, input.projectId, input.baseRevisionId);
|
|
46
|
-
const
|
|
52
|
+
const replanned = replannedImages(deps, base, submitted);
|
|
53
|
+
if (!replanned.ok)
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: "invalid-edit",
|
|
57
|
+
currentRevisionId: base.revision.id,
|
|
58
|
+
fields: replanned.fields,
|
|
59
|
+
};
|
|
60
|
+
const edit = bindNarrationSources(base, rebrandedEdit(deps.db, base.revision.config, replanned.edit));
|
|
47
61
|
const fields = [
|
|
62
|
+
...settingProblems(edit),
|
|
48
63
|
...validateUploads(deps, edit),
|
|
49
64
|
...validateTemplateIntent(base, edit),
|
|
50
65
|
...validateNarrationIntent(base, edit),
|
|
@@ -60,7 +75,8 @@ export async function saveRevision(deps, input) {
|
|
|
60
75
|
currentRevisionId: base.revision.id,
|
|
61
76
|
fields: unavailable,
|
|
62
77
|
};
|
|
63
|
-
const { edit:
|
|
78
|
+
const { edit: uploaded, assets: prepared } = await prepareEditAssets(deps, base, edit);
|
|
79
|
+
const supplied = withCueSpeakers(deps, base, uploaded);
|
|
64
80
|
try {
|
|
65
81
|
const durations = await inspectCueAudio(deps, base, supplied, prepared);
|
|
66
82
|
const result = transact(deps.db, () => {
|
|
@@ -173,12 +189,90 @@ export async function saveRevision(deps, input) {
|
|
|
173
189
|
.filter((asset) => deps.db.prepare("SELECT 1 FROM project_assets WHERE id=?").get(asset.id) === undefined));
|
|
174
190
|
}
|
|
175
191
|
}
|
|
192
|
+
// Settings Edit project changes that admission checks at Play's Start: the ambient bed's
|
|
193
|
+
// numbers and More images for long videos. Said where Edit project shows them.
|
|
194
|
+
function settingProblems(edit) {
|
|
195
|
+
const bed = edit.config.ambientBed;
|
|
196
|
+
const scale = edit.config.imageScale;
|
|
197
|
+
const scaleProblem = scale === undefined ? undefined : imageScaleProblem(scale);
|
|
198
|
+
return [
|
|
199
|
+
...(bed === undefined
|
|
200
|
+
? []
|
|
201
|
+
: ambientBedProblems(bed).map((problem) => ({
|
|
202
|
+
field: `ambientBed.${problem.field}`,
|
|
203
|
+
message: `${problem.message} Change it in Edit project → Inputs → Ambient sound.`,
|
|
204
|
+
}))),
|
|
205
|
+
...(scaleProblem === undefined
|
|
206
|
+
? []
|
|
207
|
+
: [
|
|
208
|
+
{
|
|
209
|
+
field: "imageScale",
|
|
210
|
+
message: `${scaleProblem} Change it in Edit project → Images → More images for long videos.`,
|
|
211
|
+
},
|
|
212
|
+
]),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
// The image prompts or their Numbers changed: the image definitions are planned again from
|
|
216
|
+
// them (`image-plan.ts`), keeping every image an unchanged prompt already has.
|
|
217
|
+
function replannedImages(deps, base, edit) {
|
|
218
|
+
// Planned in the counts each prompt actually makes: its Number, plus its share of the extra
|
|
219
|
+
// images More images for long videos adds (`images/scale.ts`), as the run was adopted.
|
|
220
|
+
const effective = (config) => {
|
|
221
|
+
const counts = imageCountsOf(config);
|
|
222
|
+
return config.imagePrompts.map((prompt, index) => ({
|
|
223
|
+
...prompt,
|
|
224
|
+
number: counts[index] ?? prompt.number,
|
|
225
|
+
}));
|
|
226
|
+
};
|
|
227
|
+
if (edit.config.sources.images !== "generate" &&
|
|
228
|
+
sameImagePrompts(base.revision.config.imagePrompts, edit.config.imagePrompts))
|
|
229
|
+
return { ok: true, edit };
|
|
230
|
+
const from = effective(base.revision.config);
|
|
231
|
+
const to = effective(edit.config);
|
|
232
|
+
if (sameImagePrompts(from, to))
|
|
233
|
+
return { ok: true, edit };
|
|
234
|
+
if (edit.config.sources.images !== "generate")
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
fields: [
|
|
238
|
+
{
|
|
239
|
+
field: "imagePrompts",
|
|
240
|
+
message: "Image prompts are used only while Images is set to Generate. Set Images to Generate on Inputs, or leave the prompts as they were.",
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
const fields = imagePromptFields(edit.config.imagePrompts);
|
|
245
|
+
if (fields.length > 0)
|
|
246
|
+
return { ok: false, fields };
|
|
247
|
+
const plan = replanImagePrompts({
|
|
248
|
+
from,
|
|
249
|
+
to,
|
|
250
|
+
content: edit.content,
|
|
251
|
+
rendered: edit.config.rendered,
|
|
252
|
+
values: edit.config.values,
|
|
253
|
+
body: (name) => promptByName(deps.db, "image", name)?.body,
|
|
254
|
+
key: () => deps.ids.next(),
|
|
255
|
+
});
|
|
256
|
+
if (!plan.ok)
|
|
257
|
+
return plan;
|
|
258
|
+
return {
|
|
259
|
+
ok: true,
|
|
260
|
+
edit: { ...edit, config: { ...edit.config, rendered: plan.rendered }, content: plan.content },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
176
263
|
export function advanceHead(db, projectId, baseRevisionId, revision) {
|
|
177
264
|
const result = db
|
|
178
265
|
.prepare("UPDATE project_heads SET revision_id = ? WHERE project_id = ? AND revision_id = ?")
|
|
179
266
|
.run(revision.id, projectId, baseRevisionId);
|
|
180
267
|
if (Number(result.changes) !== 1)
|
|
181
268
|
return false;
|
|
269
|
+
// The project's channel follows its settings: moved in Edit project, or restored to a
|
|
270
|
+
// version from another channel. A channel since deleted leaves the project where it is.
|
|
271
|
+
const channelId = revision.config.channelId;
|
|
272
|
+
if (channelId !== undefined &&
|
|
273
|
+
resolveChannelId(db, channelId) === channelId &&
|
|
274
|
+
projectChannelId(db, projectId) !== channelId)
|
|
275
|
+
setProjectChannel(db, projectId, channelId);
|
|
182
276
|
db.prepare("UPDATE projects SET title = ?, format = ?, config = ?, updated_at = ? WHERE id = ?").run(revision.config.title, revision.config.format, JSON.stringify(revision.config), revision.createdAt, projectId);
|
|
183
277
|
return true;
|
|
184
278
|
}
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { stageKinds } from "../../kernel/pipeline.js";
|
|
3
3
|
import { pieceKinds, pieceStates } from "../../kernel/runner/piece-repo.js";
|
|
4
4
|
import { runConfigSchema } from "../admission/schema.js";
|
|
5
|
+
import { scaledImagesMax } from "../images/scale.js";
|
|
5
6
|
import { outputSchema } from "../storage/schema.js";
|
|
6
7
|
const id = z
|
|
7
8
|
.string()
|
|
@@ -16,6 +17,13 @@ const cue = z
|
|
|
16
17
|
text: z.string().trim().min(1).max(10000),
|
|
17
18
|
start: z.number().finite().nonnegative(),
|
|
18
19
|
end: z.number().finite().positive(),
|
|
20
|
+
// The speaker's id on a multi-voice run (`voices/model.ts`); absent everywhere else.
|
|
21
|
+
speaker: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(60)
|
|
25
|
+
.regex(/^[a-z0-9-]+$/)
|
|
26
|
+
.optional(),
|
|
19
27
|
})
|
|
20
28
|
.strict()
|
|
21
29
|
.refine((value) => value.end > value.start, {
|
|
@@ -32,9 +40,11 @@ export const revisionContentSchema = z
|
|
|
32
40
|
article: id.optional(),
|
|
33
41
|
audio: id.optional(),
|
|
34
42
|
thumbnail: id.optional(),
|
|
43
|
+
reference: id.optional(),
|
|
35
44
|
})
|
|
36
45
|
.strict(),
|
|
37
|
-
|
|
46
|
+
// The most any project may hold; `rebuild/recipe-validation.ts` holds each to its own cap.
|
|
47
|
+
imageOrder: z.array(id).max(scaledImagesMax),
|
|
38
48
|
imageDefinitions: z.record(id, z
|
|
39
49
|
.object({
|
|
40
50
|
source: z.enum(["generate", "provide"]),
|
|
@@ -78,6 +88,7 @@ export const revisionContentSchema = z
|
|
|
78
88
|
})
|
|
79
89
|
.strict())
|
|
80
90
|
.optional(),
|
|
91
|
+
ambientBed: id.optional(),
|
|
81
92
|
})
|
|
82
93
|
.strict();
|
|
83
94
|
export const revisionEditSchema = z
|
|
@@ -91,7 +102,10 @@ export const revisionEditSchema = z
|
|
|
91
102
|
stagedFileId: id,
|
|
92
103
|
destination: z.discriminatedUnion("kind", [
|
|
93
104
|
z
|
|
94
|
-
.object({
|
|
105
|
+
.object({
|
|
106
|
+
kind: z.literal("provided"),
|
|
107
|
+
stage: z.enum(["audio", "thumbnail", "reference"]),
|
|
108
|
+
})
|
|
95
109
|
.strict(),
|
|
96
110
|
z.object({ kind: z.literal("image"), imageKey: id }).strict(),
|
|
97
111
|
z.object({ kind: z.literal("narration"), key: workKey }).strict(),
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { causedBy } from "../../kernel/errors.js";
|
|
3
|
+
// Living within a CLI's plan limits. When a CLI says its allowance is used up, the time it
|
|
4
|
+
// returns is stored per account; every call to that CLI - from any project, and after a
|
|
5
|
+
// restart - waits for it instead of failing. Other providers are untouched, so a project
|
|
6
|
+
// whose narration is keyed carries on while its Codex images wait.
|
|
7
|
+
// Waking a little after the stated reset, so the first call is not refused again at the edge.
|
|
8
|
+
export const resetMarginMs = 2 * 60_000;
|
|
9
|
+
// When the CLI names no reset time, the wait checks again this often.
|
|
10
|
+
export const recheckMs = 30 * 60_000;
|
|
11
|
+
const waitRow = z.object({ retry_at: z.string() });
|
|
12
|
+
export function createLimitGate(deps) {
|
|
13
|
+
// How many calls of one stage wait on one account: a stage drawing four Codex images at
|
|
14
|
+
// once shows one wait, removed when the last of them wakes.
|
|
15
|
+
const waiting = new Map();
|
|
16
|
+
const retryAt = (account) => {
|
|
17
|
+
const row = deps.db
|
|
18
|
+
.prepare("SELECT retry_at FROM plan_limit_waits WHERE account = ?")
|
|
19
|
+
.get(account);
|
|
20
|
+
if (row === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
return Date.parse(waitRow.parse(row).retry_at);
|
|
23
|
+
};
|
|
24
|
+
const begin = (account, waiter) => {
|
|
25
|
+
const key = JSON.stringify([waiter.projectId, waiter.stage, account]);
|
|
26
|
+
waiting.set(key, (waiting.get(key) ?? 0) + 1);
|
|
27
|
+
const inserted = deps.db
|
|
28
|
+
.prepare("INSERT OR IGNORE INTO plan_limit_waiters (project_id, stage, account, since) VALUES (?, ?, ?, ?)")
|
|
29
|
+
.run(waiter.projectId, waiter.stage, account, deps.clock.now().toISOString());
|
|
30
|
+
if (inserted.changes === 1)
|
|
31
|
+
deps.changed(waiter.projectId);
|
|
32
|
+
};
|
|
33
|
+
// Only a wait that ran its course removes the row. One cut short by cancel, pause or the
|
|
34
|
+
// app stopping leaves it, and the next start decides what it means (resumeAfterRestart).
|
|
35
|
+
const end = (account, waiter, woke) => {
|
|
36
|
+
const key = JSON.stringify([waiter.projectId, waiter.stage, account]);
|
|
37
|
+
const left = (waiting.get(key) ?? 1) - 1;
|
|
38
|
+
if (left > 0) {
|
|
39
|
+
waiting.set(key, left);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
waiting.delete(key);
|
|
43
|
+
if (!woke)
|
|
44
|
+
return;
|
|
45
|
+
deps.db
|
|
46
|
+
.prepare("DELETE FROM plan_limit_waiters WHERE project_id = ? AND stage = ? AND account = ?")
|
|
47
|
+
.run(waiter.projectId, waiter.stage, account);
|
|
48
|
+
deps.changed(waiter.projectId);
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
ready: async (account, waiter, signal) => {
|
|
52
|
+
for (;;) {
|
|
53
|
+
signal.throwIfAborted();
|
|
54
|
+
const due = retryAt(account);
|
|
55
|
+
if (due === undefined)
|
|
56
|
+
return;
|
|
57
|
+
const left = due - deps.clock.now().getTime();
|
|
58
|
+
if (left <= 0) {
|
|
59
|
+
// Past the reset: the allowance is back. A call refused again stores a new wait.
|
|
60
|
+
deps.db
|
|
61
|
+
.prepare("DELETE FROM plan_limit_waits WHERE account = ? AND retry_at <= ?")
|
|
62
|
+
.run(account, deps.clock.now().toISOString());
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
begin(account, waiter);
|
|
66
|
+
let woke = false;
|
|
67
|
+
try {
|
|
68
|
+
await deps.clock.sleep(left, signal);
|
|
69
|
+
woke = true;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
end(account, waiter, woke);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
exhausted: (hit) => {
|
|
77
|
+
const now = deps.clock.now().getTime();
|
|
78
|
+
const reset = hit.resetsAt === null ? Number.NaN : Date.parse(hit.resetsAt);
|
|
79
|
+
// A stated reset already past (a clock skew, a stale message) is checked again soon.
|
|
80
|
+
const due = Number.isFinite(reset) && reset > now ? reset + resetMarginMs : now + recheckMs;
|
|
81
|
+
const retry = new Date(due).toISOString();
|
|
82
|
+
try {
|
|
83
|
+
deps.db
|
|
84
|
+
.prepare(`INSERT INTO plan_limit_waits (account, resets_at, retry_at, detected_at) VALUES (?, ?, ?, ?)
|
|
85
|
+
ON CONFLICT(account) DO UPDATE SET resets_at = excluded.resets_at, retry_at = excluded.retry_at, detected_at = excluded.detected_at
|
|
86
|
+
WHERE excluded.retry_at > plan_limit_waits.retry_at`)
|
|
87
|
+
.run(hit.account, Number.isFinite(reset) && reset > now ? new Date(reset).toISOString() : null, retry, new Date(now).toISOString());
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
deps.log.write("error", "plan-limits", { detail: causedBy(error) });
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
deps.log.write("info", "plan-limits", {
|
|
94
|
+
detail: `${hit.account} plan limit used up; waiting until ${retry}`,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// At start, a stage that was waiting on a plan limit when the app stopped was marked
|
|
100
|
+
// interrupted like any other (main.ts markInterruptedStages). Those projects are resumed so
|
|
101
|
+
// the wait carries on by itself - their calls meet the stored reset and wait again. A project
|
|
102
|
+
// paused or cancelled meanwhile is left as it is.
|
|
103
|
+
export async function resumeAfterRestart(db, resume, log) {
|
|
104
|
+
const projects = db
|
|
105
|
+
.prepare(`SELECT DISTINCT w.project_id AS id FROM plan_limit_waiters w
|
|
106
|
+
JOIN stages s ON s.project_id = w.project_id AND s.kind = w.stage
|
|
107
|
+
WHERE s.state = 'failed' AND s.failure_reason = 'interrupted'
|
|
108
|
+
AND NOT EXISTS (SELECT 1 FROM project_controls c WHERE c.project_id = w.project_id AND c.paused = 1)`)
|
|
109
|
+
.all()
|
|
110
|
+
.map((row) => z.string().parse(row.id));
|
|
111
|
+
db.prepare("DELETE FROM plan_limit_waiters").run();
|
|
112
|
+
let resumed = 0;
|
|
113
|
+
for (const projectId of projects) {
|
|
114
|
+
try {
|
|
115
|
+
if (await resume(projectId))
|
|
116
|
+
resumed += 1;
|
|
117
|
+
else
|
|
118
|
+
log.write("warn", "plan-limits", {
|
|
119
|
+
projectId,
|
|
120
|
+
detail: "A project waiting for CLI limits could not be resumed after the restart; use Continue the run on the project page.",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
log.write("error", "plan-limits", { projectId, detail: causedBy(error) });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return resumed;
|
|
128
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { planAccountOf } from "../../kernel/ports/plan-limits.js";
|
|
2
|
+
import { resolveChannelId, scheduleChannel } from "../channels/repo.js";
|
|
3
|
+
import { priceCall } from "./pricing.js";
|
|
4
|
+
// Prices each call as it lands and keeps it with the rates it used; a CLI's plan windows go
|
|
5
|
+
// beside it. Separate from the anonymous telemetry log: this is per project, never leaves the
|
|
6
|
+
// machine, and is kept whether or not the usage-stats notice was dismissed.
|
|
7
|
+
export function createUsageMeter(deps) {
|
|
8
|
+
return {
|
|
9
|
+
record: (call) => {
|
|
10
|
+
const priced = priceCall(call, deps.catalogue());
|
|
11
|
+
const at = deps.clock.now().toISOString();
|
|
12
|
+
deps.db
|
|
13
|
+
.prepare(`INSERT INTO provider_usage (id, project_id, stage, kind, provider, model, tokens_in, tokens_out, tokens_cached, characters, images, seconds, size, quality, wall_ms, on_plan, cost, api_model, api_cost, price_json, created_at)
|
|
14
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
15
|
+
.run(deps.ids.next(), call.projectId, call.stage, call.kind, call.provider, call.model, call.tokensIn ?? null, call.tokensOut ?? null, call.cachedTokens ?? null, call.characters ?? null, call.images ?? null, call.seconds ?? null, call.size ?? null, call.quality ?? null, Math.round(call.wallMs), priced.onPlan ? 1 : 0, priced.cost, priced.apiModel, priced.apiCost, priced.price === null ? null : JSON.stringify(priced.price), at);
|
|
16
|
+
const account = planAccountOf(call.provider);
|
|
17
|
+
if (account !== undefined && call.limits !== undefined)
|
|
18
|
+
deps.db
|
|
19
|
+
.prepare("INSERT INTO plan_limit_readings (id, project_id, account, reading_json, created_at) VALUES (?, ?, ?, ?, ?)")
|
|
20
|
+
.run(deps.ids.next(), call.projectId, account, JSON.stringify(call.limits), at);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// The same pricing for a call made outside a project (`kernel/runner/standalone.ts`), kept
|
|
25
|
+
// against its schedule or channel with the channel it counts under on Home.
|
|
26
|
+
export function createStandaloneMeter(deps) {
|
|
27
|
+
return {
|
|
28
|
+
record: (call) => {
|
|
29
|
+
const priced = priceCall(call, deps.catalogue());
|
|
30
|
+
const account = planAccountOf(call.provider);
|
|
31
|
+
const reading = account !== undefined && call.limits !== undefined
|
|
32
|
+
? { account, json: JSON.stringify(call.limits) }
|
|
33
|
+
: undefined;
|
|
34
|
+
deps.db
|
|
35
|
+
.prepare(`INSERT INTO standalone_usage (id, owner_kind, owner_id, channel_id, purpose, kind, provider, model, tokens_in, tokens_out, tokens_cached, characters, images, seconds, size, quality, wall_ms, on_plan, cost, api_model, api_cost, price_json, account, reading_json, created_at)
|
|
36
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
37
|
+
.run(deps.ids.next(), call.owner.kind, call.owner.id, channelOf(deps.db, call.owner), call.purpose, call.kind, call.provider, call.model, call.tokensIn ?? null, call.tokensOut ?? null, call.cachedTokens ?? null, call.characters ?? null, call.images ?? null, call.seconds ?? null, call.size ?? null, call.quality ?? null, Math.round(call.wallMs), priced.onPlan ? 1 : 0, priced.cost, priced.apiModel, priced.apiCost, priced.price === null ? null : JSON.stringify(priced.price), reading?.account ?? null, reading?.json ?? null, deps.clock.now().toISOString());
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// A schedule counts under its template's channel; one whose template is gone, like a channel
|
|
42
|
+
// this install doesn't have, under the default channel.
|
|
43
|
+
function channelOf(db, owner) {
|
|
44
|
+
return owner.kind === "schedule"
|
|
45
|
+
? resolveChannelId(db, scheduleChannel(db, owner.id)?.id)
|
|
46
|
+
: resolveChannelId(db, owner.id);
|
|
47
|
+
}
|