@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,293 @@
|
|
|
1
|
+
import { render } from "../admission/substitute.js";
|
|
2
|
+
import { assColour, assText } from "../video/ass-text.js";
|
|
3
|
+
export const minTextShare = 0.024;
|
|
4
|
+
const roleShare = {
|
|
5
|
+
headline: 0.11,
|
|
6
|
+
subhead: 0.06,
|
|
7
|
+
details: 0.045,
|
|
8
|
+
small: 0.03,
|
|
9
|
+
};
|
|
10
|
+
const lineSpacing = 1.2;
|
|
11
|
+
const maxFilled = 10_000;
|
|
12
|
+
const blockGap = 0.45;
|
|
13
|
+
// Average advance per em: about half for mixed case, more for capitals.
|
|
14
|
+
export function advanceOf(text) {
|
|
15
|
+
const letters = [...text].filter((ch) => /\p{L}/u.test(ch));
|
|
16
|
+
const upper = letters.filter((ch) => ch !== ch.toLowerCase()).length;
|
|
17
|
+
return letters.length > 0 && upper / letters.length > 0.6 ? 0.68 : 0.56;
|
|
18
|
+
}
|
|
19
|
+
export function estimatedWidth(text, size) {
|
|
20
|
+
return [...text].length * size * advanceOf(text);
|
|
21
|
+
}
|
|
22
|
+
// What a line takes on the frame: the font's advances when known, plus room for the outline.
|
|
23
|
+
export function measuredWidth(text, size, font) {
|
|
24
|
+
const advance = font?.advance;
|
|
25
|
+
if (advance === undefined)
|
|
26
|
+
return estimatedWidth(text, size);
|
|
27
|
+
const guess = advanceOf(text);
|
|
28
|
+
let em = 0;
|
|
29
|
+
for (const ch of text)
|
|
30
|
+
em += advance(ch.codePointAt(0) ?? 0) ?? guess;
|
|
31
|
+
return em * size * 1.02 + size * 0.08;
|
|
32
|
+
}
|
|
33
|
+
export function safeArea(frame, preset) {
|
|
34
|
+
const { width, height } = frame;
|
|
35
|
+
const margin = Math.round(Math.min(width, height) * 0.06);
|
|
36
|
+
switch (preset) {
|
|
37
|
+
case "top-title":
|
|
38
|
+
return {
|
|
39
|
+
x: margin,
|
|
40
|
+
y: margin,
|
|
41
|
+
width: width - margin * 2,
|
|
42
|
+
height: Math.round(height * 0.45) - margin,
|
|
43
|
+
};
|
|
44
|
+
case "bottom-band": {
|
|
45
|
+
const top = Math.round(height * 0.62);
|
|
46
|
+
return { x: margin, y: top, width: width - margin * 2, height: height - top - margin };
|
|
47
|
+
}
|
|
48
|
+
case "centered":
|
|
49
|
+
return { x: margin, y: margin, width: width - margin * 2, height: height - margin * 2 };
|
|
50
|
+
case "left-column":
|
|
51
|
+
return {
|
|
52
|
+
x: margin,
|
|
53
|
+
y: margin,
|
|
54
|
+
width: Math.round(width * 0.5) - margin,
|
|
55
|
+
height: height - margin * 2,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// A word this short is never cut: it moves to its own line, and the text shrinks if even that
|
|
60
|
+
// is too wide.
|
|
61
|
+
const shortWord = 8;
|
|
62
|
+
const numeric = /\p{N}/u;
|
|
63
|
+
// A currency or code written before the number ("CHF 15", "€ 12"), and a unit after it
|
|
64
|
+
// ("20:00 Uhr", "15 €", "5 km").
|
|
65
|
+
const unitBefore = /^(?:\p{Sc}{1,3}|\p{Lu}{2,4}\.?)$/u;
|
|
66
|
+
const unitAfter = /^[\p{L}\p{Sc}%°]{1,4}[.,;:!?]?$/u;
|
|
67
|
+
// The words that must stay on one line: a number with the unit or currency beside it.
|
|
68
|
+
export function wrapUnits(text) {
|
|
69
|
+
const words = text.split(/\s+/).filter((part) => part !== "");
|
|
70
|
+
const units = [];
|
|
71
|
+
for (const word of words) {
|
|
72
|
+
const last = units.at(-1);
|
|
73
|
+
const previous = last?.at(-1);
|
|
74
|
+
const open = last !== undefined && last.length === 1 && previous !== undefined;
|
|
75
|
+
const joins = open &&
|
|
76
|
+
((numeric.test(previous) && !numeric.test(word) && unitAfter.test(word)) ||
|
|
77
|
+
(numeric.test(word) && !numeric.test(previous) && unitBefore.test(previous)));
|
|
78
|
+
if (joins)
|
|
79
|
+
last.push(word);
|
|
80
|
+
else
|
|
81
|
+
units.push([word]);
|
|
82
|
+
}
|
|
83
|
+
return units.map((unit) => unit.join(" "));
|
|
84
|
+
}
|
|
85
|
+
// Greedy wrap by width. A long word wider than the line is broken, so nothing leaves the area
|
|
86
|
+
// sideways; a short word or a number with its unit is kept whole (the caller shrinks the text),
|
|
87
|
+
// and only `split` (at the smallest size) lets a unit go across two lines.
|
|
88
|
+
export function wrapText(text, size, room, font, split = false) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
let line = "";
|
|
91
|
+
const fits = (candidate) => measuredWidth(candidate, size, font) <= room;
|
|
92
|
+
const pieces = wrapUnits(text).flatMap((unit) => split && !fits(unit) ? unit.split(" ") : [unit]);
|
|
93
|
+
for (const word of pieces) {
|
|
94
|
+
const candidate = line === "" ? word : `${line} ${word}`;
|
|
95
|
+
if (fits(candidate)) {
|
|
96
|
+
line = candidate;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (line !== "")
|
|
100
|
+
lines.push(line);
|
|
101
|
+
line = "";
|
|
102
|
+
if ([...word].length <= shortWord || word.includes(" ")) {
|
|
103
|
+
line = word;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const cuts = cutWord([...word], size, room, font);
|
|
107
|
+
lines.push(...cuts.slice(0, -1));
|
|
108
|
+
line = cuts.at(-1) ?? "";
|
|
109
|
+
}
|
|
110
|
+
if (line !== "")
|
|
111
|
+
lines.push(line);
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
// A word too wide for the line, cut into pieces that each take the longest run of characters
|
|
115
|
+
// that fits (at least one). Each character's advance has a lowest and highest share (an
|
|
116
|
+
// unknown one is guessed between `advanceOf`'s two figures), so running sums rule most cut
|
|
117
|
+
// points in or out at once and only the few in between are measured exactly: the result is
|
|
118
|
+
// what measuring every cut point would give, without the time that takes on a long URL.
|
|
119
|
+
function cutWord(chars, size, room, font) {
|
|
120
|
+
const advance = font?.advance;
|
|
121
|
+
const low = [0];
|
|
122
|
+
const high = [0];
|
|
123
|
+
for (const ch of chars) {
|
|
124
|
+
const known = advance?.(ch.codePointAt(0) ?? 0);
|
|
125
|
+
if (known !== undefined && !(known >= 0))
|
|
126
|
+
return cutWordSlowly(chars, size, room, font);
|
|
127
|
+
low.push((low.at(-1) ?? 0) + (known ?? 0.56));
|
|
128
|
+
high.push((high.at(-1) ?? 0) + (known ?? 0.68));
|
|
129
|
+
}
|
|
130
|
+
const width = (em) => advance === undefined ? em * size : em * size * 1.02 + size * 0.08;
|
|
131
|
+
const slack = 1e-6 * (Math.abs(room) + size);
|
|
132
|
+
const lowAt = (from, to) => width((low[to] ?? 0) - (low[from] ?? 0));
|
|
133
|
+
const fitsRun = (from, to) => {
|
|
134
|
+
if (lowAt(from, to) > room + slack)
|
|
135
|
+
return false;
|
|
136
|
+
if (width((high[to] ?? 0) - (high[from] ?? 0)) < room - slack)
|
|
137
|
+
return true;
|
|
138
|
+
return measuredWidth(chars.slice(from, to).join(""), size, font) <= room;
|
|
139
|
+
};
|
|
140
|
+
const pieces = [];
|
|
141
|
+
let start = 0;
|
|
142
|
+
while (!fitsRun(start, chars.length)) {
|
|
143
|
+
const top = Math.max(1, chars.length - start - 1);
|
|
144
|
+
let below = 0;
|
|
145
|
+
let above = top + 1;
|
|
146
|
+
while (above - below > 1) {
|
|
147
|
+
const middle = Math.floor((below + above) / 2);
|
|
148
|
+
if (lowAt(start, start + middle) <= room + slack)
|
|
149
|
+
below = middle;
|
|
150
|
+
else
|
|
151
|
+
above = middle;
|
|
152
|
+
}
|
|
153
|
+
let cut = Math.max(1, below);
|
|
154
|
+
while (cut > 1 && !fitsRun(start, start + cut))
|
|
155
|
+
cut -= 1;
|
|
156
|
+
pieces.push(chars.slice(start, start + cut).join(""));
|
|
157
|
+
start += cut;
|
|
158
|
+
}
|
|
159
|
+
pieces.push(chars.slice(start).join(""));
|
|
160
|
+
return pieces;
|
|
161
|
+
}
|
|
162
|
+
function cutWordSlowly(chars, size, room, font) {
|
|
163
|
+
const fits = (candidate) => measuredWidth(candidate, size, font) <= room;
|
|
164
|
+
const pieces = [];
|
|
165
|
+
let rest = [...chars];
|
|
166
|
+
while (!fits(rest.join(""))) {
|
|
167
|
+
let cut = Math.max(1, rest.length - 1);
|
|
168
|
+
while (cut > 1 && !fits(rest.slice(0, cut).join("")))
|
|
169
|
+
cut -= 1;
|
|
170
|
+
pieces.push(rest.slice(0, cut).join(""));
|
|
171
|
+
rest = rest.slice(cut);
|
|
172
|
+
}
|
|
173
|
+
pieces.push(rest.join(""));
|
|
174
|
+
return pieces;
|
|
175
|
+
}
|
|
176
|
+
export function layoutText(frame, text, values, fonts) {
|
|
177
|
+
const short = Math.min(frame.width, frame.height);
|
|
178
|
+
const floor = Math.round(short * minTextShare);
|
|
179
|
+
const area = safeArea(frame, text.preset);
|
|
180
|
+
let cutShort = false;
|
|
181
|
+
const blocks = text.blocks
|
|
182
|
+
.map((block) => {
|
|
183
|
+
const filled = render(block.text, values).replace(/\s+/g, " ").trim();
|
|
184
|
+
// More than any format holds even at the smallest size: the rest is never laid out, and
|
|
185
|
+
// the piece says it doesn't fit.
|
|
186
|
+
const chars = filled.length > maxFilled ? [...filled] : [];
|
|
187
|
+
if (chars.length <= maxFilled)
|
|
188
|
+
return { ...block, filled };
|
|
189
|
+
cutShort = true;
|
|
190
|
+
return { ...block, filled: chars.slice(0, maxFilled).join("") };
|
|
191
|
+
})
|
|
192
|
+
.filter((block) => block.filled !== "");
|
|
193
|
+
const size = (role, scale) => Math.max(floor, Math.round(short * roleShare[role] * scale));
|
|
194
|
+
const sized = (scale, split = false) => blocks.map((block) => {
|
|
195
|
+
const at = size(block.role, scale);
|
|
196
|
+
return {
|
|
197
|
+
id: block.id,
|
|
198
|
+
lines: wrapText(block.filled, at, area.width, fonts[block.fontId], split),
|
|
199
|
+
size: at,
|
|
200
|
+
font: fonts[block.fontId],
|
|
201
|
+
fontName: fonts[block.fontId]?.assName ?? "Barlow",
|
|
202
|
+
color: block.color,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
const heightOf = (set) => set.reduce((sum, block, index) => sum +
|
|
206
|
+
block.lines.length * block.size * lineSpacing +
|
|
207
|
+
(index === 0 ? 0 : block.size * blockGap), 0);
|
|
208
|
+
const widest = (set) => Math.max(0, ...set.flatMap((block) => block.lines.map((line) => measuredWidth(line, block.size, block.font))));
|
|
209
|
+
const tooBig = (set) => heightOf(set) > area.height || widest(set) > area.width;
|
|
210
|
+
let scale = 1;
|
|
211
|
+
let set = sized(scale);
|
|
212
|
+
while (tooBig(set) && set.some((block) => block.size > floor)) {
|
|
213
|
+
scale *= 0.92;
|
|
214
|
+
set = sized(scale);
|
|
215
|
+
}
|
|
216
|
+
if (widest(set) > area.width)
|
|
217
|
+
set = sized(scale, true);
|
|
218
|
+
const total = heightOf(set);
|
|
219
|
+
const overflow = cutShort || total > area.height || widest(set) > area.width + 1;
|
|
220
|
+
const vertical = text.preset === "top-title" ? 0 : (area.height - Math.min(total, area.height)) / 2;
|
|
221
|
+
const left = text.preset === "left-column";
|
|
222
|
+
const lines = [];
|
|
223
|
+
let y = area.y + vertical;
|
|
224
|
+
for (const [index, block] of set.entries()) {
|
|
225
|
+
if (index > 0)
|
|
226
|
+
y += block.size * blockGap;
|
|
227
|
+
for (const line of block.lines) {
|
|
228
|
+
lines.push({
|
|
229
|
+
blockId: block.id,
|
|
230
|
+
text: line,
|
|
231
|
+
x: Math.round(left ? area.x : area.x + area.width / 2),
|
|
232
|
+
y: Math.round(y),
|
|
233
|
+
size: block.size,
|
|
234
|
+
width: Math.round(measuredWidth(line, block.size, block.font)),
|
|
235
|
+
});
|
|
236
|
+
y += block.size * lineSpacing;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const events = [
|
|
240
|
+
...scrimEvents(text, frame, area, lines),
|
|
241
|
+
...lines.map((line) => {
|
|
242
|
+
const block = set.find((one) => one.id === line.blockId);
|
|
243
|
+
const outline = text.scrim === "none" ? Math.max(1, Math.round(line.size * 0.035)) : 0;
|
|
244
|
+
return dialogue(1, `{\\an${left ? "7" : "8"}\\pos(${String(line.x)},${String(line.y)})\\fn${(block?.fontName ?? "Barlow").replace(/[\\{}]/g, "")}\\fs${String(line.size)}\\c${assColour(block?.color ?? "#FFFFFF")}\\bord${String(outline)}\\3c&H000000&\\shad0}${assText(line.text)}`);
|
|
245
|
+
}),
|
|
246
|
+
];
|
|
247
|
+
return { ass: script(frame, events), area, lines, overflow };
|
|
248
|
+
}
|
|
249
|
+
function scrimEvents({ scrim, preset }, frame, area, lines) {
|
|
250
|
+
if (scrim === "none" || lines.length === 0)
|
|
251
|
+
return [];
|
|
252
|
+
const first = lines[0];
|
|
253
|
+
const last = lines.at(-1);
|
|
254
|
+
if (first === undefined || last === undefined)
|
|
255
|
+
return [];
|
|
256
|
+
const pad = Math.round(Math.min(frame.width, frame.height) * 0.03);
|
|
257
|
+
// A band under a bottom or top title runs to that edge of the frame.
|
|
258
|
+
const band = scrim === "band";
|
|
259
|
+
const top = band && preset === "top-title" ? 0 : Math.max(0, first.y - pad);
|
|
260
|
+
const bottom = band && preset === "bottom-band"
|
|
261
|
+
? frame.height
|
|
262
|
+
: Math.min(frame.height, last.y + last.size * lineSpacing + pad);
|
|
263
|
+
// A band runs the frame's full width; a soft scrim sits behind the text area only.
|
|
264
|
+
const [x0, x1, alpha] = band
|
|
265
|
+
? [0, frame.width, "&H40&"]
|
|
266
|
+
: [Math.max(0, area.x - pad), Math.min(frame.width, area.x + area.width + pad), "&H80&"];
|
|
267
|
+
const corners = [x0, top, x1, bottom].map((n) => String(Math.round(n)));
|
|
268
|
+
const [a, b, c, d] = corners;
|
|
269
|
+
return [
|
|
270
|
+
dialogue(0, `{\\an7\\pos(0,0)\\p1\\bord0\\shad0\\c&H000000&\\1a${alpha}}m ${a ?? "0"} ${b ?? "0"} l ${c ?? "0"} ${b ?? "0"} l ${c ?? "0"} ${d ?? "0"} l ${a ?? "0"} ${d ?? "0"}{\\p0}`),
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
function dialogue(layer, body) {
|
|
274
|
+
return `Dialogue: ${String(layer)},0:00:00.00,0:00:10.00,Design,,0,0,0,,${body}`;
|
|
275
|
+
}
|
|
276
|
+
function script(frame, events) {
|
|
277
|
+
return `${[
|
|
278
|
+
"[Script Info]",
|
|
279
|
+
"ScriptType: v4.00+",
|
|
280
|
+
`PlayResX: ${String(frame.width)}`,
|
|
281
|
+
`PlayResY: ${String(frame.height)}`,
|
|
282
|
+
"WrapStyle: 2",
|
|
283
|
+
"ScaledBorderAndShadow: yes",
|
|
284
|
+
"",
|
|
285
|
+
"[V4+ Styles]",
|
|
286
|
+
"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding",
|
|
287
|
+
`Style: Design,Barlow,${String(Math.round(Math.min(frame.width, frame.height) * 0.04))},&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1`,
|
|
288
|
+
"",
|
|
289
|
+
"[Events]",
|
|
290
|
+
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text",
|
|
291
|
+
...events,
|
|
292
|
+
].join("\n")}\n`;
|
|
293
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// A design is a picture edited step by step ("edit") or a designed piece whose text Slopify
|
|
2
|
+
// lays out on the AI's artwork ("design"). Every step is one version of the picture.
|
|
3
|
+
export const designKinds = ["edit", "design"];
|
|
4
|
+
export const stepKinds = ["upload", "edit", "generate"];
|
|
5
|
+
export const stepStates = ["making", "ready", "failed"];
|
|
6
|
+
export const designFormats = [
|
|
7
|
+
"a4",
|
|
8
|
+
"a3",
|
|
9
|
+
"flyer",
|
|
10
|
+
"square",
|
|
11
|
+
"portrait",
|
|
12
|
+
"story",
|
|
13
|
+
"wide",
|
|
14
|
+
"brochure",
|
|
15
|
+
];
|
|
16
|
+
export const formatLabels = {
|
|
17
|
+
a4: "Poster A4",
|
|
18
|
+
a3: "Poster A3",
|
|
19
|
+
flyer: "Flyer A5",
|
|
20
|
+
square: "Square 1:1",
|
|
21
|
+
portrait: "Portrait 4:5",
|
|
22
|
+
story: "Story 9:16",
|
|
23
|
+
wide: "Wide 16:9",
|
|
24
|
+
brochure: "Brochure A4",
|
|
25
|
+
};
|
|
26
|
+
export const layoutPresets = ["top-title", "bottom-band", "centered", "left-column"];
|
|
27
|
+
export const scrims = ["none", "soft", "band"];
|
|
28
|
+
export const blockRoles = ["headline", "subhead", "details", "small"];
|
|
29
|
+
export const designNameMax = 200;
|
|
30
|
+
export const instructionMax = 4000;
|
|
31
|
+
export const promptMax = 4000;
|
|
32
|
+
export const blocksMax = 8;
|
|
33
|
+
export const blockTextMax = 500;
|
|
34
|
+
export const castPerDesignMax = 3;
|
|
35
|
+
export const daysBeforeMax = 90;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { documentSettingsSchema } from "../document/theme-schema.js";
|
|
3
|
+
const bytes = z.custom((value) => value instanceof Uint8Array);
|
|
4
|
+
const millimetres = z.number().positive().max(2000);
|
|
5
|
+
export const pdfJobSchema = z.discriminatedUnion("kind", [
|
|
6
|
+
z
|
|
7
|
+
.object({ kind: z.literal("poster"), png: bytes, widthMm: millimetres, heightMm: millimetres })
|
|
8
|
+
.strict(),
|
|
9
|
+
z
|
|
10
|
+
.object({
|
|
11
|
+
kind: z.literal("brochure"),
|
|
12
|
+
cover: bytes,
|
|
13
|
+
title: z.string().min(1).max(200),
|
|
14
|
+
markdown: z.string().min(1).max(100_000),
|
|
15
|
+
settings: documentSettingsSchema,
|
|
16
|
+
writtenOn: z.string(),
|
|
17
|
+
})
|
|
18
|
+
.strict(),
|
|
19
|
+
]);
|
|
20
|
+
export const pdfWorkerMessage = z.discriminatedUnion("type", [
|
|
21
|
+
z.object({ type: z.literal("done"), bytes }).strict(),
|
|
22
|
+
z.object({ type: z.literal("error"), message: z.string().max(4000) }).strict(),
|
|
23
|
+
]);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsPDF } from "jspdf";
|
|
2
|
+
import { renderDocument } from "../document/render.js";
|
|
3
|
+
import { resolvedDocumentTheme } from "../document/theme.js";
|
|
4
|
+
// The PDFs themselves, drawn by jsPDF. Called only inside the PDF worker (`pdf-worker.ts`):
|
|
5
|
+
// jsPDF is synchronous and a print page is large, so the server never runs it in its thread.
|
|
6
|
+
// One page of the given size, the drawn piece filling it edge to edge. No bleed or crop
|
|
7
|
+
// marks: the PNG is already 300 dpi of the page.
|
|
8
|
+
export function posterPdf(job) {
|
|
9
|
+
const doc = new jsPDF({
|
|
10
|
+
orientation: job.widthMm > job.heightMm ? "landscape" : "portrait",
|
|
11
|
+
unit: "mm",
|
|
12
|
+
format: [job.widthMm, job.heightMm],
|
|
13
|
+
compress: true,
|
|
14
|
+
});
|
|
15
|
+
doc.addImage(job.png, "PNG", 0, 0, job.widthMm, job.heightMm, undefined, "FAST");
|
|
16
|
+
return new Uint8Array(doc.output("arraybuffer"));
|
|
17
|
+
}
|
|
18
|
+
// The theme's document: the designed cover on its title page, then the brochure's pages.
|
|
19
|
+
export function brochurePdf(job, assets) {
|
|
20
|
+
const rendered = renderDocument({
|
|
21
|
+
title: job.title,
|
|
22
|
+
articleMarkdown: job.markdown,
|
|
23
|
+
researchNotes: null,
|
|
24
|
+
cover: job.cover,
|
|
25
|
+
theme: resolvedDocumentTheme(job.settings),
|
|
26
|
+
writtenOn: new Date(job.writtenOn),
|
|
27
|
+
assets,
|
|
28
|
+
});
|
|
29
|
+
return { bytes: rendered.bytes, pages: rendered.pages, cover: rendered.cover };
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import { pdfWorkerMessage } from "./pdf-job.js";
|
|
3
|
+
// Draws one design PDF in a forked worker, so jsPDF's synchronous work never holds up the
|
|
4
|
+
// server. Bytes travel both ways over IPC with structured-clone serialization; a cancel kills
|
|
5
|
+
// the worker outright. The worker's address is injectable for tests.
|
|
6
|
+
export const pdfWorkerTimeoutMs = 5 * 60_000;
|
|
7
|
+
export function runPdfWorker(job, signal, worker = new URL("./pdf-worker.js", import.meta.url), timeoutMs = pdfWorkerTimeoutMs) {
|
|
8
|
+
signal.throwIfAborted();
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const options = {
|
|
11
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
12
|
+
windowsHide: true,
|
|
13
|
+
execArgv: [],
|
|
14
|
+
serialization: "advanced",
|
|
15
|
+
};
|
|
16
|
+
const child = fork(worker, [], options);
|
|
17
|
+
let finishing = false;
|
|
18
|
+
let failure;
|
|
19
|
+
let output = new Uint8Array();
|
|
20
|
+
let stderr = "";
|
|
21
|
+
const timer = setTimeout(() => finish(new Error("Making the PDF took longer than 5 minutes, so it was stopped. Press Download PDF again.")), timeoutMs);
|
|
22
|
+
const finish = (error, bytes) => {
|
|
23
|
+
if (finishing)
|
|
24
|
+
return;
|
|
25
|
+
finishing = true;
|
|
26
|
+
failure = error;
|
|
27
|
+
output = bytes ?? new Uint8Array();
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
signal.removeEventListener("abort", abort);
|
|
30
|
+
child.kill("SIGKILL");
|
|
31
|
+
};
|
|
32
|
+
const abort = () => finish(new Error("Making the PDF was canceled."));
|
|
33
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
34
|
+
child.stderr?.on("data", (data) => {
|
|
35
|
+
stderr = (stderr + data.toString("utf8")).slice(-2000);
|
|
36
|
+
});
|
|
37
|
+
child.on("error", (error) => finish(error));
|
|
38
|
+
child.on("close", (code) => {
|
|
39
|
+
if (!finishing)
|
|
40
|
+
finish(new Error(`Slopify's PDF maker stopped (exit ${String(code)}). ${stderr}`.trim()));
|
|
41
|
+
if (failure !== undefined)
|
|
42
|
+
reject(failure);
|
|
43
|
+
else
|
|
44
|
+
resolve(output);
|
|
45
|
+
});
|
|
46
|
+
child.on("message", (raw) => {
|
|
47
|
+
if (finishing)
|
|
48
|
+
return;
|
|
49
|
+
const parsed = pdfWorkerMessage.safeParse(raw);
|
|
50
|
+
if (!parsed.success) {
|
|
51
|
+
finish(new Error("Slopify's PDF maker answered with something it can't read. Press Download PDF again."));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (parsed.data.type === "error")
|
|
55
|
+
finish(new Error(`The PDF couldn't be made: ${parsed.data.message}`));
|
|
56
|
+
else
|
|
57
|
+
finish(undefined, parsed.data.bytes);
|
|
58
|
+
});
|
|
59
|
+
if (signal.aborted) {
|
|
60
|
+
abort();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
child.send(job, (error) => {
|
|
64
|
+
if (error !== null)
|
|
65
|
+
finish(error);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { errorText } from "../../kernel/errors.js";
|
|
2
|
+
import { readDocumentAssets } from "../document/fonts.js";
|
|
3
|
+
import { pdfJobSchema } from "./pdf-job.js";
|
|
4
|
+
import { brochurePdf, posterPdf } from "./pdf-pages.js";
|
|
5
|
+
// The forked process a design's PDF is drawn in (`pdf-runner.ts`): one job in, its bytes or
|
|
6
|
+
// what went wrong out, then it exits.
|
|
7
|
+
function answer(message) {
|
|
8
|
+
process.send?.(message, () => process.exit(0));
|
|
9
|
+
}
|
|
10
|
+
process.once("message", (raw) => {
|
|
11
|
+
const parsed = pdfJobSchema.safeParse(raw);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
answer({ type: "error", message: "The PDF worker was sent a job it can't read." });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const job = parsed.data;
|
|
18
|
+
const bytes = job.kind === "poster" ? posterPdf(job) : brochurePdf(job, readDocumentAssets()).bytes;
|
|
19
|
+
answer({ type: "done", bytes });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
answer({ type: "error", message: errorText(error).slice(0, 4000) });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The width and height a PNG's IHDR or a JPEG's start-of-frame states; undefined for anything
|
|
2
|
+
// truncated or not one of the two.
|
|
3
|
+
export function pictureSize(bytes) {
|
|
4
|
+
const size = bytes[0] === 0x89 && bytes[1] === 0x50
|
|
5
|
+
? pngSize(bytes)
|
|
6
|
+
: bytes[0] === 0xff
|
|
7
|
+
? jpegSize(bytes)
|
|
8
|
+
: undefined;
|
|
9
|
+
return size !== undefined && size.width > 0 && size.height > 0 ? size : undefined;
|
|
10
|
+
}
|
|
11
|
+
function pngSize(bytes) {
|
|
12
|
+
if (bytes.length < 24)
|
|
13
|
+
return undefined;
|
|
14
|
+
const ihdr = String.fromCharCode(...bytes.subarray(12, 16));
|
|
15
|
+
if (ihdr !== "IHDR")
|
|
16
|
+
return undefined;
|
|
17
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
18
|
+
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
19
|
+
}
|
|
20
|
+
// Markers that carry no length: start of image, TEM and the restart markers.
|
|
21
|
+
const standalone = (marker) => marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7);
|
|
22
|
+
// Every SOFn except DHT (C4), JPG (C8) and DAC (CC), which share the range.
|
|
23
|
+
const startOfFrame = (marker) => marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
24
|
+
function jpegSize(bytes) {
|
|
25
|
+
if (bytes[1] !== 0xd8)
|
|
26
|
+
return undefined;
|
|
27
|
+
let at = 2;
|
|
28
|
+
while (at + 1 < bytes.length) {
|
|
29
|
+
if (bytes[at] !== 0xff)
|
|
30
|
+
return undefined;
|
|
31
|
+
let marker = bytes[at + 1] ?? 0;
|
|
32
|
+
while (marker === 0xff && at + 2 < bytes.length) {
|
|
33
|
+
at += 1;
|
|
34
|
+
marker = bytes[at + 1] ?? 0;
|
|
35
|
+
}
|
|
36
|
+
if (standalone(marker)) {
|
|
37
|
+
at += 2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// End of image or start of scan before any frame: no size to read.
|
|
41
|
+
if (marker === 0xd9 || marker === 0xda)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (at + 3 >= bytes.length)
|
|
44
|
+
return undefined;
|
|
45
|
+
const length = ((bytes[at + 2] ?? 0) << 8) | (bytes[at + 3] ?? 0);
|
|
46
|
+
if (length < 2)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (startOfFrame(marker)) {
|
|
49
|
+
if (at + 8 >= bytes.length || length < 7)
|
|
50
|
+
return undefined;
|
|
51
|
+
return {
|
|
52
|
+
height: ((bytes[at + 5] ?? 0) << 8) | (bytes[at + 6] ?? 0),
|
|
53
|
+
width: ((bytes[at + 7] ?? 0) << 8) | (bytes[at + 8] ?? 0),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
at += 2 + length;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const shapes = [
|
|
61
|
+
{ format: "16:9", ratio: 16 / 9 },
|
|
62
|
+
{ format: "1:1", ratio: 1 },
|
|
63
|
+
{ format: "9:16", ratio: 9 / 16 },
|
|
64
|
+
];
|
|
65
|
+
// The provider shape closest to the picture's, measured on a log scale so 2:1 and 1:2 are
|
|
66
|
+
// equally far from square.
|
|
67
|
+
export function nearestFormat(size) {
|
|
68
|
+
const ratio = Math.log(size.width / size.height);
|
|
69
|
+
let best = { format: "1:1", ratio: 1 };
|
|
70
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
71
|
+
for (const shape of shapes) {
|
|
72
|
+
const d = Math.abs(Math.log(shape.ratio) - ratio);
|
|
73
|
+
if (d < distance) {
|
|
74
|
+
best = shape;
|
|
75
|
+
distance = d;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best.format;
|
|
79
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { castImageMaxBytes, sniffImage } from "../channels/images.js";
|
|
3
|
+
import { imageBlob } from "../channels/repo.js";
|
|
4
|
+
import { stagingPath } from "../storage/layout.js";
|
|
5
|
+
import { stagedFileById } from "../storage/repo.js";
|
|
6
|
+
import { pictureSize } from "./picture-size.js";
|
|
7
|
+
export function readPicture(bytes) {
|
|
8
|
+
if (bytes.byteLength > castImageMaxBytes)
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
reason: "not-an-image",
|
|
12
|
+
message: `This picture is larger than ${String(castImageMaxBytes / 1024 / 1024)} MB. Save it smaller (for example as a JPEG) and upload it again.`,
|
|
13
|
+
};
|
|
14
|
+
const mime = sniffImage(bytes);
|
|
15
|
+
const size = mime === undefined ? undefined : pictureSize(bytes);
|
|
16
|
+
if (mime === undefined || size === undefined)
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
reason: "not-an-image",
|
|
20
|
+
message: "This file is not a PNG or JPEG picture Slopify can read. Upload a PNG or JPEG file.",
|
|
21
|
+
};
|
|
22
|
+
return { ok: true, value: { bytes, mime, ...size } };
|
|
23
|
+
}
|
|
24
|
+
export function startingPicture(deps, input) {
|
|
25
|
+
if (input.stagedFileId !== undefined) {
|
|
26
|
+
const staged = stagedFileById(deps.db, input.stagedFileId);
|
|
27
|
+
if (staged === undefined)
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
reason: "upload-gone",
|
|
31
|
+
message: "The uploaded picture is gone. Upload it again.",
|
|
32
|
+
};
|
|
33
|
+
if (staged.state !== "staged")
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "still-uploading",
|
|
37
|
+
message: "The picture is still uploading. Wait for it to finish, then try again.",
|
|
38
|
+
};
|
|
39
|
+
if (staged.bytes > castImageMaxBytes)
|
|
40
|
+
return readPicture(new Uint8Array(castImageMaxBytes + 1));
|
|
41
|
+
const read = readPicture(new Uint8Array(readFileSync(stagingPath(deps.paths, staged.path))));
|
|
42
|
+
return read.ok ? { ok: true, value: { ...read.value, stagedId: staged.id } } : read;
|
|
43
|
+
}
|
|
44
|
+
if (input.fromStepSha !== undefined) {
|
|
45
|
+
const blob = imageBlob(deps.db, input.fromStepSha);
|
|
46
|
+
if (blob === undefined)
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: "not-found",
|
|
50
|
+
message: "That picture is no longer kept. Choose another version, or upload the picture again.",
|
|
51
|
+
};
|
|
52
|
+
return readPicture(blob.bytes);
|
|
53
|
+
}
|
|
54
|
+
return { ok: true, value: undefined };
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { estimateRequests } from "../estimate/requests.js";
|
|
2
|
+
// What one paid picture of a design costs: an edit (which sends pictures, so its price is a
|
|
3
|
+
// range) or the artwork. Quoting records nothing.
|
|
4
|
+
export function quoteDesignImage(catalogue, request) {
|
|
5
|
+
return estimateRequests([
|
|
6
|
+
{
|
|
7
|
+
kind: "image",
|
|
8
|
+
provider: request.provider,
|
|
9
|
+
model: request.model,
|
|
10
|
+
stage: request.purpose === "picture-edit" ? "Picture edit" : "Artwork",
|
|
11
|
+
variable: request.sendsPictures,
|
|
12
|
+
},
|
|
13
|
+
], catalogue);
|
|
14
|
+
}
|
|
15
|
+
// A paid step starts only when what it costs now is no more than what the person was shown,
|
|
16
|
+
// give or take half a cent of rounding, and a price nobody knows only once they accepted that.
|
|
17
|
+
export function costChanged(quote, acknowledgedHigh, acknowledgedUnknown = false) {
|
|
18
|
+
return quote.high > acknowledgedHigh + 0.005 || (quote.unknown > 0 && !acknowledgedUnknown);
|
|
19
|
+
}
|
|
20
|
+
export function costChangedMessage(quote, what) {
|
|
21
|
+
if (quote.unknown > 0)
|
|
22
|
+
return `The price changed since you looked: ${what} has no known price now, so nothing was started. Check the button, which says the price is unknown, and press it again to make it anyway.`;
|
|
23
|
+
return `The price changed since you looked: ${what} now costs up to $${quote.high.toFixed(2)}. Check the new price on the button and press it again.`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The ffmpeg arguments that draw a piece and shrink a picture.
|
|
2
|
+
export function renderArgs(input) {
|
|
3
|
+
const { crop, frame } = input;
|
|
4
|
+
const chain = [
|
|
5
|
+
`scale=${String(crop.scaleWidth)}:${String(crop.scaleHeight)}:flags=lanczos`,
|
|
6
|
+
`crop=${String(frame.width)}:${String(frame.height)}:${String(crop.x)}:${String(crop.y)}`,
|
|
7
|
+
...(input.text ? ["ass=filename=design.ass:fontsdir=fonts"] : []),
|
|
8
|
+
"format=rgb24",
|
|
9
|
+
];
|
|
10
|
+
return [
|
|
11
|
+
...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-i", input.art],
|
|
12
|
+
...[
|
|
13
|
+
"-filter_complex",
|
|
14
|
+
`[0:v]${chain.join(",")}[v]`,
|
|
15
|
+
"-map",
|
|
16
|
+
"[v]",
|
|
17
|
+
"-frames:v",
|
|
18
|
+
"1",
|
|
19
|
+
input.output,
|
|
20
|
+
],
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
// What a picture is shrunk to before it is sent to a provider again: at most 4096 pixels on
|
|
24
|
+
// its long side, as a JPEG.
|
|
25
|
+
export function shrinkArgs(input, output) {
|
|
26
|
+
return [
|
|
27
|
+
...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-i", input],
|
|
28
|
+
...[
|
|
29
|
+
"-vf",
|
|
30
|
+
"scale=w='min(iw,4096)':h='min(ih,4096)':force_original_aspect_ratio=decrease",
|
|
31
|
+
"-q:v",
|
|
32
|
+
"3",
|
|
33
|
+
output,
|
|
34
|
+
],
|
|
35
|
+
];
|
|
36
|
+
}
|