@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
|
@@ -12,14 +12,14 @@ const answerSchema = z.object({
|
|
|
12
12
|
});
|
|
13
13
|
export async function executeArticleRequests(deps, context, providers, initial) {
|
|
14
14
|
if (initial.input.kind !== "llm")
|
|
15
|
-
throw new Error("Slopify hit an internal error (the article step has no AI request).
|
|
15
|
+
throw new Error("Slopify hit an internal error (the article step has no AI request). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
16
16
|
let piece = initial;
|
|
17
17
|
let text = "";
|
|
18
18
|
let inputTokens = 0;
|
|
19
19
|
let outputTokens = 0;
|
|
20
20
|
for (let part = 0; part <= continuationLimit; part += 1) {
|
|
21
21
|
if (piece.input.kind !== "llm")
|
|
22
|
-
throw new Error("Slopify hit an internal error (an article continuation has no AI request).
|
|
22
|
+
throw new Error("Slopify hit an internal error (an article continuation has no AI request). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
23
23
|
const saved = deps.db
|
|
24
24
|
.prepare("SELECT result_json FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
25
25
|
.get(piece.id, context.work.workId);
|
|
@@ -38,9 +38,9 @@ export async function executeArticleRequests(deps, context, providers, initial)
|
|
|
38
38
|
webSearch: input.webSearch,
|
|
39
39
|
previewLabel: part === 0 ? "Article" : `Continuation ${part}`,
|
|
40
40
|
check: (value) => value.text.trim() === ""
|
|
41
|
-
? "The AI model returned an empty article.
|
|
41
|
+
? "The AI model returned an empty article. Try again; if it keeps happening, choose a different model in Edit project → Providers."
|
|
42
42
|
: part === continuationLimit && value.finishReason === "length"
|
|
43
|
-
? `The article was still unfinished after ${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
|
|
43
|
+
? `The article was still unfinished after ${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.`
|
|
44
44
|
: undefined,
|
|
45
45
|
}, (event) => {
|
|
46
46
|
if (event.type === "delta")
|
|
@@ -72,7 +72,7 @@ export async function executeArticleRequests(deps, context, providers, initial)
|
|
|
72
72
|
return { ok: false, reason: "held" };
|
|
73
73
|
piece = next;
|
|
74
74
|
}
|
|
75
|
-
throw new Error(`The article was still unfinished after ${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
|
|
75
|
+
throw new Error(`The article was still unfinished after ${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.`);
|
|
76
76
|
}
|
|
77
77
|
function continuationPiece(deps, context, initial, part, text) {
|
|
78
78
|
return transact(deps.db, () => {
|
|
@@ -28,7 +28,7 @@ export async function executeDocumentRecipe(deps, context, piece) {
|
|
|
28
28
|
? view.articleMarkdown
|
|
29
29
|
: text("article_md", "article:body");
|
|
30
30
|
if (article === null)
|
|
31
|
-
throw new Error("The article isn't finished yet, so there's nothing to make the document from. Let the Article stage finish (Resume, or
|
|
31
|
+
throw new Error("The article isn't finished yet, so there's nothing to make the document from. Let the Article stage finish (Resume, or Try again on Article), then use Try again on Document.");
|
|
32
32
|
const thumbnail = ready(view, "thumbnail", "thumbnail:image");
|
|
33
33
|
let cover = null;
|
|
34
34
|
if (config.sources.thumbnail !== "off" && thumbnail !== undefined) {
|
|
@@ -36,7 +36,7 @@ export async function executeDocumentRecipe(deps, context, piece) {
|
|
|
36
36
|
cover = new Uint8Array(readFileSync(outputPath(deps.paths, projectId, thumbnail.output.path)));
|
|
37
37
|
}
|
|
38
38
|
catch (error) {
|
|
39
|
-
throw new Error(`The document's cover is the project's thumbnail, and its file can't be read (${error instanceof Error ? error.message : String(error)}). Regenerate or upload the thumbnail again (
|
|
39
|
+
throw new Error(`The document's cover is the project's thumbnail, and its file can't be read (${error instanceof Error ? error.message : String(error)}). Regenerate or upload the thumbnail again (More → make it again in its section on Thumbnail, or Edit project → Thumbnail), then use Try again on Document.`, { cause: error });
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
const rendered = renderDocument({
|
|
@@ -69,10 +69,10 @@ function documentView(deps, context, piece) {
|
|
|
69
69
|
.prepare("SELECT recipe_context FROM revision_work WHERE id=?")
|
|
70
70
|
.get(context.work.workId);
|
|
71
71
|
if (view === undefined || row === undefined)
|
|
72
|
-
throw new Error("Slopify hit an internal error (the project version the document belongs to is missing). Use
|
|
72
|
+
throw new Error("Slopify hit an internal error (the project version the document belongs to is missing). Use Try again on Document; if it happens again, use Download diagnostics in Settings and report it.");
|
|
73
73
|
const recipe = executionPlan(deps, view, savedCatalogue(row.recipe_context)).recipes.find((one) => one.key === piece.key && one.fingerprint === piece.fingerprint);
|
|
74
74
|
if (recipe === undefined || recipe.unresolved)
|
|
75
|
-
throw new Error("The project changed after this document was queued, so it no longer matches the saved article. Use
|
|
75
|
+
throw new Error("The project changed after this document was queued, so it no longer matches the saved article. Use Choose what to remake (in the project's More menu) (or Try again on Document) to make it from the current version.");
|
|
76
76
|
return view;
|
|
77
77
|
}
|
|
78
78
|
function ready(view, role, workKey) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { outputPath } from "../storage/layout.js";
|
|
3
|
+
import { usesAmbientBed } from "../video/ambient-bed.js";
|
|
4
|
+
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
5
|
+
// The long video's ambient bed as the plan takes it (`video/ambient-bed.ts`), or undefined for
|
|
6
|
+
// none. The user's own file is checked readable before the render, so a bad file is named as
|
|
7
|
+
// the ambient sound rather than as a failed render.
|
|
8
|
+
export async function exportBed(deps, context, view) {
|
|
9
|
+
const config = view.revision.config;
|
|
10
|
+
const bed = config.ambientBed;
|
|
11
|
+
if (bed === undefined || !usesAmbientBed(config))
|
|
12
|
+
return undefined;
|
|
13
|
+
const settings = {
|
|
14
|
+
levelDb: bed.levelDb,
|
|
15
|
+
fadeInSeconds: bed.fadeInSeconds,
|
|
16
|
+
tailSeconds: bed.tailSeconds,
|
|
17
|
+
};
|
|
18
|
+
if (bed.source !== "upload")
|
|
19
|
+
return { source: { kind: "noise", preset: bed.source }, ...settings };
|
|
20
|
+
const fix = "Make the video again from Play with another file under Outputs → Export → Ambient sound, or pick Rain, Fireplace or Wind there instead.";
|
|
21
|
+
const assetId = view.revision.content.ambientBed;
|
|
22
|
+
const row = z
|
|
23
|
+
.object({ path: z.string() })
|
|
24
|
+
.safeParse(assetId === undefined
|
|
25
|
+
? undefined
|
|
26
|
+
: deps.db
|
|
27
|
+
.prepare("SELECT path FROM project_assets WHERE project_id=? AND id=?")
|
|
28
|
+
.get(context.work.projectId, assetId));
|
|
29
|
+
if (!row.success)
|
|
30
|
+
throw new Error(`The ambient sound's audio file is no longer in the project. ${fix}`);
|
|
31
|
+
const path = outputPath(deps.paths, context.work.projectId, row.data.path);
|
|
32
|
+
let durationMs;
|
|
33
|
+
try {
|
|
34
|
+
durationMs = await probeDurationMs(deps.ffmpeg, path, context.signal, deps.log);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
context.signal.throwIfAborted();
|
|
38
|
+
throw new Error(`The ambient sound's audio file couldn't be read as audio (${error instanceof Error ? error.message : String(error)}). ${fix}`);
|
|
39
|
+
}
|
|
40
|
+
if (durationMs <= 0)
|
|
41
|
+
throw new Error(`The ambient sound's audio file holds no sound. ${fix}`);
|
|
42
|
+
return { source: { kind: "file", path }, ...settings };
|
|
43
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { extname } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { usesYoutubeDescription } from "../admission/rules.js";
|
|
4
|
+
import { usesFigureCards, usesYoutubeDescription } from "../admission/rules.js";
|
|
5
|
+
import { captionFont, captionFontDeps } from "../fonts/coverage.js";
|
|
5
6
|
import { resolveFont } from "../fonts/index.js";
|
|
6
7
|
import { outputPath } from "../storage/layout.js";
|
|
7
8
|
import { headingChapters } from "../video/chapters.js";
|
|
8
9
|
import { sentenceCutPoints } from "../video/cuts.js";
|
|
9
10
|
import { usesChapterCards, usesNarrationCuts, videoEditOf, } from "../video/edit-settings.js";
|
|
10
11
|
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
12
|
+
import { passageSpans } from "../video/figure-spans.js";
|
|
11
13
|
import { wordsSchema } from "./runtime-subtitles.js";
|
|
12
14
|
// The containers an uploaded clip may come in; anything else in the images is a still.
|
|
13
15
|
const clipExtensions = new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -39,23 +41,31 @@ export async function exportEdit(deps, context, view, images) {
|
|
|
39
41
|
.safeParse(JSON.parse(row.piece.payload ?? "null"));
|
|
40
42
|
return parsed.success ? [parsed.data.fallback] : [];
|
|
41
43
|
});
|
|
42
|
-
|
|
44
|
+
const endScreen = config.endScreen?.text.trim() ?? "";
|
|
45
|
+
const figures = usesFigureCards(config) ? figureShots(deps, context, view) : [];
|
|
46
|
+
const withFigures = figures.length === 0 ? {} : { figures };
|
|
47
|
+
if (config.videoEdit === undefined) {
|
|
48
|
+
const titles = endScreen === "" ? undefined : await titleFont(deps, config);
|
|
43
49
|
return {
|
|
44
|
-
edit:
|
|
50
|
+
edit: titles !== undefined
|
|
51
|
+
? { clips, cards: { chapters: [], ...titles, endScreen }, ...withFigures }
|
|
52
|
+
: clips.some((clip) => clip !== undefined) || figures.length > 0
|
|
53
|
+
? { clips, ...withFigures }
|
|
54
|
+
: undefined,
|
|
45
55
|
warnings,
|
|
46
56
|
settings: undefined,
|
|
47
57
|
};
|
|
58
|
+
}
|
|
48
59
|
const settings = videoEditOf(config);
|
|
49
60
|
const cuts = usesNarrationCuts(config);
|
|
50
61
|
const cards = usesChapterCards(config);
|
|
51
62
|
const words = cuts || cards ? timingWords(deps, context, view) : [];
|
|
52
63
|
const chapters = cuts || cards ? chaptersOf(view, config, words) : [];
|
|
53
|
-
const
|
|
54
|
-
? await resolveFont(deps.paths, config.subtitles?.fontId ?? "default")
|
|
55
|
-
: undefined;
|
|
64
|
+
const titles = cards || endScreen !== "" ? await titleFont(deps, config) : undefined;
|
|
56
65
|
return {
|
|
57
66
|
edit: {
|
|
58
67
|
clips,
|
|
68
|
+
...withFigures,
|
|
59
69
|
...(cuts
|
|
60
70
|
? {
|
|
61
71
|
narration: {
|
|
@@ -73,22 +83,40 @@ export async function exportEdit(deps, context, view, images) {
|
|
|
73
83
|
grade: settings.grade,
|
|
74
84
|
atmosphere: settings.atmosphere,
|
|
75
85
|
},
|
|
76
|
-
...(
|
|
86
|
+
...(titles === undefined
|
|
77
87
|
? {}
|
|
78
|
-
: {
|
|
88
|
+
: {
|
|
89
|
+
cards: {
|
|
90
|
+
chapters: cards ? chapters : [],
|
|
91
|
+
...titles,
|
|
92
|
+
...(endScreen === "" ? {} : { endScreen }),
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
79
95
|
},
|
|
80
96
|
warnings,
|
|
81
97
|
settings,
|
|
82
98
|
};
|
|
83
99
|
}
|
|
84
|
-
|
|
100
|
+
// The cards' font and colour: the brand kit's title style, else the caption font in white.
|
|
101
|
+
async function titleFont(deps, config) {
|
|
102
|
+
const fontId = config.titleStyle?.fontId ?? config.subtitles?.fontId ?? "default";
|
|
103
|
+
// Chapter titles are in the project language too, so they need its letters.
|
|
104
|
+
const font = config.language === undefined || config.language === "en"
|
|
105
|
+
? await resolveFont(deps.paths, fontId)
|
|
106
|
+
: await captionFont(captionFontDeps(deps.paths), fontId, config.language);
|
|
107
|
+
return {
|
|
108
|
+
font: { path: font.path, name: font.assName },
|
|
109
|
+
...(config.titleStyle?.color === undefined ? {} : { color: config.titleStyle.color }),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function timingWords(deps, context, view) {
|
|
85
113
|
const timing = view.outputs.find((row) => row.selected &&
|
|
86
114
|
row.available &&
|
|
87
115
|
row.state === "ready" &&
|
|
88
116
|
row.workKey === "subtitles:timing" &&
|
|
89
117
|
row.output.role === "subtitle_words");
|
|
90
118
|
if (timing === undefined)
|
|
91
|
-
throw new Error("The narration's word timing, which the video's cuts and chapter cards follow, is missing. Use
|
|
119
|
+
throw new Error("The narration's word timing, which the video's cuts and chapter cards follow, is missing. Use More → Render the video again in the Video section, then Try again.");
|
|
92
120
|
return wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8"))).words;
|
|
93
121
|
}
|
|
94
122
|
// The YouTube description's chapters when that step runs, the article's headings otherwise.
|
|
@@ -114,6 +142,37 @@ async function clipSeconds(deps, context, path, place) {
|
|
|
114
142
|
ms = 0;
|
|
115
143
|
}
|
|
116
144
|
if (!(ms > 0))
|
|
117
|
-
throw new Error(`Image ${String(place)} is a video clip Slopify can't read, so the video can't show it. Replace it with another clip or an image in Edit project → Images, then
|
|
145
|
+
throw new Error(`Image ${String(place)} is a video clip Slopify can't read, so the video can't show it. Replace it with another clip or an image in Edit project → Images, then Try again.`);
|
|
118
146
|
return ms / 1000;
|
|
119
147
|
}
|
|
148
|
+
// Each card in the video's frame, shown where its description is spoken: the description's
|
|
149
|
+
// answer found in the word timing (`video/figure-spans.ts`). A card whose description can't be
|
|
150
|
+
// found in the timing is left out rather than shown at the wrong moment.
|
|
151
|
+
export function figureShots(deps, context, view, format = view.revision.config.format, words = timingWords(deps, context, view)) {
|
|
152
|
+
const cards = view.outputs
|
|
153
|
+
.filter((row) => row.selected &&
|
|
154
|
+
row.available &&
|
|
155
|
+
row.state === "ready" &&
|
|
156
|
+
row.output.role === "figure_card" &&
|
|
157
|
+
row.workKey.startsWith("figure:card:") &&
|
|
158
|
+
(row.output.meta.format ?? view.revision.config.format) === format)
|
|
159
|
+
.map((row) => ({
|
|
160
|
+
index: Number(row.workKey.slice("figure:card:".length)),
|
|
161
|
+
path: outputPath(deps.paths, context.work.projectId, row.output.path),
|
|
162
|
+
}))
|
|
163
|
+
.toSorted((a, b) => a.index - b.index);
|
|
164
|
+
const passages = cards.map((card) => {
|
|
165
|
+
const row = view.pieces.find((one) => one.key === `narration:describe:${String(card.index)}` &&
|
|
166
|
+
one.selected &&
|
|
167
|
+
one.piece.state === "done");
|
|
168
|
+
const parsed = z
|
|
169
|
+
.object({ text: z.string() })
|
|
170
|
+
.safeParse(JSON.parse(row?.piece.payload ?? "null"));
|
|
171
|
+
return parsed.success ? parsed.data.text : "";
|
|
172
|
+
});
|
|
173
|
+
const spans = passageSpans(words, passages);
|
|
174
|
+
return cards.flatMap((card, at) => {
|
|
175
|
+
const span = spans[at];
|
|
176
|
+
return span === undefined ? [] : [{ path: card.path, start: span.start, end: span.end }];
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { usesNarrationPreparation, usesPronunciationGlossary } from "../admission/rules.js";
|
|
3
|
+
import { narrationAliasesOf, usesNarrationPreparation, usesPronunciationGlossary, } from "../admission/rules.js";
|
|
4
4
|
import { plainText } from "../article/plain.js";
|
|
5
5
|
import { splitEndMatter } from "../article/split.js";
|
|
6
|
+
import { usesLoudness } from "../loudness/model.js";
|
|
6
7
|
import { outputPath } from "../storage/layout.js";
|
|
7
8
|
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
8
9
|
import { audioTimeline } from "../video/plan.js";
|
|
10
|
+
import { usesVoices } from "../voices/model.js";
|
|
11
|
+
import { levelKey } from "./recipe-loudness.js";
|
|
9
12
|
import { joinedNarration, narrationTextParts } from "./runtime-narration-text.js";
|
|
10
13
|
import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
|
|
11
14
|
export function exportSnapshot(deps, context, piece) {
|
|
@@ -14,19 +17,33 @@ export function exportSnapshot(deps, context, piece) {
|
|
|
14
17
|
.prepare("SELECT recipe_context FROM revision_work WHERE id=?")
|
|
15
18
|
.get(context.work.workId);
|
|
16
19
|
if (view === undefined || row === undefined)
|
|
17
|
-
throw new Error("Slopify hit an internal error (the project version being exported is missing).
|
|
20
|
+
throw new Error("Slopify hit an internal error (the project version being exported is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
18
21
|
const plan = executionPlan(deps, view, savedCatalogue(row.recipe_context));
|
|
19
22
|
const recipe = plan.recipes.find((one) => one.key === piece.key && one.fingerprint === piece.fingerprint);
|
|
20
23
|
if (recipe === undefined || recipe.deferred || recipe.unresolved)
|
|
21
|
-
throw new Error("Slopify hit an internal error (the export no longer matches the project's saved inputs).
|
|
24
|
+
throw new Error("Slopify hit an internal error (the export no longer matches the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
22
25
|
return { view, plan };
|
|
23
26
|
}
|
|
24
|
-
export async function revisionAudio(deps, context, view
|
|
27
|
+
export async function revisionAudio(deps, context, view,
|
|
28
|
+
// Level the volume: what plays the sound (the video, a short, the audio files) asks for the
|
|
29
|
+
// levelled joins (`recipe-loudness.ts`); what only times it (the word timing, the description,
|
|
30
|
+
// the shorts' pick) reads the plain ones, which it waits for. An uploaded narration has no
|
|
31
|
+
// levelled join: only the master reaches it.
|
|
32
|
+
options = {}) {
|
|
25
33
|
const config = view.revision.config;
|
|
26
34
|
if (config.sources.audio === "off")
|
|
27
35
|
return [];
|
|
36
|
+
const levelled = options.levelled === true && usesLoudness(config) && config.sources.audio === "generate";
|
|
28
37
|
const input = async (role) => {
|
|
29
|
-
const
|
|
38
|
+
const segment = role === "audio_body" ? "body" : role === "audio_intro" ? "intro" : "outro";
|
|
39
|
+
const row = view.outputs.find((one) => one.selected &&
|
|
40
|
+
one.available &&
|
|
41
|
+
one.state === "ready" &&
|
|
42
|
+
(levelled
|
|
43
|
+
? one.output.role === "audio_levelled" && one.workKey === levelKey(segment)
|
|
44
|
+
: one.output.role === role));
|
|
45
|
+
if (row === undefined && levelled)
|
|
46
|
+
throw new Error(`The levelled ${segment === "body" ? "narration" : segment} isn't finished yet, so the sound can't be exported at an even volume. Let the Narration stage finish (Resume, or Try again on it), then retry this stage; or turn off Level the volume in Edit project → Pauses and volume.`);
|
|
30
47
|
if (row === undefined)
|
|
31
48
|
return undefined;
|
|
32
49
|
const path = outputPath(deps.paths, context.work.projectId, row.output.path);
|
|
@@ -34,12 +51,12 @@ export async function revisionAudio(deps, context, view) {
|
|
|
34
51
|
(await (deps.measureAudio?.(path, context.signal) ??
|
|
35
52
|
probeDurationMs(deps.ffmpeg, path, context.signal, deps.log)));
|
|
36
53
|
if (!Number.isFinite(duration) || duration <= 0)
|
|
37
|
-
throw new Error("Slopify couldn't read the length of the narration audio, so the file may be damaged or empty. Regenerate the narration (or upload your audio file again in Edit project → Narration), then
|
|
54
|
+
throw new Error("Slopify couldn't read the length of the narration audio, so the file may be damaged or empty. Regenerate the narration (or upload your audio file again in Edit project → Narration), then Try again.");
|
|
38
55
|
return { path, seconds: duration / 1000 };
|
|
39
56
|
};
|
|
40
57
|
const body = await input("audio_body");
|
|
41
58
|
if (body === undefined)
|
|
42
|
-
throw new Error("The narration audio isn't finished yet. Let the Narration stage finish (Resume, or
|
|
59
|
+
throw new Error("The narration audio isn't finished yet. Let the Narration stage finish (Resume, or Try again on it), then retry this stage.");
|
|
43
60
|
return audioTimeline({
|
|
44
61
|
body,
|
|
45
62
|
intro: config.sources.audio === "generate" && config.intro !== undefined
|
|
@@ -55,7 +72,10 @@ export async function revisionAudio(deps, context, view) {
|
|
|
55
72
|
export function revisionTranscript(deps, snapshot, kind) {
|
|
56
73
|
const { view, plan } = snapshot;
|
|
57
74
|
if (usesNarrationPreparation(view.revision.config) ||
|
|
58
|
-
usesPronunciationGlossary(view.revision.config)
|
|
75
|
+
usesPronunciationGlossary(view.revision.config) ||
|
|
76
|
+
(kind === "body" && usesVoices(view.revision.config)) ||
|
|
77
|
+
// Aliased requests say something other than the text; the transcript is the text.
|
|
78
|
+
narrationAliasesOf(view.revision.config).length > 0)
|
|
59
79
|
return joinedNarration(narrationTextParts(view, plan, kind));
|
|
60
80
|
const concat = plan.recipes.find((one) => one.key === (kind === "body" ? "audio:body:concat" : `audio:${kind}`));
|
|
61
81
|
if (concat !== undefined) {
|
|
@@ -73,7 +93,7 @@ export function revisionTranscript(deps, snapshot, kind) {
|
|
|
73
93
|
Array.isArray(recipe.input.semantic) &&
|
|
74
94
|
typeof recipe.input.semantic[0] === "string")
|
|
75
95
|
return recipe.input.semantic[0];
|
|
76
|
-
throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or
|
|
96
|
+
throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Try again on it), or regenerate the missing chunk in Edit project → Narration.");
|
|
77
97
|
});
|
|
78
98
|
if (texts.length > 0)
|
|
79
99
|
return texts.join("\n");
|
|
@@ -85,7 +105,7 @@ export function revisionTranscript(deps, snapshot, kind) {
|
|
|
85
105
|
if (view.articleMarkdown !== null)
|
|
86
106
|
return plainText(splitEndMatter(view.articleMarkdown).body);
|
|
87
107
|
}
|
|
88
|
-
throw new Error(`The saved text of the ${kind === "body" ? "article" : kind} is missing, so it can't be matched to the narration. Use
|
|
108
|
+
throw new Error(`The saved text of the ${kind === "body" ? "article" : kind} is missing, so it can't be matched to the narration. Use More → Write the article again in the Article section, then Try again.`);
|
|
89
109
|
}
|
|
90
110
|
export function retainedOutput(deps, view, row) {
|
|
91
111
|
const asset = z
|
|
@@ -113,3 +133,25 @@ export function retainedOutput(deps, view, row) {
|
|
|
113
133
|
output: { ...row.output, id: deps.ids.next(), createdAt: deps.clock.now().toISOString() },
|
|
114
134
|
};
|
|
115
135
|
}
|
|
136
|
+
// The body's speaker turns in narration order, as the plan asked for them: a native
|
|
137
|
+
// multi-speaker request lists its own lines, every other part says whose turn it speaks.
|
|
138
|
+
export function revisionTurns(snapshot) {
|
|
139
|
+
const { view, plan } = snapshot;
|
|
140
|
+
if (!usesVoices(view.revision.config))
|
|
141
|
+
return [];
|
|
142
|
+
const concat = plan.recipes.find((one) => one.key === "audio:body:concat");
|
|
143
|
+
return (concat?.dependsOn ?? []).flatMap((key) => {
|
|
144
|
+
const input = plan.recipes.find((one) => one.key === key)?.input;
|
|
145
|
+
if (input?.kind !== "tts")
|
|
146
|
+
return [];
|
|
147
|
+
if (input.dialogue !== undefined)
|
|
148
|
+
return input.dialogue.map((line) => ({
|
|
149
|
+
speaker: line.speaker,
|
|
150
|
+
turn: line.turn,
|
|
151
|
+
text: line.text,
|
|
152
|
+
}));
|
|
153
|
+
return input.speaker === undefined || input.turn === undefined
|
|
154
|
+
? []
|
|
155
|
+
: [{ speaker: input.speaker, turn: input.turn, text: input.spokenText ?? input.text }];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { relative, sep } from "node:path";
|
|
2
|
+
import { resolveBoldFont } from "../fonts/index.js";
|
|
3
|
+
import { masterGoal } from "../loudness/model.js";
|
|
4
|
+
import { renderShort, shortFrame } from "../shorts/render.js";
|
|
5
|
+
import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
|
|
6
|
+
import { projectDir } from "../storage/layout.js";
|
|
7
|
+
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
8
|
+
import { slideshowImages } from "./runtime-export.js";
|
|
9
|
+
import { preparedResult, preparedText, publishResult } from "./runtime-publication.js";
|
|
10
|
+
import { timingWords } from "./runtime-shorts.js";
|
|
11
|
+
// A short-mode project's video (`admission/short-mode.ts`): the whole narration timeline as
|
|
12
|
+
// one clip through the Shorts renderer, so it looks like every short Slopify cuts from a long
|
|
13
|
+
// video - the images moving as the project says, the title on top and big word-by-word
|
|
14
|
+
// captions from the word timing.
|
|
15
|
+
export async function executeShortExport(deps, context, piece, snapshot, timeline) {
|
|
16
|
+
const { view } = snapshot;
|
|
17
|
+
const config = view.revision.config;
|
|
18
|
+
const images = slideshowImages(deps, context, view);
|
|
19
|
+
const words = timingWords(deps, context, view);
|
|
20
|
+
const seconds = timeline.reduce((sum, segment) => sum + segment.seconds, 0);
|
|
21
|
+
if (seconds <= 0)
|
|
22
|
+
throw new Error("The short has no narration to play, so there is nothing to render. Let the Narration stage finish (Resume, or Try again on it), then retry this stage.");
|
|
23
|
+
const font = await resolveBoldFont(deps.paths, config.subtitles?.fontId ?? "default");
|
|
24
|
+
if (!context.maySubmit(piece.id))
|
|
25
|
+
return "held";
|
|
26
|
+
const pending = allocateAsset(deps, context.work.projectId, "video.mp4");
|
|
27
|
+
const prepared = [];
|
|
28
|
+
const directory = projectDir(deps.paths, context.work.projectId);
|
|
29
|
+
const relativePath = (path) => relative(directory, path).split(sep).join("/");
|
|
30
|
+
try {
|
|
31
|
+
const master = await renderShort({
|
|
32
|
+
bin: deps.ffmpeg,
|
|
33
|
+
master: masterGoal(config, "video"),
|
|
34
|
+
timeline,
|
|
35
|
+
start: 0,
|
|
36
|
+
end: seconds,
|
|
37
|
+
images,
|
|
38
|
+
imageSeconds: config.imageSeconds,
|
|
39
|
+
motionStyle: config.motionStyle,
|
|
40
|
+
zoomPercent: config.zoomPercent,
|
|
41
|
+
words,
|
|
42
|
+
font,
|
|
43
|
+
title: config.title,
|
|
44
|
+
output: pending.absolutePath,
|
|
45
|
+
scratch: directory,
|
|
46
|
+
signal: context.signal,
|
|
47
|
+
log: deps.log,
|
|
48
|
+
onProgress: (elapsedMs) => context.emit({
|
|
49
|
+
type: "stage.progress",
|
|
50
|
+
projectId: context.work.projectId,
|
|
51
|
+
stage: "video",
|
|
52
|
+
current: Math.min(100, Math.round(elapsedMs / (seconds * 10))),
|
|
53
|
+
total: 100,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
context.signal.throwIfAborted();
|
|
57
|
+
const durationMs = await probeDurationMs(deps.ffmpeg, pending.absolutePath, context.signal, deps.log);
|
|
58
|
+
const asset = sealAsset(deps, pending);
|
|
59
|
+
prepared.push(preparedResult(deps, context, piece, "video", asset, durationMs, {
|
|
60
|
+
subtitlesMode: "off",
|
|
61
|
+
...(master === undefined ? {} : { master }),
|
|
62
|
+
}));
|
|
63
|
+
prepared.push(preparedText(deps, context, piece, "render_params", "render.json", JSON.stringify({
|
|
64
|
+
mode: "short",
|
|
65
|
+
...shortFrame,
|
|
66
|
+
totalSeconds: seconds,
|
|
67
|
+
imageSeconds: config.imageSeconds,
|
|
68
|
+
motionStyle: config.motionStyle,
|
|
69
|
+
zoomPercent: config.zoomPercent,
|
|
70
|
+
images: images.map(relativePath),
|
|
71
|
+
audio: timeline.map((segment) => ({
|
|
72
|
+
...segment,
|
|
73
|
+
path: segment.path === null ? null : relativePath(segment.path),
|
|
74
|
+
})),
|
|
75
|
+
fontId: config.subtitles?.fontId ?? "default",
|
|
76
|
+
output: pending.path,
|
|
77
|
+
})));
|
|
78
|
+
await publishResult(deps, context, piece, prepared, { totalSeconds: seconds }, asset);
|
|
79
|
+
deps.count?.("stage.completed", { stage: "video" });
|
|
80
|
+
return "done";
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
discardPreparedAssets(deps, [pending, ...prepared.map((one) => one.asset)]);
|
|
84
|
+
}
|
|
85
|
+
}
|