@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
|
@@ -2,8 +2,30 @@ import { z } from "zod";
|
|
|
2
2
|
import { defaultEdgeSilenceSeconds, defaultImageSeconds, defaultZoomPercent, edgeSilenceSecondsProblem, imageSecondsProblem, numberPerPromptMax, zoomPercentProblem, } from "../admission/rules.js";
|
|
3
3
|
import { runDraftSchema } from "../admission/schema.js";
|
|
4
4
|
import { draftDocumentThemeOf } from "../document/model.js";
|
|
5
|
+
import { defaultExpectedWords, everyMinutesMax, everyMinutesMin, expectedWordsMax, imagesPerHourMax, imagesPerHourMin, perHourFromMinutes, wordCount, } from "../images/scale.js";
|
|
6
|
+
import { defaultLoudness, loudnessOfForm, } from "../loudness/model.js";
|
|
7
|
+
import { pausesOfForm } from "../narration/pauses-model.js";
|
|
8
|
+
import { reviewSettingsFromForm } from "../reviews/model.js";
|
|
9
|
+
import { stageMakesItems } from "../reviews/rules.js";
|
|
5
10
|
import { defaultShorts, shortsExtrasOf, shortsSettingsProblems, } from "../shorts/model.js";
|
|
6
11
|
import { defaultSubtitles } from "../subtitles/model.js";
|
|
12
|
+
import { ambientBedOfForm, ambientBedProblems } from "../video/ambient-bed.js";
|
|
13
|
+
// Said where the control is, since the music sits in a closed disclosure on the Export rail.
|
|
14
|
+
const musicMessages = {
|
|
15
|
+
uploading: "The shorts' background music is still uploading, so the run can't start yet. Wait for it to finish, or remove it under Outputs → Export → More shorts options → Background music.",
|
|
16
|
+
missing: "The shorts' background music file is no longer available (it was not uploaded again after the draft was copied, or the upload failed). Choose the file again under Outputs → Export → More shorts options → Background music, or remove it.",
|
|
17
|
+
};
|
|
18
|
+
function pickMusic(id) {
|
|
19
|
+
return id === undefined ? {} : { shortsMusic: id };
|
|
20
|
+
}
|
|
21
|
+
const bedWhere = "under Outputs → Export → Ambient sound";
|
|
22
|
+
const bedMessages = {
|
|
23
|
+
uploading: `The ambient sound's audio file is still uploading, so the run can't start yet. Wait for it to finish ${bedWhere}.`,
|
|
24
|
+
missing: `The ambient sound's audio file is missing (it was never chosen, was not uploaded again after the draft was copied, or the upload failed). Choose the file ${bedWhere}, or pick Rain, Fireplace or Wind instead.`,
|
|
25
|
+
};
|
|
26
|
+
function pickBed(id) {
|
|
27
|
+
return id === undefined ? {} : { ambientBed: id };
|
|
28
|
+
}
|
|
7
29
|
export function toAdmissionDraft(input) {
|
|
8
30
|
const { form } = input.document;
|
|
9
31
|
const fields = [];
|
|
@@ -49,15 +71,18 @@ export function toAdmissionDraft(input) {
|
|
|
49
71
|
});
|
|
50
72
|
return undefined;
|
|
51
73
|
};
|
|
52
|
-
const file = (ref, kind, field
|
|
74
|
+
const file = (ref, kind, field, messages = {
|
|
75
|
+
uploading: "This file is still uploading. Wait for it to finish.",
|
|
76
|
+
missing: "That upload is no longer available. Choose the file again.",
|
|
77
|
+
}) => {
|
|
53
78
|
const attachment = input.attachments.find((item) => item.id === ref?.attachmentId && item.kind === kind && item.name === ref.name);
|
|
54
79
|
if (attachment?.state === "ready" && attachment.stagedFileId !== null)
|
|
55
80
|
return attachment.stagedFileId;
|
|
56
81
|
fields.push({
|
|
57
82
|
field,
|
|
58
83
|
message: attachment?.state === "copying" || attachment?.state === "pending"
|
|
59
|
-
?
|
|
60
|
-
:
|
|
84
|
+
? messages.uploading
|
|
85
|
+
: messages.missing,
|
|
61
86
|
});
|
|
62
87
|
return undefined;
|
|
63
88
|
};
|
|
@@ -97,7 +122,28 @@ export function toAdmissionDraft(input) {
|
|
|
97
122
|
: defaultShorts.maxSeconds,
|
|
98
123
|
};
|
|
99
124
|
};
|
|
125
|
+
// Only the reviews of stages that make something; none left is no reviews at all, which is
|
|
126
|
+
// what every draft saved before them was.
|
|
127
|
+
const reviewed = form.reviews === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: reviewSettingsFromForm(form.reviews, (stage) => stageMakesItems({ sources, shorts: shortsOn ? { enabled: true } : undefined }, stage));
|
|
130
|
+
if (reviewed?.retriesProblem !== undefined)
|
|
131
|
+
fields.push({ field: "reviews.retries", message: reviewed.retriesProblem });
|
|
132
|
+
const reviews = reviewed?.settings;
|
|
133
|
+
// The ambient bed lies under the long video's narration, so a bed left set with either Off
|
|
134
|
+
// asks for nothing. Refused in the rule's words, so Play and the run say the same sentence.
|
|
135
|
+
const bed = sources.video === "generate" && sources.audio !== "off"
|
|
136
|
+
? ambientBedOfForm(form.ambientBed)
|
|
137
|
+
: undefined;
|
|
138
|
+
const bedProblems = bed === undefined ? [] : ambientBedProblems(bed);
|
|
139
|
+
for (const problem of bedProblems)
|
|
140
|
+
fields.push({
|
|
141
|
+
field: `ambientBed.${problem.field}`,
|
|
142
|
+
message: `${problem.message} Change it ${bedWhere}.`,
|
|
143
|
+
});
|
|
144
|
+
const bedRefused = bedProblems.length;
|
|
100
145
|
const draft = {
|
|
146
|
+
...(reviews === undefined ? {} : { reviews }),
|
|
101
147
|
...(form.checkpoints === undefined ? {} : { checkpoints: form.checkpoints }),
|
|
102
148
|
title: form.title,
|
|
103
149
|
format: form.format,
|
|
@@ -112,17 +158,46 @@ export function toAdmissionDraft(input) {
|
|
|
112
158
|
: {}),
|
|
113
159
|
llm: form.llm,
|
|
114
160
|
audio: sources.audio === "generate" || form.audio.usePronunciationGlossary !== undefined
|
|
115
|
-
?
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
161
|
+
? onlyWhenOn(
|
|
162
|
+
// With the glossary on, sharing it is on unless the draft turned it off, as Play
|
|
163
|
+
// shows it for a draft saved before the switch existed.
|
|
164
|
+
form.audio.usePronunciationGlossary === true && form.audio.shareGlossary === undefined
|
|
165
|
+
? { ...form.audio, shareGlossary: true }
|
|
166
|
+
: form.audio)
|
|
120
167
|
: undefined,
|
|
121
168
|
images: form.images,
|
|
169
|
+
// The establishing image belongs to generated images; Off, or images not generated,
|
|
170
|
+
// leaves it out, which is what every draft saved before it was.
|
|
171
|
+
...(sources.images === "generate" &&
|
|
172
|
+
form.reference !== undefined &&
|
|
173
|
+
form.reference.source !== "off"
|
|
174
|
+
? {
|
|
175
|
+
reference: {
|
|
176
|
+
source: form.reference.source,
|
|
177
|
+
...(form.reference.source === "prompt" ? { prompt: form.reference.prompt } : {}),
|
|
178
|
+
thumbnail: form.reference.thumbnail,
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
// Three only for a thumbnail the image provider draws; one leaves it out, which is what
|
|
183
|
+
// every draft saved before it was.
|
|
184
|
+
...(form.thumbnailCount === 3 &&
|
|
185
|
+
(sources.thumbnail === "from_prompt" || sources.thumbnail === "prompt_by_llm")
|
|
186
|
+
? { thumbnailCount: 3 }
|
|
187
|
+
: {}),
|
|
122
188
|
articlePrompt: sources.article === "generate" ? form.articlePrompt : undefined,
|
|
123
189
|
...(sources.audio === "generate" && form.narrationPrompt?.trim()
|
|
124
190
|
? { narrationPrompt: form.narrationPrompt }
|
|
125
191
|
: {}),
|
|
192
|
+
// English is never stored on the run, so an English project is the run it always was.
|
|
193
|
+
...(form.language === undefined || form.language === "en" ? {} : { language: form.language }),
|
|
194
|
+
// Drawn for the blocks the narration describes, so only with a video and describing on.
|
|
195
|
+
...(sources.video === "generate" &&
|
|
196
|
+
sources.audio === "generate" &&
|
|
197
|
+
form.audio.describeFigures === true &&
|
|
198
|
+
form.showFigures === true
|
|
199
|
+
? { showFigures: true }
|
|
200
|
+
: {}),
|
|
126
201
|
// Timed from the narration, so a switch left on with narration Off asks for nothing.
|
|
127
202
|
...(sources.audio !== "off" && form.youtubeDescription === true
|
|
128
203
|
? {
|
|
@@ -139,6 +214,9 @@ export function toAdmissionDraft(input) {
|
|
|
139
214
|
number: number(prompt.number, `imagePrompts.${index}.number`, numberPerPromptMax),
|
|
140
215
|
}))
|
|
141
216
|
: [],
|
|
217
|
+
...(sources.images === "generate" && form.imageScale !== undefined
|
|
218
|
+
? { imageScale: imageScaleOf(input.document, fields) }
|
|
219
|
+
: {}),
|
|
142
220
|
thumbnailPrompt: ["from_prompt", "prompt_by_llm"].includes(sources.thumbnail)
|
|
143
221
|
? form.thumbnailPrompt
|
|
144
222
|
: undefined,
|
|
@@ -154,12 +232,24 @@ export function toAdmissionDraft(input) {
|
|
|
154
232
|
thumbnail: sources.thumbnail === "provide"
|
|
155
233
|
? file(form.provided.thumbnail, "thumbnail", "provided.thumbnail")
|
|
156
234
|
: undefined,
|
|
235
|
+
reference: sources.images === "generate" && form.reference?.source === "provide"
|
|
236
|
+
? file(form.provided.reference ?? null, "reference", "provided.reference")
|
|
237
|
+
: undefined,
|
|
157
238
|
images: sources.images === "provide"
|
|
158
239
|
? form.provided.images.flatMap((ref, index) => {
|
|
159
240
|
const id = file(ref, "images", `provided.images.${index}`);
|
|
160
241
|
return id === undefined ? [] : [id];
|
|
161
242
|
})
|
|
162
243
|
: [],
|
|
244
|
+
// Optional, so only a file that was attached is checked; with Shorts off it is kept on
|
|
245
|
+
// the draft for when Shorts is turned back on, and never reaches the run.
|
|
246
|
+
...(shortsOn && form.provided.shortsMusic
|
|
247
|
+
? pickMusic(file(form.provided.shortsMusic, "audio", "shorts.music", musicMessages))
|
|
248
|
+
: {}),
|
|
249
|
+
// Only while the bed plays the user's own file; kept on the draft otherwise.
|
|
250
|
+
...(bed?.source === "upload"
|
|
251
|
+
? pickBed(file(form.provided.ambientBed ?? null, "audio", "ambientBed.file", bedMessages))
|
|
252
|
+
: {}),
|
|
163
253
|
},
|
|
164
254
|
chunking: {
|
|
165
255
|
mode: chunkMode,
|
|
@@ -177,7 +267,11 @@ export function toAdmissionDraft(input) {
|
|
|
177
267
|
mode,
|
|
178
268
|
fontSize: number(form.subtitles.fontSize, "subtitles.fontSize", 120, 16),
|
|
179
269
|
},
|
|
180
|
-
|
|
270
|
+
// This run's own gap when one was typed, else the one Settings has. Text that is not a
|
|
271
|
+
// number is NaN, which admission refuses at silenceGapSeconds.
|
|
272
|
+
silenceGapSeconds: form.silenceGapSeconds === undefined || form.silenceGapSeconds.trim() === ""
|
|
273
|
+
? input.silenceGapSeconds
|
|
274
|
+
: Number(form.silenceGapSeconds),
|
|
181
275
|
imageSeconds: measure("imageSeconds", sources.video === "generate" || shortsOn, defaultImageSeconds, imageSecondsProblem),
|
|
182
276
|
zoomPercent: measure("zoomPercent", sources.video === "generate", defaultZoomPercent, zoomPercentProblem),
|
|
183
277
|
// A choice from a list, so there is nothing to refuse; a run without a video keeps it
|
|
@@ -185,6 +279,15 @@ export function toAdmissionDraft(input) {
|
|
|
185
279
|
motionStyle: form.motionStyle,
|
|
186
280
|
// Kept the same way; admission checks it only while the video renders.
|
|
187
281
|
...(form.videoEdit === undefined ? {} : { videoEdit: form.videoEdit }),
|
|
282
|
+
// Spoken by the speakers only while narration is generated.
|
|
283
|
+
...(form.voices !== undefined && sources.audio === "generate" ? { voices: form.voices } : {}),
|
|
284
|
+
// Under the long video's narration only; a number already refused above leaves it out
|
|
285
|
+
// rather than saved as NaN.
|
|
286
|
+
...(bed !== undefined && bedRefused === 0 ? { ambientBed: bed } : {}),
|
|
287
|
+
// Only while on, and only with a narration to level, so the run is otherwise the one it was.
|
|
288
|
+
...pickLoudness(sources.audio, loudnessOfForm(form.loudness, input.loudness ?? defaultLoudness)),
|
|
289
|
+
// Pauses between sentences: the default for a new run, with a narration to pace.
|
|
290
|
+
...(sources.audio === "generate" ? pausesOfForm(form) : {}),
|
|
188
291
|
edgeSilenceSeconds: measure("edgeSilenceSeconds", sources.audio !== "off", defaultEdgeSilenceSeconds, edgeSilenceSecondsProblem),
|
|
189
292
|
};
|
|
190
293
|
const parsed = runDraftSchema.safeParse(draft);
|
|
@@ -198,3 +301,51 @@ export function toAdmissionDraft(input) {
|
|
|
198
301
|
? { ok: false, fields }
|
|
199
302
|
: { ok: true, draft: parsed.data };
|
|
200
303
|
}
|
|
304
|
+
function pickLoudness(audio, loudness) {
|
|
305
|
+
return audio === "off" || loudness === undefined ? {} : { loudness };
|
|
306
|
+
}
|
|
307
|
+
// More images for long videos, from what Play's control holds: the rate as images per hour,
|
|
308
|
+
// and the narration length it is planned for, which is the provided article's own words or
|
|
309
|
+
// else Review's expected words (`images/scale.ts`).
|
|
310
|
+
function imageScaleOf(document, fields) {
|
|
311
|
+
const { form } = document;
|
|
312
|
+
const typed = form.imageScale?.value.trim() ?? "";
|
|
313
|
+
const value = typed === "" ? Number.NaN : Number(typed);
|
|
314
|
+
const minutes = form.imageScale?.every !== "hour";
|
|
315
|
+
const inRange = minutes
|
|
316
|
+
? value >= everyMinutesMin && value <= everyMinutesMax
|
|
317
|
+
: value >= imagesPerHourMin && value <= imagesPerHourMax;
|
|
318
|
+
if (!inRange)
|
|
319
|
+
fields.push({
|
|
320
|
+
field: "imageScale.value",
|
|
321
|
+
message: minutes
|
|
322
|
+
? `Enter a number of minutes between ${String(everyMinutesMin)} and ${String(everyMinutesMax)} under Images → More images for long videos.`
|
|
323
|
+
: `Enter a number of images per hour between ${String(imagesPerHourMin)} and ${String(imagesPerHourMax)} under Images → More images for long videos.`,
|
|
324
|
+
});
|
|
325
|
+
const perHour = !inRange
|
|
326
|
+
? imagesPerHourMin
|
|
327
|
+
: minutes
|
|
328
|
+
? perHourFromMinutes(value)
|
|
329
|
+
: Math.round(value * 10000) / 10000;
|
|
330
|
+
if (form.sources.article === "provide")
|
|
331
|
+
return { perHour, words: Math.max(1, wordCount(form.provided.article)) };
|
|
332
|
+
// Review checks the expected words on its own (`review-inputs.ts`); a draft whose figure is
|
|
333
|
+
// not a whole number yet plans for Play's starting one.
|
|
334
|
+
const expected = Number(document.expectedWords.trim());
|
|
335
|
+
return {
|
|
336
|
+
perHour,
|
|
337
|
+
words: Number.isInteger(expected) && expected >= 1 && expected <= expectedWordsMax
|
|
338
|
+
? expected
|
|
339
|
+
: defaultExpectedWords,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
// "Describe tables and figures" and "Skip code" are stored only when on, so a run started
|
|
343
|
+
// with them off saves the config every run made before them did.
|
|
344
|
+
function onlyWhenOn(audio) {
|
|
345
|
+
const { describeFigures, skipCode, ...rest } = audio;
|
|
346
|
+
return {
|
|
347
|
+
...rest,
|
|
348
|
+
...(describeFigures === true ? { describeFigures } : {}),
|
|
349
|
+
...(describeFigures === true && skipCode === true ? { skipCode } : {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -2,6 +2,7 @@ import { checkRuntimeModel } from "../../catalog/runtime-models.js";
|
|
|
2
2
|
import { modelFields } from "../../catalog/validate.js";
|
|
3
3
|
import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
4
4
|
import { usesNarrationPreparation, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
|
|
5
|
+
import { usesReviews } from "../reviews/rules.js";
|
|
5
6
|
import { cliPathChanged } from "../settings/cli-paths.js";
|
|
6
7
|
import { hasKey, listVoices } from "../settings/repo.js";
|
|
7
8
|
import { usesAnimation } from "../video/edit-settings.js";
|
|
@@ -36,6 +37,8 @@ export function choices(runs) {
|
|
|
36
37
|
usesShorts(d) ||
|
|
37
38
|
usesAnimation(d),
|
|
38
39
|
},
|
|
40
|
+
// The reviewer is its own text model, checked like the LLM row.
|
|
41
|
+
{ field: "reviews", family: "llm", choice: d.reviews, needed: usesReviews(d) },
|
|
39
42
|
])
|
|
40
43
|
.flatMap((c) => (c.needed && c.choice ? [{ ...c, ...c.choice }] : []));
|
|
41
44
|
return [...new Map(selected.map((c) => [`${c.family}:${c.provider}:${c.model}`, c])).values()];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { fingerprint } from "../../kernel/runner/work.js";
|
|
3
|
+
import { draftAttachmentKinds } from "./schema.js";
|
|
3
4
|
const rowSchema = z.object({
|
|
4
5
|
id: z.string(),
|
|
5
6
|
schema_version: z.number(),
|
|
@@ -21,7 +22,7 @@ const attachmentSchema = z.object({
|
|
|
21
22
|
id: z.string(),
|
|
22
23
|
draft_id: z.string(),
|
|
23
24
|
staged_file_id: z.string().nullable(),
|
|
24
|
-
kind: z.enum(
|
|
25
|
+
kind: z.enum(draftAttachmentKinds),
|
|
25
26
|
original_filename: z.string(),
|
|
26
27
|
status: z.enum(["pending", "ready", "reattach"]),
|
|
27
28
|
error: z.string().nullable(),
|
|
@@ -52,6 +53,13 @@ export function attachmentRefs(document) {
|
|
|
52
53
|
...(p.audio === null ? [] : [{ ...p.audio, kind: "audio" }]),
|
|
53
54
|
...p.images.map((file) => ({ ...file, kind: "images" })),
|
|
54
55
|
...(p.thumbnail === null ? [] : [{ ...p.thumbnail, kind: "thumbnail" }]),
|
|
56
|
+
...(p.reference === undefined || p.reference === null
|
|
57
|
+
? []
|
|
58
|
+
: [{ ...p.reference, kind: "reference" }]),
|
|
59
|
+
// The shorts' music is staged like narration audio, so it is an audio attachment.
|
|
60
|
+
...(p.shortsMusic == null ? [] : [{ ...p.shortsMusic, kind: "audio" }]),
|
|
61
|
+
// So is the ambient bed's own file.
|
|
62
|
+
...(p.ambientBed == null ? [] : [{ ...p.ambientBed, kind: "audio" }]),
|
|
55
63
|
];
|
|
56
64
|
}
|
|
57
65
|
export function requestHash(value) {
|
|
@@ -3,6 +3,7 @@ import { readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { modelFields } from "../../catalog/validate.js";
|
|
5
5
|
import { admit } from "../admission/rules.js";
|
|
6
|
+
import { brandedForm, brandedRun, castSnapshot, castVoicedRun, draftChannel, } from "../channels/runs.js";
|
|
6
7
|
import { estimateRun } from "../estimate/index.js";
|
|
7
8
|
import { listEntries } from "../library/repo.js";
|
|
8
9
|
import { pickTemplates, renderPicked } from "../library/slots.js";
|
|
@@ -51,12 +52,27 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
51
52
|
field: "variants",
|
|
52
53
|
message: "You can review at most 50 videos at once, counting the first one. Remove some variations.",
|
|
53
54
|
});
|
|
54
|
-
const
|
|
55
|
-
|
|
55
|
+
const entries = [...(document.librarySnapshot?.entries ?? []), ...listEntries(deps.db)];
|
|
56
|
+
// The channel's brand kit fills what the setup leaves at its default, and its cast rides
|
|
57
|
+
// along for the images (`slices/channels/runs.ts`).
|
|
58
|
+
const channel = draftChannel(deps.db, document);
|
|
59
|
+
const useBrandKit = document.form.useBrandKit !== false;
|
|
60
|
+
const branded = toAdmissionDraft({
|
|
61
|
+
document: {
|
|
62
|
+
...document,
|
|
63
|
+
form: brandedForm(deps.db, document.form, channel.brand, entries),
|
|
64
|
+
},
|
|
56
65
|
attachments: fresh.value.attachments,
|
|
57
|
-
entries
|
|
66
|
+
entries,
|
|
58
67
|
silenceGapSeconds: readSettings(deps).silenceGapSeconds,
|
|
68
|
+
loudness: readSettings(deps).loudness,
|
|
59
69
|
});
|
|
70
|
+
const converted = branded.ok
|
|
71
|
+
? {
|
|
72
|
+
ok: true,
|
|
73
|
+
draft: castVoicedRun(deps.db, brandedRun(branded.draft, channel, castSnapshot(deps.db, channel.id), useBrandKit)),
|
|
74
|
+
}
|
|
75
|
+
: branded;
|
|
60
76
|
if (!converted.ok)
|
|
61
77
|
fields.push(...converted.fields);
|
|
62
78
|
if (!converted.ok || !words.success || fields.length)
|
|
@@ -110,7 +126,10 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
110
126
|
const activeIds = new Set(runs.flatMap((run) => [
|
|
111
127
|
run.draft.provided.audio,
|
|
112
128
|
run.draft.provided.thumbnail,
|
|
129
|
+
run.draft.provided.reference,
|
|
113
130
|
...(run.draft.provided.images ?? []),
|
|
131
|
+
run.draft.provided.shortsMusic,
|
|
132
|
+
run.draft.provided.ambientBed,
|
|
114
133
|
]));
|
|
115
134
|
const attachmentIdentity = fresh.value.attachments.flatMap((file) => file.stagedFileId !== null && activeIds.has(file.stagedFileId)
|
|
116
135
|
? [{ id: file.id, stagedFileId: file.stagedFileId, bytes: file.bytes }]
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formats, stageKinds } from "../../kernel/pipeline.js";
|
|
3
|
+
import { languageSchema } from "../../kernel/ports/languages.js";
|
|
3
4
|
import { thinkingModes } from "../../kernel/ports/llm.js";
|
|
4
|
-
import { motionStyles, stageSources } from "../admission/model.js";
|
|
5
|
+
import { motionStyles, referenceSources, stageSources } from "../admission/model.js";
|
|
5
6
|
import { defaultEdgeSilenceSeconds, defaultImageSeconds, defaultMotionStyle, defaultZoomPercent, } from "../admission/rules.js";
|
|
6
7
|
import { runDraftSchema, videoEditSchema } from "../admission/schema.js";
|
|
7
8
|
import { checkpointRowSchema, checkpointStageSchema } from "../checkpoints/schema.js";
|
|
8
9
|
import { documentSettingsSchema } from "../document/theme-schema.js";
|
|
9
10
|
import { librarySnapshotSchema } from "../library/snapshot.js";
|
|
10
11
|
import { chunkModes } from "../narration/chunk.js";
|
|
12
|
+
import { reviewModes, reviewStages } from "../reviews/model.js";
|
|
11
13
|
import { subtitleModes, subtitlePositions } from "../subtitles/model.js";
|
|
14
|
+
import { ambientBedSources } from "../video/ambient-bed.js";
|
|
15
|
+
import { voicesSettingsSchema } from "../voices/model.js";
|
|
12
16
|
const id = z.uuid();
|
|
13
17
|
const text = z.string();
|
|
14
18
|
const values = z.record(text, text).readonly();
|
|
15
19
|
const provider = z
|
|
16
20
|
.object({ provider: text, model: text, thinking: z.enum(thinkingModes).optional() })
|
|
17
21
|
.strict();
|
|
22
|
+
// What a draft's uploads are for: a stage's files, or the Images stage's establishing image.
|
|
23
|
+
export const draftAttachmentKinds = ["audio", "images", "thumbnail", "reference"];
|
|
18
24
|
const file = z.object({ attachmentId: id, name: text }).strict().readonly();
|
|
19
25
|
export const playDraftFormSchema = z
|
|
20
26
|
.object({
|
|
@@ -42,14 +48,52 @@ export const playDraftFormSchema = z
|
|
|
42
48
|
voice: text,
|
|
43
49
|
usePronunciationGlossary: z.boolean().optional(),
|
|
44
50
|
shareGlossary: z.boolean().optional(),
|
|
51
|
+
useNarrationAliases: z.boolean().optional(),
|
|
52
|
+
describeFigures: z.boolean().optional(),
|
|
53
|
+
skipCode: z.boolean().optional(),
|
|
45
54
|
})
|
|
46
55
|
.readonly(),
|
|
47
56
|
images: provider.readonly(),
|
|
57
|
+
// Absent on drafts and templates saved before the establishing image: Off. The prompt is
|
|
58
|
+
// an image prompt's name, "" when none is picked.
|
|
59
|
+
reference: z
|
|
60
|
+
.object({
|
|
61
|
+
source: z.enum(["off", ...referenceSources]),
|
|
62
|
+
prompt: text,
|
|
63
|
+
thumbnail: z.boolean(),
|
|
64
|
+
})
|
|
65
|
+
.strict()
|
|
66
|
+
.readonly()
|
|
67
|
+
.optional(),
|
|
48
68
|
articlePrompt: text,
|
|
49
69
|
narrationPrompt: text.optional(),
|
|
70
|
+
// Absent on drafts and templates saved before three thumbnails: one.
|
|
71
|
+
thumbnailCount: z.union([z.literal(1), z.literal(3)]).optional(),
|
|
72
|
+
// Level the volume (`loudness/model.ts`). Absent on drafts and templates nobody changed it
|
|
73
|
+
// in: Settings → General's default, which is on for a new run. The targets are LUFS; absent
|
|
74
|
+
// is Settings' own.
|
|
75
|
+
loudness: z
|
|
76
|
+
.object({
|
|
77
|
+
enabled: z.boolean(),
|
|
78
|
+
videoLufs: z.number().optional(),
|
|
79
|
+
audioFilesLufs: z.number().optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict()
|
|
82
|
+
.readonly()
|
|
83
|
+
.optional(),
|
|
84
|
+
// Pauses between sentences and paragraphs in seconds, raw text like every number on Play.
|
|
85
|
+
// Absent on drafts and templates nobody changed them in: the sentence default and no
|
|
86
|
+
// paragraph minimum (`narration/pauses-model.ts`).
|
|
87
|
+
sentencePause: text.optional(),
|
|
88
|
+
paragraphPause: text.optional(),
|
|
50
89
|
// Absent on drafts and templates saved before the YouTube description: off, built-in prompt.
|
|
51
90
|
youtubeDescription: z.boolean().optional(),
|
|
91
|
+
showFigures: z.boolean().optional(),
|
|
52
92
|
descriptionPrompt: text.optional(),
|
|
93
|
+
// The project language (`kernel/ports/languages.ts`). Absent on drafts and templates saved
|
|
94
|
+
// before it, and on a draft nobody picked one for: the channel's language, else English.
|
|
95
|
+
// English picked on purpose is kept as "en", so a channel's language does not replace it.
|
|
96
|
+
language: languageSchema.optional(),
|
|
53
97
|
// Absent on drafts and templates saved before Shorts: off. The numbers are raw text, like
|
|
54
98
|
// every other number on Play; the prompts are names, "" being the built-in ones.
|
|
55
99
|
shorts: z
|
|
@@ -70,7 +114,33 @@ export const playDraftFormSchema = z
|
|
|
70
114
|
.strict()
|
|
71
115
|
.readonly()
|
|
72
116
|
.optional(),
|
|
117
|
+
// Absent on drafts and templates saved before automatic reviews: every review Off. The
|
|
118
|
+
// retries are raw text like every other number on Play; a stage's prompt is a Review
|
|
119
|
+
// prompt's name, "" being the built-in one.
|
|
120
|
+
reviews: z
|
|
121
|
+
.object({
|
|
122
|
+
provider: text,
|
|
123
|
+
model: text,
|
|
124
|
+
thinking: z.enum(thinkingModes).optional(),
|
|
125
|
+
retries: text,
|
|
126
|
+
stages: z
|
|
127
|
+
.partialRecord(z.enum(reviewStages), z
|
|
128
|
+
.object({ mode: z.enum(reviewModes), prompt: text })
|
|
129
|
+
.strict()
|
|
130
|
+
.readonly())
|
|
131
|
+
.readonly(),
|
|
132
|
+
})
|
|
133
|
+
.strict()
|
|
134
|
+
.readonly()
|
|
135
|
+
.optional(),
|
|
73
136
|
imagePrompts: z.array(z.object({ name: text, number: text }).strict().readonly()).readonly(),
|
|
137
|
+
// More images for long videos, as typed: "every [value] minutes" or "[value] per hour".
|
|
138
|
+
// Absent is off, which is what every draft and template saved before it was.
|
|
139
|
+
imageScale: z
|
|
140
|
+
.object({ every: z.enum(["minutes", "hour"]), value: text })
|
|
141
|
+
.strict()
|
|
142
|
+
.readonly()
|
|
143
|
+
.optional(),
|
|
74
144
|
thumbnailPrompt: text,
|
|
75
145
|
intro: text,
|
|
76
146
|
outro: text,
|
|
@@ -94,9 +164,30 @@ export const playDraftFormSchema = z
|
|
|
94
164
|
edgeSilenceSeconds: text.default(String(defaultEdgeSilenceSeconds)),
|
|
95
165
|
zoomPercent: text.default(String(defaultZoomPercent)),
|
|
96
166
|
motionStyle: z.enum(motionStyles).default(defaultMotionStyle),
|
|
167
|
+
// The silence between narration segments for this run, raw text like the numbers above.
|
|
168
|
+
// Absent or empty: the gap Settings has.
|
|
169
|
+
silenceGapSeconds: text.optional(),
|
|
97
170
|
// Absent on drafts and templates saved before the edit settings: today's slideshow. Every
|
|
98
171
|
// field is a pick from a list, so it is kept as the settings themselves.
|
|
99
172
|
videoEdit: videoEditSchema.strict().readonly().optional(),
|
|
173
|
+
// Whether the channel's brand kit fills what this setup leaves at its default
|
|
174
|
+
// (`slices/channels/runs.ts`). Absent is on.
|
|
175
|
+
useBrandKit: z.boolean().optional(),
|
|
176
|
+
// Absent on drafts and templates saved before multiple voices: the Narration format. Every
|
|
177
|
+
// number is a pick from a list, so it is kept as the settings themselves.
|
|
178
|
+
voices: voicesSettingsSchema.strict().readonly().optional(),
|
|
179
|
+
// The ambient bed under the long video (`video/ambient-bed.ts`), numbers as typed. Absent
|
|
180
|
+
// takes the channel's brand kit's, if any; "none" asks for no bed whatever the channel has.
|
|
181
|
+
ambientBed: z
|
|
182
|
+
.object({
|
|
183
|
+
source: z.enum(["none", ...ambientBedSources]),
|
|
184
|
+
level: text,
|
|
185
|
+
fadeIn: text,
|
|
186
|
+
tail: text,
|
|
187
|
+
})
|
|
188
|
+
.strict()
|
|
189
|
+
.readonly()
|
|
190
|
+
.optional(),
|
|
100
191
|
values,
|
|
101
192
|
provided: z
|
|
102
193
|
.object({
|
|
@@ -105,6 +196,14 @@ export const playDraftFormSchema = z
|
|
|
105
196
|
audio: file.nullable(),
|
|
106
197
|
thumbnail: file.nullable(),
|
|
107
198
|
images: z.array(file).readonly(),
|
|
199
|
+
// The uploaded establishing image; absent on drafts saved before it.
|
|
200
|
+
reference: file.nullable().optional(),
|
|
201
|
+
// The Shorts step's background music, uploaded as an audio attachment. Absent on
|
|
202
|
+
// drafts and templates saved before Play offered it: no music.
|
|
203
|
+
shortsMusic: file.nullable().optional(),
|
|
204
|
+
// The ambient bed's own file, an audio attachment; used only while its source is
|
|
205
|
+
// "upload". Absent on drafts and templates saved before it.
|
|
206
|
+
ambientBed: file.nullable().optional(),
|
|
108
207
|
})
|
|
109
208
|
.strict()
|
|
110
209
|
.readonly(),
|
|
@@ -120,6 +219,8 @@ export const playDraftDocumentSchema = z
|
|
|
120
219
|
.strict()
|
|
121
220
|
.readonly()
|
|
122
221
|
.optional(),
|
|
222
|
+
// The channel picked on Play. Absent runs in the template's channel, or the default one.
|
|
223
|
+
channelId: id.optional(),
|
|
123
224
|
form: playDraftFormSchema,
|
|
124
225
|
section: z.enum(["content", "outputs", "style", "review"]),
|
|
125
226
|
variants: z.array(z.object({ id, title: text, values }).strict().readonly()).readonly(),
|
|
@@ -150,7 +251,7 @@ export const discardDraftInputSchema = z
|
|
|
150
251
|
export const draftAttachmentSchema = z
|
|
151
252
|
.object({
|
|
152
253
|
id,
|
|
153
|
-
kind: z.enum(
|
|
254
|
+
kind: z.enum(draftAttachmentKinds),
|
|
154
255
|
name: text,
|
|
155
256
|
state: z.enum(["pending", "copying", "ready", "reattach"]),
|
|
156
257
|
stagedFileId: text.nullable(),
|
|
@@ -202,6 +303,9 @@ const costEstimateSchema = z
|
|
|
202
303
|
low: z.number().nullable(),
|
|
203
304
|
high: z.number().nullable(),
|
|
204
305
|
detail: text,
|
|
306
|
+
onPlan: z.boolean().optional(),
|
|
307
|
+
apiLow: z.number().nullable().optional(),
|
|
308
|
+
apiHigh: z.number().nullable().optional(),
|
|
205
309
|
})
|
|
206
310
|
.strict()
|
|
207
311
|
.readonly())
|
|
@@ -209,6 +313,9 @@ const costEstimateSchema = z
|
|
|
209
313
|
low: z.number(),
|
|
210
314
|
high: z.number(),
|
|
211
315
|
unknown: z.number(),
|
|
316
|
+
apiLow: z.number().optional(),
|
|
317
|
+
apiHigh: z.number().optional(),
|
|
318
|
+
apiUnknown: z.number().optional(),
|
|
212
319
|
expectedWords: z.number(),
|
|
213
320
|
catalogueDate: text.nullable(),
|
|
214
321
|
assumptions: z.array(text).readonly(),
|
|
@@ -44,7 +44,7 @@ function parseStoredJson(value) {
|
|
|
44
44
|
throw error;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
function documentOf(row) {
|
|
47
|
+
export function documentOf(row) {
|
|
48
48
|
if (row.schema_version !== 1)
|
|
49
49
|
return null;
|
|
50
50
|
const parsed = playDraftDocumentSchema.safeParse(parseStoredJson(row.document_json));
|
|
@@ -208,6 +208,15 @@ export function forkDraft(deps, input) {
|
|
|
208
208
|
audio: p.audio === null ? null : rewrite(p.audio),
|
|
209
209
|
thumbnail: p.thumbnail === null ? null : rewrite(p.thumbnail),
|
|
210
210
|
images: p.images.map(rewrite),
|
|
211
|
+
...(p.reference === undefined
|
|
212
|
+
? {}
|
|
213
|
+
: { reference: p.reference === null ? null : rewrite(p.reference) }),
|
|
214
|
+
...(p.shortsMusic === undefined
|
|
215
|
+
? {}
|
|
216
|
+
: { shortsMusic: p.shortsMusic === null ? null : rewrite(p.shortsMusic) }),
|
|
217
|
+
...(p.ambientBed === undefined
|
|
218
|
+
? {}
|
|
219
|
+
: { ambientBed: p.ambientBed === null ? null : rewrite(p.ambientBed) }),
|
|
211
220
|
},
|
|
212
221
|
},
|
|
213
222
|
};
|
|
@@ -80,7 +80,47 @@ export async function startPlayDraft(deps, input) {
|
|
|
80
80
|
};
|
|
81
81
|
const checked = await checkDraftReadiness(captured, claimed.value.runs);
|
|
82
82
|
const oldFiles = attachmentRows(deps.db, input.draftId).flatMap((a) => a.staged_file_id === null ? [] : [a.staged_file_id]);
|
|
83
|
-
|
|
83
|
+
let result;
|
|
84
|
+
try {
|
|
85
|
+
result = createReviewedRuns(deps, input, captured, checked, catalogue);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (!(error instanceof StartedRunMismatch))
|
|
89
|
+
throw error;
|
|
90
|
+
// Nothing the review promised was created: the transaction rolled back.
|
|
91
|
+
releaseStartClaim(deps, input, true);
|
|
92
|
+
try {
|
|
93
|
+
deps.log.write("error", "play.start.run-count", { detail: error.message });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* The refusal below still reaches the person. */
|
|
97
|
+
}
|
|
98
|
+
return { ok: false, reason: "stale-review", currentVersion: input.baseVersion, fields: [] };
|
|
99
|
+
}
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
releaseStartClaim(deps, input, result.reason === "stale-review");
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
if (!result.value.replayed) {
|
|
105
|
+
afterCommit(deps, () => deps.recordStarted(result.value.projectIds));
|
|
106
|
+
for (const id of oldFiles)
|
|
107
|
+
afterCommit(deps, () => releaseStagedFile(deps, id));
|
|
108
|
+
afterCommit(deps, () => {
|
|
109
|
+
if (result.value.queue.length)
|
|
110
|
+
pumpQueue(deps.db, deps.runner);
|
|
111
|
+
else
|
|
112
|
+
for (const id of result.value.projectIds)
|
|
113
|
+
deps.runner.tick(id);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
// Start creates exactly the runs the person reviewed: one project for one run, otherwise a
|
|
119
|
+
// queue entry and project per run. Anything else is thrown so the transaction rolls back.
|
|
120
|
+
class StartedRunMismatch extends Error {
|
|
121
|
+
}
|
|
122
|
+
function createReviewedRuns(deps, input, captured, checked, catalogue) {
|
|
123
|
+
return transact(deps.db, () => {
|
|
84
124
|
const receipt = readStartReceipt(deps.db, input.reviewId);
|
|
85
125
|
if (receipt)
|
|
86
126
|
return replayResult(receipt, input);
|
|
@@ -109,27 +149,12 @@ export async function startPlayDraft(deps, input) {
|
|
|
109
149
|
replayed: false,
|
|
110
150
|
...(checkpointSet.length ? { checkpointSet } : {}),
|
|
111
151
|
};
|
|
152
|
+
if (projectIds.length !== runs.length || queue.length !== (runs.length === 1 ? 0 : runs.length))
|
|
153
|
+
throw new StartedRunMismatch(`Start would create ${projectIds.length} of ${runs.length} reviewed videos (${queue.length} queued).`);
|
|
112
154
|
insertStartReceipt(deps, input, value);
|
|
113
155
|
markDraftStartedAndReleaseRefs(deps.db, input);
|
|
114
156
|
return { ok: true, value };
|
|
115
157
|
});
|
|
116
|
-
if (!result.ok) {
|
|
117
|
-
releaseStartClaim(deps, input, result.reason === "stale-review");
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
if (!result.value.replayed) {
|
|
121
|
-
afterCommit(deps, () => deps.recordStarted(result.value.projectIds));
|
|
122
|
-
for (const id of oldFiles)
|
|
123
|
-
afterCommit(deps, () => releaseStagedFile(deps, id));
|
|
124
|
-
afterCommit(deps, () => {
|
|
125
|
-
if (result.value.queue.length)
|
|
126
|
-
pumpQueue(deps.db, deps.runner);
|
|
127
|
-
else
|
|
128
|
-
for (const id of result.value.projectIds)
|
|
129
|
-
deps.runner.tick(id);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
158
|
}
|
|
134
159
|
function afterCommit(deps, run) {
|
|
135
160
|
try {
|
|
@@ -29,7 +29,8 @@ export async function uploadDraftAttachment(deps, input) {
|
|
|
29
29
|
return refuse("conflict");
|
|
30
30
|
const storage = { ...deps, emit: () => undefined };
|
|
31
31
|
const result = await stageUpload(storage, {
|
|
32
|
-
|
|
32
|
+
// The establishing image is the Images stage's upload.
|
|
33
|
+
stageKind: attachment.kind === "reference" ? "images" : attachment.kind,
|
|
33
34
|
originalFilename: attachment.original_filename,
|
|
34
35
|
content: input.content,
|
|
35
36
|
onAllocated(file) {
|