@gentbajko/slopify 3.25.9 → 4.1.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/dist/adapters/account-limits.js +30 -0
- package/dist/adapters/alignment/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +4 -6
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +2 -4
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +3 -2
- package/dist/adapters/explain.js +10 -12
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +21 -4
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +42 -9
- package/dist/adapters/image/fal.js +21 -53
- package/dist/adapters/image/google.js +33 -56
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +22 -48
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +21 -49
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +54 -3
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +11 -6
- package/dist/adapters/llm/assistant-cli.js +388 -0
- package/dist/adapters/llm/child-session.js +83 -0
- package/dist/adapters/llm/claude-code-models.js +52 -99
- package/dist/adapters/llm/claude-code.js +16 -4
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +12 -48
- package/dist/adapters/llm/run-cli.js +9 -24
- package/dist/adapters/llm/sse-lines.js +5 -1
- package/dist/adapters/model-access.js +37 -0
- package/dist/adapters/tts/cartesia.js +15 -46
- package/dist/adapters/tts/elevenlabs.js +20 -52
- package/dist/adapters/tts/gemini.js +21 -41
- package/dist/adapters/tts/inworld-async.js +6 -2
- package/dist/adapters/tts/inworld.js +6 -2
- package/dist/adapters/tts/openai.js +20 -50
- package/dist/adapters/tts/system.js +2 -1
- package/dist/assets/models-lifecycle.yaml +6 -0
- package/dist/assistant-wiring.js +60 -0
- package/dist/autopilot-timers.js +59 -0
- package/dist/catalog/lifecycle.js +52 -0
- package/dist/catalog/propose.js +204 -0
- package/dist/catalog/registry.js +23 -14
- package/dist/catalog/schema.js +4 -1
- package/dist/catalog/store.js +39 -0
- package/dist/catalog/validate.js +5 -2
- package/dist/edge/autostart/native.js +3 -5
- package/dist/edge/autostart/prompt.js +2 -1
- package/dist/edge/autostart/service.js +3 -4
- package/dist/edge/cli-args.js +3 -4
- package/dist/edge/cli.js +2 -1
- package/dist/edge/docker-install/apply.js +11 -11
- package/dist/edge/docker-install/run.js +2 -1
- package/dist/edge/docker-install/state.js +4 -3
- package/dist/edge/docker-install/tree.js +2 -4
- package/dist/edge/docker-install/volume.js +3 -2
- package/dist/edge/docker.js +5 -4
- package/dist/edge/events/hub.js +5 -5
- package/dist/edge/http/actions.js +11 -32
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +86 -11
- package/dist/edge/http/assistant.js +129 -0
- package/dist/edge/http/audio-preview.js +4 -11
- package/dist/edge/http/auditions.js +1 -1
- package/dist/edge/http/autostart.js +5 -5
- package/dist/edge/http/backups.js +2 -4
- package/dist/edge/http/channel-memory.js +9 -22
- package/dist/edge/http/channels.js +14 -27
- package/dist/edge/http/checkpoints.js +39 -21
- package/dist/edge/http/design-templates.js +167 -0
- package/dist/edge/http/designs.js +178 -0
- package/dist/edge/http/diagnostics.js +5 -1
- package/dist/edge/http/document-themes.js +5 -14
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +5 -10
- package/dist/edge/http/drafts.js +3 -3
- package/dist/edge/http/entries.js +6 -9
- package/dist/edge/http/extension-auth.js +27 -0
- package/dist/edge/http/external-reviews.js +12 -11
- package/dist/edge/http/files.js +5 -11
- package/dist/edge/http/folder-location.js +1 -4
- package/dist/edge/http/fonts.js +0 -7
- package/dist/edge/http/home.js +47 -14
- package/dist/edge/http/host-cli.js +18 -8
- package/dist/edge/http/ids.js +8 -0
- package/dist/edge/http/keywords.js +35 -0
- package/dist/edge/http/model-checks.js +132 -0
- package/dist/edge/http/narration-peaks.js +5 -13
- package/dist/edge/http/onboarding.js +30 -26
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +7 -14
- package/dist/edge/http/patch-notes.js +6 -12
- package/dist/edge/http/planning.js +3 -7
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +2 -1
- package/dist/edge/http/project-templates.js +31 -19
- package/dist/edge/http/projects.js +24 -16
- package/dist/edge/http/prompts.js +9 -16
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +48 -34
- package/dist/edge/http/refusal.js +29 -0
- package/dist/edge/http/remote-access.js +40 -0
- package/dist/edge/http/remote-guard.js +71 -0
- package/dist/edge/http/request-guard.js +91 -0
- package/dist/edge/http/reviews.js +12 -25
- package/dist/edge/http/revision-files.js +6 -11
- package/dist/edge/http/revisions.js +14 -19
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +69 -40
- package/dist/edge/http/settings.js +27 -14
- package/dist/edge/http/social.js +283 -0
- package/dist/edge/http/staging.js +3 -5
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +33 -25
- package/dist/edge/http/studio.js +94 -69
- package/dist/edge/http/style-preview.js +3 -6
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +436 -0
- package/dist/edge/http/trash.js +11 -12
- package/dist/edge/http/tutorial.js +1 -2
- package/dist/edge/http/tutorials.js +5 -11
- package/dist/edge/http/update.js +7 -13
- package/dist/edge/http/waveform.js +3 -6
- package/dist/edge/http/youtube-edits.js +3 -4
- package/dist/edge/mcp/assistant.js +279 -0
- package/dist/edge/mcp/attribution.js +8 -2
- package/dist/edge/mcp/call-route.js +11 -2
- package/dist/edge/mcp/excluded-routes.js +29 -3
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +6 -4
- package/dist/edge/mcp/library-tools.js +9 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +11 -9
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +19 -7
- package/dist/edge/mcp/tools-planning.js +40 -21
- package/dist/edge/mcp/tools-projects.js +38 -11
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +23 -10
- package/dist/edge/mcp/tools-schedules.js +32 -8
- package/dist/edge/mcp/tools-setup.js +75 -11
- package/dist/edge/mcp/tools-social.js +47 -0
- package/dist/edge/mcp/tools-storage.js +11 -3
- package/dist/edge/mcp/tools-template-studio.js +166 -0
- package/dist/edge/mcp/tools.js +6 -0
- package/dist/edge/remote-access/cli.js +71 -0
- package/dist/edge/remote-access/model.js +32 -0
- package/dist/edge/remote-access/rules.js +207 -0
- package/dist/edge/remote-access/service.js +203 -0
- package/dist/edge/remote-access/store.js +44 -0
- package/dist/edge/remote-access/views.js +43 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/install.js +2 -2
- package/dist/host-cli/open-folder.js +2 -2
- package/dist/host-cli/paths.js +2 -4
- package/dist/host-cli/server.js +4 -4
- package/dist/host-cli/service.js +3 -3
- package/dist/host-cli/status.js +7 -7
- package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
- package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
- package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
- package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
- package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
- package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
- package/dist/kernel/db/migrations/0070-designs.sql +53 -0
- package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
- package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
- package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
- package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
- package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
- package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
- package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
- package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
- package/dist/kernel/db/tx.js +2 -4
- package/dist/kernel/errors.js +27 -1
- package/dist/kernel/json-answer.js +15 -0
- package/dist/kernel/lock.js +4 -9
- package/dist/kernel/paths.js +8 -0
- package/dist/kernel/pipeline.js +12 -0
- package/dist/kernel/ports/assistant-cli.js +4 -0
- package/dist/kernel/ports/host-cli.js +3 -0
- package/dist/kernel/ports/interest.js +4 -0
- package/dist/kernel/ports/languages.js +0 -3
- package/dist/kernel/ports/model.js +2 -0
- package/dist/kernel/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +14 -4
- package/dist/kernel/runner/audition.js +3 -1
- package/dist/kernel/runner/checkpoint-authority.js +2 -1
- package/dist/kernel/runner/index.js +8 -10
- package/dist/kernel/runner/model-access.js +40 -0
- package/dist/kernel/runner/model-check.js +153 -0
- package/dist/kernel/runner/providers.js +18 -12
- package/dist/kernel/runner/standalone.js +11 -7
- package/dist/main.js +338 -21
- package/dist/model-catalog.js +20 -0
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +11 -7
- package/dist/slices/admission/rules.js +50 -15
- package/dist/slices/admission/schema.js +8 -0
- package/dist/slices/admission/stage-writes.js +0 -3
- package/dist/slices/admission/start.js +48 -31
- package/dist/slices/admission/state-words.js +34 -5
- package/dist/slices/admission/substitute.js +7 -4
- package/dist/slices/agents/attention-posts.js +31 -0
- package/dist/slices/agents/attention-templates.js +33 -0
- package/dist/slices/agents/attention.js +10 -1
- package/dist/slices/agents/connection.js +2 -6
- package/dist/slices/agents/model.js +6 -3
- package/dist/slices/agents/repo.js +15 -7
- package/dist/slices/article/continuation.js +0 -63
- package/dist/slices/article/plain.js +3 -2
- package/dist/slices/article/segments.js +2 -62
- package/dist/slices/assistant/access.js +1 -0
- package/dist/slices/assistant/asks.js +80 -0
- package/dist/slices/assistant/cost.js +54 -0
- package/dist/slices/assistant/gate.js +222 -0
- package/dist/slices/assistant/model.js +86 -0
- package/dist/slices/assistant/names.js +23 -0
- package/dist/slices/assistant/prompt.js +74 -0
- package/dist/slices/assistant/repo.js +74 -0
- package/dist/slices/assistant/tokens.js +18 -0
- package/dist/slices/assistant/turn.js +344 -0
- package/dist/slices/autopilot/buffer.js +76 -0
- package/dist/slices/autopilot/model.js +14 -0
- package/dist/slices/autopilot/notify.js +27 -0
- package/dist/slices/autopilot/repo.js +34 -0
- package/dist/slices/autopilot/signals.js +194 -0
- package/dist/slices/autopilot/warnings.js +85 -0
- package/dist/slices/autopilot/weekly.js +100 -0
- package/dist/slices/backups/files.js +2 -6
- package/dist/slices/backups/repo.js +3 -15
- package/dist/slices/backups/service.js +8 -7
- package/dist/slices/channels/cast-images.js +3 -2
- package/dist/slices/channels/model.js +0 -1
- package/dist/slices/channels/rebrand.js +0 -3
- package/dist/slices/channels/repo.js +4 -0
- package/dist/slices/channels/schema.js +7 -1
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +3 -15
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +2 -28
- package/dist/slices/designs/artwork.js +66 -0
- package/dist/slices/designs/budget.js +23 -0
- package/dist/slices/designs/calls.js +124 -0
- package/dist/slices/designs/check-context.js +38 -0
- package/dist/slices/designs/check-text.js +43 -0
- package/dist/slices/designs/fields.js +88 -0
- package/dist/slices/designs/frames.js +34 -0
- package/dist/slices/designs/gate.js +130 -0
- package/dist/slices/designs/layout.js +293 -0
- package/dist/slices/designs/model.js +35 -0
- package/dist/slices/designs/pdf-job.js +23 -0
- package/dist/slices/designs/pdf-pages.js +30 -0
- package/dist/slices/designs/pdf-runner.js +68 -0
- package/dist/slices/designs/pdf-worker.js +24 -0
- package/dist/slices/designs/picture-size.js +79 -0
- package/dist/slices/designs/pictures.js +55 -0
- package/dist/slices/designs/quote.js +24 -0
- package/dist/slices/designs/render-args.js +36 -0
- package/dist/slices/designs/render-input.js +59 -0
- package/dist/slices/designs/render.js +215 -0
- package/dist/slices/designs/repo.js +101 -0
- package/dist/slices/designs/requests.js +80 -0
- package/dist/slices/designs/runner.js +239 -0
- package/dist/slices/designs/sample-repo.js +91 -0
- package/dist/slices/designs/sample.js +223 -0
- package/dist/slices/designs/schema.js +87 -0
- package/dist/slices/designs/sections.js +44 -0
- package/dist/slices/designs/service.js +233 -0
- package/dist/slices/designs/steps-repo.js +102 -0
- package/dist/slices/designs/studio-kind.js +39 -0
- package/dist/slices/designs/template-checks.js +44 -0
- package/dist/slices/designs/template-make.js +118 -0
- package/dist/slices/designs/templates.js +218 -0
- package/dist/slices/document/fonts.js +2 -1
- package/dist/slices/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +6 -2
- package/dist/slices/estimate/index.js +72 -28
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +5 -3
- package/dist/slices/fonts/coverage.js +2 -1
- package/dist/slices/fonts/discovery.js +3 -2
- package/dist/slices/fonts/files.js +2 -6
- package/dist/slices/fonts/index.js +0 -1
- package/dist/slices/fonts/sfnt.js +45 -10
- package/dist/slices/images/appearance.js +8 -16
- package/dist/slices/images/scenes.js +102 -28
- package/dist/slices/keywords/digest.js +85 -0
- package/dist/slices/keywords/facts.js +92 -0
- package/dist/slices/keywords/fill.js +80 -0
- package/dist/slices/keywords/model.js +75 -0
- package/dist/slices/keywords/rules.js +187 -0
- package/dist/slices/keywords/schema.js +28 -0
- package/dist/slices/keywords/specs.js +260 -0
- package/dist/slices/keywords/values.js +74 -0
- package/dist/slices/library/lint.js +29 -12
- package/dist/slices/library/model.js +2 -0
- package/dist/slices/library/photorealistic.js +4 -12
- package/dist/slices/library/rename.js +9 -7
- package/dist/slices/library/save.js +14 -10
- package/dist/slices/library/slots.js +37 -11
- package/dist/slices/library/transfer.js +5 -4
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +53 -12
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- package/dist/slices/model-checks/cost.js +61 -0
- package/dist/slices/model-checks/listing.js +124 -0
- package/dist/slices/model-checks/model.js +20 -0
- package/dist/slices/model-checks/record.js +32 -0
- package/dist/slices/model-checks/repo.js +120 -0
- package/dist/slices/model-checks/rules.js +125 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +72 -27
- package/dist/slices/model-upkeep/usage.js +39 -13
- package/dist/slices/narration/breaths.js +3 -1
- package/dist/slices/narration/clean-pieces.js +3 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +6 -4
- package/dist/slices/narration/steering.js +4 -3
- package/dist/slices/notifications/notifier.js +4 -3
- package/dist/slices/notifications/rules.js +5 -2
- package/dist/slices/notifications/settings.js +4 -12
- package/dist/slices/notifications/subject.js +4 -3
- package/dist/slices/onboarding/copy.js +21 -3
- package/dist/slices/onboarding/first-run.js +16 -40
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +6 -4
- package/dist/slices/onboarding/model.js +26 -37
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +105 -65
- package/dist/slices/onboarding/sample.js +4 -2
- package/dist/slices/onboarding/state.js +9 -11
- package/dist/slices/onboarding/template.js +8 -6
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +4 -3
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +9 -3
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +14 -1
- package/dist/slices/project-templates/stand-ins.js +22 -0
- package/dist/slices/rebuild/activity.js +2 -0
- package/dist/slices/rebuild/admission-repo.js +3 -2
- package/dist/slices/rebuild/narration-history.js +4 -5
- package/dist/slices/rebuild/narration-retry.js +2 -1
- package/dist/slices/rebuild/preview-details.js +32 -14
- package/dist/slices/rebuild/preview-plan.js +3 -8
- package/dist/slices/rebuild/recipe-build.js +8 -3
- package/dist/slices/rebuild/recipe-describe.js +3 -3
- package/dist/slices/rebuild/recipe-digest.js +58 -0
- package/dist/slices/rebuild/recipe-input-schema.js +10 -0
- package/dist/slices/rebuild/recipe-keywords.js +71 -0
- package/dist/slices/rebuild/recipe-legacy.js +5 -1
- package/dist/slices/rebuild/recipe-model.js +4 -0
- package/dist/slices/rebuild/recipe-narration-text.js +2 -1
- package/dist/slices/rebuild/recipe-preparation.js +4 -4
- package/dist/slices/rebuild/recipe-reference.js +8 -4
- package/dist/slices/rebuild/recipe-reviews.js +27 -7
- package/dist/slices/rebuild/recipe-save.js +8 -5
- package/dist/slices/rebuild/recipe-scenes.js +40 -11
- package/dist/slices/rebuild/recipe-shorts.js +8 -8
- package/dist/slices/rebuild/recipe-social.js +46 -0
- package/dist/slices/rebuild/recipe-text.js +64 -20
- package/dist/slices/rebuild/recipe-validation.js +102 -31
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +19 -7
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +9 -8
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +3 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -3
- package/dist/slices/rebuild/runtime-agent-review.js +11 -5
- package/dist/slices/rebuild/runtime-animate.js +10 -9
- package/dist/slices/rebuild/runtime-article.js +7 -5
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +11 -13
- package/dist/slices/rebuild/runtime-export-bed.js +8 -12
- package/dist/slices/rebuild/runtime-export-edit.js +8 -16
- package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
- package/dist/slices/rebuild/runtime-export-short.js +9 -5
- package/dist/slices/rebuild/runtime-export.js +38 -15
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +8 -12
- package/dist/slices/rebuild/runtime-instructions.js +2 -0
- package/dist/slices/rebuild/runtime-keywords.js +59 -0
- package/dist/slices/rebuild/runtime-lines.js +2 -11
- package/dist/slices/rebuild/runtime-local.js +17 -22
- package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
- package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
- package/dist/slices/rebuild/runtime-narration-text.js +13 -17
- package/dist/slices/rebuild/runtime-pictures.js +2 -4
- package/dist/slices/rebuild/runtime-piece-label.js +10 -6
- package/dist/slices/rebuild/runtime-plan.js +31 -5
- package/dist/slices/rebuild/runtime-provider.js +67 -25
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +39 -33
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +31 -50
- package/dist/slices/rebuild/runtime-store.js +20 -10
- package/dist/slices/rebuild/runtime-subtitles.js +28 -25
- package/dist/slices/rebuild/runtime-voices.js +10 -16
- package/dist/slices/rebuild/runtime-youtube.js +14 -31
- package/dist/slices/rebuild/service-readiness.js +11 -3
- package/dist/slices/rebuild/transition-repo.js +6 -5
- package/dist/slices/rebuild/work-records.js +6 -0
- package/dist/slices/reruns/cascade.js +1 -2
- package/dist/slices/reruns/index.js +6 -9
- package/dist/slices/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +4 -1
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +32 -11
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +4 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +9 -5
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +3 -3
- package/dist/slices/revisions/repo.js +3 -1
- package/dist/slices/revisions/schema.js +1 -0
- package/dist/slices/revisions/view.js +20 -0
- package/dist/slices/run-cost/limits.js +5 -2
- package/dist/slices/run-cost/meter.js +12 -5
- package/dist/slices/run-cost/panel.js +8 -74
- package/dist/slices/run-cost/plan-readings.js +127 -0
- package/dist/slices/run-cost/plan-use.js +169 -0
- package/dist/slices/run-cost/pricing.js +3 -3
- package/dist/slices/run-cost/week.js +32 -4
- package/dist/slices/schedules/agenda.js +18 -7
- package/dist/slices/schedules/by-date.js +42 -0
- package/dist/slices/schedules/calendar.js +5 -0
- package/dist/slices/schedules/design-agenda.js +36 -0
- package/dist/slices/schedules/design-check.js +41 -0
- package/dist/slices/schedules/design-run.js +126 -0
- package/dist/slices/schedules/next-run.js +62 -0
- package/dist/slices/schedules/prepare.js +18 -2
- package/dist/slices/schedules/repo.js +30 -21
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +57 -3
- package/dist/slices/schedules/score.js +107 -0
- package/dist/slices/schedules/service.js +57 -24
- package/dist/slices/schedules/topic-list.js +15 -0
- package/dist/slices/schedules/topic-scores.js +104 -0
- package/dist/slices/schedules/topics.js +53 -41
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +8 -43
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +14 -2
- package/dist/slices/settings/playback.js +34 -23
- package/dist/slices/settings/probe-read.js +16 -0
- package/dist/slices/settings/repo.js +33 -0
- package/dist/slices/settings/tutorial.js +2 -2
- package/dist/slices/settings/voices.js +4 -12
- package/dist/slices/shorts/keep.js +2 -1
- package/dist/slices/shorts/pick.js +6 -18
- package/dist/slices/shorts/prompts.js +4 -14
- package/dist/slices/shorts/render.js +6 -2
- package/dist/slices/social/channel.js +44 -0
- package/dist/slices/social/defaults.js +17 -0
- package/dist/slices/social/due.js +36 -0
- package/dist/slices/social/extension.js +18 -0
- package/dist/slices/social/fit.js +115 -0
- package/dist/slices/social/limits.js +186 -0
- package/dist/slices/social/links.js +21 -0
- package/dist/slices/social/media.js +132 -0
- package/dist/slices/social/model.js +75 -0
- package/dist/slices/social/notice.js +19 -0
- package/dist/slices/social/pack.js +176 -0
- package/dist/slices/social/post-prompt.js +56 -0
- package/dist/slices/social/queue.js +31 -0
- package/dist/slices/social/repo.js +133 -0
- package/dist/slices/social/reshape.js +97 -0
- package/dist/slices/social/rules.js +5 -0
- package/dist/slices/social/schema.js +70 -0
- package/dist/slices/social/serve.js +51 -0
- package/dist/slices/social/service.js +129 -0
- package/dist/slices/social/settings.js +21 -0
- package/dist/slices/social/sources.js +114 -0
- package/dist/slices/social/state.js +110 -0
- package/dist/slices/social/text.js +149 -0
- package/dist/slices/storage/arrange.js +3 -1
- package/dist/slices/storage/assets.js +8 -10
- package/dist/slices/storage/backup-designs.js +74 -0
- package/dist/slices/storage/backup-export.js +8 -3
- package/dist/slices/storage/backup-format.js +10 -0
- package/dist/slices/storage/backup-import.js +22 -4
- package/dist/slices/storage/delete-project.js +6 -5
- package/dist/slices/storage/files-location.js +6 -8
- package/dist/slices/storage/layout.js +13 -2
- package/dist/slices/storage/model.js +3 -0
- package/dist/slices/storage/portable.js +17 -12
- package/dist/slices/storage/schema.js +2 -0
- package/dist/slices/storage/staging-refs.js +2 -1
- package/dist/slices/storage/staging.js +6 -9
- package/dist/slices/storage/trim.js +26 -9
- package/dist/slices/studio/calendar.js +7 -2
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +7 -16
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +20 -57
- package/dist/slices/studio/videos.js +35 -4
- package/dist/slices/style-preview/render.js +37 -16
- package/dist/slices/style-preview/schema.js +14 -1
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/telemetry/collector-client.js +2 -4
- package/dist/slices/telemetry/flush.js +2 -4
- package/dist/slices/telemetry/model.js +0 -7
- package/dist/slices/telemetry/record.js +10 -3
- package/dist/slices/template-studio/anatomy-view.js +161 -0
- package/dist/slices/template-studio/anatomy.js +73 -0
- package/dist/slices/template-studio/bakeoff-make.js +225 -0
- package/dist/slices/template-studio/bakeoff-repo.js +83 -0
- package/dist/slices/template-studio/bakeoff.js +333 -0
- package/dist/slices/template-studio/budget.js +106 -0
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +322 -0
- package/dist/slices/template-studio/document-sample.js +56 -0
- package/dist/slices/template-studio/edit.js +15 -0
- package/dist/slices/template-studio/for-all.js +161 -0
- package/dist/slices/template-studio/free-video.js +31 -0
- package/dist/slices/template-studio/free.js +176 -0
- package/dist/slices/template-studio/gate.js +40 -0
- package/dist/slices/template-studio/hidden.js +20 -0
- package/dist/slices/template-studio/jobs.js +79 -0
- package/dist/slices/template-studio/loudness.js +111 -0
- package/dist/slices/template-studio/make.js +40 -0
- package/dist/slices/template-studio/model.js +58 -0
- package/dist/slices/template-studio/pack-import.js +253 -0
- package/dist/slices/template-studio/pack-samples.js +60 -0
- package/dist/slices/template-studio/pack.js +207 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +355 -0
- package/dist/slices/template-studio/quote.js +155 -0
- package/dist/slices/template-studio/ready.js +104 -0
- package/dist/slices/template-studio/repo.js +134 -0
- package/dist/slices/template-studio/reviews-path.js +58 -0
- package/dist/slices/template-studio/runner.js +84 -0
- package/dist/slices/template-studio/save.js +236 -0
- package/dist/slices/template-studio/say-it-as.js +248 -0
- package/dist/slices/template-studio/schedule-gate.js +34 -0
- package/dist/slices/template-studio/schedule-price.js +32 -0
- package/dist/slices/template-studio/schedule-sample.js +91 -0
- package/dist/slices/template-studio/schema.js +243 -0
- package/dist/slices/template-studio/scope.js +124 -0
- package/dist/slices/template-studio/sections.js +176 -0
- package/dist/slices/template-studio/service.js +175 -0
- package/dist/slices/template-studio/status.js +191 -0
- package/dist/slices/template-studio/topic.js +223 -0
- package/dist/slices/thumbnail/by-llm.js +1 -1
- package/dist/slices/trash/designs.js +76 -0
- package/dist/slices/trash/model.js +13 -4
- package/dist/slices/trash/service.js +52 -2
- package/dist/slices/tutorials/library.js +3 -2
- package/dist/slices/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.js +3 -2
- package/dist/slices/video/ass-text.js +17 -0
- package/dist/slices/video/audio-export.js +2 -64
- package/dist/slices/video/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +169 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +4 -16
- package/dist/slices/youtube/edits-repo.js +3 -12
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +34 -33
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +42 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +94 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +115 -71
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +14 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +53 -21
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
- package/dist/tutorials/Notifications.md +27 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +48 -44
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
- package/dist/tutorials/Schedules.md +46 -27
- package/dist/tutorials/Settings-Reference.md +194 -174
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +128 -0
- package/dist/tutorials/Templates.md +51 -77
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +19 -14
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-Cpa58woP.js +459 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- package/dist/slices/article/provided-entries.js +0 -66
- package/dist/slices/article/run.js +0 -145
- package/dist/slices/images/run.js +0 -234
- package/dist/slices/narration/legacy-plan.js +0 -58
- package/dist/slices/narration/run.js +0 -270
- package/dist/slices/rebuild/runtime-actions.js +0 -90
- package/dist/slices/research/run.js +0 -224
- package/dist/slices/subtitles/prepare.js +0 -194
- package/dist/slices/subtitles/transcript.js +0 -34
- package/dist/slices/thumbnail/run.js +0 -219
- package/dist/slices/video/audio-inputs.js +0 -33
- package/dist/slices/video/reuse-audio.js +0 -62
- package/dist/slices/video/run.js +0 -78
- package/dist/slices/video/write-export.js +0 -158
- package/dist/slices/video/write-subtitles.js +0 -59
- package/dist/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -161
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
- package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// The facts a run copies when it starts, for the keywords Slopify fills from them, and how each
|
|
2
|
+
// becomes a keyword's text. Browser-safe.
|
|
3
|
+
import { previousVideoLink } from "../youtube/placeholders.js";
|
|
4
|
+
// The description's own link placeholder for the next video, filled when it is shown like
|
|
5
|
+
// "Previous video".
|
|
6
|
+
export const nextVideoLink = "Next video";
|
|
7
|
+
// Every keyword the facts fill, for one place. A Description prompt keeps the links as the
|
|
8
|
+
// placeholders the description page fills when it shows the description, so they stay live.
|
|
9
|
+
export function factValues(facts, title, place) {
|
|
10
|
+
const described = place === "description";
|
|
11
|
+
const links = facts?.links ?? [];
|
|
12
|
+
const previous = facts?.previousVideo;
|
|
13
|
+
return {
|
|
14
|
+
Slopify_Title: title,
|
|
15
|
+
Slopify_Channel: facts?.channel ?? "",
|
|
16
|
+
Slopify_Series: facts?.series?.trim() ?? "",
|
|
17
|
+
Slopify_Links: links
|
|
18
|
+
.map((link) => `${link.name}: ${described ? `{{${link.name}}}` : link.url}`)
|
|
19
|
+
.join("\n"),
|
|
20
|
+
Slopify_Previous_Video: described
|
|
21
|
+
? `{{${previousVideoLink}}}`
|
|
22
|
+
: previous === undefined
|
|
23
|
+
? ""
|
|
24
|
+
: previous.url === undefined || previous.url === ""
|
|
25
|
+
? previous.title
|
|
26
|
+
: `${previous.title} – ${previous.url}`,
|
|
27
|
+
Slopify_Next_Video: described ? `{{${nextVideoLink}}}` : (facts?.nextVideo?.title ?? ""),
|
|
28
|
+
Slopify_Cast: (facts?.cast ?? [])
|
|
29
|
+
.map((member) => member.description.trim() === "" ? member.name : `${member.name}: ${member.description}`)
|
|
30
|
+
.join("\n"),
|
|
31
|
+
Slopify_Covered_Before: (facts?.coveredBefore ?? []).join("\n"),
|
|
32
|
+
Slopify_Episode: facts?.episode === undefined ? "" : String(facts.episode),
|
|
33
|
+
Slopify_Brand: brandText(facts?.brand),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function brandText(brand) {
|
|
37
|
+
if (brand === undefined)
|
|
38
|
+
return "";
|
|
39
|
+
const parts = [
|
|
40
|
+
...(brand.colours.length === 0 ? [] : [`Colors: ${brand.colours.join(", ")}.`]),
|
|
41
|
+
...(brand.fonts.length === 0 ? [] : [`Fonts: ${brand.fonts.join(", ")}.`]),
|
|
42
|
+
];
|
|
43
|
+
return parts.join(" ");
|
|
44
|
+
}
|
|
45
|
+
// The finished length as the description and post prompts say it: "42 min 10 s".
|
|
46
|
+
export function durationText(seconds) {
|
|
47
|
+
const total = Math.max(0, Math.round(seconds));
|
|
48
|
+
const hours = Math.floor(total / 3600);
|
|
49
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
50
|
+
const rest = total % 60;
|
|
51
|
+
return [
|
|
52
|
+
...(hours > 0 ? [`${String(hours)} h`] : []),
|
|
53
|
+
...(hours > 0 || minutes > 0 ? [`${String(minutes)} min`] : []),
|
|
54
|
+
`${String(rest)} s`,
|
|
55
|
+
].join(" ");
|
|
56
|
+
}
|
|
57
|
+
// The release date as a prompt says it: "Thursday 22 October 2026", in the computer's time zone
|
|
58
|
+
// like every other release time Slopify shows. Blank when the video has no release time.
|
|
59
|
+
export function releaseDateText(at, timeZone) {
|
|
60
|
+
if (at === undefined || at.trim() === "")
|
|
61
|
+
return "";
|
|
62
|
+
const date = new Date(at);
|
|
63
|
+
if (Number.isNaN(date.getTime()))
|
|
64
|
+
return "";
|
|
65
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
66
|
+
weekday: "long",
|
|
67
|
+
day: "numeric",
|
|
68
|
+
month: "long",
|
|
69
|
+
year: "numeric",
|
|
70
|
+
...(timeZone === undefined ? {} : { timeZone }),
|
|
71
|
+
})
|
|
72
|
+
.format(date)
|
|
73
|
+
.replace(",", "");
|
|
74
|
+
}
|
|
@@ -2,14 +2,35 @@
|
|
|
2
2
|
// restated here - it is admission/substitute.ts's, imported so the editor and the run see
|
|
3
3
|
// the same parse of the same body.
|
|
4
4
|
import { detectSlots } from "../admission/substitute.js";
|
|
5
|
+
import { keywordProblems, positionOf, textEntryProblems } from "../keywords/rules.js";
|
|
5
6
|
import { bodyMax, nameMax } from "./model.js";
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// `grandfathered` is the slot names the stored template already has: an unknown `Slopify_`
|
|
8
|
+
// name saved before the prefix was reserved does not stop it saving again.
|
|
9
|
+
export function lintPrompt(draft, grandfathered = []) {
|
|
10
|
+
return lint(draft.name, draft.body, draft.kind, grandfathered);
|
|
8
11
|
}
|
|
9
|
-
export function lintEntry(draft) {
|
|
10
|
-
return lint(draft.name, draft.body);
|
|
12
|
+
export function lintEntry(draft, grandfathered = []) {
|
|
13
|
+
return lint(draft.name, draft.body, draft.category, grandfathered);
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
// What the editor warns about without refusing the save: a keyword where it can't be filled
|
|
16
|
+
// (Play refuses that run) and an old keyword name.
|
|
17
|
+
export function keywordWarnings(draft) {
|
|
18
|
+
return [
|
|
19
|
+
...keywordProblems(draft.body, placeOf(draft))
|
|
20
|
+
.filter((problem) => problem.kind !== "unknown")
|
|
21
|
+
.map((problem) => ({ field: "body", message: problem.message })),
|
|
22
|
+
...("category" in draft && draft.mode === "text"
|
|
23
|
+
? textEntryProblems(draft.body, draft.category).map((problem) => ({
|
|
24
|
+
field: "body",
|
|
25
|
+
message: problem.message,
|
|
26
|
+
}))
|
|
27
|
+
: []),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function placeOf(draft) {
|
|
31
|
+
return "kind" in draft ? draft.kind : draft.category;
|
|
32
|
+
}
|
|
33
|
+
function lint(name, body, place, grandfathered) {
|
|
13
34
|
const fields = [];
|
|
14
35
|
const trimmed = name.trim();
|
|
15
36
|
if (trimmed === "") {
|
|
@@ -35,6 +56,9 @@ function lint(name, body) {
|
|
|
35
56
|
for (const error of detectSlots(body).errors) {
|
|
36
57
|
fields.push({ field: "body", message: describe(error, body) });
|
|
37
58
|
}
|
|
59
|
+
for (const problem of keywordProblems(body, place, grandfathered))
|
|
60
|
+
if (problem.kind === "unknown")
|
|
61
|
+
fields.push({ field: "body", message: problem.message });
|
|
38
62
|
return fields;
|
|
39
63
|
}
|
|
40
64
|
// The parser reports a character offset; a person editing a textarea counts lines and
|
|
@@ -50,10 +74,3 @@ function describe(error, body) {
|
|
|
50
74
|
return `The keyword at ${where} contains a brace. Keywords cannot be placed inside other keywords.`;
|
|
51
75
|
}
|
|
52
76
|
}
|
|
53
|
-
function positionOf(body, at) {
|
|
54
|
-
const before = body.slice(0, at);
|
|
55
|
-
const lastBreak = before.lastIndexOf("\n");
|
|
56
|
-
const line = before.split("\n").length;
|
|
57
|
-
const column = at - lastBreak;
|
|
58
|
-
return `line ${String(line)}, column ${String(column)}`;
|
|
59
|
-
}
|
|
@@ -12,6 +12,8 @@ export const promptKinds = [
|
|
|
12
12
|
"review",
|
|
13
13
|
// Speaker turns for a multi-voice run (`slices/voices/script.ts`), in place of an article.
|
|
14
14
|
"script",
|
|
15
|
+
// A post for another site: X, LinkedIn, Instagram, TikTok, Reddit or Bluesky (`slices/social`).
|
|
16
|
+
"post",
|
|
15
17
|
];
|
|
16
18
|
export const entryCategories = ["intro", "outro"];
|
|
17
19
|
// The name is what the Play pickers show, so it shares admission's title bound.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deleteSetting, readJsonSetting, stringListSchema, writeSetting, } from "../settings/repo.js";
|
|
2
2
|
import { promptById, promptByName } from "./repo.js";
|
|
3
3
|
// An Image prompt's "Draws photorealistic pictures" tick (Library → Prompts → the prompt).
|
|
4
4
|
// Pictures an image model draws in such a style can pass for real scenes, which is YouTube's
|
|
@@ -9,16 +9,8 @@ import { promptById, promptByName } from "./repo.js";
|
|
|
9
9
|
// for when it is restored.
|
|
10
10
|
export const photorealisticPromptsKey = "library.photorealisticPrompts";
|
|
11
11
|
export function photorealisticPromptIds(db) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
return new Set();
|
|
15
|
-
try {
|
|
16
|
-
const value = JSON.parse(stored);
|
|
17
|
-
return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return new Set();
|
|
21
|
-
}
|
|
12
|
+
const value = readJsonSetting(db, photorealisticPromptsKey, stringListSchema, []);
|
|
13
|
+
return new Set(value);
|
|
22
14
|
}
|
|
23
15
|
// The prompts as the Library lists them, the ticked ones marked.
|
|
24
16
|
export function withPhotorealistic(db, prompts) {
|
|
@@ -34,7 +26,7 @@ export function setPromptPhotorealistic(db, id, on) {
|
|
|
34
26
|
const others = [...photorealisticPromptIds(db)].filter((one) => one !== id);
|
|
35
27
|
const next = on ? [...others, id].sort() : others;
|
|
36
28
|
if (next.length === 0)
|
|
37
|
-
db
|
|
29
|
+
deleteSetting(db, photorealisticPromptsKey);
|
|
38
30
|
else
|
|
39
31
|
writeSetting(db, photorealisticPromptsKey, JSON.stringify(next));
|
|
40
32
|
return { ok: true };
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// Renaming a Library prompt or intro/outro. Everything names the Library by name, so a rename
|
|
2
|
-
// rewrites the name where a future run reads it:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// rewrites the name where a future run reads it: every version of every template (its form and
|
|
3
|
+
// the copy it saved of the Library) and every Play draft still being edited. Every version, not
|
|
4
|
+
// only the newest: a gated template's schedules run its newest ready version (`template-studio/
|
|
5
|
+
// gate.ts`), Export pack carries that one, a version's samples stay current only while the
|
|
6
|
+
// versions since differ in what they changed, and a trashed template or an old version can be
|
|
7
|
+
// put back. The name is a pointer, so rewriting it changes nothing a version means. A project
|
|
8
|
+
// revision is the record of what its run used and keeps the old name; "Used by" finds it
|
|
9
|
+
// through `formerNames`.
|
|
6
10
|
import { z } from "zod";
|
|
7
11
|
import { renamedIn } from "./used-by.js";
|
|
8
12
|
const documentRow = z.object({ key: z.string(), version: z.number(), document_json: z.string() });
|
|
@@ -56,9 +60,7 @@ export function renameReferences(db, ref, id, to) {
|
|
|
56
60
|
if (ref.name === to)
|
|
57
61
|
return;
|
|
58
62
|
const templates = db
|
|
59
|
-
.prepare(
|
|
60
|
-
JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version
|
|
61
|
-
WHERE t.deleted_at IS NULL`)
|
|
63
|
+
.prepare("SELECT template_id AS key,version,document_json FROM project_template_revisions")
|
|
62
64
|
.all()
|
|
63
65
|
.map((row) => documentRow.parse(row));
|
|
64
66
|
const updateTemplate = db.prepare("UPDATE project_template_revisions SET document_json=? WHERE template_id=? AND version=?");
|
|
@@ -8,8 +8,10 @@ import { lintEntry, lintPrompt } from "./lint.js";
|
|
|
8
8
|
import { entryCategories, promptKinds } from "./model.js";
|
|
9
9
|
import { renameReferences } from "./rename.js";
|
|
10
10
|
import { entryById, insertEntry, insertPrompt, promptById, replaceEntry, replacePrompt, trashEntry, trashPrompt, } from "./repo.js";
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// Duplicate names the prompt it copies (`copyOf`): a copy of one saved before the `Slopify_`
|
|
12
|
+
// prefix was reserved keeps the names that one already has, as an edit of it would.
|
|
13
|
+
export function createPrompt(deps, draft, copyOf) {
|
|
14
|
+
const fields = lintPrompt(draft, copyOf === undefined ? [] : (promptById(deps.db, copyOf)?.slots ?? []));
|
|
13
15
|
if (fields.length > 0) {
|
|
14
16
|
return { ok: false, reason: "invalid", fields };
|
|
15
17
|
}
|
|
@@ -21,8 +23,10 @@ export function createPrompt(deps, draft) {
|
|
|
21
23
|
}, prompt);
|
|
22
24
|
}
|
|
23
25
|
// A save replaces the row whole and keeps what was saved as a new version (`history.ts`).
|
|
24
|
-
export function updatePrompt(deps, id, draft, restoredFrom
|
|
25
|
-
|
|
26
|
+
export function updatePrompt(deps, id, draft, restoredFrom,
|
|
27
|
+
// Slot names a restored version had, which it may keep like the stored prompt's.
|
|
28
|
+
restoredSlots = []) {
|
|
29
|
+
const fields = lintPrompt(draft, [...(promptById(deps.db, id)?.slots ?? []), ...restoredSlots]);
|
|
26
30
|
if (fields.length > 0) {
|
|
27
31
|
return { ok: false, reason: "invalid", fields };
|
|
28
32
|
}
|
|
@@ -47,7 +51,7 @@ export function restorePrompt(deps, id, version) {
|
|
|
47
51
|
const kind = promptKinds.find((one) => one === saved?.kind);
|
|
48
52
|
if (saved === undefined || kind === undefined || promptById(deps.db, id) === undefined)
|
|
49
53
|
return { ok: false, reason: "not-found" };
|
|
50
|
-
return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version);
|
|
54
|
+
return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version, detectSlots(saved.body).names);
|
|
51
55
|
}
|
|
52
56
|
// Delete moves the prompt to the trash with its history; Settings → Trash puts it back or
|
|
53
57
|
// removes it for good (`slices/trash`).
|
|
@@ -56,8 +60,8 @@ export function removePrompt(deps, id) {
|
|
|
56
60
|
? { ok: true, value: null }
|
|
57
61
|
: { ok: false, reason: "not-found" };
|
|
58
62
|
}
|
|
59
|
-
export function createEntry(deps, draft) {
|
|
60
|
-
const fields = lintEntry(draft);
|
|
63
|
+
export function createEntry(deps, draft, copyOf) {
|
|
64
|
+
const fields = lintEntry(draft, copyOf === undefined ? [] : (entryById(deps.db, copyOf)?.slots ?? []));
|
|
61
65
|
if (fields.length > 0) {
|
|
62
66
|
return { ok: false, reason: "invalid", fields };
|
|
63
67
|
}
|
|
@@ -68,8 +72,8 @@ export function createEntry(deps, draft) {
|
|
|
68
72
|
return true;
|
|
69
73
|
}, entry);
|
|
70
74
|
}
|
|
71
|
-
export function updateEntry(deps, id, draft, restoredFrom) {
|
|
72
|
-
const fields = lintEntry(draft);
|
|
75
|
+
export function updateEntry(deps, id, draft, restoredFrom, restoredSlots = []) {
|
|
76
|
+
const fields = lintEntry(draft, [...(entryById(deps.db, id)?.slots ?? []), ...restoredSlots]);
|
|
73
77
|
if (fields.length > 0) {
|
|
74
78
|
return { ok: false, reason: "invalid", fields };
|
|
75
79
|
}
|
|
@@ -95,7 +99,7 @@ export function restoreEntry(deps, id, version) {
|
|
|
95
99
|
saved.mode === null ||
|
|
96
100
|
entryById(deps.db, id) === undefined)
|
|
97
101
|
return { ok: false, reason: "not-found" };
|
|
98
|
-
return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version);
|
|
102
|
+
return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version, detectSlots(saved.body).names);
|
|
99
103
|
}
|
|
100
104
|
export function removeEntry(deps, id) {
|
|
101
105
|
return trashEntry(deps.db, id, deps.clock.now().toISOString())
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
// is the one that runs.
|
|
5
5
|
import { normaliseDraft, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
|
|
6
6
|
import { collectFields, render } from "../admission/substitute.js";
|
|
7
|
+
import { keywordUsesOf, placedProblems, textEntryProblems, } from "../keywords/rules.js";
|
|
7
8
|
import { reviewPromptKey } from "../reviews/model.js";
|
|
8
9
|
import { activeReviewStages } from "../reviews/rules.js";
|
|
10
|
+
import { socialPlatforms } from "../social/model.js";
|
|
9
11
|
import { usesScriptPrompt } from "../voices/model.js";
|
|
10
12
|
import { snapshotEntry, snapshotPrompt } from "./snapshot.js";
|
|
11
13
|
export function pickTemplates(db, input, snapshot) {
|
|
@@ -16,40 +18,63 @@ export function pickTemplates(db, input, snapshot) {
|
|
|
16
18
|
// entries. A prompt left selected on a stage set to Provide asks for no field.
|
|
17
19
|
const text = [];
|
|
18
20
|
const image = [];
|
|
21
|
+
// Each picked body with the field that picked it and where it runs, for the keyword checks.
|
|
22
|
+
const placed = [];
|
|
23
|
+
const body = (kind, name, field, into, key) => {
|
|
24
|
+
const at = into.length;
|
|
25
|
+
pickBody(db, kind, name, field, missing, into, key, snapshot);
|
|
26
|
+
const picked = into[at];
|
|
27
|
+
if (picked !== undefined)
|
|
28
|
+
placed.push({ field, place: kind, body: picked.body });
|
|
29
|
+
};
|
|
19
30
|
if (sources.article === "generate") {
|
|
20
|
-
body(
|
|
31
|
+
body(usesScriptPrompt(draft) ? "script" : "article", draft.articlePrompt, "articlePrompt", text, "article");
|
|
21
32
|
}
|
|
22
33
|
const intro = pickEntry(db, "intro", draft.intro, missing, snapshot);
|
|
23
34
|
if (usesNarrationPreparation(draft))
|
|
24
|
-
body(
|
|
35
|
+
body("narration", draft.narrationPrompt, "narrationPrompt", text, "narration");
|
|
25
36
|
const outro = pickEntry(db, "outro", draft.outro, missing, snapshot);
|
|
26
37
|
push(text, "intro", intro);
|
|
27
38
|
push(text, "outro", outro);
|
|
39
|
+
if (intro !== undefined)
|
|
40
|
+
placed.push({ field: "intro", place: "intro", body: intro.body });
|
|
41
|
+
if (outro !== undefined)
|
|
42
|
+
placed.push({ field: "outro", place: "outro", body: outro.body });
|
|
43
|
+
for (const entry of [intro, outro])
|
|
44
|
+
if (entry?.mode === "text")
|
|
45
|
+
missing.push(...textEntryProblems(entry.body, entry.category));
|
|
28
46
|
// None picked is the built-in prompt, which asks for no keyword.
|
|
29
47
|
if (usesYoutubeDescription(draft))
|
|
30
|
-
body(
|
|
48
|
+
body("description", draft.descriptionPrompt, "descriptionPrompt", text, "description");
|
|
31
49
|
// The Shorts step's two prompts; none picked is the built-in one, which asks for no keyword.
|
|
32
50
|
if (usesShorts(draft)) {
|
|
33
|
-
body(
|
|
34
|
-
body(
|
|
51
|
+
body("shorts", draft.shorts?.prompt, "shorts.prompt", text, "shorts");
|
|
52
|
+
body("image", draft.shorts?.imagePrompt, "shorts.imagePrompt", image, "shortsImage");
|
|
35
53
|
}
|
|
36
54
|
// Each reviewed stage's Review prompt; none picked is the stage's built-in one.
|
|
37
55
|
for (const stage of activeReviewStages(draft))
|
|
38
|
-
body(
|
|
56
|
+
body("review", draft.reviews?.stages[stage]?.prompt, `reviews.stages.${stage}.prompt`, text, reviewPromptKey(stage));
|
|
57
|
+
// Each site's post prompt, for the sites whose post is written by one; none picked is the
|
|
58
|
+
// built-in one, which asks for no keyword.
|
|
59
|
+
for (const platform of socialPlatforms) {
|
|
60
|
+
const choice = draft.social?.[platform];
|
|
61
|
+
if (choice?.text === "prompt")
|
|
62
|
+
body("post", choice.prompt, `social.${platform}.prompt`, text, `social.${platform}`);
|
|
63
|
+
}
|
|
39
64
|
// The establishing image's prompt: an image prompt like the others, its keywords filled the
|
|
40
65
|
// same way.
|
|
41
66
|
if (usesReference(draft) && draft.reference?.source === "prompt")
|
|
42
|
-
body(
|
|
67
|
+
body("image", draft.reference.prompt, "reference.prompt", image, "referencePrompt");
|
|
43
68
|
if (sources.images === "generate") {
|
|
44
69
|
for (const [index, picked] of draft.imagePrompts.entries()) {
|
|
45
70
|
const field = `imagePrompts.${String(index)}.name`;
|
|
46
|
-
body(
|
|
71
|
+
body("image", picked.name, field, image, `imagePrompts.${String(index)}`);
|
|
47
72
|
}
|
|
48
73
|
}
|
|
49
74
|
// Both Generate modes read the same thumbnail template; Prompt by LLM sends it as the
|
|
50
75
|
// instruction rather than to the image provider.
|
|
51
76
|
if (sources.thumbnail === "from_prompt" || sources.thumbnail === "prompt_by_llm") {
|
|
52
|
-
body(
|
|
77
|
+
body("thumbnail", draft.thumbnailPrompt, "thumbnailPrompt", image, "thumbnailPrompt");
|
|
53
78
|
}
|
|
54
79
|
const fields = collectFields(
|
|
55
80
|
// The title's keywords are asked for like a prompt's: it is filled when the run starts.
|
|
@@ -58,7 +83,8 @@ export function pickTemplates(db, input, snapshot) {
|
|
|
58
83
|
draft: withSavedModes(draft, intro, outro),
|
|
59
84
|
requiredSlots: fields.map((field) => field.name),
|
|
60
85
|
bodies: [...text, ...image],
|
|
61
|
-
missing,
|
|
86
|
+
missing: [...missing, ...placedProblems(placed, draft.values)],
|
|
87
|
+
keywordUses: keywordUsesOf(placed),
|
|
62
88
|
};
|
|
63
89
|
}
|
|
64
90
|
// Every picked body rendered once, with the trimmed values
|
|
@@ -70,7 +96,7 @@ export function renderPicked(picked, values) {
|
|
|
70
96
|
}
|
|
71
97
|
return rendered;
|
|
72
98
|
}
|
|
73
|
-
function
|
|
99
|
+
function pickBody(db, kind, name, field, missing, into, key, snapshot) {
|
|
74
100
|
// An unpicked prompt is admission's rule to state ("Pick an article prompt."), so this
|
|
75
101
|
// says nothing about it and the form marks the field once.
|
|
76
102
|
if (name === undefined || name.trim() === "") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { errorText } from "../../kernel/errors.js";
|
|
2
3
|
import { documentThemeNameMax } from "../document/model.js";
|
|
3
4
|
import { documentThemeSchema } from "../document/theme-schema.js";
|
|
4
5
|
import { narrationAliasSchema } from "../narration/aliases-schema.js";
|
|
@@ -16,7 +17,7 @@ const itemsMax = 10_000;
|
|
|
16
17
|
const sectionWords = {
|
|
17
18
|
prompts: "prompts",
|
|
18
19
|
entries: "intros or outros",
|
|
19
|
-
documentThemes: "PDF
|
|
20
|
+
documentThemes: "PDF looks",
|
|
20
21
|
aliases: "narration aliases",
|
|
21
22
|
};
|
|
22
23
|
// Only what the editor saves travels: ids, keywords and dates are the install's own.
|
|
@@ -92,7 +93,7 @@ export function readLibraryFile(text, section) {
|
|
|
92
93
|
if (!top.success)
|
|
93
94
|
return {
|
|
94
95
|
ok: false,
|
|
95
|
-
message: `it isn't a Slopify library file. Pick a .json file made by Export on
|
|
96
|
+
message: `it isn't a Slopify library file. Pick a .json file made by Export on Prompts, the PDF looks or Pronunciation.`,
|
|
96
97
|
};
|
|
97
98
|
if (top.data.version > libraryFileVersion)
|
|
98
99
|
return {
|
|
@@ -103,7 +104,7 @@ export function readLibraryFile(text, section) {
|
|
|
103
104
|
if (!Array.isArray(list) || list.length === 0)
|
|
104
105
|
return {
|
|
105
106
|
ok: false,
|
|
106
|
-
message: `it holds no ${what}. Import it on the
|
|
107
|
+
message: `it holds no ${what}. Import it on the page it was exported from.`,
|
|
107
108
|
};
|
|
108
109
|
if (list.length > itemsMax)
|
|
109
110
|
return {
|
|
@@ -202,7 +203,7 @@ export async function importEach(items, options) {
|
|
|
202
203
|
break;
|
|
203
204
|
}
|
|
204
205
|
catch (error) {
|
|
205
|
-
reason =
|
|
206
|
+
reason = errorText(error);
|
|
206
207
|
break;
|
|
207
208
|
}
|
|
208
209
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// `snapshot.ts` use.
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { liveProject } from "../admission/repo.js";
|
|
8
|
+
import { runnableVersion } from "../template-studio/gate.js";
|
|
9
|
+
import { gatesOf, notTemplateSample } from "../template-studio/repo.js";
|
|
8
10
|
function record(value) {
|
|
9
11
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
10
12
|
? value
|
|
@@ -40,6 +42,8 @@ function slotsIn(value, ref) {
|
|
|
40
42
|
return field(shorts, "prompt");
|
|
41
43
|
case "review":
|
|
42
44
|
return Object.values(record(record(value.reviews)?.stages) ?? {}).flatMap((stage) => field(record(stage), "prompt"));
|
|
45
|
+
case "post":
|
|
46
|
+
return Object.values(record(value.social) ?? {}).flatMap((one) => field(record(one), "prompt"));
|
|
43
47
|
case "image":
|
|
44
48
|
return [
|
|
45
49
|
...(Array.isArray(value.imagePrompts) ? value.imagePrompts : []).flatMap((one) => field(record(one), "name")),
|
|
@@ -106,18 +110,28 @@ export function usedBy(db, ref, formerNames = []) {
|
|
|
106
110
|
const parsed = templateRow.parse(row);
|
|
107
111
|
return matches(formOf(parsed.document_json)) ? [{ id: parsed.id, name: parsed.name }] : [];
|
|
108
112
|
});
|
|
109
|
-
// A schedule runs its template
|
|
110
|
-
//
|
|
113
|
+
// A schedule runs the version of its template the studio gate lets it run
|
|
114
|
+
// (`template-studio/schedule-gate.ts`), which can be older than the newest one.
|
|
115
|
+
const runsOf = new Map();
|
|
116
|
+
const formRun = (templateId, head) => {
|
|
117
|
+
if (!runsOf.has(templateId)) {
|
|
118
|
+
const version = runnableVersion(gatesOf(db, templateId), head) ?? head;
|
|
119
|
+
const row = db
|
|
120
|
+
.prepare("SELECT document_json FROM project_template_revisions WHERE template_id=? AND version=?")
|
|
121
|
+
.get(templateId, version);
|
|
122
|
+
runsOf.set(templateId, row === undefined ? undefined : formOf(String(row.document_json)));
|
|
123
|
+
}
|
|
124
|
+
return runsOf.get(templateId);
|
|
125
|
+
};
|
|
111
126
|
const schedules = db
|
|
112
|
-
.prepare(`SELECT s.id,s.name,s.status,
|
|
127
|
+
.prepare(`SELECT s.id,s.name,s.status,s.template_id,t.head_version FROM schedules s
|
|
113
128
|
JOIN project_templates t ON t.id=s.template_id
|
|
114
|
-
|
|
115
|
-
WHERE s.deleted_at IS NULL AND s.status IN ('active','paused') AND ${mentions("r.document_json")}
|
|
129
|
+
WHERE s.deleted_at IS NULL AND s.status IN ('active','paused') AND s.template_kind='project'
|
|
116
130
|
ORDER BY lower(s.name),s.id`)
|
|
117
|
-
.all(
|
|
131
|
+
.all()
|
|
118
132
|
.flatMap((row) => {
|
|
119
133
|
const parsed = scheduleRow.parse(row);
|
|
120
|
-
return matches(
|
|
134
|
+
return matches(formRun(parsed.template_id, parsed.head_version))
|
|
121
135
|
? [{ id: parsed.id, name: parsed.name, status: parsed.status }]
|
|
122
136
|
: [];
|
|
123
137
|
});
|
|
@@ -126,7 +140,7 @@ export function usedBy(db, ref, formerNames = []) {
|
|
|
126
140
|
(SELECT count(*) FROM project_revisions x WHERE x.project_id=r.project_id) AS total
|
|
127
141
|
FROM project_revisions r JOIN projects p ON p.id=r.project_id
|
|
128
142
|
LEFT JOIN project_heads h ON h.project_id=r.project_id
|
|
129
|
-
WHERE ${liveProject("p")} AND ${mentions("r.config")}
|
|
143
|
+
WHERE ${liveProject("p")} AND ${notTemplateSample("p")} AND ${mentions("r.config")}
|
|
130
144
|
ORDER BY p.created_at DESC,p.id,r.created_at`)
|
|
131
145
|
.all(...plain)
|
|
132
146
|
.map((row) => revisionRow.parse(row));
|
|
@@ -146,7 +160,7 @@ export function usedBy(db, ref, formerNames = []) {
|
|
|
146
160
|
// A project from before revisions existed has none yet; its own config is what it ran with.
|
|
147
161
|
for (const row of db
|
|
148
162
|
.prepare(`SELECT p.id,p.title,p.config FROM projects p
|
|
149
|
-
WHERE ${liveProject("p")}
|
|
163
|
+
WHERE ${liveProject("p")} AND ${notTemplateSample("p")}
|
|
150
164
|
AND NOT EXISTS (SELECT 1 FROM project_revisions r WHERE r.project_id=p.id)
|
|
151
165
|
AND ${mentions("p.config")} ORDER BY p.created_at DESC,p.id`)
|
|
152
166
|
.all(...plain)
|
|
@@ -167,7 +181,8 @@ const scheduleRow = z.object({
|
|
|
167
181
|
id: z.string(),
|
|
168
182
|
name: z.string(),
|
|
169
183
|
status: z.string(),
|
|
170
|
-
|
|
184
|
+
template_id: z.string(),
|
|
185
|
+
head_version: z.number(),
|
|
171
186
|
});
|
|
172
187
|
const revisionRow = z.object({
|
|
173
188
|
project_id: z.string(),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { renameSync } from "node:fs";
|
|
1
|
+
import { renameSync, rmSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
4
5
|
// ceiling: below -70 LUFS (the filter's absolute gate) or under 0.4 s (one measuring block)
|
|
5
6
|
// there is nothing to measure: a piece that quiet or short is left as it is.
|
|
@@ -89,6 +90,7 @@ export async function measure(run, input, goal, chain = "") {
|
|
|
89
90
|
args: measureArgs(input, goal, chain),
|
|
90
91
|
signal: run.signal,
|
|
91
92
|
log: run.log,
|
|
93
|
+
priority: run.priority,
|
|
92
94
|
onProgress: () => { },
|
|
93
95
|
onStderr: (text) => {
|
|
94
96
|
stderr += text;
|
|
@@ -96,7 +98,7 @@ export async function measure(run, input, goal, chain = "") {
|
|
|
96
98
|
});
|
|
97
99
|
const measured = parseMeasured(stderr);
|
|
98
100
|
if (measured === undefined)
|
|
99
|
-
throw new Error(
|
|
101
|
+
throw new Error(`Slopify couldn't measure the loudness of the narration (ffmpeg printed no measurement). Try again; if it happens again, turn off Level the volume in ${editSettings.sound}, then use Download diagnostics in Settings and report it.`);
|
|
100
102
|
return measured;
|
|
101
103
|
}
|
|
102
104
|
export async function measureFile(run, path, goal) {
|
|
@@ -109,8 +111,15 @@ export async function normalizeFile(run, input, output, goal, format) {
|
|
|
109
111
|
// The channels are set before anything is measured: a stereo file mixed down to mono is
|
|
110
112
|
// louder per channel (the mix keeps its loudness, so each sample is about 3 dB higher), and
|
|
111
113
|
// the gain and the peak ceiling have to hold for what is written.
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
+
const measured = await measure(run, ["-i", input], goal, channelsOf(format));
|
|
115
|
+
await writeLevelled(run, input, output, goal, measured, format);
|
|
116
|
+
return measured;
|
|
117
|
+
}
|
|
118
|
+
function channelsOf(format) {
|
|
119
|
+
return `aformat=channel_layouts=${format.channels === 1 ? "mono" : "stereo"}`;
|
|
120
|
+
}
|
|
121
|
+
// `input` written to `output` with the gain that takes `measured` to `goal` (`gainFilter`).
|
|
122
|
+
async function writeLevelled(run, input, output, goal, measured, format) {
|
|
114
123
|
await runFfmpeg({
|
|
115
124
|
bin: run.bin,
|
|
116
125
|
args: [
|
|
@@ -125,7 +134,7 @@ export async function normalizeFile(run, input, output, goal, format) {
|
|
|
125
134
|
"-map",
|
|
126
135
|
"0:a:0",
|
|
127
136
|
"-af",
|
|
128
|
-
`${
|
|
137
|
+
`${channelsOf(format)},${gainFilter(goal, measured, format.sampleRate)}`,
|
|
129
138
|
"-c:a",
|
|
130
139
|
"pcm_f32le",
|
|
131
140
|
"-f",
|
|
@@ -134,23 +143,55 @@ export async function normalizeFile(run, input, output, goal, format) {
|
|
|
134
143
|
],
|
|
135
144
|
signal: run.signal,
|
|
136
145
|
log: run.log,
|
|
146
|
+
priority: run.priority,
|
|
137
147
|
onProgress: () => { },
|
|
138
148
|
});
|
|
139
|
-
return measured;
|
|
140
149
|
}
|
|
141
150
|
// ceiling: within a fifth of a LU of the level no one hears the difference.
|
|
142
151
|
const correctAbove = 0.2;
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
152
|
+
// ceiling: two corrections land a voice whose peaks stand 17 dB over its loudness (raw TTS)
|
|
153
|
+
// within a few tenths of a LU; a third would only chase the last tenth.
|
|
154
|
+
const corrections = 2;
|
|
155
|
+
// ceiling: the most gain a correction adds over the plain gain. Past it the limiter would
|
|
156
|
+
// flatten the voice; the file lands short instead, and its report says how far.
|
|
157
|
+
const extraGainMax = 6;
|
|
158
|
+
// One file brought to `goal` (`normalizeFile`), measured again, and corrected when the limiter
|
|
159
|
+
// left it more than a fifth of a LU under the level. Each correction writes the input again
|
|
160
|
+
// with more gain (so the limiter holds the peaks once, never twice), the gain found from the
|
|
161
|
+
// levels the earlier gains gave. Returns the first measurement and the last.
|
|
146
162
|
export async function levelFile(run, input, output, goal, format) {
|
|
147
163
|
const before = await normalizeFile(run, input, output, goal, format);
|
|
148
164
|
let after = await measureFile(run, output, goal);
|
|
149
|
-
if (measurable(
|
|
165
|
+
if (!measurable(before))
|
|
166
|
+
return { before, after };
|
|
167
|
+
const plain = goal.lufs - before.integrated;
|
|
168
|
+
const tried = [
|
|
169
|
+
{ gain: plain, lufs: after.integrated },
|
|
170
|
+
];
|
|
171
|
+
for (let pass = 0; pass < corrections; pass += 1) {
|
|
172
|
+
if (!measurable(after) || Math.abs(after.integrated - goal.lufs) <= correctAbove)
|
|
173
|
+
break;
|
|
174
|
+
const last = tried[tried.length - 1];
|
|
175
|
+
const prior = tried[tried.length - 2];
|
|
176
|
+
if (last === undefined)
|
|
177
|
+
break;
|
|
178
|
+
const slope = prior === undefined || last.lufs === prior.lufs
|
|
179
|
+
? 1
|
|
180
|
+
: (last.lufs - prior.lufs) / (last.gain - prior.gain);
|
|
181
|
+
const gain = Math.min(plain + extraGainMax, last.gain + (goal.lufs - last.lufs) / (slope > 0.05 ? slope : 1));
|
|
182
|
+
if (Math.abs(gain - last.gain) < 0.05)
|
|
183
|
+
break;
|
|
150
184
|
const corrected = `${output}.corrected.wav`;
|
|
151
|
-
await
|
|
185
|
+
await writeLevelled(run, input, corrected, { ...goal, lufs: before.integrated + gain }, before, format);
|
|
186
|
+
const next = await measureFile(run, corrected, goal);
|
|
187
|
+
// A correction that landed further off than the file it would replace is dropped.
|
|
188
|
+
if (Math.abs(next.integrated - goal.lufs) >= Math.abs(after.integrated - goal.lufs)) {
|
|
189
|
+
rmSync(corrected, { force: true });
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
152
192
|
renameSync(corrected, output);
|
|
153
|
-
after =
|
|
193
|
+
after = next;
|
|
194
|
+
tried.push({ gain, lufs: next.integrated });
|
|
154
195
|
}
|
|
155
196
|
return { before, after };
|
|
156
197
|
}
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
// The ambient bed and the shorts' music are mixed before the master and ducked by the levelled
|
|
10
10
|
// voice, so they keep their place under it.
|
|
11
11
|
//
|
|
12
|
-
// Browser-safe:
|
|
12
|
+
// Browser-safe: Create, Edit settings and Settings convert the same numbers.
|
|
13
13
|
//
|
|
14
14
|
// A run without the setting has no `loudness` at all, which is what every project saved before
|
|
15
15
|
// it was, and every recipe leaves it out of its fingerprint then.
|
|
16
|
+
import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
|
|
16
17
|
// YouTube and Spotify play everything back at about -14 LUFS, so a video mastered there is
|
|
17
18
|
// neither turned down nor left quiet. Audiobook shops ask for about -18 to -20 LUFS with more
|
|
18
19
|
// headroom, so the listening files sit lower.
|
|
@@ -87,7 +88,7 @@ export function loudnessProblem(lufs, target) {
|
|
|
87
88
|
db >= loudnessDbMin - 1e-9 &&
|
|
88
89
|
db <= loudnessDbMax + 1e-9
|
|
89
90
|
? undefined
|
|
90
|
-
: `The volume must be between ${String(loudnessDbMin)} dB and +${String(loudnessDbMax)} dB of the recommended level, in steps of ${String(loudnessDbStep)} dB. Change it under Level the volume (
|
|
91
|
+
: `The volume must be between ${String(loudnessDbMin)} dB and +${String(loudnessDbMax)} dB of the recommended level, in steps of ${String(loudnessDbStep)} dB. Change it under Level the volume (${createRows.video}, ${editSettings.sound}, or Settings → Production defaults).`;
|
|
91
92
|
}
|
|
92
93
|
export function loudnessFields(settings) {
|
|
93
94
|
if (settings === undefined)
|