@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,7 +1,14 @@
|
|
|
1
1
|
import { stageKinds } from "../../kernel/pipeline.js";
|
|
2
|
+
import { imageScaleProblem } from "../images/scale.js";
|
|
3
|
+
import { loudnessFields } from "../loudness/model.js";
|
|
4
|
+
import { pauseFields } from "../narration/pauses-model.js";
|
|
5
|
+
import { reviewFields } from "../reviews/rules.js";
|
|
2
6
|
import { shortsSettingsProblems } from "../shorts/model.js";
|
|
7
|
+
import { ambientBedProblems, usesAmbientBed } from "../video/ambient-bed.js";
|
|
3
8
|
import { usesAnimation, videoEditProblems } from "../video/edit-settings.js";
|
|
9
|
+
import { usesVoices, voicesProblems } from "../voices/model.js";
|
|
4
10
|
import { sourceOf } from "./model.js";
|
|
11
|
+
import { shortModeFields } from "./short-mode.js";
|
|
5
12
|
export const titleMax = 200;
|
|
6
13
|
export const valueMax = 200;
|
|
7
14
|
export const numberPerPromptMax = 20;
|
|
@@ -84,7 +91,8 @@ export function admit(input) {
|
|
|
84
91
|
draft.outro?.mode === "llm" ||
|
|
85
92
|
usesNarrationPreparation(draft) ||
|
|
86
93
|
usesYoutubeDescription(draft) ||
|
|
87
|
-
usesShorts(draft)
|
|
94
|
+
usesShorts(draft) ||
|
|
95
|
+
(usesVoices(draft) && draft.voices?.source === "attribute");
|
|
88
96
|
if (needsLlm && !chosen(draft.llm)) {
|
|
89
97
|
fields.push({ field: "llm", message: "Choose a text (LLM) provider and model." });
|
|
90
98
|
}
|
|
@@ -93,6 +101,7 @@ export function admit(input) {
|
|
|
93
101
|
}
|
|
94
102
|
const voiced = draft.audio;
|
|
95
103
|
fields.push(...narrationPreparationFields(draft));
|
|
104
|
+
fields.push(...voicesFields(draft));
|
|
96
105
|
if (sources.audio === "generate") {
|
|
97
106
|
if (voiced === undefined || !chosen(voiced)) {
|
|
98
107
|
fields.push({ field: "audio", message: "Choose a narration provider and model." });
|
|
@@ -124,7 +133,15 @@ export function admit(input) {
|
|
|
124
133
|
}
|
|
125
134
|
fields.push(...youtubeDescriptionFields(draft));
|
|
126
135
|
fields.push(...shortsFields(draft));
|
|
136
|
+
fields.push(...shortModeFields(draft));
|
|
127
137
|
fields.push(...videoEditFields(draft));
|
|
138
|
+
fields.push(...ambientBedFields(draft, input.staged));
|
|
139
|
+
fields.push(...loudnessFields(draft.loudness));
|
|
140
|
+
fields.push(...pauseFields(draft));
|
|
141
|
+
fields.push(...referenceFields(draft));
|
|
142
|
+
fields.push(...reviewFields(draft));
|
|
143
|
+
if (usesReference(draft) && draft.reference?.source === "provide")
|
|
144
|
+
checkFile(input.staged, draft.provided.reference, "images", "provided.reference", "Upload the establishing image, or set Establishing image to Off in the Images section.", fields);
|
|
128
145
|
checkProvided(draft, input.staged, fields);
|
|
129
146
|
checkValues(draft, input.requiredSlots, fields);
|
|
130
147
|
if (!Number.isFinite(draft.silenceGapSeconds) ||
|
|
@@ -207,6 +224,9 @@ function checkImagePrompts(draft, fields) {
|
|
|
207
224
|
message: `A video can have at most ${imagesPerRunMax} images; these prompts ask for ${total}. Lower the numbers.`,
|
|
208
225
|
});
|
|
209
226
|
}
|
|
227
|
+
const scaleProblem = draft.imageScale === undefined ? undefined : imageScaleProblem(draft.imageScale);
|
|
228
|
+
if (scaleProblem !== undefined)
|
|
229
|
+
fields.push({ field: "imageScale", message: scaleProblem });
|
|
210
230
|
}
|
|
211
231
|
function checkProvided(draft, staged, fields) {
|
|
212
232
|
const { sources, provided } = draft;
|
|
@@ -243,6 +263,26 @@ function checkProvided(draft, staged, fields) {
|
|
|
243
263
|
checkFile(staged, id, "images", `provided.images.${index}`, "Pick an image.", fields);
|
|
244
264
|
}
|
|
245
265
|
}
|
|
266
|
+
checkShortsMusic(draft, staged, fields);
|
|
267
|
+
}
|
|
268
|
+
// The shorts' background music is optional, so only a file that was picked is checked, and
|
|
269
|
+
// only while Shorts is on. Named by where the control sits: Play's Export rail.
|
|
270
|
+
function checkShortsMusic(draft, staged, fields) {
|
|
271
|
+
const id = draft.provided.shortsMusic;
|
|
272
|
+
if (draft.shorts?.enabled !== true || id === undefined)
|
|
273
|
+
return;
|
|
274
|
+
const where = "under Outputs → Export → More shorts options → Background music";
|
|
275
|
+
const file = staged.find((candidate) => candidate.id === id);
|
|
276
|
+
if (file === undefined || file.stageKind !== "audio")
|
|
277
|
+
fields.push({
|
|
278
|
+
field: "shorts.music",
|
|
279
|
+
message: `The shorts' background music file is no longer available, so it can't be added to the project. Choose the file again ${where}, or remove it.`,
|
|
280
|
+
});
|
|
281
|
+
else if (file.state !== "staged")
|
|
282
|
+
fields.push({
|
|
283
|
+
field: "shorts.music",
|
|
284
|
+
message: `The shorts' background music is still uploading, so the run can't start yet. Wait for it to finish, or remove it ${where}.`,
|
|
285
|
+
});
|
|
246
286
|
}
|
|
247
287
|
// A run never starts with provided content that is missing or still copying.
|
|
248
288
|
function checkFile(staged, id, kind, field, missing, fields) {
|
|
@@ -297,6 +337,21 @@ export function narrationPreparationFields(draft) {
|
|
|
297
337
|
]
|
|
298
338
|
: [];
|
|
299
339
|
}
|
|
340
|
+
// Multiple voices, checked only while narration is generated: a format left set with
|
|
341
|
+
// narration Off or uploaded asks for nothing.
|
|
342
|
+
export function voicesFields(draft) {
|
|
343
|
+
if (draft.voices === undefined || !usesVoices(draft))
|
|
344
|
+
return [];
|
|
345
|
+
const fields = [...voicesProblems(draft.voices)];
|
|
346
|
+
// Preparation adds delivery tags to the turns of speakers on Inworld TTS-2, so it needs one.
|
|
347
|
+
if (!blank(draft.narrationPrompt) &&
|
|
348
|
+
!draft.voices.speakers.some((speaker) => speaker.voice.provider === "inworld" && speaker.voice.model === "inworld-tts-2"))
|
|
349
|
+
fields.push({
|
|
350
|
+
field: "narrationPrompt",
|
|
351
|
+
message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or clear Narration Preparation in Audio → Advanced.",
|
|
352
|
+
});
|
|
353
|
+
return fields;
|
|
354
|
+
}
|
|
300
355
|
// The YouTube description is written from the narration's word timings, so it needs
|
|
301
356
|
// narration; it runs whether the video renders or only the WAV is exported.
|
|
302
357
|
export function usesYoutubeDescription(draft) {
|
|
@@ -312,6 +367,30 @@ export function youtubeDescriptionFields(draft) {
|
|
|
312
367
|
]
|
|
313
368
|
: [];
|
|
314
369
|
}
|
|
370
|
+
// The establishing image belongs to the Images stage: it is made, and the images drawn with
|
|
371
|
+
// it, only while images are Generate.
|
|
372
|
+
export function usesReference(draft) {
|
|
373
|
+
return draft.reference !== undefined && draft.sources.images === "generate";
|
|
374
|
+
}
|
|
375
|
+
// Whether the thumbnail is drawn with it too: on unless turned off, and only for a thumbnail
|
|
376
|
+
// the image provider makes.
|
|
377
|
+
export function referenceForThumbnail(draft) {
|
|
378
|
+
return (usesReference(draft) &&
|
|
379
|
+
draft.reference?.thumbnail !== false &&
|
|
380
|
+
(draft.sources.thumbnail === "from_prompt" || draft.sources.thumbnail === "prompt_by_llm"));
|
|
381
|
+
}
|
|
382
|
+
export function referenceFields(draft) {
|
|
383
|
+
return usesReference(draft) &&
|
|
384
|
+
draft.reference?.source === "prompt" &&
|
|
385
|
+
(draft.reference.prompt === undefined || draft.reference.prompt.trim() === "")
|
|
386
|
+
? [
|
|
387
|
+
{
|
|
388
|
+
field: "reference.prompt",
|
|
389
|
+
message: "Pick an image prompt for the establishing image, upload one instead, or set Establishing image to Off in the Images section.",
|
|
390
|
+
},
|
|
391
|
+
]
|
|
392
|
+
: [];
|
|
393
|
+
}
|
|
315
394
|
// Shorts are cut from the narration and captioned from its word timings, so they need
|
|
316
395
|
// narration, like the YouTube description; they run whether the video renders or not.
|
|
317
396
|
export function usesShorts(draft) {
|
|
@@ -347,9 +426,55 @@ export function videoEditFields(draft) {
|
|
|
347
426
|
message: problem.message,
|
|
348
427
|
}));
|
|
349
428
|
}
|
|
429
|
+
// The ambient bed, checked only while the long video renders with narration; its own file only
|
|
430
|
+
// while the source is "upload". Named by where the control sits: Play's Export rail.
|
|
431
|
+
export function ambientBedFields(draft, staged) {
|
|
432
|
+
const bed = draft.ambientBed;
|
|
433
|
+
if (bed === undefined || !usesAmbientBed(draft))
|
|
434
|
+
return [];
|
|
435
|
+
const where = "under Outputs → Export → Ambient sound";
|
|
436
|
+
const fields = ambientBedProblems(bed).map((problem) => ({
|
|
437
|
+
field: `ambientBed.${problem.field}`,
|
|
438
|
+
message: `${problem.message} Change it ${where}.`,
|
|
439
|
+
}));
|
|
440
|
+
if (bed.source !== "upload")
|
|
441
|
+
return fields;
|
|
442
|
+
const id = draft.provided.ambientBed;
|
|
443
|
+
const file = id === undefined ? undefined : staged.find((candidate) => candidate.id === id);
|
|
444
|
+
if (file === undefined || file.stageKind !== "audio")
|
|
445
|
+
fields.push({
|
|
446
|
+
field: "ambientBed.file",
|
|
447
|
+
message: `The ambient sound's audio file is missing, so it can't be added to the project. Choose the file ${where}, or pick Rain, Fireplace or Wind instead.`,
|
|
448
|
+
});
|
|
449
|
+
else if (file.state !== "staged")
|
|
450
|
+
fields.push({
|
|
451
|
+
field: "ambientBed.file",
|
|
452
|
+
message: `The ambient sound's audio file is still uploading, so the run can't start yet. Wait for it to finish ${where}.`,
|
|
453
|
+
});
|
|
454
|
+
return fields;
|
|
455
|
+
}
|
|
350
456
|
function chosen(choice) {
|
|
351
457
|
return choice !== undefined && choice.provider.trim() !== "" && choice.model.trim() !== "";
|
|
352
458
|
}
|
|
459
|
+
// The aliases a run narrates with: its copied Library → Aliases while Use narration aliases is
|
|
460
|
+
// on for generated audio, otherwise none. Any voice provider: an alias is plain text.
|
|
461
|
+
export function narrationAliasesOf(draft) {
|
|
462
|
+
return draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
|
|
463
|
+
? (draft.narrationAliases ?? [])
|
|
464
|
+
: [];
|
|
465
|
+
}
|
|
466
|
+
// Describe tables and figures in the narration: generated narration, the setting on, and a
|
|
467
|
+
// text model to write the descriptions. Without a text model the narration flattens the
|
|
468
|
+
// article as it always did, so the default being on never asks for one.
|
|
469
|
+
export function usesDescribedNarration(draft) {
|
|
470
|
+
return (draft.sources.audio === "generate" && draft.audio?.describeFigures === true && chosen(draft.llm));
|
|
471
|
+
}
|
|
472
|
+
// "Show tables and figures on screen": a video, and the narration describing its blocks.
|
|
473
|
+
export function usesFigureCards(draft) {
|
|
474
|
+
return (usesDescribedNarration(draft) &&
|
|
475
|
+
draft.sources.video === "generate" &&
|
|
476
|
+
draft.showFigures === true);
|
|
477
|
+
}
|
|
353
478
|
export function usesPronunciationGlossary(draft) {
|
|
354
479
|
return (draft.sources.audio === "generate" &&
|
|
355
480
|
draft.audio?.usePronunciationGlossary === true &&
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { languageSchema } from "../../kernel/ports/languages.js";
|
|
2
3
|
import { thinkingModes } from "../../kernel/ports/llm.js";
|
|
4
|
+
import { castSnapshotSchema } from "../channels/schema.js";
|
|
3
5
|
import { checkpointStageSchema } from "../checkpoints/schema.js";
|
|
4
6
|
import { documentSettingsSchema } from "../document/theme-schema.js";
|
|
7
|
+
import { narrationAliasesSchema } from "../narration/aliases-schema.js";
|
|
5
8
|
import { chunkModes } from "../narration/chunk.js";
|
|
9
|
+
import { reviewModes, reviewStages } from "../reviews/model.js";
|
|
6
10
|
import { subtitleConfigSchema } from "../subtitles/model.js";
|
|
11
|
+
import { ambientBedSchema } from "../video/ambient-bed-schema.js";
|
|
7
12
|
import { animateModes, atmospheres, colorGrades, cutModes, lookLevels, transitionKinds, } from "../video/edit-settings.js";
|
|
8
|
-
import {
|
|
13
|
+
import { voicesSettingsSchema } from "../voices/model.js";
|
|
14
|
+
import { entryModes, formats, motionStyles, referenceSources, runModes, stageSources, } from "./model.js";
|
|
9
15
|
import { defaultEdgeSilenceSeconds, defaultImageSeconds, defaultMotionStyle, defaultZoomPercent, } from "./rules.js";
|
|
16
|
+
const hexColour = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
|
|
10
17
|
const providerChoice = z.object({
|
|
11
18
|
provider: z.string(),
|
|
12
19
|
model: z.string(),
|
|
@@ -29,6 +36,8 @@ export const videoEditSchema = z.object({
|
|
|
29
36
|
// The shape Play posts and the shape `projects.config` holds, in one place: the second
|
|
30
37
|
// is the first plus the rendered prompt texts.
|
|
31
38
|
export const runDraftSchema = z.object({
|
|
39
|
+
// Absent is the long video.
|
|
40
|
+
mode: z.enum(runModes).optional(),
|
|
32
41
|
checkpoints: z
|
|
33
42
|
.array(checkpointStageSchema)
|
|
34
43
|
.max(3)
|
|
@@ -55,8 +64,12 @@ export const runDraftSchema = z.object({
|
|
|
55
64
|
voice: z.string(),
|
|
56
65
|
usePronunciationGlossary: z.boolean().optional(),
|
|
57
66
|
shareGlossary: z.boolean().optional(),
|
|
67
|
+
useNarrationAliases: z.boolean().optional(),
|
|
68
|
+
describeFigures: z.boolean().optional(),
|
|
69
|
+
skipCode: z.boolean().optional(),
|
|
58
70
|
})
|
|
59
71
|
.optional(),
|
|
72
|
+
narrationAliases: narrationAliasesSchema.optional(),
|
|
60
73
|
sharedGlossary: z
|
|
61
74
|
.array(z.object({
|
|
62
75
|
term: z.string().min(1).max(200),
|
|
@@ -66,10 +79,20 @@ export const runDraftSchema = z.object({
|
|
|
66
79
|
.readonly()
|
|
67
80
|
.optional(),
|
|
68
81
|
images: providerChoice.optional(),
|
|
82
|
+
reference: z
|
|
83
|
+
.object({
|
|
84
|
+
source: z.enum(referenceSources),
|
|
85
|
+
prompt: z.string().optional(),
|
|
86
|
+
thumbnail: z.boolean().optional(),
|
|
87
|
+
})
|
|
88
|
+
.optional(),
|
|
69
89
|
articlePrompt: z.string().optional(),
|
|
70
90
|
narrationPrompt: z.string().optional(),
|
|
71
91
|
imagePrompts: z.array(z.object({ name: z.string(), number: z.number() })),
|
|
92
|
+
// The ranges are `slices/images/scale.ts`'s, checked by admission, not the schema's.
|
|
93
|
+
imageScale: z.object({ perHour: z.number(), words: z.number() }).optional(),
|
|
72
94
|
thumbnailPrompt: z.string().optional(),
|
|
95
|
+
thumbnailCount: z.union([z.literal(1), z.literal(3)]).optional(),
|
|
73
96
|
intro: entryChoice.optional(),
|
|
74
97
|
outro: entryChoice.optional(),
|
|
75
98
|
values: z.record(z.string(), z.string()),
|
|
@@ -79,6 +102,11 @@ export const runDraftSchema = z.object({
|
|
|
79
102
|
audio: z.string().optional(),
|
|
80
103
|
images: z.array(z.string()).optional(),
|
|
81
104
|
thumbnail: z.string().optional(),
|
|
105
|
+
reference: z.string().optional(),
|
|
106
|
+
// The staged file of the shorts' background music; used only while Shorts is on.
|
|
107
|
+
shortsMusic: z.string().optional(),
|
|
108
|
+
// The staged file of the ambient bed; used only while its source is "upload".
|
|
109
|
+
ambientBed: z.string().optional(),
|
|
82
110
|
}),
|
|
83
111
|
// Optional until Play carries the control; unknown keys are stripped by this schema, so
|
|
84
112
|
// a mode not listed here would never reach the audio stage.
|
|
@@ -99,6 +127,7 @@ export const runDraftSchema = z.object({
|
|
|
99
127
|
subtitles: subtitleConfigSchema.optional(),
|
|
100
128
|
document: documentSettingsSchema.optional(),
|
|
101
129
|
youtubeDescription: z.boolean().optional(),
|
|
130
|
+
showFigures: z.boolean().optional(),
|
|
102
131
|
descriptionPrompt: z.string().optional(),
|
|
103
132
|
// The ranges are `slices/shorts/model.ts`'s, checked by admission, not the schema's.
|
|
104
133
|
shorts: z
|
|
@@ -117,6 +146,44 @@ export const runDraftSchema = z.object({
|
|
|
117
146
|
.optional(),
|
|
118
147
|
// The ranges are `slices/video/edit-settings.ts`'s, checked by admission, not the schema's.
|
|
119
148
|
videoEdit: videoEditSchema.optional(),
|
|
149
|
+
channelId: z.string().optional(),
|
|
150
|
+
useBrandKit: z.literal(false).optional(),
|
|
151
|
+
cast: castSnapshotSchema.optional(),
|
|
152
|
+
earlierEpisodes: z
|
|
153
|
+
.array(z.object({ title: z.string(), summary: z.string() }))
|
|
154
|
+
.readonly()
|
|
155
|
+
.optional(),
|
|
156
|
+
titleStyle: z
|
|
157
|
+
.object({
|
|
158
|
+
fontId: z
|
|
159
|
+
.string()
|
|
160
|
+
.max(160)
|
|
161
|
+
.regex(/^[A-Za-z0-9_-]+$/)
|
|
162
|
+
.optional(),
|
|
163
|
+
color: hexColour.optional(),
|
|
164
|
+
})
|
|
165
|
+
.optional(),
|
|
166
|
+
endScreen: z.object({ text: z.string().max(200) }).optional(),
|
|
167
|
+
// The retry range is `slices/reviews/model.ts`'s, checked by admission, not the schema's.
|
|
168
|
+
reviews: z
|
|
169
|
+
.object({
|
|
170
|
+
provider: z.string(),
|
|
171
|
+
model: z.string(),
|
|
172
|
+
thinking: z.enum(thinkingModes).optional(),
|
|
173
|
+
retries: z.number().optional(),
|
|
174
|
+
stages: z.partialRecord(z.enum(reviewStages), z.object({ mode: z.enum(reviewModes), prompt: z.string().optional() })),
|
|
175
|
+
})
|
|
176
|
+
.optional(),
|
|
177
|
+
// The rules are `slices/voices/model.ts`'s, checked by admission, not the schema's.
|
|
178
|
+
voices: voicesSettingsSchema.optional(),
|
|
179
|
+
ambientBed: ambientBedSchema.optional(),
|
|
180
|
+
// Level the volume (`loudness/model.ts`); the range is checked by admission.
|
|
181
|
+
loudness: z.object({ videoLufs: z.number(), audioFilesLufs: z.number() }).strict().optional(),
|
|
182
|
+
// Pauses between sentences and paragraphs (`narration/pauses-model.ts`); checked by admission.
|
|
183
|
+
sentencePauseSeconds: z.number().optional(),
|
|
184
|
+
paragraphPauseSeconds: z.number().optional(),
|
|
185
|
+
// Absent is English (`kernel/ports/languages.ts`).
|
|
186
|
+
language: languageSchema.optional(),
|
|
120
187
|
});
|
|
121
188
|
export const runConfigSchema = runDraftSchema.extend({
|
|
122
189
|
rendered: z.record(z.string(), z.string()),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sourceOf } from "./model.js";
|
|
2
|
+
// The short-only run mode: the same stages as a long video, narrowed to one vertical clip of
|
|
3
|
+
// about a minute. The article is written for a short, the narration and images are made as
|
|
4
|
+
// usual, and the Video stage renders the whole narration through the Shorts renderer
|
|
5
|
+
// (`shorts/render.ts`) with its word-by-word captions instead of the slideshow. A short has
|
|
6
|
+
// no thumbnail, PDF or shorts of its own.
|
|
7
|
+
export function usesShortMode(draft) {
|
|
8
|
+
return draft.mode === "short";
|
|
9
|
+
}
|
|
10
|
+
// Said where each control sits, so a draft or a Save that breaks the mode names the fix.
|
|
11
|
+
export function shortModeFields(draft) {
|
|
12
|
+
if (!usesShortMode(draft))
|
|
13
|
+
return [];
|
|
14
|
+
const fields = [];
|
|
15
|
+
if (draft.format !== "9:16")
|
|
16
|
+
fields.push({
|
|
17
|
+
field: "format",
|
|
18
|
+
message: "A short is vertical. Choose the 9:16 format, or make a long video instead.",
|
|
19
|
+
});
|
|
20
|
+
if (sourceOf(draft.sources, "video") !== "generate")
|
|
21
|
+
fields.push({
|
|
22
|
+
field: "sources.video",
|
|
23
|
+
message: "A short is a video. Set Video to Generate.",
|
|
24
|
+
});
|
|
25
|
+
if (sourceOf(draft.sources, "audio") === "off")
|
|
26
|
+
fields.push({
|
|
27
|
+
field: "sources.audio",
|
|
28
|
+
message: "A short's length and captions come from its narration. Set Narration to Generate or Provide.",
|
|
29
|
+
});
|
|
30
|
+
if (sourceOf(draft.sources, "images") === "off")
|
|
31
|
+
fields.push({
|
|
32
|
+
field: "sources.images",
|
|
33
|
+
message: "A short shows images behind its captions. Set Images to Generate or Provide.",
|
|
34
|
+
});
|
|
35
|
+
for (const kind of ["thumbnail", "document"])
|
|
36
|
+
if (sourceOf(draft.sources, kind) !== "off")
|
|
37
|
+
fields.push({
|
|
38
|
+
field: `sources.${kind}`,
|
|
39
|
+
message: `A short has no ${kind === "thumbnail" ? "thumbnail" : "PDF"}. Set ${kind === "thumbnail" ? "Thumbnail" : "Document"} to Off.`,
|
|
40
|
+
});
|
|
41
|
+
if (draft.shorts?.enabled === true)
|
|
42
|
+
fields.push({
|
|
43
|
+
field: "shorts.enabled",
|
|
44
|
+
message: "A short isn't cut into more shorts. Turn Shorts off.",
|
|
45
|
+
});
|
|
46
|
+
return fields;
|
|
47
|
+
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { transact } from "../../kernel/db/tx.js";
|
|
2
2
|
import { stageKinds } from "../../kernel/pipeline.js";
|
|
3
3
|
import { storeArticleText } from "../article/store.js";
|
|
4
|
+
import { resolveChannelId, setProjectChannel } from "../channels/repo.js";
|
|
4
5
|
import { defaultDocumentTheme } from "../document/model.js";
|
|
6
|
+
import { earlierEpisodesFor } from "../episodes/related.js";
|
|
7
|
+
import { listNarrationAliases } from "../narration/aliases-library.js";
|
|
5
8
|
import { collectSharedGlossary } from "../narration/shared-glossary.js";
|
|
6
9
|
import { admitInitialRevision } from "../rebuild/runtime-admission.js";
|
|
7
10
|
import { adoptBaseline } from "../revisions/adopt.js";
|
|
11
|
+
import { insertAsset } from "../revisions/repo.js";
|
|
12
|
+
import { discardPreparedAssets } from "../storage/assets.js";
|
|
13
|
+
import { prepareStagedFile } from "../storage/prepare.js";
|
|
8
14
|
import { attachStagedFile, storeText } from "../storage/staging.js";
|
|
9
15
|
import { releaseStagedFile } from "../storage/staging-refs.js";
|
|
16
|
+
import { usesAmbientBed } from "../video/ambient-bed.js";
|
|
10
17
|
import { sourceOf, } from "./model.js";
|
|
11
18
|
import { insertProject, insertStage } from "./repo.js";
|
|
12
|
-
import { usesPronunciationGlossary } from "./rules.js";
|
|
19
|
+
import { usesPronunciationGlossary, usesReference } from "./rules.js";
|
|
20
|
+
import { render } from "./substitute.js";
|
|
13
21
|
// Provide → `provided` with its output attached; Off → `skipped`;
|
|
14
22
|
// everything else → `pending`.
|
|
15
23
|
export function initialState(source) {
|
|
@@ -26,8 +34,20 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
|
|
|
26
34
|
const shared = usesPronunciationGlossary(draft) && draft.audio?.shareGlossary === true
|
|
27
35
|
? collectSharedGlossary(deps).entries
|
|
28
36
|
: [];
|
|
37
|
+
// Library → Aliases as they are now: later edits there never change this project.
|
|
38
|
+
const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
|
|
39
|
+
? listNarrationAliases(deps.db)
|
|
40
|
+
: [];
|
|
41
|
+
// The title may name keywords too ("D&D Lore: {{Topic}}"), filled like a prompt's, so a
|
|
42
|
+
// template or Play keeps the pattern and each project gets its own title.
|
|
43
|
+
const title = render(draft.title, draft.values).trim() || draft.title;
|
|
44
|
+
// The channel's related earlier episodes as they are now (`slices/episodes`): later edits
|
|
45
|
+
// there never change this project. A draft never brings its own.
|
|
46
|
+
const { earlierEpisodes: _carried, ...fresh } = draft;
|
|
47
|
+
const earlier = earlierEpisodesFor(deps.db, draft, title);
|
|
29
48
|
const config = {
|
|
30
|
-
...
|
|
49
|
+
...fresh,
|
|
50
|
+
title,
|
|
31
51
|
// A new project always names its document theme: a config without one reads as the
|
|
32
52
|
// DiceMaster of older projects (see documentThemeOf), which no new project should get.
|
|
33
53
|
...(sourceOf(draft.sources, "document") === "generate" && draft.document === undefined
|
|
@@ -35,10 +55,12 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
|
|
|
35
55
|
: {}),
|
|
36
56
|
rendered,
|
|
37
57
|
...(shared.length === 0 ? {} : { sharedGlossary: shared }),
|
|
58
|
+
...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
|
|
59
|
+
...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
|
|
38
60
|
};
|
|
39
61
|
const project = {
|
|
40
62
|
id,
|
|
41
|
-
title
|
|
63
|
+
title,
|
|
42
64
|
format: draft.format,
|
|
43
65
|
config,
|
|
44
66
|
createdAt: at,
|
|
@@ -63,19 +85,40 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
|
|
|
63
85
|
// the project folder rather than moved, so a rollback leaves the staging files exactly
|
|
64
86
|
// as the form left them and the same Play can simply be pressed again.
|
|
65
87
|
const moved = [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
let music;
|
|
89
|
+
let bed;
|
|
90
|
+
try {
|
|
91
|
+
transact(deps.db, () => {
|
|
92
|
+
insertProject(deps.db, project);
|
|
93
|
+
setProjectChannel(deps.db, id, resolveChannelId(deps.db, draft.channelId));
|
|
94
|
+
for (const stage of stages) {
|
|
95
|
+
insertStage(deps.db, stage);
|
|
96
|
+
}
|
|
97
|
+
attachProvided(deps, id, draft, moved, retainStaged);
|
|
98
|
+
music = attachShortsMusic(deps, id, draft, moved);
|
|
99
|
+
bed = attachAmbientBed(deps, id, draft, moved);
|
|
100
|
+
// The music is named by the first revision, as Edit project → Shorts names it, so the
|
|
101
|
+
// project gets its revision now even where it would otherwise wait for its first view.
|
|
102
|
+
// The ambient bed's own file is named the same way.
|
|
103
|
+
if (deps.catalogue !== undefined || music !== undefined || bed !== undefined) {
|
|
104
|
+
const baseline = adoptBaseline(deps, id, templates, {
|
|
105
|
+
...(music === undefined ? {} : { shortsMusic: music.id }),
|
|
106
|
+
...(bed === undefined ? {} : { ambientBed: bed.id }),
|
|
107
|
+
});
|
|
108
|
+
if (!baseline.ok)
|
|
109
|
+
throw new Error("The new project has no revision.");
|
|
110
|
+
if (deps.catalogue !== undefined)
|
|
111
|
+
admitInitialRevision(deps, baseline.view, deps.catalogue.read());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// The copy was written outside the transaction; nothing names it once it rolled back.
|
|
117
|
+
const written = [music, bed].flatMap((asset) => (asset === undefined ? [] : [asset]));
|
|
118
|
+
if (written.length > 0)
|
|
119
|
+
discardPreparedAssets(deps, written);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
79
122
|
for (const source of retainStaged ? [] : moved) {
|
|
80
123
|
releaseStagedFile(deps, source);
|
|
81
124
|
}
|
|
@@ -103,6 +146,9 @@ function attachProvided(deps, projectId, draft, collected, retainStaged) {
|
|
|
103
146
|
if (sources.thumbnail === "provide") {
|
|
104
147
|
attach(deps, projectId, "thumbnail", provided.thumbnail, "thumbnail", collected, retainStaged);
|
|
105
148
|
}
|
|
149
|
+
if (usesReference(draft) && draft.reference?.source === "provide") {
|
|
150
|
+
attach(deps, projectId, "images", provided.reference, "reference", collected, retainStaged);
|
|
151
|
+
}
|
|
106
152
|
if (sources.images === "provide") {
|
|
107
153
|
// Slideshow order is the order the user left the list in.
|
|
108
154
|
for (const [index, stagedFileId] of (provided.images ?? []).entries()) {
|
|
@@ -110,6 +156,34 @@ function attachProvided(deps, projectId, draft, collected, retainStaged) {
|
|
|
110
156
|
}
|
|
111
157
|
}
|
|
112
158
|
}
|
|
159
|
+
// The Shorts step's background music, copied into the project as an asset of its own (never
|
|
160
|
+
// an output), exactly as Edit project → Shorts keeps an uploaded one. Only while Shorts is on.
|
|
161
|
+
function attachShortsMusic(deps, projectId, draft, collected) {
|
|
162
|
+
const stagedFileId = draft.provided.shortsMusic;
|
|
163
|
+
if (draft.shorts?.enabled !== true || stagedFileId === undefined)
|
|
164
|
+
return undefined;
|
|
165
|
+
const result = prepareStagedFile(deps, { projectId, stagedFileId, role: "audio_body" });
|
|
166
|
+
if (!result.ok)
|
|
167
|
+
// admit() already refused a missing or still-copying file, as for the stages' own files.
|
|
168
|
+
throw new Error(`the shorts' background music could not be attached: ${result.reason}`);
|
|
169
|
+
insertAsset(deps.db, result.asset);
|
|
170
|
+
collected.push(stagedFileId);
|
|
171
|
+
return result.asset;
|
|
172
|
+
}
|
|
173
|
+
// The ambient bed's own file, copied into the project as an asset of its own like the shorts'
|
|
174
|
+
// music. Only while the long video gets the bed and its source is "upload".
|
|
175
|
+
function attachAmbientBed(deps, projectId, draft, collected) {
|
|
176
|
+
const stagedFileId = draft.provided.ambientBed;
|
|
177
|
+
if (draft.ambientBed?.source !== "upload" || !usesAmbientBed(draft) || stagedFileId === undefined)
|
|
178
|
+
return undefined;
|
|
179
|
+
const result = prepareStagedFile(deps, { projectId, stagedFileId, role: "audio_body" });
|
|
180
|
+
if (!result.ok)
|
|
181
|
+
// admit() already refused a missing or still-copying file, as for the stages' own files.
|
|
182
|
+
throw new Error(`the ambient sound's audio file could not be attached: ${result.reason}`);
|
|
183
|
+
insertAsset(deps.db, result.asset);
|
|
184
|
+
collected.push(stagedFileId);
|
|
185
|
+
return result.asset;
|
|
186
|
+
}
|
|
113
187
|
function attach(deps, projectId, kind, stagedFileId, role, collected, retainStaged, index) {
|
|
114
188
|
if (stagedFileId === undefined) {
|
|
115
189
|
throw new Error(`the ${kind} stage is provided but carries no staged file`);
|
|
@@ -82,7 +82,7 @@ function truncated(answer) {
|
|
|
82
82
|
// Empty response → failed attempt.
|
|
83
83
|
function written(answer) {
|
|
84
84
|
return answer.text.trim() === ""
|
|
85
|
-
? "The AI model returned an empty article.
|
|
85
|
+
? "The AI model returned an empty article. Try again; if it keeps happening, choose a different model in Edit project → Providers."
|
|
86
86
|
: undefined;
|
|
87
87
|
}
|
|
88
88
|
function finished(answer) {
|
|
@@ -91,6 +91,6 @@ function finished(answer) {
|
|
|
91
91
|
return empty;
|
|
92
92
|
}
|
|
93
93
|
return truncated(answer)
|
|
94
|
-
? `The article was still unfinished after ${String(continuationLimit)} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then
|
|
94
|
+
? `The article was still unfinished after ${String(continuationLimit)} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Try again.`
|
|
95
95
|
: undefined;
|
|
96
96
|
}
|
|
@@ -20,11 +20,11 @@ export async function prepareProvidedArticleSegments(deps, audioContext, provide
|
|
|
20
20
|
audioContext.signal.throwIfAborted();
|
|
21
21
|
const article = stagesOf(deps.db, projectId).find((stage) => stage.kind === "article");
|
|
22
22
|
if (!article)
|
|
23
|
-
throw new Error("Slopify hit an internal error (the project has no article stage).
|
|
23
|
+
throw new Error("Slopify hit an internal error (the project has no article stage). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
24
24
|
const outputs = outputsOf(deps.db, projectId);
|
|
25
25
|
const plain = outputs.find((output) => output.role === "article_txt");
|
|
26
26
|
if (!plain)
|
|
27
|
-
throw new Error("The article text you provided is missing, so the intro and outro can't be written from it. Paste the article again in Edit project → Article, then
|
|
27
|
+
throw new Error("The article text you provided is missing, so the intro and outro can't be written from it. Paste the article again in Edit project → Article, then Try again.");
|
|
28
28
|
const narration = readFileSync(outputPath(deps.paths, projectId, plain.path), "utf8");
|
|
29
29
|
const saved = outputs.find((output) => output.stageKind === "article" && output.role === "instructions");
|
|
30
30
|
let instructions = saved
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { messageRoles } from "../../kernel/ports/llm.js";
|
|
4
4
|
import { insertPiece, piecesOf, setPiece } from "../../kernel/runner/piece-repo.js";
|
|
5
5
|
import { projectById } from "../admission/repo.js";
|
|
6
|
+
import { withEarlierEpisodes } from "../episodes/related.js";
|
|
6
7
|
import { outputPath } from "../storage/layout.js";
|
|
7
8
|
import { outputsOf } from "../storage/repo.js";
|
|
8
9
|
import { storeText } from "../storage/staging.js";
|
|
@@ -20,10 +21,13 @@ export async function runArticle(deps, context, providers) {
|
|
|
20
21
|
if (choice === undefined || articlePrompt === undefined) {
|
|
21
22
|
// Admission refuses a run whose article is Generate without both, so
|
|
22
23
|
// reaching here is a bug in admission rather than something the user did.
|
|
23
|
-
throw new Error("No AI model or article prompt is set for writing the article. Choose a model in Edit project → Providers and check Edit project → Prompts, then
|
|
24
|
+
throw new Error("No AI model or article prompt is set for writing the article. Choose a model in Edit project → Providers and check Edit project → Prompts, then Try again.");
|
|
24
25
|
}
|
|
25
26
|
const notes = researchNotes(deps, projectId);
|
|
26
|
-
const brief = {
|
|
27
|
+
const brief = {
|
|
28
|
+
articlePrompt: withEarlierEpisodes(articlePrompt, project.config.earlierEpisodes),
|
|
29
|
+
...(notes === undefined ? {} : { notes }),
|
|
30
|
+
};
|
|
27
31
|
const result = await articleBody(deps, context, providers, choice, brief);
|
|
28
32
|
if (!result.ok)
|
|
29
33
|
return "held";
|
|
@@ -7,12 +7,12 @@ export async function writeSegment(providers, choice, config, category, article,
|
|
|
7
7
|
return { ok: true, value: undefined };
|
|
8
8
|
const body = config.rendered[category];
|
|
9
9
|
if (body === undefined)
|
|
10
|
-
throw new Error(`Slopify hit an internal error (the ${category} text was never filled in).
|
|
10
|
+
throw new Error(`Slopify hit an internal error (the ${category} text was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
11
11
|
const common = { category, name: picked.name, mode: picked.mode };
|
|
12
12
|
if (picked.mode === "text")
|
|
13
13
|
return { ok: true, value: { ...common, text: body, tokens: noTokens } };
|
|
14
14
|
if (choice === undefined)
|
|
15
|
-
throw new Error(`No AI model is set for writing the ${category}. Choose one in Edit project → Providers, then
|
|
15
|
+
throw new Error(`No AI model is set for writing the ${category}. Choose one in Edit project → Providers, then Try again.`);
|
|
16
16
|
const messages = segmentMessages(body, config, article);
|
|
17
17
|
sent.push({ label: category === "intro" ? "Intro" : "Outro", messages });
|
|
18
18
|
const answer = await providers.llm({
|
|
@@ -21,7 +21,7 @@ export async function writeSegment(providers, choice, config, category, article,
|
|
|
21
21
|
...(choice.thinking === undefined ? {} : { thinking: choice.thinking }),
|
|
22
22
|
messages,
|
|
23
23
|
check: (given) => given.text.trim() === ""
|
|
24
|
-
? `The AI model returned an empty ${category}.
|
|
24
|
+
? `The AI model returned an empty ${category}. Try again; if it keeps happening, choose a different model in Edit project → Providers.`
|
|
25
25
|
: undefined,
|
|
26
26
|
});
|
|
27
27
|
if (!answer.ok)
|