@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { thinkingModes } from "../../kernel/ports/llm.js";
|
|
4
|
+
import { plainText } from "../article/plain.js";
|
|
5
|
+
import { defaultReviewPrompts, narrationItemKey, reviewStages, } from "../reviews/model.js";
|
|
6
|
+
import { nextAttempt, reviewOutcome } from "../reviews/outcome.js";
|
|
7
|
+
import { latestVerdict, saveVerdict } from "../reviews/repo.js";
|
|
8
|
+
import { parseVerdict, reviewMessages } from "../reviews/verdict.js";
|
|
9
|
+
import { pickedShortsOf } from "../shorts/clips.js";
|
|
10
|
+
import { outputPath } from "../storage/layout.js";
|
|
11
|
+
import { exportSnapshot } from "./runtime-export-inputs.js";
|
|
12
|
+
import { savedCatalogue } from "./runtime-plan.js";
|
|
13
|
+
import { publishResult } from "./runtime-publication.js";
|
|
14
|
+
import { wordsSchema } from "./runtime-subtitles.js";
|
|
15
|
+
// What `recipe-reviews.ts` saves in the step's values, by position.
|
|
16
|
+
const valuesSchema = z.tuple([
|
|
17
|
+
z.enum(reviewStages),
|
|
18
|
+
z.string(),
|
|
19
|
+
z.array(z.tuple([z.string(), z.unknown()])),
|
|
20
|
+
z.string(),
|
|
21
|
+
z.string(),
|
|
22
|
+
z.string().nullable(),
|
|
23
|
+
z.string(),
|
|
24
|
+
z.enum(["flag", "redo"]),
|
|
25
|
+
z.number().int().nonnegative(),
|
|
26
|
+
]);
|
|
27
|
+
// ceiling: enough of a long article or research for a reviewer to judge it, and well inside
|
|
28
|
+
// every supported model's context.
|
|
29
|
+
const textMax = 60_000;
|
|
30
|
+
// Asks the reviewer about one finished item and saves its verdict. A malformed answer is a
|
|
31
|
+
// failed attempt the provider wrapper asks again; a verdict that sends the item back is
|
|
32
|
+
// saved as a pending redo, which `review-redo.ts` starts once this step has finished, and
|
|
33
|
+
// until then the item's dependents are held (`reviewHold`).
|
|
34
|
+
export async function executeReviewRecipe(deps, context, providers, piece) {
|
|
35
|
+
if (deps.db
|
|
36
|
+
.prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
37
|
+
.get(piece.id, context.work.workId)?.state === "done")
|
|
38
|
+
return "done";
|
|
39
|
+
if (!context.maySubmit(piece.id))
|
|
40
|
+
return "held";
|
|
41
|
+
context.signal.throwIfAborted();
|
|
42
|
+
const input = piece.input;
|
|
43
|
+
const values = input.kind === "local" && input.operation === "review-v1"
|
|
44
|
+
? valuesSchema.safeParse(input.values)
|
|
45
|
+
: undefined;
|
|
46
|
+
if (values === undefined || !values.success)
|
|
47
|
+
throw new Error("Slopify hit an internal error (a review step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
48
|
+
const [stage, itemKey, , provider, model, thinking, prompt, mode, retries] = values.data;
|
|
49
|
+
const { view, plan } = exportSnapshot(deps, context, piece);
|
|
50
|
+
const material = reviewMaterial(deps, context, view, plan.recipes, stage, itemKey, prompt);
|
|
51
|
+
const previous = latestVerdict(deps.db, context.work.projectId, itemKey);
|
|
52
|
+
const attempt = nextAttempt(previous, material.itemFingerprint);
|
|
53
|
+
const row = deps.db
|
|
54
|
+
.prepare("SELECT recipe_context FROM revision_work WHERE id=?")
|
|
55
|
+
.get(context.work.workId);
|
|
56
|
+
const catalogued = savedCatalogue(row?.recipe_context).llm.find((one) => one.provider === provider && one.id === model);
|
|
57
|
+
const level = thinking === null ? undefined : z.enum(thinkingModes).parse(thinking);
|
|
58
|
+
if (!context.maySubmit(piece.id))
|
|
59
|
+
return "held";
|
|
60
|
+
const answer = await providers.forPiece(piece.id).llm({
|
|
61
|
+
provider,
|
|
62
|
+
model,
|
|
63
|
+
...(level === undefined ? {} : { thinking: level }),
|
|
64
|
+
thinkingConfig: level === undefined ? null : (catalogued?.llm.thinking?.[level] ?? null),
|
|
65
|
+
messages: reviewMessages(material.request),
|
|
66
|
+
webSearch: false,
|
|
67
|
+
...(material.images.length === 0 ? {} : { images: material.images }),
|
|
68
|
+
previewLabel: piece.key,
|
|
69
|
+
check: (value) => {
|
|
70
|
+
const parsed = parseVerdict(value.text);
|
|
71
|
+
return parsed.ok ? undefined : parsed.reason;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (!answer.ok)
|
|
75
|
+
return "held";
|
|
76
|
+
const verdict = parseVerdict(answer.value.text);
|
|
77
|
+
if (!verdict.ok)
|
|
78
|
+
throw new Error(verdict.reason);
|
|
79
|
+
const outcome = reviewOutcome({ passed: verdict.value.passed, mode, attempt, retries });
|
|
80
|
+
const saved = saveVerdict(deps.db, {
|
|
81
|
+
id: deps.ids.next(),
|
|
82
|
+
projectId: context.work.projectId,
|
|
83
|
+
revisionId: context.work.revisionId,
|
|
84
|
+
itemKey,
|
|
85
|
+
stage,
|
|
86
|
+
itemFingerprint: material.itemFingerprint,
|
|
87
|
+
reviewFingerprint: piece.fingerprint,
|
|
88
|
+
passed: verdict.value.passed,
|
|
89
|
+
reasons: verdict.value.reasons,
|
|
90
|
+
outcome,
|
|
91
|
+
attempt,
|
|
92
|
+
action: null,
|
|
93
|
+
actionAt: null,
|
|
94
|
+
redoState: outcome === "redo" ? "pending" : null,
|
|
95
|
+
redoError: null,
|
|
96
|
+
createdAt: deps.clock.now().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
await publishResult(deps, context, piece, [], {
|
|
99
|
+
verdictId: saved.id,
|
|
100
|
+
passed: saved.passed,
|
|
101
|
+
reasons: saved.reasons,
|
|
102
|
+
outcome: saved.outcome,
|
|
103
|
+
attempt: saved.attempt,
|
|
104
|
+
});
|
|
105
|
+
deps.count?.("stage.completed", {
|
|
106
|
+
provider,
|
|
107
|
+
model,
|
|
108
|
+
tokensIn: answer.value.usage?.inputTokens ?? 0,
|
|
109
|
+
tokensOut: answer.value.usage?.outputTokens ?? 0,
|
|
110
|
+
});
|
|
111
|
+
return "done";
|
|
112
|
+
}
|
|
113
|
+
function reviewMaterial(deps, context, view, recipes, stage, itemKey, prompt) {
|
|
114
|
+
const config = view.revision.config;
|
|
115
|
+
const output = (workKey, role) => view.outputs.find((row) => row.workKey === workKey &&
|
|
116
|
+
row.selected &&
|
|
117
|
+
row.available &&
|
|
118
|
+
row.state === "ready" &&
|
|
119
|
+
(role === undefined || row.output.role === role));
|
|
120
|
+
const path = (row) => outputPath(deps.paths, context.work.projectId, row.output.path);
|
|
121
|
+
const text = (row) => clip(readFileSync(path(row), "utf8"));
|
|
122
|
+
const missing = (what, fix) => new Error(`The ${what} this review looks at is missing, so it could not be reviewed. ${fix}, then use Try again.`);
|
|
123
|
+
const request = (sections, images = []) => ({
|
|
124
|
+
stage,
|
|
125
|
+
instruction: prompt.trim() === "" ? defaultReviewPrompts[stage] : prompt,
|
|
126
|
+
sections,
|
|
127
|
+
images,
|
|
128
|
+
});
|
|
129
|
+
switch (stage) {
|
|
130
|
+
case "article": {
|
|
131
|
+
const article = output(itemKey, "article_md");
|
|
132
|
+
if (article === undefined)
|
|
133
|
+
throw missing("article", "Use More → Write the article again in the Article section");
|
|
134
|
+
const notes = output("research:notes", "notes");
|
|
135
|
+
const research = notes !== undefined ? text(notes) : clip(config.provided.research?.trim() ?? "");
|
|
136
|
+
return {
|
|
137
|
+
request: request([
|
|
138
|
+
{
|
|
139
|
+
label: "Article prompt the article was written from",
|
|
140
|
+
text: clip(config.rendered.article ?? ""),
|
|
141
|
+
},
|
|
142
|
+
...(research === "" ? [] : [{ label: "Research notes", text: research }]),
|
|
143
|
+
{ label: "Article", text: text(article) },
|
|
144
|
+
]),
|
|
145
|
+
images: [],
|
|
146
|
+
itemFingerprint: article.fingerprint,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case "images":
|
|
150
|
+
case "thumbnail": {
|
|
151
|
+
const image = output(itemKey);
|
|
152
|
+
if (image === undefined)
|
|
153
|
+
throw missing(stage === "images" ? "image" : "thumbnail", stage === "images"
|
|
154
|
+
? "Use More → make it again in its section on Images"
|
|
155
|
+
: "Use More → make it again in its section on Thumbnail");
|
|
156
|
+
const recipe = recipes.find((one) => one.key === itemKey);
|
|
157
|
+
const brief = recipe?.input.kind === "image" ? recipe.input.prompt : "";
|
|
158
|
+
const reference = stage === "images" ? output("reference:image") : undefined;
|
|
159
|
+
return {
|
|
160
|
+
request: request([
|
|
161
|
+
...(stage === "thumbnail" ? [{ label: "Video title", text: config.title }] : []),
|
|
162
|
+
...(brief === "" ? [] : [{ label: "Brief the image was drawn from", text: brief }]),
|
|
163
|
+
], [
|
|
164
|
+
stage === "images" ? "the image to review" : "the thumbnail to review",
|
|
165
|
+
...(reference === undefined ? [] : ["the establishing image it must match"]),
|
|
166
|
+
]),
|
|
167
|
+
images: [
|
|
168
|
+
{
|
|
169
|
+
path: path(image),
|
|
170
|
+
name: stage === "images" ? "the image to review" : "the thumbnail to review",
|
|
171
|
+
},
|
|
172
|
+
...(reference === undefined
|
|
173
|
+
? []
|
|
174
|
+
: [{ path: path(reference), name: "the establishing image it must match" }]),
|
|
175
|
+
],
|
|
176
|
+
itemFingerprint: image.fingerprint,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case "narration": {
|
|
180
|
+
const timing = output("subtitles:timing", "subtitle_words");
|
|
181
|
+
if (timing === undefined || itemKey !== narrationItemKey)
|
|
182
|
+
throw missing("narration's word timing", "Use More → Render the video again in the Video section");
|
|
183
|
+
const { words, omissions } = wordsSchema.parse(JSON.parse(readFileSync(path(timing), "utf8")));
|
|
184
|
+
const unsure = words.filter((word) => (word.confidence ?? 1) < 0.3).length;
|
|
185
|
+
return {
|
|
186
|
+
request: request([
|
|
187
|
+
{
|
|
188
|
+
label: "Text the narration was read from",
|
|
189
|
+
text: clip(plainText(view.articleMarkdown ?? "")),
|
|
190
|
+
},
|
|
191
|
+
{ label: "What the timing heard", text: clip(words.map((word) => word.text).join(" ")) },
|
|
192
|
+
{
|
|
193
|
+
label: "Stretches of text the timing could not hear",
|
|
194
|
+
text: omissions.length === 0
|
|
195
|
+
? "None."
|
|
196
|
+
: omissions.map((gap) => `At ${gap.start.toFixed(1)} s: ${gap.text}`).join("\n"),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
label: "Words heard with low confidence",
|
|
200
|
+
text: `${String(unsure)} of ${String(words.length)}.`,
|
|
201
|
+
},
|
|
202
|
+
]),
|
|
203
|
+
images: [],
|
|
204
|
+
itemFingerprint: timing.fingerprint,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
case "shorts": {
|
|
208
|
+
const render = output(`${itemKey}:render`);
|
|
209
|
+
if (render === undefined)
|
|
210
|
+
throw missing("short", "Use Make this short again in Edit project → Shorts");
|
|
211
|
+
const number = Number(itemKey.slice("shorts:".length));
|
|
212
|
+
const pick = view.pieces.find((row) => row.key === "shorts:pick" && row.selected && row.piece.state === "done");
|
|
213
|
+
const title = pickedShortsOf(pick?.piece.payload)?.shorts.find((clip) => clip.number === number)?.title ??
|
|
214
|
+
"";
|
|
215
|
+
const stills = view.outputs
|
|
216
|
+
.filter((row) => row.workKey.startsWith(`${itemKey}:image:`) &&
|
|
217
|
+
row.selected &&
|
|
218
|
+
row.available &&
|
|
219
|
+
row.state === "ready")
|
|
220
|
+
.slice(0, 6);
|
|
221
|
+
if (stills.length === 0)
|
|
222
|
+
throw missing("short's images", "Use Make this short again in Edit project → Shorts");
|
|
223
|
+
const names = stills.map((_, index) => `image ${String(index + 1)} of the short`);
|
|
224
|
+
return {
|
|
225
|
+
request: request([{ label: "Title of the short", text: title }], names),
|
|
226
|
+
images: stills.map((row, index) => ({
|
|
227
|
+
path: path(row),
|
|
228
|
+
name: names[index] ?? "an image of the short",
|
|
229
|
+
})),
|
|
230
|
+
itemFingerprint: render.fingerprint,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function clip(value) {
|
|
236
|
+
return value.length <= textMax ? value : `${value.slice(0, textMax)}\n[... cut for length]`;
|
|
237
|
+
}
|
|
@@ -4,8 +4,10 @@ import { executeExportRecipe } from "./runtime-export.js";
|
|
|
4
4
|
import { executeLocalRecipe } from "./runtime-local.js";
|
|
5
5
|
import { pieceLabel } from "./runtime-piece-label.js";
|
|
6
6
|
import { executeProviderRecipe } from "./runtime-provider.js";
|
|
7
|
+
import { executeReviewRecipe } from "./runtime-review.js";
|
|
7
8
|
import { executeShortsRecipe } from "./runtime-shorts.js";
|
|
8
9
|
import { executeSubtitleRecipe } from "./runtime-subtitles.js";
|
|
10
|
+
import { executeVoicesRecipe } from "./runtime-voices.js";
|
|
9
11
|
import { executeYoutubeRecipe } from "./runtime-youtube.js";
|
|
10
12
|
import { workPieces } from "./work-records.js";
|
|
11
13
|
export async function runRevisionInvocation(deps, context, providers) {
|
|
@@ -22,17 +24,21 @@ export async function runRevisionInvocation(deps, context, providers) {
|
|
|
22
24
|
? await executeExportRecipe(deps, context, piece)
|
|
23
25
|
: piece.key.startsWith("document:")
|
|
24
26
|
? await executeDocumentRecipe(deps, context, piece)
|
|
25
|
-
: piece.key.startsWith("
|
|
26
|
-
? await
|
|
27
|
-
: piece.key.startsWith("
|
|
28
|
-
? await
|
|
29
|
-
: piece.key.startsWith("
|
|
30
|
-
? await
|
|
31
|
-
: piece.
|
|
32
|
-
|
|
33
|
-
piece.
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
: piece.key.startsWith("review:")
|
|
28
|
+
? await executeReviewRecipe(deps, context, providers, piece)
|
|
29
|
+
: piece.key.startsWith("voices:")
|
|
30
|
+
? await executeVoicesRecipe(deps, context, piece)
|
|
31
|
+
: piece.key.startsWith("youtube:")
|
|
32
|
+
? await executeYoutubeRecipe(deps, context, providers, piece)
|
|
33
|
+
: piece.key.startsWith("shorts:")
|
|
34
|
+
? await executeShortsRecipe(deps, context, providers, piece)
|
|
35
|
+
: piece.key.startsWith("animate:")
|
|
36
|
+
? await executeAnimateRecipe(deps, context, providers, piece)
|
|
37
|
+
: piece.input.kind === "llm" ||
|
|
38
|
+
piece.input.kind === "tts" ||
|
|
39
|
+
piece.input.kind === "image"
|
|
40
|
+
? await executeProviderRecipe(deps, context, providers, piece)
|
|
41
|
+
: await executeLocalRecipe(deps, context, piece);
|
|
36
42
|
if (outcome === "held")
|
|
37
43
|
return outcome;
|
|
38
44
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import { withLanguage, wordTimingUnavailable } from "../../kernel/ports/languages.js";
|
|
4
|
+
import { usesFigureCards, usesShorts } from "../admission/rules.js";
|
|
5
|
+
import { captionBoldFont } from "../fonts/coverage.js";
|
|
4
6
|
import { resolveBoldFont } from "../fonts/index.js";
|
|
7
|
+
import { masterGoal } from "../loudness/model.js";
|
|
5
8
|
import { clipWords } from "../shorts/captions.js";
|
|
6
9
|
import { effectiveClips, pickedShortsOf } from "../shorts/clips.js";
|
|
7
10
|
import { defaultShortsPrompt, musicVolumeOf, shortImageCount, shortsSpeedOf, } from "../shorts/model.js";
|
|
@@ -11,15 +14,18 @@ import { renderShort } from "../shorts/render.js";
|
|
|
11
14
|
import { allocateAsset, discardPreparedAssets, sealAsset, writeAsset } from "../storage/assets.js";
|
|
12
15
|
import { outputPath, projectDir } from "../storage/layout.js";
|
|
13
16
|
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
17
|
+
import { usesVoices } from "../voices/model.js";
|
|
14
18
|
import { sentencesText, transcriptSentences } from "../youtube/transcript.js";
|
|
15
19
|
import { shortsPickKey } from "./recipe-shorts.js";
|
|
20
|
+
import { figureShots } from "./runtime-export-edit.js";
|
|
16
21
|
import { exportSnapshot, revisionAudio } from "./runtime-export-inputs.js";
|
|
22
|
+
import { imageCall } from "./runtime-image.js";
|
|
17
23
|
import { savedCatalogue } from "./runtime-plan.js";
|
|
18
24
|
import { preparedResult, preparedTexts, publishResult } from "./runtime-publication.js";
|
|
19
25
|
import { wordsSchema } from "./runtime-subtitles.js";
|
|
20
26
|
// The Shorts step's four kinds of work (`recipe-shorts.ts`): the pick, each clip's image
|
|
21
27
|
// prompts, each image, and each clip's render. Each is one piece of its own, so a failure
|
|
22
|
-
// names the clip it belongs to and
|
|
28
|
+
// names the clip it belongs to and Try again redoes only what did not finish.
|
|
23
29
|
export async function executeShortsRecipe(deps, context, providers, piece) {
|
|
24
30
|
if (deps.db
|
|
25
31
|
.prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
@@ -39,7 +45,7 @@ export async function executeShortsRecipe(deps, context, providers, piece) {
|
|
|
39
45
|
throw setupError();
|
|
40
46
|
const clip = savedClips(snapshot).find((one) => one.number === number);
|
|
41
47
|
if (clip === undefined)
|
|
42
|
-
throw new Error("The clip this short was cut from is no longer among the picked shorts. Use Pick different moments in Edit project → Shorts, then
|
|
48
|
+
throw new Error("The clip this short was cut from is no longer among the picked shorts. Use Pick different moments in Edit project → Shorts, then Try again.");
|
|
43
49
|
if (matched[2] === "prompts")
|
|
44
50
|
return prompts(deps, context, providers, piece, snapshot, clip);
|
|
45
51
|
if (matched[2] === "render")
|
|
@@ -57,10 +63,10 @@ async function pick(deps, context, providers, piece, snapshot) {
|
|
|
57
63
|
throw setupError();
|
|
58
64
|
const llm = config.llm;
|
|
59
65
|
if (llm === undefined || llm.provider.trim() === "" || llm.model.trim() === "")
|
|
60
|
-
throw new Error("Shorts need an AI text model to pick the clips, and none is chosen. Choose one in Edit project → Providers, then
|
|
66
|
+
throw new Error("Shorts need an AI text model to pick the clips, and none is chosen. Choose one in Edit project → Providers, then Try again.");
|
|
61
67
|
const sentences = transcriptSentences(timingWords(deps, context, view));
|
|
62
68
|
if (sentences.length === 0)
|
|
63
|
-
throw new Error("The narration's word timing holds no words, so there is nothing to cut shorts from. Use
|
|
69
|
+
throw new Error("The narration's word timing holds no words, so there is nothing to cut shorts from. Use More → Render the video again in the Video section, then Try again.");
|
|
64
70
|
const audio = await revisionAudio(deps, context, view);
|
|
65
71
|
const durationSeconds = audio.reduce((sum, segment) => sum + segment.seconds, 0);
|
|
66
72
|
const limits = {
|
|
@@ -80,13 +86,17 @@ async function pick(deps, context, providers, piece, snapshot) {
|
|
|
80
86
|
count: shorts.count,
|
|
81
87
|
minSeconds: shorts.minSeconds,
|
|
82
88
|
maxSeconds: shorts.maxSeconds,
|
|
83
|
-
sentences: sentencesText(sentences)
|
|
89
|
+
sentences: sentencesText(sentences, usesVoices(config) && config.voices !== undefined
|
|
90
|
+
? Object.fromEntries(config.voices.speakers.map((speaker) => [speaker.id, speaker.name.trim()]))
|
|
91
|
+
: undefined),
|
|
84
92
|
};
|
|
85
93
|
const ask = llmCall(deps, context, piece, view, (text) => {
|
|
86
94
|
const checked = checkPicks(text, sentences, limits);
|
|
87
95
|
return checked.ok ? undefined : checked.reason;
|
|
88
96
|
});
|
|
89
|
-
const first = await providers
|
|
97
|
+
const first = await providers
|
|
98
|
+
.forPiece(piece.id)
|
|
99
|
+
.llm(ask(withLanguage(pickMessages(brief), config.language)));
|
|
90
100
|
if (!first.ok)
|
|
91
101
|
return "held";
|
|
92
102
|
let checked = checkPicks(first.value.text, sentences, limits);
|
|
@@ -99,7 +109,7 @@ async function pick(deps, context, providers, piece, snapshot) {
|
|
|
99
109
|
return "held";
|
|
100
110
|
const second = await providers
|
|
101
111
|
.forPiece(piece.id)
|
|
102
|
-
.llm(ask(pickRetryMessages(brief, first.value.text, checked.problems)));
|
|
112
|
+
.llm(ask(withLanguage(pickRetryMessages(brief, first.value.text, checked.problems), config.language)));
|
|
103
113
|
if (!second.ok)
|
|
104
114
|
return "held";
|
|
105
115
|
const again = checkPicks(second.value.text, sentences, limits);
|
|
@@ -163,12 +173,9 @@ async function image(deps, context, providers, piece, clip, index) {
|
|
|
163
173
|
const input = piece.input;
|
|
164
174
|
if (input.kind !== "image")
|
|
165
175
|
throw setupError();
|
|
166
|
-
const made = await providers
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
prompt: input.prompt,
|
|
170
|
-
aspect: input.aspect,
|
|
171
|
-
});
|
|
176
|
+
const made = await providers
|
|
177
|
+
.forPiece(piece.id)
|
|
178
|
+
.image(imageCall(deps, context.work.projectId, input, `Short ${String(clip.number)} image ${String(index)}`));
|
|
172
179
|
if (!made.ok)
|
|
173
180
|
return "held";
|
|
174
181
|
const asset = writeAsset(deps, context.work.projectId, made.value.mime === "image/jpeg" ? "short-image.jpg" : "short-image.png", made.value.bytes);
|
|
@@ -193,13 +200,27 @@ async function render(deps, context, piece, snapshot, clip) {
|
|
|
193
200
|
.map((recipe) => {
|
|
194
201
|
const row = view.outputs.find((one) => one.workKey === recipe.key && one.selected && one.available && one.state === "ready");
|
|
195
202
|
if (row === undefined)
|
|
196
|
-
throw new Error(`Short ${String(clip.number)} is missing one of its images.
|
|
203
|
+
throw new Error(`Short ${String(clip.number)} is missing one of its images. Try again to make it again; if it keeps happening, use Make this short again in Edit project → Shorts.`);
|
|
197
204
|
return outputPath(deps.paths, context.work.projectId, row.output.path);
|
|
198
205
|
});
|
|
199
|
-
const
|
|
200
|
-
const
|
|
206
|
+
const allWords = timingWords(deps, context, view);
|
|
207
|
+
const words = clipWords(allWords, clip.start, clip.end);
|
|
208
|
+
// The upright cards whose descriptions fall in the clip, on the clip's own timeline.
|
|
209
|
+
const figures = usesFigureCards(config)
|
|
210
|
+
? figureShots(deps, context, view, "9:16", allWords).flatMap((figure) => figure.end <= clip.start || figure.start >= clip.end
|
|
211
|
+
? []
|
|
212
|
+
: [
|
|
213
|
+
{
|
|
214
|
+
path: figure.path,
|
|
215
|
+
start: Math.max(0, figure.start - clip.start),
|
|
216
|
+
end: Math.min(clip.end, figure.end) - clip.start,
|
|
217
|
+
},
|
|
218
|
+
])
|
|
219
|
+
: [];
|
|
220
|
+
const timeline = await revisionAudio(deps, context, view, { levelled: true });
|
|
201
221
|
// The bundled Barlow is drawn in its own Bold face; any other font is emboldened.
|
|
202
|
-
|
|
222
|
+
// In another language, a font that has its letters (`fonts/coverage.ts`).
|
|
223
|
+
const font = await captionBoldFont(deps.paths, config.subtitles?.fontId ?? "default", config.language);
|
|
203
224
|
const shorts = config.shorts;
|
|
204
225
|
const speed = shorts === undefined ? 1 : shortsSpeedOf(shorts);
|
|
205
226
|
const music = await backgroundMusic(deps, context, view);
|
|
@@ -209,8 +230,9 @@ async function render(deps, context, piece, snapshot, clip) {
|
|
|
209
230
|
const prepared = [];
|
|
210
231
|
const seconds = (clip.end - clip.start) / speed;
|
|
211
232
|
try {
|
|
212
|
-
await renderShort({
|
|
233
|
+
const master = await renderShort({
|
|
213
234
|
bin: deps.ffmpeg,
|
|
235
|
+
master: masterGoal(config, "video"),
|
|
214
236
|
timeline,
|
|
215
237
|
start: clip.start,
|
|
216
238
|
end: clip.end,
|
|
@@ -219,8 +241,10 @@ async function render(deps, context, piece, snapshot, clip) {
|
|
|
219
241
|
motionStyle: config.motionStyle,
|
|
220
242
|
zoomPercent: config.zoomPercent,
|
|
221
243
|
words,
|
|
244
|
+
...(wordTimingUnavailable(config.language) === undefined ? {} : { wordByWord: false }),
|
|
222
245
|
font,
|
|
223
246
|
...(shorts?.titleOnScreen === true ? { title: clip.title } : {}),
|
|
247
|
+
...(figures.length === 0 ? {} : { figures }),
|
|
224
248
|
speed,
|
|
225
249
|
music,
|
|
226
250
|
output: pending.absolutePath,
|
|
@@ -241,6 +265,7 @@ async function render(deps, context, piece, snapshot, clip) {
|
|
|
241
265
|
prepared.push(preparedResult(deps, context, piece, "short_video", asset, durationMs, {
|
|
242
266
|
short: clip.number,
|
|
243
267
|
sentences: [clip.first, clip.last],
|
|
268
|
+
...(master === undefined ? {} : { master }),
|
|
244
269
|
}));
|
|
245
270
|
await publishResult(deps, context, piece, prepared, { short: clip.number, start: clip.start, end: clip.end, durationMs }, asset);
|
|
246
271
|
// One per rendered short, with no stage: stage "video" would read as a finished video.
|
|
@@ -273,7 +298,7 @@ async function backgroundMusic(deps, context, view) {
|
|
|
273
298
|
const shorts = view.revision.config.shorts;
|
|
274
299
|
if (assetId === undefined || shorts === undefined)
|
|
275
300
|
return undefined;
|
|
276
|
-
const fix = "Choose another file under Background music in Edit project → Shorts, or remove the music, then
|
|
301
|
+
const fix = "Choose another file under Background music in Edit project → Shorts, or remove the music, then Try again.";
|
|
277
302
|
const row = z
|
|
278
303
|
.object({ path: z.string() })
|
|
279
304
|
.safeParse(deps.db
|
|
@@ -294,14 +319,14 @@ async function backgroundMusic(deps, context, view) {
|
|
|
294
319
|
throw new Error(`The shorts' background music file holds no sound. ${fix}`);
|
|
295
320
|
return { path, volume: musicVolumeOf(shorts) };
|
|
296
321
|
}
|
|
297
|
-
function timingWords(deps, context, view) {
|
|
322
|
+
export function timingWords(deps, context, view) {
|
|
298
323
|
const timing = view.outputs.find((row) => row.selected &&
|
|
299
324
|
row.available &&
|
|
300
325
|
row.state === "ready" &&
|
|
301
326
|
row.workKey === "subtitles:timing" &&
|
|
302
327
|
row.output.role === "subtitle_words");
|
|
303
328
|
if (timing === undefined)
|
|
304
|
-
throw new Error("The narration's word timing, which the shorts are cut and captioned from, is missing. Use
|
|
329
|
+
throw new Error("The narration's word timing, which the shorts are cut and captioned from, is missing. Use More → Render the video again in the Video section, then Try again.");
|
|
305
330
|
return wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8"))).words;
|
|
306
331
|
}
|
|
307
332
|
// The project's text model, with the thinking settings saved for this step's catalogue.
|
|
@@ -326,5 +351,5 @@ function imageIndex(key) {
|
|
|
326
351
|
return Number(key.split(":").at(-1));
|
|
327
352
|
}
|
|
328
353
|
function setupError() {
|
|
329
|
-
return new Error("Slopify hit an internal error (a Shorts step was set up wrongly).
|
|
354
|
+
return new Error("Slopify hit an internal error (a Shorts step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
330
355
|
}
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { stageKinds, stageStates } from "../../kernel/pipeline.js";
|
|
3
3
|
import { sourceOf } from "../admission/model.js";
|
|
4
4
|
import { usesShorts, usesYoutubeDescription } from "../admission/rules.js";
|
|
5
|
+
import { reviewsNarration } from "../reviews/rules.js";
|
|
5
6
|
import { currentRevisionId } from "../revisions/repo.js";
|
|
6
7
|
import { getRevisionView } from "../revisions/view.js";
|
|
7
8
|
import { settleScheduleRunsForProject } from "../schedules/repo.js";
|
|
@@ -40,7 +41,8 @@ export function executionStages(deps, projectId) {
|
|
|
40
41
|
view?.revision.content.subtitleCues !== undefined &&
|
|
41
42
|
!usesYoutubeDescription(view.revision.config) &&
|
|
42
43
|
!usesShorts(view.revision.config) &&
|
|
43
|
-
!editNeedsTiming(view.revision.config)
|
|
44
|
+
!editNeedsTiming(view.revision.config) &&
|
|
45
|
+
!reviewsNarration(view.revision.config))
|
|
44
46
|
return false;
|
|
45
47
|
if (piece.input.kind === "deferred" && piece.key.endsWith(":future")) {
|
|
46
48
|
const cacheKey = String(row.id);
|
|
@@ -78,10 +80,13 @@ export function executionStages(deps, projectId) {
|
|
|
78
80
|
}
|
|
79
81
|
export function invocationReady(deps, work) {
|
|
80
82
|
const row = deps.db
|
|
81
|
-
.prepare("SELECT recipe_context FROM revision_work WHERE id=? AND project_id=? AND revision_id=?")
|
|
83
|
+
.prepare("SELECT recipe_context,retry_at FROM revision_work WHERE id=? AND project_id=? AND revision_id=?")
|
|
82
84
|
.get(work.workId, work.projectId, work.revisionId);
|
|
83
85
|
if (row?.recipe_context === null || row === undefined)
|
|
84
86
|
return false;
|
|
87
|
+
// Waiting out a failure time can fix: `dueRetries` clears the wait when it runs out.
|
|
88
|
+
if (row.retry_at !== null)
|
|
89
|
+
return false;
|
|
85
90
|
const view = executionView(deps, work.projectId, work.revisionId);
|
|
86
91
|
if (view === undefined)
|
|
87
92
|
return false;
|
|
@@ -129,6 +134,9 @@ export function executionStandings(deps, projectId, derived) {
|
|
|
129
134
|
return retained ? { ...entry, state: "done" } : entry;
|
|
130
135
|
});
|
|
131
136
|
let state = source === "off" ? "skipped" : source === "provide" ? "provided" : "pending";
|
|
137
|
+
const retryAt = group.some((entry) => entry.state === "failed")
|
|
138
|
+
? null
|
|
139
|
+
: latestRetry(deps, group);
|
|
132
140
|
if (group.some((entry) => entry.state === "running"))
|
|
133
141
|
state = "running";
|
|
134
142
|
else if (group.some((entry) => entry.state === "failed"))
|
|
@@ -141,9 +149,23 @@ export function executionStandings(deps, projectId, derived) {
|
|
|
141
149
|
? "provided"
|
|
142
150
|
: "done"
|
|
143
151
|
: "pending";
|
|
144
|
-
return { kind, state };
|
|
152
|
+
return state === "pending" && retryAt !== null ? { kind, state, retryAt } : { kind, state };
|
|
145
153
|
});
|
|
146
154
|
}
|
|
155
|
+
// The latest wait among a stage's steps that went back to wait after a failure time can fix.
|
|
156
|
+
function latestRetry(deps, group) {
|
|
157
|
+
let latest = null;
|
|
158
|
+
for (const entry of group) {
|
|
159
|
+
if (entry.state !== "pending")
|
|
160
|
+
continue;
|
|
161
|
+
const at = deps.db
|
|
162
|
+
.prepare("SELECT retry_at FROM revision_work WHERE id=?")
|
|
163
|
+
.get(entry.work.workId)?.retry_at;
|
|
164
|
+
if (typeof at === "string" && (latest === null || at > latest))
|
|
165
|
+
latest = at;
|
|
166
|
+
}
|
|
167
|
+
return latest;
|
|
168
|
+
}
|
|
147
169
|
export function projectStandings(deps, projectId) {
|
|
148
170
|
// One derivation serves every stage: each builds the whole execution plan.
|
|
149
171
|
const work = executionStages(deps, projectId);
|
|
@@ -159,15 +181,19 @@ export function projectStandings(deps, projectId) {
|
|
|
159
181
|
.get(entry.work.workId);
|
|
160
182
|
return total + fraction(progress?.progress_current, progress?.progress_total);
|
|
161
183
|
}, 0);
|
|
162
|
-
|
|
184
|
+
// A failed step, or else one waiting to run again, says why on the stage row.
|
|
185
|
+
const failed = all.find((work) => work.state === "failed") ??
|
|
186
|
+
(stage.retryAt === undefined || stage.retryAt === null
|
|
187
|
+
? undefined
|
|
188
|
+
: all.find((work) => work.state === "pending"));
|
|
163
189
|
const failure = failed === undefined
|
|
164
|
-
?
|
|
165
|
-
:
|
|
166
|
-
.prepare("SELECT failure_reason FROM revision_work WHERE id=?")
|
|
167
|
-
.get(failed.work.workId)
|
|
190
|
+
? undefined
|
|
191
|
+
: deps.db
|
|
192
|
+
.prepare("SELECT failure_reason,failure_kind FROM revision_work WHERE id=?")
|
|
193
|
+
.get(failed.work.workId);
|
|
168
194
|
deps.db
|
|
169
|
-
.prepare("UPDATE stages SET state=?,failure_reason=?,progress_current=?,progress_total=?,finished_at=CASE WHEN ?='done' THEN ? ELSE NULL END WHERE project_id=? AND kind=?")
|
|
170
|
-
.run(stage.state, failure, invocations.length === 0 ? null : current, invocations.length === 0 ? null : invocations.length, stage.state, deps.clock.now().toISOString(), projectId, stage.kind);
|
|
195
|
+
.prepare("UPDATE stages SET state=?,failure_reason=?,failure_kind=?,retry_at=?,progress_current=?,progress_total=?,finished_at=CASE WHEN ?='done' THEN ? ELSE NULL END WHERE project_id=? AND kind=?")
|
|
196
|
+
.run(stage.state, textOf(failure?.failure_reason), textOf(failure?.failure_kind), stage.retryAt ?? null, invocations.length === 0 ? null : current, invocations.length === 0 ? null : invocations.length, stage.state, deps.clock.now().toISOString(), projectId, stage.kind);
|
|
171
197
|
}
|
|
172
198
|
settleScheduleRunsForProject(deps.db, projectId, deps.clock.now().toISOString());
|
|
173
199
|
}
|
|
@@ -225,6 +251,9 @@ const spokenKey = /^audio:(?:intro|body|outro)(?::(?!concat$|future$).+)?$/;
|
|
|
225
251
|
function spoken(deps, workId) {
|
|
226
252
|
return workPieces(deps.db, workId).some((piece) => spokenKey.test(piece.key));
|
|
227
253
|
}
|
|
254
|
+
function textOf(value) {
|
|
255
|
+
return typeof value === "string" ? value : null;
|
|
256
|
+
}
|
|
228
257
|
function fraction(current, total) {
|
|
229
258
|
if (typeof current !== "number" || typeof total !== "number" || total <= 0)
|
|
230
259
|
return 0;
|