@gentbajko/slopify 3.25.8 → 4.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/dist/adapters/account-limits.js +30 -0
- package/dist/adapters/alignment/cache.js +3 -5
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +0 -3
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/worker.js +2 -1
- package/dist/adapters/explain.js +0 -3
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +19 -3
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/codex.js +36 -4
- package/dist/adapters/image/fal.js +17 -50
- package/dist/adapters/image/google.js +31 -55
- package/dist/adapters/image/openai.js +20 -47
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +18 -47
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +53 -2
- package/dist/adapters/llm/antigravity.js +4 -0
- 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 +51 -98
- package/dist/adapters/llm/claude-code.js +13 -2
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/openrouter.js +10 -47
- package/dist/adapters/llm/run-cli.js +6 -22
- 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 +19 -40
- package/dist/adapters/tts/inworld-async.js +4 -1
- package/dist/adapters/tts/inworld.js +3 -0
- package/dist/adapters/tts/openai.js +20 -50
- 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 +3 -1
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +39 -0
- 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 +4 -25
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +85 -10
- package/dist/edge/http/assistant.js +127 -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 +1 -3
- package/dist/edge/http/channel-memory.js +7 -20
- package/dist/edge/http/channels.js +13 -26
- package/dist/edge/http/checkpoints.js +33 -17
- 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 +2 -11
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +4 -9
- 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 +38 -13
- 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 +1 -14
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +4 -12
- package/dist/edge/http/patch-notes.js +4 -10
- package/dist/edge/http/planning.js +2 -6
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +1 -0
- package/dist/edge/http/project-templates.js +29 -17
- package/dist/edge/http/projects.js +2 -12
- package/dist/edge/http/prompts.js +7 -14
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +39 -24
- 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 +11 -24
- package/dist/edge/http/revision-files.js +5 -10
- package/dist/edge/http/revisions.js +10 -15
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +59 -32
- package/dist/edge/http/settings.js +22 -13
- package/dist/edge/http/social.js +282 -0
- package/dist/edge/http/staging.js +2 -4
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +23 -23
- package/dist/edge/http/studio.js +23 -50
- package/dist/edge/http/style-preview.js +1 -4
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +433 -0
- package/dist/edge/http/trash.js +10 -11
- 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 +2 -3
- 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 +25 -0
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +5 -3
- package/dist/edge/mcp/library-tools.js +1 -0
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +2 -0
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +14 -2
- package/dist/edge/mcp/tools-planning.js +23 -4
- package/dist/edge/mcp/tools-projects.js +28 -1
- package/dist/edge/mcp/tools-runs.js +14 -1
- package/dist/edge/mcp/tools-schedules.js +28 -4
- package/dist/edge/mcp/tools-setup.js +61 -3
- package/dist/edge/mcp/tools-social.js +46 -0
- package/dist/edge/mcp/tools-storage.js +8 -0
- 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/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/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/runner/attempt.js +3 -1
- 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 +15 -10
- package/dist/kernel/runner/standalone.js +9 -5
- package/dist/main.js +334 -19
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/3.25.9.md +5 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/repo.js +2 -1
- package/dist/slices/admission/rules.js +43 -8
- 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/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 +79 -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 +342 -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 +4 -3
- package/dist/slices/channels/cast-images.js +2 -1
- 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 +6 -0
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +2 -14
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +0 -26
- 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 +128 -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 +222 -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/episodes/summarize.js +5 -1
- package/dist/slices/estimate/index.js +45 -7
- package/dist/slices/fonts/catalog.js +3 -2
- 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 +6 -15
- package/dist/slices/images/scenes.js +100 -27
- package/dist/slices/keywords/digest.js +84 -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 +2 -1
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +51 -11
- 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 +112 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/switch.js +70 -25
- package/dist/slices/model-upkeep/usage.js +26 -11
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +3 -2
- 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 +2 -1
- package/dist/slices/onboarding/copy.js +2 -1
- package/dist/slices/onboarding/first-run.js +3 -2
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +4 -3
- package/dist/slices/onboarding/model.js +0 -16
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/quick-short.js +2 -1
- package/dist/slices/onboarding/sample.js +0 -1
- package/dist/slices/onboarding/state.js +2 -11
- package/dist/slices/onboarding/template.js +2 -1
- package/dist/slices/patch-notes/library.js +3 -2
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/review-inputs.js +8 -2
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup.js +8 -0
- 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 +27 -9
- 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 +3 -3
- 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 +6 -3
- 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 +98 -28
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-work.js +15 -3
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +3 -2
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +2 -1
- 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 +9 -8
- package/dist/slices/rebuild/runtime-article.js +3 -2
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +8 -11
- package/dist/slices/rebuild/runtime-export-bed.js +6 -11
- package/dist/slices/rebuild/runtime-export-edit.js +6 -15
- package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
- package/dist/slices/rebuild/runtime-export-short.js +8 -4
- package/dist/slices/rebuild/runtime-export.js +34 -12
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +4 -9
- 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 +14 -20
- 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 +10 -15
- 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 +60 -19
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +33 -28
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +34 -50
- package/dist/slices/rebuild/runtime-store.js +3 -8
- package/dist/slices/rebuild/runtime-subtitles.js +16 -14
- package/dist/slices/rebuild/runtime-voices.js +8 -15
- package/dist/slices/rebuild/runtime-youtube.js +11 -29
- 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/reviews/external-model.js +3 -0
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/model.js +26 -8
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +1 -0
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/mutation-assets.js +7 -4
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +0 -1
- 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 +11 -2
- 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 +24 -16
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +54 -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 +48 -36
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +2 -11
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +10 -0
- package/dist/slices/settings/playback.js +33 -22
- 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/pick.js +2 -15
- package/dist/slices/shorts/prompts.js +2 -13
- package/dist/slices/shorts/render.js +3 -0
- 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 +131 -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 +55 -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 +128 -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 +20 -2
- 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 +16 -11
- 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 +10 -3
- package/dist/slices/studio/calendar.js +4 -1
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +6 -15
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +14 -51
- package/dist/slices/studio/videos.js +12 -0
- package/dist/slices/style-preview/render.js +34 -13
- package/dist/slices/style-preview/schema.js +13 -0
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- 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 +118 -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 +103 -0
- package/dist/slices/template-studio/checks.js +273 -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 +54 -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 +205 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +348 -0
- package/dist/slices/template-studio/quote.js +148 -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 +30 -0
- package/dist/slices/template-studio/schedule-sample.js +87 -0
- package/dist/slices/template-studio/schema.js +218 -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 +173 -0
- package/dist/slices/template-studio/status.js +174 -0
- package/dist/slices/template-studio/topic.js +223 -0
- 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/video/ass-text.js +17 -0
- package/dist/slices/video/audio-export.js +2 -64
- package/dist/slices/video/encoding.js +160 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/youtube/answer.js +2 -15
- package/dist/slices/youtube/edits-repo.js +2 -11
- package/dist/tutorials/Agents-MCP.md +2 -1
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Channels.md +2 -0
- package/dist/tutorials/Home-and-Projects.md +19 -0
- package/dist/tutorials/Install.md +1 -0
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +33 -1
- package/dist/tutorials/Notifications.md +6 -0
- package/dist/tutorials/Play-Images.md +4 -0
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Prompts.md +10 -6
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
- package/dist/tutorials/Schedules.md +21 -1
- package/dist/tutorials/Settings-Reference.md +50 -8
- package/dist/tutorials/Template-Studio.md +125 -0
- package/dist/tutorials/Templates.md +3 -0
- package/dist/tutorials/_Sidebar.md +5 -0
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-CkAI_2E7.css +1 -0
- package/dist/web/assets/index-DcD0cdfo.js +334 -0
- package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -1
- 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/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { copyFileSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { join, relative, sep } from "node:path";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { subtitleModelDir } from "../../kernel/paths.js";
|
|
6
|
-
import { projectLanguage } from "../../kernel/ports/languages.js";
|
|
7
|
-
import { projectById } from "../admission/repo.js";
|
|
8
|
-
import { setStageProgress } from "../admission/stage-writes.js";
|
|
9
|
-
import { captionFont, captionFontDeps } from "../fonts/coverage.js";
|
|
10
|
-
import { resolveFont } from "../fonts/index.js";
|
|
11
|
-
import { outputPath, projectDir } from "../storage/layout.js";
|
|
12
|
-
import { outputsOf } from "../storage/repo.js";
|
|
13
|
-
import { spoken } from "../video/plan.js";
|
|
14
|
-
import { captionCues, serializeAss, serializeSrt, serializeVtt } from "./captions.js";
|
|
15
|
-
import { defaultSubtitles, timingOperation } from "./model.js";
|
|
16
|
-
import { spokenText } from "./transcript.js";
|
|
17
|
-
export const subtitleRoles = [
|
|
18
|
-
"subtitles_srt",
|
|
19
|
-
"subtitles_vtt",
|
|
20
|
-
"subtitle_words",
|
|
21
|
-
"subtitle_ass",
|
|
22
|
-
"subtitle_font",
|
|
23
|
-
];
|
|
24
|
-
const wordSchema = z.object({
|
|
25
|
-
text: z.string(),
|
|
26
|
-
start: z.number().nonnegative(),
|
|
27
|
-
end: z.number().positive(),
|
|
28
|
-
confidence: z.number().optional(),
|
|
29
|
-
});
|
|
30
|
-
const fontSchema = z.object({
|
|
31
|
-
id: z.string(),
|
|
32
|
-
name: z.string(),
|
|
33
|
-
assName: z.string(),
|
|
34
|
-
extension: z.enum([".ttf", ".otf", ".ttc"]),
|
|
35
|
-
});
|
|
36
|
-
const cacheSchema = z.object({
|
|
37
|
-
key: z.string(),
|
|
38
|
-
words: z.array(wordSchema),
|
|
39
|
-
font: fontSchema,
|
|
40
|
-
omissions: z
|
|
41
|
-
.array(z.object({ start: z.number().finite().nonnegative(), text: z.string() }))
|
|
42
|
-
.default([]),
|
|
43
|
-
});
|
|
44
|
-
// Preparation writes into a new directory; export writers commit its output rows.
|
|
45
|
-
// The old caption files/font/timing stay usable if alignment or rendering fails.
|
|
46
|
-
export async function prepareSubtitles(deps, context, audio, frame) {
|
|
47
|
-
const { projectId } = context.stage;
|
|
48
|
-
const project = projectById(deps.db, projectId);
|
|
49
|
-
if (project === undefined)
|
|
50
|
-
throw new Error("This project no longer exists; it may have been deleted while it was running.");
|
|
51
|
-
const config = project.config.subtitles ?? defaultSubtitles;
|
|
52
|
-
if (config.mode === "off")
|
|
53
|
-
return undefined;
|
|
54
|
-
context.signal.throwIfAborted();
|
|
55
|
-
setStageProgress(deps.db, context.stage.id, 0, 100);
|
|
56
|
-
context.emit({ type: "stage.progress", projectId, stage: "video", current: 0, total: 100 });
|
|
57
|
-
if (audio.length === 0)
|
|
58
|
-
throw new Error("Captions need narration audio, but this project has none. Turn captions off in Edit project → Subtitles, or turn narration on, then Try again.");
|
|
59
|
-
const outputs = outputsOf(deps.db, projectId);
|
|
60
|
-
const segments = audio.map((segment) => ({
|
|
61
|
-
...segment,
|
|
62
|
-
text: spoken(segment.kind) ? spokenText(deps, projectId, segment.kind, outputs) : "",
|
|
63
|
-
}));
|
|
64
|
-
const language = projectLanguage(project.config);
|
|
65
|
-
const key = await timingKey(segments, language, context.signal);
|
|
66
|
-
const cache = readCache(deps, projectId, outputs);
|
|
67
|
-
const dir = projectDir(deps.paths, projectId);
|
|
68
|
-
const directory = mkdtempSync(join(dir, "captions-"));
|
|
69
|
-
try {
|
|
70
|
-
const font = await snapshotFont(deps, projectId, outputs, config.fontId, cache, directory, language);
|
|
71
|
-
const omissions = cache?.key === key ? [...cache.omissions] : [];
|
|
72
|
-
const words = cache?.key === key
|
|
73
|
-
? cache.words
|
|
74
|
-
: await alignSegments(deps, context, segments, omissions, language);
|
|
75
|
-
context.signal.throwIfAborted();
|
|
76
|
-
const cues = captionCues(words);
|
|
77
|
-
if (cues.length === 0)
|
|
78
|
-
throw new Error("None of the narration could be matched to the article text, so captions can't be timed. The narration must be in the project language set in Edit project → Language; if you uploaded your own audio, make sure it reads the article text, then Try again.");
|
|
79
|
-
writeFileSync(join(directory, "subtitles.srt"), serializeSrt(cues), { mode: 0o600 });
|
|
80
|
-
writeFileSync(join(directory, "subtitles.vtt"), serializeVtt(cues), { mode: 0o600 });
|
|
81
|
-
writeFileSync(join(directory, "subtitles.ass"), serializeAss(cues, {
|
|
82
|
-
...frame,
|
|
83
|
-
fontSize: config.fontSize,
|
|
84
|
-
fontName: font.assName,
|
|
85
|
-
position: config.position,
|
|
86
|
-
color: config.color,
|
|
87
|
-
outlineColor: config.outlineColor,
|
|
88
|
-
}), { mode: 0o600 });
|
|
89
|
-
writeFileSync(join(directory, "subtitles.json"), JSON.stringify({ key, words, font, omissions }), {
|
|
90
|
-
mode: 0o600,
|
|
91
|
-
});
|
|
92
|
-
const files = [
|
|
93
|
-
["subtitles_srt", "subtitles.srt"],
|
|
94
|
-
["subtitles_vtt", "subtitles.vtt"],
|
|
95
|
-
["subtitle_words", "subtitles.json"],
|
|
96
|
-
["subtitle_ass", "subtitles.ass"],
|
|
97
|
-
["subtitle_font", `fonts/selected${font.extension}`],
|
|
98
|
-
];
|
|
99
|
-
return {
|
|
100
|
-
directory,
|
|
101
|
-
omissions,
|
|
102
|
-
burnIn: config.mode === "burn-in" && project.config.sources.video !== "off",
|
|
103
|
-
assets: files.map(([role, path]) => ({
|
|
104
|
-
role,
|
|
105
|
-
path: relative(dir, join(directory, path)).split(sep).join("/"),
|
|
106
|
-
})),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
rmSync(directory, { recursive: true, force: true });
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async function timingKey(segments, language, signal) {
|
|
115
|
-
// Bump when alignment normalization/model changes. Hash file contents, not timestamps.
|
|
116
|
-
// English hashes the operation it always did (`timingOperation`).
|
|
117
|
-
const hash = createHash("sha256").update(timingOperation(language));
|
|
118
|
-
for (const segment of segments) {
|
|
119
|
-
signal.throwIfAborted();
|
|
120
|
-
hash.update(JSON.stringify({ kind: segment.kind, seconds: segment.seconds, text: segment.text }));
|
|
121
|
-
if (segment.path !== null) {
|
|
122
|
-
const stream = createReadStream(segment.path, { signal });
|
|
123
|
-
for await (const chunk of stream)
|
|
124
|
-
hash.update(chunk);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return hash.digest("hex");
|
|
128
|
-
}
|
|
129
|
-
async function alignSegments(deps, context, segments, omissions, language) {
|
|
130
|
-
if (deps.alignSubtitles === undefined)
|
|
131
|
-
throw new Error("Slopify hit an internal error (the caption timing tool is missing from this build). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
132
|
-
const words = [];
|
|
133
|
-
const total = segments.reduce((sum, segment) => sum + segment.seconds, 0);
|
|
134
|
-
let offset = 0;
|
|
135
|
-
for (const segment of segments) {
|
|
136
|
-
context.signal.throwIfAborted();
|
|
137
|
-
if (segment.path !== null) {
|
|
138
|
-
const aligned = await deps.alignSubtitles({
|
|
139
|
-
audioPath: segment.path,
|
|
140
|
-
onOmission: (omission) => omissions.push({ ...omission, start: omission.start + offset }),
|
|
141
|
-
text: segment.text,
|
|
142
|
-
cacheDir: subtitleModelDir(deps.paths.dataDir, language),
|
|
143
|
-
...(language === "en" ? {} : { language }),
|
|
144
|
-
ffmpeg: deps.ffmpeg,
|
|
145
|
-
signal: context.signal,
|
|
146
|
-
onProgress: (current, maximum) => {
|
|
147
|
-
const progress = Math.round((35 * (offset + segment.seconds * (maximum > 0 ? current / maximum : 0))) / total);
|
|
148
|
-
setStageProgress(deps.db, context.stage.id, progress, 100);
|
|
149
|
-
context.emit({
|
|
150
|
-
type: "stage.progress",
|
|
151
|
-
projectId: context.stage.projectId,
|
|
152
|
-
stage: "video",
|
|
153
|
-
current: progress,
|
|
154
|
-
total: 100,
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
for (const word of aligned) {
|
|
159
|
-
if (word.end > segment.seconds + 0.1)
|
|
160
|
-
throw new Error("Caption timing came out longer than the narration audio. Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
161
|
-
words.push({
|
|
162
|
-
...word,
|
|
163
|
-
start: word.start + offset,
|
|
164
|
-
end: Math.min(word.end, segment.seconds) + offset,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
offset += segment.seconds;
|
|
169
|
-
}
|
|
170
|
-
return words;
|
|
171
|
-
}
|
|
172
|
-
function readCache(deps, projectId, outputs) {
|
|
173
|
-
const output = outputs.find((one) => one.role === "subtitle_words");
|
|
174
|
-
if (output === undefined)
|
|
175
|
-
return undefined;
|
|
176
|
-
try {
|
|
177
|
-
return cacheSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, projectId, output.path), "utf8")));
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
async function snapshotFont(deps, projectId, outputs, id, cache, directory, language) {
|
|
184
|
-
const previous = outputs.find((one) => one.role === "subtitle_font");
|
|
185
|
-
const previousPath = previous === undefined ? undefined : outputPath(deps.paths, projectId, previous.path);
|
|
186
|
-
const saved = language !== "en"
|
|
187
|
-
? await captionFont(captionFontDeps(deps.paths), id, language)
|
|
188
|
-
: cache?.font.id === id && previousPath !== undefined && existsSync(previousPath)
|
|
189
|
-
? { ...cache.font, path: previousPath }
|
|
190
|
-
: await resolveFont(deps.paths, id);
|
|
191
|
-
mkdirSync(join(directory, "fonts"), { mode: 0o700 });
|
|
192
|
-
copyFileSync(saved.path, join(directory, "fonts", `selected${saved.extension}`));
|
|
193
|
-
return { id: saved.id, name: saved.name, assName: saved.assName, extension: saved.extension };
|
|
194
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { piecesOf } from "../../kernel/runner/piece-repo.js";
|
|
4
|
-
import { stagesOf } from "../admission/repo.js";
|
|
5
|
-
import { outputPath } from "../storage/layout.js";
|
|
6
|
-
const textPayload = z.object({ text: z.string(), category: z.string().optional() });
|
|
7
|
-
export function spokenText(deps, projectId, kind, outputs) {
|
|
8
|
-
const stages = stagesOf(deps.db, projectId);
|
|
9
|
-
if (kind === "body") {
|
|
10
|
-
const audio = stages.find((stage) => stage.kind === "audio");
|
|
11
|
-
if (audio?.source === "generate") {
|
|
12
|
-
const chunks = piecesOf(deps.db, audio.id, "chunk");
|
|
13
|
-
if (chunks.length > 0)
|
|
14
|
-
return chunks.map((piece) => payload(piece.payload).text).join("\n");
|
|
15
|
-
}
|
|
16
|
-
const article = outputs.find((output) => output.role === "article_txt");
|
|
17
|
-
if (article !== undefined)
|
|
18
|
-
return readFileSync(outputPath(deps.paths, projectId, article.path), "utf8");
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
const article = stages.find((stage) => stage.kind === "article");
|
|
22
|
-
if (article !== undefined) {
|
|
23
|
-
const segment = piecesOf(deps.db, article.id, "segment")
|
|
24
|
-
.map((piece) => payload(piece.payload))
|
|
25
|
-
.find((piece) => piece.category === kind);
|
|
26
|
-
if (segment !== undefined)
|
|
27
|
-
return segment.text;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
throw new Error(`The saved text of the ${kind === "body" ? "article" : kind} is missing, and captions need the text spoken in the narration. Use More → Write the article again in the Article section, then Try again.`);
|
|
31
|
-
}
|
|
32
|
-
function payload(raw) {
|
|
33
|
-
return textPayload.parse(JSON.parse(raw ?? "null"));
|
|
34
|
-
}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { thumbnailAspect } from "../../kernel/pipeline.js";
|
|
5
|
-
import { insertPiece, piecesOf, setPiece } from "../../kernel/runner/piece-repo.js";
|
|
6
|
-
import { projectById } from "../admission/repo.js";
|
|
7
|
-
import { outputFileName, outputPath } from "../storage/layout.js";
|
|
8
|
-
import { insertOutput, outputsOf } from "../storage/repo.js";
|
|
9
|
-
import { storeText } from "../storage/staging.js";
|
|
10
|
-
import { noTokens, plusUsage } from "../telemetry/model.js";
|
|
11
|
-
import { thumbnailMessages, writtenPrompt } from "./by-llm.js";
|
|
12
|
-
// The `prompt_written` sub-step, persisted for resume: the prompt the LLM wrote and the
|
|
13
|
-
// messages that asked for it, so a resumed run reproduces the record without asking
|
|
14
|
-
// again.
|
|
15
|
-
const writtenPayload = z.object({ prompt: z.string(), sent: z.string() });
|
|
16
|
-
export async function runThumbnail(deps, context, providers) {
|
|
17
|
-
const { projectId } = context.stage;
|
|
18
|
-
const project = projectById(deps.db, projectId);
|
|
19
|
-
if (project === undefined) {
|
|
20
|
-
throw new Error("This project no longer exists; it may have been deleted while it was running.");
|
|
21
|
-
}
|
|
22
|
-
const source = project.config.sources.thumbnail;
|
|
23
|
-
if (source !== "from_prompt" && source !== "prompt_by_llm") {
|
|
24
|
-
// Off is `skipped` and Provide is `provided` at project creation,
|
|
25
|
-
// so the runner never starts this stage for either.
|
|
26
|
-
throw new Error(`Slopify hit an internal error (the thumbnail stage cannot run with its source set to ${source}). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
27
|
-
}
|
|
28
|
-
const choice = project.config.images;
|
|
29
|
-
if (choice === undefined) {
|
|
30
|
-
// Admission requires the image provider whenever the thumbnail is generated, so reaching
|
|
31
|
-
// here is a bug in admission rather than the user's.
|
|
32
|
-
throw new Error("No image model is set for the thumbnail. Choose one in Edit project → Providers, then Try again.");
|
|
33
|
-
}
|
|
34
|
-
const result = source === "from_prompt" ? undefined : await byLlm(deps, context, providers, project);
|
|
35
|
-
if (result !== undefined && !result.ok)
|
|
36
|
-
return "held";
|
|
37
|
-
const written = result?.value;
|
|
38
|
-
const prompt = written?.prompt ?? fromTemplate(project);
|
|
39
|
-
if ((await make(deps, context, providers, project, choice, prompt)) === "held")
|
|
40
|
-
return "held";
|
|
41
|
-
// Tokens are counted with a provider and model name per stage, and a payload names one
|
|
42
|
-
// provider. This stage can use two - the LLM that writes the prompt and the image model
|
|
43
|
-
// that draws it - so the event names whichever one's usage it reports, because both are
|
|
44
|
-
// shown beside the token columns on the Usage page. A thumbnail from a picked template
|
|
45
|
-
// makes no LLM call, so it names the image model and reports zero tokens.
|
|
46
|
-
deps.count("stage.completed", {
|
|
47
|
-
stage: "thumbnail",
|
|
48
|
-
provider: written?.provider ?? choice.provider,
|
|
49
|
-
model: written?.model ?? choice.model,
|
|
50
|
-
...(written?.tokens ?? noTokens),
|
|
51
|
-
thumbnails: 1,
|
|
52
|
-
});
|
|
53
|
-
deps.log.write("info", "thumbnail.done", {
|
|
54
|
-
projectId,
|
|
55
|
-
stage: "thumbnail",
|
|
56
|
-
detail: source === "from_prompt" ? "from the picked prompt" : "from the prompt the LLM wrote",
|
|
57
|
-
});
|
|
58
|
-
return "done";
|
|
59
|
-
}
|
|
60
|
-
// The rendered thumbnail template goes to the image provider as it is.
|
|
61
|
-
function fromTemplate(project) {
|
|
62
|
-
const rendered = project.config.rendered.thumbnailPrompt;
|
|
63
|
-
if (rendered === undefined) {
|
|
64
|
-
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
65
|
-
}
|
|
66
|
-
return rendered;
|
|
67
|
-
}
|
|
68
|
-
// A written prompt already on the project is reused, so a manual retry redoes only the
|
|
69
|
-
// image call and the wording the user is looking at does not change under them.
|
|
70
|
-
async function byLlm(deps, context, providers, project) {
|
|
71
|
-
const kept = piecesOf(deps.db, context.stage.id, "prompt_written")[0];
|
|
72
|
-
const llm = project.config.llm;
|
|
73
|
-
if (kept !== undefined && kept.state === "done") {
|
|
74
|
-
// The wording the user is looking at does not change under them. No call was
|
|
75
|
-
// made, so this run counts none - the run that wrote the prompt already did.
|
|
76
|
-
return {
|
|
77
|
-
ok: true,
|
|
78
|
-
value: {
|
|
79
|
-
prompt: payloadOf(kept).prompt,
|
|
80
|
-
...(llm === undefined ? {} : named(llm)),
|
|
81
|
-
tokens: noTokens,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (llm === undefined) {
|
|
86
|
-
// Admission requires the LLM row when the thumbnail source is Prompt by LLM.
|
|
87
|
-
throw new Error("No AI model is set for writing the thumbnail prompt. Choose one in Edit project → Providers, then Try again.");
|
|
88
|
-
}
|
|
89
|
-
const messages = thumbnailMessages(brief(deps, project));
|
|
90
|
-
const answer = await providers.llm({
|
|
91
|
-
provider: llm.provider,
|
|
92
|
-
model: llm.model,
|
|
93
|
-
...(llm.thinking === undefined ? {} : { thinking: llm.thinking }),
|
|
94
|
-
messages,
|
|
95
|
-
// An empty answer is a failed attempt, and the wrapper is what retries it.
|
|
96
|
-
check: (given) => writtenPrompt(given.text),
|
|
97
|
-
});
|
|
98
|
-
// The image prompt is exactly the LLM's output, never edited by the app.
|
|
99
|
-
if (!answer.ok)
|
|
100
|
-
return answer;
|
|
101
|
-
const prompt = answer.value.text.trim();
|
|
102
|
-
keepWritten(deps, context, kept?.id, prompt, messages);
|
|
103
|
-
// The messages sent are stored beside the article stage's, per stage.
|
|
104
|
-
storeText(deps, {
|
|
105
|
-
projectId: context.stage.projectId,
|
|
106
|
-
stageKind: "thumbnail",
|
|
107
|
-
role: "instructions",
|
|
108
|
-
text: instructionsText(messages),
|
|
109
|
-
});
|
|
110
|
-
return {
|
|
111
|
-
ok: true,
|
|
112
|
-
value: { prompt, ...named(llm), tokens: plusUsage(noTokens, answer.value.usage) },
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function named(choice) {
|
|
116
|
-
return { provider: choice.provider, model: choice.model };
|
|
117
|
-
}
|
|
118
|
-
function brief(deps, project) {
|
|
119
|
-
const instruction = project.config.rendered.thumbnailPrompt;
|
|
120
|
-
if (instruction === undefined) {
|
|
121
|
-
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
instruction,
|
|
125
|
-
title: project.title,
|
|
126
|
-
values: project.config.values,
|
|
127
|
-
format: project.format,
|
|
128
|
-
article: articleText(deps, project.id),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
// The stage never starts without an article on the project. The runner's graph holds the other
|
|
132
|
-
// half - the thumbnail waits on the article stage - so a project with no article row here is a
|
|
133
|
-
// stage that started against a run that never wrote one.
|
|
134
|
-
function articleText(deps, projectId) {
|
|
135
|
-
const article = outputsOf(deps.db, projectId).find((output) => output.role === "article_txt");
|
|
136
|
-
if (article === undefined) {
|
|
137
|
-
throw new Error("The thumbnail prompt is written from the article, but the article isn't ready. Let the Article stage finish, then Try again.");
|
|
138
|
-
}
|
|
139
|
-
return readFileSync(outputPath(deps.paths, projectId, article.path), "utf8");
|
|
140
|
-
}
|
|
141
|
-
// The one row is keyed by `idx` within the stage, so a stage that runs again replaces what
|
|
142
|
-
// it wrote rather than colliding with it.
|
|
143
|
-
function keepWritten(deps, context, existing, prompt, messages) {
|
|
144
|
-
const payload = JSON.stringify({ prompt, sent: instructionsText(messages) });
|
|
145
|
-
if (existing === undefined) {
|
|
146
|
-
insertPiece(deps.db, {
|
|
147
|
-
id: deps.ids.next(),
|
|
148
|
-
stageId: context.stage.id,
|
|
149
|
-
kind: "prompt_written",
|
|
150
|
-
idx: 1,
|
|
151
|
-
state: "done",
|
|
152
|
-
payload,
|
|
153
|
-
});
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
setPiece(deps.db, existing, "done", payload);
|
|
157
|
-
}
|
|
158
|
-
// One call, the same aspect rule as the slideshow, stored apart from it
|
|
159
|
-
// with the prompt text, the provider and the model.
|
|
160
|
-
async function make(deps, context, providers, project, choice, prompt) {
|
|
161
|
-
const { projectId } = context.stage;
|
|
162
|
-
if (outputsOf(deps.db, projectId).some((output) => output.role === "thumbnail")) {
|
|
163
|
-
// The `image-done` sub-step: a retry that failed after the image landed keeps it.
|
|
164
|
-
return "done";
|
|
165
|
-
}
|
|
166
|
-
const result = await providers.image({
|
|
167
|
-
provider: choice.provider,
|
|
168
|
-
model: choice.model,
|
|
169
|
-
...(choice.thinking === undefined ? {} : { thinking: choice.thinking }),
|
|
170
|
-
prompt,
|
|
171
|
-
aspect: thumbnailAspect(project.format),
|
|
172
|
-
});
|
|
173
|
-
if (!result.ok)
|
|
174
|
-
return "held";
|
|
175
|
-
const made = result.value;
|
|
176
|
-
const name = outputFileName("thumbnail", 1, made.mime === "image/png" ? ".png" : ".jpg", "thumbnail");
|
|
177
|
-
write(deps, projectId, name, made);
|
|
178
|
-
// A cancel protects an output already stored, not one about to be: one landing between
|
|
179
|
-
// the write and the row leaves a file the boot reconcile collects.
|
|
180
|
-
context.signal.throwIfAborted();
|
|
181
|
-
const output = {
|
|
182
|
-
id: deps.ids.next(),
|
|
183
|
-
projectId,
|
|
184
|
-
stageKind: "thumbnail",
|
|
185
|
-
role: "thumbnail",
|
|
186
|
-
path: name,
|
|
187
|
-
originalFilename: null,
|
|
188
|
-
bytes: made.bytes.byteLength,
|
|
189
|
-
durationMs: null,
|
|
190
|
-
// There is no `index`: the thumbnail stays out of the slideshow, and the render reads
|
|
191
|
-
// that list by role.
|
|
192
|
-
meta: {
|
|
193
|
-
promptName: project.config.thumbnailPrompt ?? "",
|
|
194
|
-
prompt,
|
|
195
|
-
provider: choice.provider,
|
|
196
|
-
model: choice.model,
|
|
197
|
-
...(choice.thinking === undefined ? {} : { thinking: choice.thinking }),
|
|
198
|
-
},
|
|
199
|
-
createdAt: deps.clock.now().toISOString(),
|
|
200
|
-
};
|
|
201
|
-
// No `image.landed`: the thumbnail stays out of the slideshow, and that event's `index`
|
|
202
|
-
// is a place in it. The stage reaching `done` is what tells the page.
|
|
203
|
-
insertOutput(deps.db, output);
|
|
204
|
-
return "done";
|
|
205
|
-
}
|
|
206
|
-
function write(deps, projectId, name, made) {
|
|
207
|
-
const target = outputPath(deps.paths, projectId, name);
|
|
208
|
-
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
|
|
209
|
-
writeFileSync(target, made.bytes, { mode: 0o600 });
|
|
210
|
-
}
|
|
211
|
-
function instructionsText(messages) {
|
|
212
|
-
return `=== Thumbnail prompt ===\n\n${messages.map((message) => message.content).join("\n\n")}\n`;
|
|
213
|
-
}
|
|
214
|
-
function payloadOf(piece) {
|
|
215
|
-
if (piece.payload === null) {
|
|
216
|
-
throw new Error("Slopify hit an internal error (the saved thumbnail prompt is empty). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
217
|
-
}
|
|
218
|
-
return writtenPayload.parse(JSON.parse(piece.payload));
|
|
219
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { relative } from "node:path";
|
|
2
|
-
import { outputPath, projectDir } from "../storage/layout.js";
|
|
3
|
-
import { probeDurationMs } from "./ffmpeg.js";
|
|
4
|
-
export async function audioInputs(deps, projectId, outputs, signal) {
|
|
5
|
-
const dir = projectDir(deps.paths, projectId);
|
|
6
|
-
const body = await audio(deps, outputs, "audio_body", dir, signal);
|
|
7
|
-
if (!body)
|
|
8
|
-
throw new Error("The video has no narration audio to play. Let the Narration stage finish, then Try again.");
|
|
9
|
-
if (body.seconds <= 0)
|
|
10
|
-
throw new Error("The narration audio contains no sound; the file may be damaged or empty. Regenerate the narration (or upload a different audio file in Edit project → Narration), then Try again.");
|
|
11
|
-
const intro = await audio(deps, outputs, "audio_intro", dir, signal);
|
|
12
|
-
const outro = await audio(deps, outputs, "audio_outro", dir, signal);
|
|
13
|
-
return { body, intro, outro };
|
|
14
|
-
}
|
|
15
|
-
async function audio(deps, outputs, role, dir, signal) {
|
|
16
|
-
const output = outputs.find((candidate) => candidate.role === role);
|
|
17
|
-
if (output === undefined) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
const path = outputPath(deps.paths, output.projectId, output.path);
|
|
21
|
-
const durationMs = output.durationMs ??
|
|
22
|
-
(deps.measureAudio === undefined
|
|
23
|
-
? await probeDurationMs(deps.ffmpeg, path, signal, deps.log)
|
|
24
|
-
: await deps.measureAudio(path, signal));
|
|
25
|
-
if (output.durationMs === null) {
|
|
26
|
-
deps.log.write("info", "video.probe", {
|
|
27
|
-
projectId: output.projectId,
|
|
28
|
-
stage: "video",
|
|
29
|
-
detail: `${relative(dir, path)} is ${durationMs} ms`,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return { path, seconds: durationMs / 1000 };
|
|
33
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { outputPath } from "../storage/layout.js";
|
|
4
|
-
const planSchema = z.object({
|
|
5
|
-
sampleRate: z.number(),
|
|
6
|
-
channels: z.number(),
|
|
7
|
-
codec: z.string(),
|
|
8
|
-
gapSeconds: z.number(),
|
|
9
|
-
// Absent from records written before the edge silence existed, which then never match.
|
|
10
|
-
edgeSeconds: z.number().optional(),
|
|
11
|
-
totalSeconds: z.number(),
|
|
12
|
-
audio: z.array(z.object({ kind: z.string(), path: z.string().nullable(), seconds: z.number() })),
|
|
13
|
-
output: z.string(),
|
|
14
|
-
sourceIds: z.array(z.string()).optional(),
|
|
15
|
-
sourceHashes: z
|
|
16
|
-
.array(z.object({ id: z.string(), sha256: z.string().regex(/^[a-f0-9]{64}$/) }))
|
|
17
|
-
.optional(),
|
|
18
|
-
});
|
|
19
|
-
// Old releases did not record source IDs. Their relative timeline plus source dates and
|
|
20
|
-
// file metadata let subtitle-only edits reuse those exports without a one-time re-encode.
|
|
21
|
-
export function reusableAudioExport(paths, projectId, outputs, plan) {
|
|
22
|
-
const media = outputs.find((output) => output.role === "audio_export");
|
|
23
|
-
const record = outputs.find((output) => output.role === "render_params");
|
|
24
|
-
if (media === undefined || record === undefined)
|
|
25
|
-
return undefined;
|
|
26
|
-
let saved;
|
|
27
|
-
try {
|
|
28
|
-
saved = planSchema.parse(JSON.parse(readFileSync(outputPath(paths, projectId, record.path), "utf8")));
|
|
29
|
-
const { sourceIds: previousIds, sourceHashes: previousHashes, ...previousPlan } = saved;
|
|
30
|
-
const { sourceIds, sourceHashes, ...currentPlan } = plan;
|
|
31
|
-
if (JSON.stringify(previousPlan) !== JSON.stringify(currentPlan) ||
|
|
32
|
-
(previousIds !== undefined && JSON.stringify(previousIds) !== JSON.stringify(sourceIds)) ||
|
|
33
|
-
(previousHashes !== undefined &&
|
|
34
|
-
JSON.stringify(previousHashes) !== JSON.stringify(sourceHashes)))
|
|
35
|
-
return undefined;
|
|
36
|
-
const wav = statSync(outputPath(paths, projectId, media.path));
|
|
37
|
-
if (!wav.isFile() ||
|
|
38
|
-
wav.size !== media.bytes ||
|
|
39
|
-
media.durationMs !== Math.round(plan.totalSeconds * 1000))
|
|
40
|
-
return undefined;
|
|
41
|
-
for (const segment of plan.audio) {
|
|
42
|
-
if (segment.path === null)
|
|
43
|
-
continue;
|
|
44
|
-
const source = outputs.find((output) => ["audio_body", "audio_intro", "audio_outro"].includes(output.role) &&
|
|
45
|
-
output.path === segment.path);
|
|
46
|
-
if (source === undefined ||
|
|
47
|
-
!Number.isFinite(Date.parse(source.createdAt)) ||
|
|
48
|
-
Date.parse(source.createdAt) > Date.parse(media.createdAt))
|
|
49
|
-
return undefined;
|
|
50
|
-
const file = statSync(outputPath(paths, projectId, source.path));
|
|
51
|
-
if (!file.isFile() ||
|
|
52
|
-
file.size !== source.bytes ||
|
|
53
|
-
(previousHashes === undefined && file.mtimeMs > wav.mtimeMs))
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
return media;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// Missing/corrupt old records or files require a normal export.
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
}
|
package/dist/slices/video/run.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { relative } from "node:path";
|
|
2
|
-
import { projectById } from "../admission/repo.js";
|
|
3
|
-
import { outputPath, projectDir } from "../storage/layout.js";
|
|
4
|
-
import { outputsOf } from "../storage/repo.js";
|
|
5
|
-
import { prepareSubtitles } from "../subtitles/prepare.js";
|
|
6
|
-
import { exportAudioWav } from "./audio-export.js";
|
|
7
|
-
import { audioInputs } from "./audio-inputs.js";
|
|
8
|
-
import { withPaths } from "./edit-list.js";
|
|
9
|
-
import { planRender } from "./plan.js";
|
|
10
|
-
import { renderSlideshow } from "./slideshow.js";
|
|
11
|
-
import { writeExport } from "./write-export.js";
|
|
12
|
-
export async function renderVideo(deps, context) {
|
|
13
|
-
const { projectId } = context.stage;
|
|
14
|
-
const project = projectById(deps.db, projectId);
|
|
15
|
-
if (!project)
|
|
16
|
-
throw new Error("This project no longer exists; it may have been deleted while it was running.");
|
|
17
|
-
if (project.config.sources.video === "off") {
|
|
18
|
-
if (project.config.sources.audio === "off")
|
|
19
|
-
throw new Error("There is nothing to export because both video and narration are turned off for this project. Turn one on in Edit project, then Try again.");
|
|
20
|
-
await exportAudioWav(deps, context, project.config.silenceGapSeconds, project.config.edgeSilenceSeconds);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const outputs = outputsOf(deps.db, projectId);
|
|
24
|
-
const dir = projectDir(deps.paths, projectId);
|
|
25
|
-
const narration = project.config.sources.audio === "off"
|
|
26
|
-
? {}
|
|
27
|
-
: await audioInputs(deps, projectId, outputs, context.signal);
|
|
28
|
-
const plan = planRender({
|
|
29
|
-
format: project.format,
|
|
30
|
-
gapSeconds: project.config.silenceGapSeconds,
|
|
31
|
-
edgeSeconds: project.config.edgeSilenceSeconds,
|
|
32
|
-
imageSeconds: project.config.imageSeconds,
|
|
33
|
-
zoomPercent: project.config.zoomPercent,
|
|
34
|
-
motionStyle: project.config.motionStyle,
|
|
35
|
-
...narration,
|
|
36
|
-
images: slideshow(outputs).map((output) => outputPath(deps.paths, projectId, output.path)),
|
|
37
|
-
output: outputPath(deps.paths, projectId, "video.mp4"),
|
|
38
|
-
});
|
|
39
|
-
const subtitles = await prepareSubtitles(deps, context, plan.editList.audio, plan.editList);
|
|
40
|
-
await writeExport(deps, context, {
|
|
41
|
-
role: "video",
|
|
42
|
-
filename: "video.mp4",
|
|
43
|
-
partName: "video.part.mp4",
|
|
44
|
-
totalSeconds: plan.totalSeconds,
|
|
45
|
-
record: { ...recorded(plan, dir), subtitles: project.config.subtitles ?? null },
|
|
46
|
-
subtitles,
|
|
47
|
-
render: async (part, onProgress) => {
|
|
48
|
-
await renderSlideshow({
|
|
49
|
-
bin: deps.ffmpeg,
|
|
50
|
-
edit: plan.editList,
|
|
51
|
-
output: part,
|
|
52
|
-
burnSubtitles: subtitles?.burnIn ?? false,
|
|
53
|
-
cwd: subtitles?.directory,
|
|
54
|
-
scratch: dir,
|
|
55
|
-
signal: context.signal,
|
|
56
|
-
log: deps.log,
|
|
57
|
-
onProgress,
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
deps.count("stage.completed", { stage: "video" });
|
|
62
|
-
}
|
|
63
|
-
// Written with project-relative paths, so the record of what was rendered can be read
|
|
64
|
-
// beside the files it names and carries no absolute path off the machine.
|
|
65
|
-
function recorded(plan, dir) {
|
|
66
|
-
return {
|
|
67
|
-
...plan,
|
|
68
|
-
editList: withPaths(plan.editList, (path) => relative(dir, path)),
|
|
69
|
-
output: relative(dir, plan.output),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// Every slideshow image, once each, in slideshow order; the plan cycles through them.
|
|
73
|
-
// The thumbnail is a different role, so it is never in this list.
|
|
74
|
-
function slideshow(outputs) {
|
|
75
|
-
return outputs
|
|
76
|
-
.filter((output) => output.role === "image")
|
|
77
|
-
.toSorted((left, right) => (left.meta.index ?? 0) - (right.meta.index ?? 0));
|
|
78
|
-
}
|