@gentbajko/slopify 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { narrationPreparationFields, shortsFields, usesNarrationPreparation, usesShorts, usesYoutubeDescription, videoEditFields, youtubeDescriptionFields, } from "../admission/rules.js";
|
|
1
|
+
import { narrationPreparationFields, shortsFields, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, videoEditFields, youtubeDescriptionFields, } from "../admission/rules.js";
|
|
2
2
|
import { detectSlots, render } from "../admission/substitute.js";
|
|
3
|
+
import { imagesPerVideoMax } from "../images/scale.js";
|
|
4
|
+
import { loudnessFields } from "../loudness/model.js";
|
|
5
|
+
import { pauseFields } from "../narration/pauses-model.js";
|
|
6
|
+
import { reviewFields } from "../reviews/rules.js";
|
|
3
7
|
import { usesAnimation } from "../video/edit-settings.js";
|
|
4
8
|
export function validateRecipeInputs(config, content) {
|
|
5
9
|
const fields = [
|
|
@@ -7,6 +11,9 @@ export function validateRecipeInputs(config, content) {
|
|
|
7
11
|
...youtubeDescriptionFields(config),
|
|
8
12
|
...shortsFields(config),
|
|
9
13
|
...videoEditFields(config),
|
|
14
|
+
...reviewFields(config),
|
|
15
|
+
...loudnessFields(config.loudness),
|
|
16
|
+
...pauseFields(config),
|
|
10
17
|
];
|
|
11
18
|
const llm = (config.sources.research === "generate" && config.sources.article !== "provide") ||
|
|
12
19
|
(config.sources.article === "generate" && !content.articleEdited) ||
|
|
@@ -33,8 +40,13 @@ export function validateRecipeInputs(config, content) {
|
|
|
33
40
|
usesAnimation(config)) &&
|
|
34
41
|
(!config.images?.provider.trim() || !config.images.model.trim()))
|
|
35
42
|
fields.push({ field: "images", message: "Pick an image provider and model." });
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
// 60, or more for a project that scales its images with the narration (`images/scale.ts`).
|
|
44
|
+
const imagesMax = imagesPerVideoMax(config);
|
|
45
|
+
if (content.imageOrder.length > imagesMax)
|
|
46
|
+
fields.push({
|
|
47
|
+
field: "content.imageOrder",
|
|
48
|
+
message: `Keep at most ${String(imagesMax)} images.`,
|
|
49
|
+
});
|
|
38
50
|
const chunking = config.chunking;
|
|
39
51
|
const size = chunking?.mode === "words"
|
|
40
52
|
? chunking.words
|
|
@@ -93,6 +105,20 @@ export function validateRecipeInputs(config, content) {
|
|
|
93
105
|
raw: content.promptTemplates.thumbnailPrompt ?? null,
|
|
94
106
|
literal: config.rendered.thumbnailPrompt,
|
|
95
107
|
});
|
|
108
|
+
// The establishing image: its prompt is checked like the thumbnail's, an upload must be there.
|
|
109
|
+
if (usesReference(config) && config.reference?.source === "prompt")
|
|
110
|
+
prompts.push({
|
|
111
|
+
field: "rendered.referencePrompt",
|
|
112
|
+
raw: content.promptTemplates.referencePrompt ?? null,
|
|
113
|
+
literal: config.rendered.referencePrompt,
|
|
114
|
+
});
|
|
115
|
+
if (usesReference(config) &&
|
|
116
|
+
config.reference?.source === "provide" &&
|
|
117
|
+
content.provided.reference === undefined)
|
|
118
|
+
fields.push({
|
|
119
|
+
field: "content.provided.reference",
|
|
120
|
+
message: "Upload the establishing image, choose From a prompt instead, or set Establishing image to Off.",
|
|
121
|
+
});
|
|
96
122
|
if (config.sources.images !== "off")
|
|
97
123
|
for (const key of content.imageOrder) {
|
|
98
124
|
const image = content.imageDefinitions[key];
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import { thumbnailCountOf, thumbnailKey } from "../admission/model.js";
|
|
2
|
+
import { usesShortMode } from "../admission/short-mode.js";
|
|
1
3
|
import { render } from "../admission/substitute.js";
|
|
4
|
+
import { usesAmbientBed } from "../video/ambient-bed.js";
|
|
5
|
+
import { castFor } from "./recipe-cast.js";
|
|
6
|
+
import { noMaster } from "./recipe-loudness.js";
|
|
2
7
|
import { recipe, resourceIdentity, } from "./recipe-model.js";
|
|
8
|
+
import { castField, imageChoice } from "./recipe-reference.js";
|
|
3
9
|
import { matchingText, renderedPrompt } from "./recipe-text.js";
|
|
4
10
|
export function visualRecipes(config, content, audioFingerprint, captionFingerprint,
|
|
5
11
|
// What the Video stage's edit settings add (`recipe-edit.ts`), from the images planned here.
|
|
6
|
-
edit
|
|
12
|
+
edit,
|
|
13
|
+
// The establishing image the generated images are drawn from, when it is on.
|
|
14
|
+
reference,
|
|
15
|
+
// The word timing's identity, which a short's captions are drawn from.
|
|
16
|
+
timing = null,
|
|
17
|
+
// Level the volume: the levelled narration and the master (`recipe-loudness.ts`).
|
|
18
|
+
master = noMaster) {
|
|
7
19
|
const recipes = [];
|
|
8
20
|
const imageKeys = config.sources.images === "off" ? [] : content.imageOrder;
|
|
9
21
|
for (const key of imageKeys) {
|
|
@@ -19,14 +31,23 @@ edit) {
|
|
|
19
31
|
: {
|
|
20
32
|
kind: "image",
|
|
21
33
|
version: 1,
|
|
22
|
-
|
|
23
|
-
model: config.images?.model ?? "",
|
|
34
|
+
...imageChoice(config),
|
|
24
35
|
aspect: config.format,
|
|
25
36
|
prompt: prompt ?? "",
|
|
26
|
-
|
|
37
|
+
...(reference === undefined ? {} : { reference: reference.input }),
|
|
38
|
+
...castField(castFor(config, prompt)),
|
|
39
|
+
}, image.source === "provide" || reference === undefined ? [] : [reference.key], { unresolved: image.source === "provide" ? image.assetId === null : !prompt?.trim() }));
|
|
27
40
|
}
|
|
28
41
|
if (config.sources.video !== "off") {
|
|
29
|
-
const
|
|
42
|
+
const short = usesShortMode(config);
|
|
43
|
+
// A short is rendered by the Shorts renderer, which has no cuts, transitions or Look.
|
|
44
|
+
const edited = (short ? undefined : edit?.([...recipes])) ?? {
|
|
45
|
+
recipes: [],
|
|
46
|
+
values: [],
|
|
47
|
+
dependsOn: [],
|
|
48
|
+
};
|
|
49
|
+
// A short (rendered by the Shorts renderer) never has the bed.
|
|
50
|
+
const bed = short ? undefined : ambientBedValues(config, content);
|
|
30
51
|
const audioKeys = config.sources.audio === "off"
|
|
31
52
|
? []
|
|
32
53
|
: [
|
|
@@ -61,19 +82,51 @@ edit) {
|
|
|
61
82
|
// Only what the edit settings change; nothing at all for today's slideshow, so
|
|
62
83
|
// its fingerprint is the one it always had.
|
|
63
84
|
...(edited.values.length === 0 ? [] : [["video-edit", ...edited.values]]),
|
|
85
|
+
// Only for a short, so every long video keeps its fingerprint: the word-by-word
|
|
86
|
+
// captions are drawn from the word timing in the caption font, under the title.
|
|
87
|
+
...(short
|
|
88
|
+
? [["short-v1", config.subtitles?.fontId ?? "default", config.title, timing]]
|
|
89
|
+
: []),
|
|
90
|
+
// Only while the video has an ambient bed, so every video without one keeps the
|
|
91
|
+
// fingerprint it always had.
|
|
92
|
+
...(bed === undefined ? [] : [bed]),
|
|
93
|
+
// Only while the volume is levelled, so every video without it keeps its fingerprint.
|
|
94
|
+
...master.values,
|
|
64
95
|
],
|
|
65
96
|
}, [
|
|
66
97
|
...recipes.map((value) => value.key),
|
|
67
98
|
...audioKeys,
|
|
99
|
+
...master.keys,
|
|
68
100
|
...(config.subtitles?.mode === "burn-in" ? ["subtitles:files"] : []),
|
|
101
|
+
...(short ? ["subtitles:timing"] : []),
|
|
69
102
|
...edited.dependsOn,
|
|
70
|
-
], {
|
|
103
|
+
], {
|
|
104
|
+
unresolved: imageKeys.length === 0 ||
|
|
105
|
+
(config.ambientBed?.source === "upload" && content.ambientBed === undefined),
|
|
106
|
+
}));
|
|
71
107
|
// After the export, so the images' recipes above stay the only ones its first values list.
|
|
72
108
|
recipes.splice(recipes.length - 1, 0, ...edited.recipes);
|
|
73
109
|
}
|
|
74
110
|
return recipes;
|
|
75
111
|
}
|
|
76
|
-
|
|
112
|
+
// What the ambient bed adds to the render's fingerprint: its settings and, for the user's own
|
|
113
|
+
// file, the project asset it plays. Undefined without a bed.
|
|
114
|
+
export function ambientBedValues(config, content) {
|
|
115
|
+
const bed = config.ambientBed;
|
|
116
|
+
if (bed === undefined || !usesAmbientBed(config))
|
|
117
|
+
return undefined;
|
|
118
|
+
return [
|
|
119
|
+
"ambient-bed-v1",
|
|
120
|
+
bed.source,
|
|
121
|
+
bed.levelDb,
|
|
122
|
+
bed.fadeInSeconds,
|
|
123
|
+
bed.tailSeconds,
|
|
124
|
+
bed.source === "upload" ? (content.ambientBed ?? null) : null,
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
export function thumbnailRecipes(context, textRecipes,
|
|
128
|
+
// The establishing image, when it is on and the thumbnail is drawn from it too.
|
|
129
|
+
reference) {
|
|
77
130
|
const { config, content } = context;
|
|
78
131
|
if (config.sources.thumbnail === "off")
|
|
79
132
|
return [];
|
|
@@ -90,28 +143,50 @@ export function thumbnailRecipes(context, textRecipes) {
|
|
|
90
143
|
const prompt = config.sources.thumbnail === "prompt_by_llm" && promptRecipe !== undefined
|
|
91
144
|
? matchingText(context, promptRecipe, "prompt")
|
|
92
145
|
: renderedPrompt(context, "thumbnailPrompt");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
const variants = Array.from({ length: thumbnailCountOf(config) }, (_, index) => index + 1);
|
|
147
|
+
return variants.map((variant) => recipe(context, thumbnailKey(variant), "thumbnail", prompt === null
|
|
148
|
+
? {
|
|
149
|
+
kind: "deferred",
|
|
150
|
+
version: 1,
|
|
151
|
+
operation: "thumbnail-image",
|
|
152
|
+
template: [
|
|
153
|
+
promptRecipe?.fingerprint ?? null,
|
|
154
|
+
config.images?.provider ?? null,
|
|
155
|
+
config.images?.model ?? null,
|
|
156
|
+
config.format,
|
|
157
|
+
// Only what is in use, so a thumbnail made before these existed keeps its
|
|
158
|
+
// fingerprint.
|
|
159
|
+
...(config.images?.thinking === undefined ? [] : [config.images.thinking]),
|
|
160
|
+
...(reference === undefined ? [] : [reference.input.fingerprint]),
|
|
161
|
+
...(variant === 1 ? [] : [["thumbnail-variant", variant]]),
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
: {
|
|
165
|
+
kind: "image",
|
|
166
|
+
version: 1,
|
|
167
|
+
...imageChoice(config),
|
|
168
|
+
aspect: config.format,
|
|
169
|
+
prompt: thumbnailVariantPrompt(prompt, variant),
|
|
170
|
+
...(reference === undefined ? {} : { reference: reference.input }),
|
|
171
|
+
// The thumbnail stands for the whole video, like the establishing image.
|
|
172
|
+
...castField(castFor(config, config.title, prompt)),
|
|
173
|
+
}, [
|
|
174
|
+
...(promptRecipe === undefined ? [] : [promptRecipe.key]),
|
|
175
|
+
...(reference === undefined ? [] : [reference.key]),
|
|
176
|
+
]));
|
|
177
|
+
}
|
|
178
|
+
// The second and third thumbnails are the same prompt asked for another composition, so the
|
|
179
|
+
// three test one idea against itself rather than three ideas. The first is the prompt as it
|
|
180
|
+
// is, which keeps the thumbnail every project already has.
|
|
181
|
+
const variantFraming = {
|
|
182
|
+
2: "a tight close-up where the main subject fills most of the frame, seen from a different angle",
|
|
183
|
+
3: "a wider shot that shows the main subject in its setting, placed off-centre with room around it",
|
|
184
|
+
};
|
|
185
|
+
export function thumbnailVariantPrompt(prompt, variant) {
|
|
186
|
+
const framing = variantFraming[variant];
|
|
187
|
+
if (framing === undefined)
|
|
188
|
+
return prompt;
|
|
189
|
+
return `${prompt}\n\nThis is thumbnail ${String(variant)} of 3 for a YouTube thumbnail test. Keep the subject, style, colours and any text asked for above, but use a clearly different composition: ${framing}.`;
|
|
115
190
|
}
|
|
116
191
|
export function visualAssets(context, recipes) {
|
|
117
192
|
const images = recipes.filter((value) => value.stage === "images");
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { aliasMatches, applyAliases } from "../../kernel/ports/narration-aliases.js";
|
|
2
|
+
import { splitText } from "../../kernel/ports/text.js";
|
|
3
|
+
import { narrationAliasesOf, usesNarrationPreparation } from "../admission/rules.js";
|
|
4
|
+
import { aliasSpans, withAliasSpans } from "../narration/aliases.js";
|
|
5
|
+
import { normalizeNarrationText } from "../narration/plan.js";
|
|
6
|
+
import { parsePronunciationGlossary, pronunciationSpans, } from "../narration/pronunciation.js";
|
|
7
|
+
import { withSharedGlossary } from "../narration/shared-glossary.js";
|
|
8
|
+
import { prepareRequests } from "../narration/steering.js";
|
|
9
|
+
import { groupTurns } from "../voices/grouping.js";
|
|
10
|
+
import { speakerPace } from "../voices/model.js";
|
|
11
|
+
import { parseScript } from "../voices/script.js";
|
|
12
|
+
import { describedText, describeFuture, describing } from "./recipe-describe.js";
|
|
13
|
+
import { recipe, resourceIdentity, } from "./recipe-model.js";
|
|
14
|
+
import { pauseValues } from "./recipe-pauses.js";
|
|
15
|
+
import { preparationForGroup, preparationFuture, preparationTemplate, } from "./recipe-preparation.js";
|
|
16
|
+
export function voiceBodyRecipes(context, voices, script, glossary) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
const preparations = [];
|
|
19
|
+
const layout = [];
|
|
20
|
+
// Which parts end a turn, for the pauses between sentences (a turn gap of 0 is still a turn).
|
|
21
|
+
const turnEnds = new Set();
|
|
22
|
+
let transcript = script.fingerprint ?? script.text ?? null;
|
|
23
|
+
const describe = describing(context);
|
|
24
|
+
const parsed = script.text === null ? undefined : parseScript(script.text, voices.speakers, describe);
|
|
25
|
+
const prepare = voices.speakers.some((speaker) => preparesTurns(context.config, speaker));
|
|
26
|
+
// The narration still to be worked out: the whole script's while it is being written, or
|
|
27
|
+
// every turn's while a turn's delivery cues are (as a single voice waits for all of its own).
|
|
28
|
+
const future = () => recipe(context, "audio:body:future", "audio", {
|
|
29
|
+
kind: "deferred",
|
|
30
|
+
version: 1,
|
|
31
|
+
operation: "body-narration",
|
|
32
|
+
template: [
|
|
33
|
+
script.fingerprint ?? script.text ?? null,
|
|
34
|
+
speakerValues(voices),
|
|
35
|
+
...(prepare ? [preparationTemplate(context)] : []),
|
|
36
|
+
...(described.length === 0
|
|
37
|
+
? []
|
|
38
|
+
: [["described-v1", described.map((row) => row.fingerprint)]]),
|
|
39
|
+
],
|
|
40
|
+
}, [
|
|
41
|
+
...new Set([
|
|
42
|
+
...script.dependsOn,
|
|
43
|
+
...described.map((row) => row.key),
|
|
44
|
+
...preparations.map((row) => row.key),
|
|
45
|
+
]),
|
|
46
|
+
]);
|
|
47
|
+
let pending = false;
|
|
48
|
+
// A table, figure, equation or code block inside a turn is described within that turn: the
|
|
49
|
+
// turn says the description where the block was, in the same voice.
|
|
50
|
+
const described = [];
|
|
51
|
+
const cards = [];
|
|
52
|
+
let turns = parsed?.ok === true ? parsed.script.turns : [];
|
|
53
|
+
if (describe && script.text === null) {
|
|
54
|
+
const from = script.dependsOn[0];
|
|
55
|
+
if (from !== undefined)
|
|
56
|
+
described.push(describeFuture(context, { key: from, fingerprint: script.fingerprint ?? from }));
|
|
57
|
+
}
|
|
58
|
+
else if (describe && parsed?.ok === true) {
|
|
59
|
+
let offset = 0;
|
|
60
|
+
turns = turns.map((turn) => {
|
|
61
|
+
if (turn.markdown === undefined)
|
|
62
|
+
return turn;
|
|
63
|
+
const said = describedText(context, turn.markdown, script.dependsOn, offset);
|
|
64
|
+
if (said.recipes.length === 0)
|
|
65
|
+
return turn;
|
|
66
|
+
offset += said.recipes.length;
|
|
67
|
+
described.push(...said.recipes);
|
|
68
|
+
cards.push(...said.cards);
|
|
69
|
+
if (said.spoken === null) {
|
|
70
|
+
pending = true;
|
|
71
|
+
return turn;
|
|
72
|
+
}
|
|
73
|
+
return { ...turn, text: said.spoken.trim().replace(/\s*\n\s*/g, " ") };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
preparations.push(...described);
|
|
77
|
+
if (script.text === null) {
|
|
78
|
+
// Turns not yet known are prepared behind one future step, as a single voice's text is.
|
|
79
|
+
const from = script.dependsOn[0];
|
|
80
|
+
if (prepare && from !== undefined)
|
|
81
|
+
preparations.push(preparationFuture(context, "body", { key: from, fingerprint: script.fingerprint ?? from }));
|
|
82
|
+
parts.push(future());
|
|
83
|
+
}
|
|
84
|
+
else if (parsed !== undefined && !parsed.ok) {
|
|
85
|
+
parts.push(refused(context, "audio:body:turn:1", script.dependsOn, `The script can't be narrated: ${parsed.reason} Fix it in Edit project → Article, then Try again.`));
|
|
86
|
+
}
|
|
87
|
+
else if (pending) {
|
|
88
|
+
parts.push(future());
|
|
89
|
+
}
|
|
90
|
+
else if (parsed !== undefined) {
|
|
91
|
+
transcript = ["voices-transcript-v1", turns.map((turn) => [turn.speaker, turn.text])];
|
|
92
|
+
const aliases = narrationAliasesOf(context.config);
|
|
93
|
+
for (const group of groupTurns(turns, voices.speakers, voices.nativeDialogue, (text) => applyAliases(text, aliases).length)) {
|
|
94
|
+
const first = group.turns[0];
|
|
95
|
+
const speaker = voices.speakers.find((one) => one.id === first?.speaker);
|
|
96
|
+
if (first === undefined || speaker === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
const made = groupParts(context, voices, group.native, group.turns, speaker, glossary, script, preparations);
|
|
99
|
+
if (made.length === 0)
|
|
100
|
+
pending = true;
|
|
101
|
+
made.forEach((part, index) => {
|
|
102
|
+
parts.push(part);
|
|
103
|
+
if (index === made.length - 1)
|
|
104
|
+
turnEnds.add(layout.length);
|
|
105
|
+
layout.push([speakerPace(speaker), index === made.length - 1 ? voices.turnGapSeconds : 0]);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// No gap after the last turn: the timeline's own gap follows it.
|
|
109
|
+
const last = layout.at(-1);
|
|
110
|
+
if (last !== undefined)
|
|
111
|
+
last[1] = 0;
|
|
112
|
+
if (pending) {
|
|
113
|
+
parts.length = 0;
|
|
114
|
+
layout.length = 0;
|
|
115
|
+
turnEnds.clear();
|
|
116
|
+
parts.push(future());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const body = recipe(context, "audio:body:concat", "audio", {
|
|
120
|
+
kind: "local",
|
|
121
|
+
version: 1,
|
|
122
|
+
operation: "concat-turns-v1",
|
|
123
|
+
values: [
|
|
124
|
+
parts.map((part) => resourceIdentity(context, part)),
|
|
125
|
+
layout,
|
|
126
|
+
// Pauses between sentences inside a turn; the turn gap stays the layout's.
|
|
127
|
+
...pauseValues(context.config, layout.map(([, gap], at) => at === layout.length - 1 ? "end" : gap > 0 || turnEnds.has(at) ? "turn" : "sentence")),
|
|
128
|
+
],
|
|
129
|
+
}, parts.map((part) => part.key), { unresolved: parsed?.ok !== true || pending });
|
|
130
|
+
return {
|
|
131
|
+
preparations,
|
|
132
|
+
cards,
|
|
133
|
+
parts,
|
|
134
|
+
body,
|
|
135
|
+
transcript,
|
|
136
|
+
...(parsed?.ok === true ? { sections: parsed.script.sections } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function groupParts(context, voices, native, turns, speaker, glossary, script, preparations) {
|
|
140
|
+
const first = turns[0];
|
|
141
|
+
if (first === undefined)
|
|
142
|
+
return [];
|
|
143
|
+
const logicalKey = `audio:body:turn:${String(first.index)}`;
|
|
144
|
+
const override = context.content.narrationOverrides[logicalKey];
|
|
145
|
+
if (override?.kind === "asset")
|
|
146
|
+
return [
|
|
147
|
+
recipe(context, `${logicalKey}:1`, "audio", {
|
|
148
|
+
kind: "provided",
|
|
149
|
+
version: 1,
|
|
150
|
+
assetId: override.assetId,
|
|
151
|
+
semantic: [turns.map((turn) => turn.text).join("\n"), speaker.id],
|
|
152
|
+
}, script.dependsOn, { tokenKey: logicalKey }),
|
|
153
|
+
];
|
|
154
|
+
if (native) {
|
|
155
|
+
const byId = new Map(voices.speakers.map((one) => [one.id, one]));
|
|
156
|
+
// Narration aliases reach every line of the one request, each turn on its own so an alias
|
|
157
|
+
// never joins the end of one turn to the start of the next. The clean turns stay the
|
|
158
|
+
// transcript, as they do for a voice that gets one request per turn.
|
|
159
|
+
const aliases = narrationAliasesOf(context.config);
|
|
160
|
+
const spoken = turns.map((turn) => applyAliases(turn.text, aliases));
|
|
161
|
+
const logicalText = turns.map((turn) => turn.text).join("\n");
|
|
162
|
+
const text = spoken.join("\n");
|
|
163
|
+
return [
|
|
164
|
+
recipe(context, `${logicalKey}:1`, "audio", {
|
|
165
|
+
kind: "tts",
|
|
166
|
+
version: 1,
|
|
167
|
+
provider: speaker.voice.provider,
|
|
168
|
+
model: speaker.voice.model,
|
|
169
|
+
voice: speaker.voice.voice,
|
|
170
|
+
text,
|
|
171
|
+
...(text === logicalText ? {} : { spokenText: logicalText }),
|
|
172
|
+
logicalKey,
|
|
173
|
+
logicalText,
|
|
174
|
+
segment: "body",
|
|
175
|
+
pronunciation: null,
|
|
176
|
+
dialogue: turns.map((turn, index) => ({
|
|
177
|
+
speaker: turn.speaker,
|
|
178
|
+
turn: turn.index,
|
|
179
|
+
voice: byId.get(turn.speaker)?.voice.voice ?? "",
|
|
180
|
+
text: spoken[index] ?? turn.text,
|
|
181
|
+
})),
|
|
182
|
+
}, script.dependsOn, { tokenKey: logicalKey }),
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
const logicalText = normalizeNarrationText(override?.kind === "text" ? override.text : first.text);
|
|
186
|
+
const entries = speakerGlossary(context, speaker, glossary);
|
|
187
|
+
if (!entries.ok)
|
|
188
|
+
return [refused(context, logicalKey, script.dependsOn, entries.reason)];
|
|
189
|
+
const maxCharacters = context.catalogue?.tts.find((row) => row.provider === speaker.voice.provider &&
|
|
190
|
+
row.id === speaker.voice.model &&
|
|
191
|
+
row.enabled &&
|
|
192
|
+
!row.deprecated)?.tts.maxCharacters ?? Math.max(2, logicalText.length * 4);
|
|
193
|
+
// Narration aliases apply to every voice, the speaker's pronunciations to IPA voices only.
|
|
194
|
+
const spans = withAliasSpans(pronunciationSpans(logicalText, entries.entries), aliasSpans(logicalText, narrationAliasesOf(context.config)));
|
|
195
|
+
if (preparesTurns(context.config, speaker)) {
|
|
196
|
+
const prepared = preparationForGroup(context, logicalKey, logicalText, "body", script.dependsOn, maxCharacters, spans, aliasMatches(logicalText, narrationAliasesOf(context.config)), { name: speaker.name.trim(), role: speaker.role });
|
|
197
|
+
preparations.push(prepared.preparation);
|
|
198
|
+
if (prepared.refusal !== null)
|
|
199
|
+
return [refused(context, logicalKey, [prepared.preparation.key], prepared.refusal)];
|
|
200
|
+
return (prepared.requests ?? []).map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
|
|
201
|
+
kind: "tts",
|
|
202
|
+
version: 1,
|
|
203
|
+
provider: speaker.voice.provider,
|
|
204
|
+
model: speaker.voice.model,
|
|
205
|
+
voice: speaker.voice.voice,
|
|
206
|
+
text: request.text,
|
|
207
|
+
spokenText: request.spokenText,
|
|
208
|
+
logicalKey,
|
|
209
|
+
logicalText,
|
|
210
|
+
segment: "body",
|
|
211
|
+
pronunciation: null,
|
|
212
|
+
speaker: speaker.id,
|
|
213
|
+
turn: first.index,
|
|
214
|
+
}, [prepared.preparation.key], { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
|
|
215
|
+
}
|
|
216
|
+
let requests;
|
|
217
|
+
if (spans.length > 0) {
|
|
218
|
+
const prepared = prepareRequests(logicalText, [], maxCharacters, spans);
|
|
219
|
+
if (!prepared.ok)
|
|
220
|
+
return [refused(context, logicalKey, script.dependsOn, prepared.reason)];
|
|
221
|
+
requests = prepared.requests;
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
requests = splitText(logicalText, maxCharacters).map((text) => ({ text }));
|
|
225
|
+
return requests.map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
|
|
226
|
+
kind: "tts",
|
|
227
|
+
version: 1,
|
|
228
|
+
provider: speaker.voice.provider,
|
|
229
|
+
model: speaker.voice.model,
|
|
230
|
+
voice: speaker.voice.voice,
|
|
231
|
+
text: request.text,
|
|
232
|
+
...(request.spokenText === undefined ? {} : { spokenText: request.spokenText }),
|
|
233
|
+
logicalKey,
|
|
234
|
+
logicalText,
|
|
235
|
+
segment: "body",
|
|
236
|
+
pronunciation: null,
|
|
237
|
+
speaker: speaker.id,
|
|
238
|
+
turn: first.index,
|
|
239
|
+
}, script.dependsOn, { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
|
|
240
|
+
}
|
|
241
|
+
// IPA pronunciations reach only the voices that read IPA: the speaker's own first, then the
|
|
242
|
+
// run's glossary where Use Pronunciation Glossary is on.
|
|
243
|
+
function speakerGlossary(context, speaker, glossary) {
|
|
244
|
+
if (!readsIpa(speaker))
|
|
245
|
+
return { ok: true, entries: [] };
|
|
246
|
+
const own = speaker.pronunciations?.trim()
|
|
247
|
+
? parsePronunciationGlossary(speaker.pronunciations, context.config.language)
|
|
248
|
+
: { ok: true, entries: [] };
|
|
249
|
+
if (!own.ok)
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
reason: `${speaker.name.trim()}'s pronunciations can't be used: ${own.reason.replace("Edit the glossary or turn off Use Pronunciation Glossary.", "Fix them under Speakers (Play → Audio, or Edit project → Providers).")}`,
|
|
253
|
+
};
|
|
254
|
+
const run = context.config.audio?.usePronunciationGlossary === true && glossary?.ok === true
|
|
255
|
+
? glossary.entries
|
|
256
|
+
: [];
|
|
257
|
+
return { ok: true, entries: withSharedGlossary(own.entries, run) };
|
|
258
|
+
}
|
|
259
|
+
// A turn is prepared when the run has a Narration Preparation prompt and its speaker's voice
|
|
260
|
+
// takes Inworld TTS-2's delivery tags; every other speaker's turns are spoken as written.
|
|
261
|
+
export function preparesTurns(config, speaker) {
|
|
262
|
+
return (usesNarrationPreparation(config) &&
|
|
263
|
+
speaker.voice.provider === "inworld" &&
|
|
264
|
+
speaker.voice.model === "inworld-tts-2");
|
|
265
|
+
}
|
|
266
|
+
export function readsIpa(speaker) {
|
|
267
|
+
return (speaker.voice.provider === "inworld" &&
|
|
268
|
+
(speaker.voice.model === "inworld-tts-2" || speaker.voice.model === "inworld-tts-2-flash"));
|
|
269
|
+
}
|
|
270
|
+
function refused(context, logicalKey, dependsOn, reason) {
|
|
271
|
+
return recipe(context, `${logicalKey}:1`, "audio", { kind: "deferred", version: 1, operation: "resolve-revision-recipe", template: reason }, dependsOn, { unresolved: true, refusal: reason });
|
|
272
|
+
}
|
|
273
|
+
// What a pending script will be spoken with, so a voice change before the script lands is
|
|
274
|
+
// still a change.
|
|
275
|
+
export function speakerValues(voices) {
|
|
276
|
+
return [
|
|
277
|
+
voices.nativeDialogue,
|
|
278
|
+
voices.speakers.map((speaker) => [
|
|
279
|
+
speaker.id,
|
|
280
|
+
speaker.voice.provider,
|
|
281
|
+
speaker.voice.model,
|
|
282
|
+
speaker.voice.voice,
|
|
283
|
+
speaker.pronunciations ?? null,
|
|
284
|
+
]),
|
|
285
|
+
];
|
|
286
|
+
}
|