@gentbajko/slopify 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { apiEquivalentOf, tokenCost } from "../run-cost/pricing.js";
|
|
3
|
+
import { isLocalCliProvider } from "../settings/model.js";
|
|
2
4
|
const quantity = z.number().finite().nonnegative();
|
|
3
5
|
const common = z.object({
|
|
4
6
|
stage: z.string(),
|
|
@@ -6,7 +8,12 @@ const common = z.object({
|
|
|
6
8
|
variable: z.boolean().optional(),
|
|
7
9
|
expectedWords: quantity.optional(),
|
|
8
10
|
});
|
|
9
|
-
const provider = common.extend({
|
|
11
|
+
const provider = common.extend({
|
|
12
|
+
provider: z.string(),
|
|
13
|
+
model: z.string(),
|
|
14
|
+
// A CLI request whose API figure cannot be told in advance.
|
|
15
|
+
apiUnknown: z.literal(true).optional(),
|
|
16
|
+
});
|
|
10
17
|
const requestSchema = z.discriminatedUnion("kind", [
|
|
11
18
|
provider
|
|
12
19
|
.extend({ kind: z.literal("llm"), inputCharacters: quantity, outputCharacters: quantity })
|
|
@@ -26,6 +33,7 @@ export function estimateRequests(requests, catalogue) {
|
|
|
26
33
|
low: rows.reduce((total, row) => total + (row.low ?? 0), 0),
|
|
27
34
|
high: rows.reduce((total, row) => total + (row.high ?? 0), 0),
|
|
28
35
|
unknown: rows.filter((row) => row.low === null).length,
|
|
36
|
+
...apiTotals(rows),
|
|
29
37
|
expectedWords: parsed.reduce((total, request) => total + (request.expectedWords ?? 0), 0),
|
|
30
38
|
catalogueDate: catalogue.updatedAt,
|
|
31
39
|
assumptions: [
|
|
@@ -39,16 +47,64 @@ export function groupEstimateRows(estimate) {
|
|
|
39
47
|
const grouped = new Map();
|
|
40
48
|
for (const row of estimate.rows) {
|
|
41
49
|
const previous = grouped.get(row.stage);
|
|
50
|
+
const plan = previous?.onPlan === true || row.onPlan === true;
|
|
51
|
+
const sum = (left, right) => (left === null || right === null ? null : (left ?? 0) + (right ?? 0));
|
|
42
52
|
grouped.set(row.stage, previous === undefined
|
|
43
53
|
? row
|
|
44
54
|
: {
|
|
45
55
|
...previous,
|
|
46
56
|
low: previous.low === null || row.low === null ? null : previous.low + row.low,
|
|
47
57
|
high: previous.high === null || row.high === null ? null : previous.high + row.high,
|
|
58
|
+
...(plan
|
|
59
|
+
? {
|
|
60
|
+
onPlan: true,
|
|
61
|
+
apiLow: sum(previous.apiLow, row.apiLow),
|
|
62
|
+
apiHigh: sum(previous.apiHigh, row.apiHigh),
|
|
63
|
+
}
|
|
64
|
+
: {}),
|
|
48
65
|
});
|
|
49
66
|
}
|
|
50
67
|
const rows = [...grouped.values()];
|
|
51
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
...estimate,
|
|
70
|
+
rows,
|
|
71
|
+
unknown: rows.filter((row) => row.low === null).length,
|
|
72
|
+
...apiTotals(rows),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function apiTotals(rows) {
|
|
76
|
+
const plan = rows.filter((row) => row.onPlan === true);
|
|
77
|
+
if (plan.length === 0)
|
|
78
|
+
return {};
|
|
79
|
+
return {
|
|
80
|
+
apiLow: plan.reduce((total, row) => total + (row.apiLow ?? 0), 0),
|
|
81
|
+
apiHigh: plan.reduce((total, row) => total + (row.apiHigh ?? 0), 0),
|
|
82
|
+
apiUnknown: plan.filter((row) => row.apiLow === null || row.apiLow === undefined).length,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// A CLI call adds nothing to the bill - it runs on the user's plan - so it is known at $0,
|
|
86
|
+
// with what the same tokens would cost through the API beside it, priced by the same
|
|
87
|
+
// catalogue and the same token assumptions as a keyed model. A Codex image has no API
|
|
88
|
+
// figure: how many drafts the agent draws is not known in advance.
|
|
89
|
+
function onPlan(request, catalogue) {
|
|
90
|
+
const api = request.kind === "llm"
|
|
91
|
+
? apiEquivalentOf(request.provider, request.model, catalogue)
|
|
92
|
+
: undefined;
|
|
93
|
+
const amount = api === undefined || request.kind !== "llm" || request.apiUnknown === true
|
|
94
|
+
? null
|
|
95
|
+
: tokenCost(api.pricing, request.inputCharacters / 4, request.outputCharacters / 4);
|
|
96
|
+
return {
|
|
97
|
+
stage: request.stage,
|
|
98
|
+
low: 0,
|
|
99
|
+
high: 0,
|
|
100
|
+
detail: request.detail ??
|
|
101
|
+
(amount === null
|
|
102
|
+
? "Runs on your CLI plan, so it adds no charge; no API price is listed for this model."
|
|
103
|
+
: `Runs on your CLI plan, so it adds no charge; priced as ${api?.name ?? "the API model"} through the API.`),
|
|
104
|
+
onPlan: true,
|
|
105
|
+
apiLow: amount === null ? null : amount * 0.5,
|
|
106
|
+
apiHigh: amount === null ? null : amount * 1.5,
|
|
107
|
+
};
|
|
52
108
|
}
|
|
53
109
|
function price(request, catalogue) {
|
|
54
110
|
if (request.kind === "unknown")
|
|
@@ -65,6 +121,8 @@ function price(request, catalogue) {
|
|
|
65
121
|
high: 0,
|
|
66
122
|
detail: request.detail ?? "Local processing or retained output; no API fee.",
|
|
67
123
|
};
|
|
124
|
+
if ((request.kind === "llm" || request.kind === "image") && isLocalCliProvider(request.provider))
|
|
125
|
+
return onPlan(request, catalogue);
|
|
68
126
|
const family = request.kind === "tts-estimate" ? "tts" : request.kind;
|
|
69
127
|
const model = catalogue[family].find((entry) => entry.provider === request.provider &&
|
|
70
128
|
entry.id === request.model &&
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { providers } from "../settings/model.js";
|
|
2
|
+
const signIn = {
|
|
3
|
+
"claude-code": { name: "Claude Code", command: "claude auth login" },
|
|
4
|
+
codex: { name: "Codex", command: "codex login" },
|
|
5
|
+
gemini: { name: "Gemini", command: "gemini" },
|
|
6
|
+
};
|
|
7
|
+
const signedOut = /not signed in|sign-in has expired|not logged in|run\s+"?(?:codex login|claude auth login)|authentication required/i;
|
|
8
|
+
const diskFull = /ENOSPC|no space left on device|disk (?:is )?full|not enough (?:free )?(?:disk )?space/i;
|
|
9
|
+
const refusedWords = /refused to make|content (?:rules|policy|filter)|safety (?:system|filter)|flagged|moderation/i;
|
|
10
|
+
const retiredModel = /\bunknown model\b|\bmodel\b[^\n]{0,80}?\b(?:not found|does not exist|retired|deprecated|decommissioned|no longer (?:available|supported)|is not available)/i;
|
|
11
|
+
const keyTrouble = /did not accept the API key|API key is saved|invalid api key|key may be wrong/i;
|
|
12
|
+
// Stages whose failed call draws an image from a prompt, which Soften and retry can reword.
|
|
13
|
+
const drawn = ["images", "thumbnail"];
|
|
14
|
+
export function fixFor(step) {
|
|
15
|
+
const reason = step.reason ?? "";
|
|
16
|
+
const cli = cliOf(step.provider, reason);
|
|
17
|
+
if (cli !== undefined && (signedOut.test(reason) || step.kind === "auth"))
|
|
18
|
+
return {
|
|
19
|
+
kind: "sign-in",
|
|
20
|
+
label: `Sign in to ${signIn[cli].name}`,
|
|
21
|
+
cli,
|
|
22
|
+
command: signIn[cli].command,
|
|
23
|
+
};
|
|
24
|
+
if (diskFull.test(reason))
|
|
25
|
+
return { kind: "free-space", label: "Free space" };
|
|
26
|
+
if (step.kind === "refusal" || refusedWords.test(reason))
|
|
27
|
+
return drawn.includes(step.stage)
|
|
28
|
+
? { kind: "refused", label: "Soften and retry", soften: true }
|
|
29
|
+
: { kind: "refused", label: "Edit prompt", soften: false };
|
|
30
|
+
if (retiredModel.test(reason))
|
|
31
|
+
return { kind: "switch-model", label: "Switch model" };
|
|
32
|
+
if (step.kind === "auth" || step.kind === "missing_key" || keyTrouble.test(reason)) {
|
|
33
|
+
const name = nameOf(step.provider);
|
|
34
|
+
return {
|
|
35
|
+
kind: "provider-settings",
|
|
36
|
+
label: name === undefined ? "Open Settings → Providers" : `Open Settings → Providers → ${name}`,
|
|
37
|
+
provider: step.provider ?? "",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
// The CLI a failure names: the project's own choice, or the one its sentence mentions.
|
|
43
|
+
function cliOf(provider, reason) {
|
|
44
|
+
if (provider === "claude-code" || provider === "gemini")
|
|
45
|
+
return provider;
|
|
46
|
+
if (provider === "codex" || provider === "codex-image")
|
|
47
|
+
return "codex";
|
|
48
|
+
if (/Codex CLI/.test(reason))
|
|
49
|
+
return "codex";
|
|
50
|
+
if (/Claude Code CLI/.test(reason))
|
|
51
|
+
return "claude-code";
|
|
52
|
+
if (/Gemini CLI/.test(reason))
|
|
53
|
+
return "gemini";
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function nameOf(provider) {
|
|
57
|
+
return providers.find((one) => one.id === provider)?.displayName;
|
|
58
|
+
}
|
|
59
|
+
// Which provider a stage's calls go to, from the project's choices.
|
|
60
|
+
export function stageProvider(config, stage) {
|
|
61
|
+
switch (stage) {
|
|
62
|
+
case "audio":
|
|
63
|
+
return config.audio?.provider;
|
|
64
|
+
case "images":
|
|
65
|
+
case "thumbnail":
|
|
66
|
+
return config.images?.provider;
|
|
67
|
+
case "research":
|
|
68
|
+
case "article":
|
|
69
|
+
return config.llm?.provider;
|
|
70
|
+
default:
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { languageInfo, languageLetters } from "../../kernel/ports/languages.js";
|
|
3
|
+
import { resolveBoldFont, resolveFont } from "./catalog.js";
|
|
4
|
+
import { discoverSystemFonts } from "./discovery.js";
|
|
5
|
+
import { readFontFile } from "./files.js";
|
|
6
|
+
import { mappedCodepoints, readFontMetadata } from "./sfnt.js";
|
|
7
|
+
// Captions in a language the chosen font can't draw would come out as empty boxes. For every
|
|
8
|
+
// language but English the chosen font is checked against that language's letters
|
|
9
|
+
// (`languageLetters`); when it misses any, the captions use a bundled Noto font for the script,
|
|
10
|
+
// else a font on this computer that has them all, else the render stops and says how to fix
|
|
11
|
+
// it. English is never checked, so an English project renders with exactly the font it chose.
|
|
12
|
+
// Small enough to ship: Latin, Greek and Cyrillic in one family, and the other alphabets'
|
|
13
|
+
// own. Chinese, Japanese and Korean fonts run to 10-20 MB each, so they are not bundled.
|
|
14
|
+
const bundled = {
|
|
15
|
+
latin: "NotoSans-Regular.ttf",
|
|
16
|
+
greek: "NotoSans-Regular.ttf",
|
|
17
|
+
cyrillic: "NotoSans-Regular.ttf",
|
|
18
|
+
arabic: "NotoSansArabic-Regular.ttf",
|
|
19
|
+
hebrew: "NotoSansHebrew-Regular.ttf",
|
|
20
|
+
devanagari: "NotoSansDevanagari-Regular.ttf",
|
|
21
|
+
thai: "NotoSansThai-Regular.ttf",
|
|
22
|
+
};
|
|
23
|
+
export function captionFontDeps(paths) {
|
|
24
|
+
return {
|
|
25
|
+
resolve: (id) => resolveFont(paths, id),
|
|
26
|
+
system: discoverSystemFonts,
|
|
27
|
+
bundledDir: fileURLToPath(new URL("../../assets/fonts/noto/", import.meta.url)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// The font captions are drawn in, for the chosen font and the project language.
|
|
31
|
+
export async function captionFont(deps, fontId, language) {
|
|
32
|
+
const chosen = await deps.resolve(fontId);
|
|
33
|
+
if (language === undefined || language === "en")
|
|
34
|
+
return chosen;
|
|
35
|
+
const letters = languageLetters(language);
|
|
36
|
+
if (await covers(chosen, letters))
|
|
37
|
+
return chosen;
|
|
38
|
+
const info = languageInfo(language);
|
|
39
|
+
const file = bundled[info.script];
|
|
40
|
+
if (file !== undefined) {
|
|
41
|
+
const font = await fontFile(`${deps.bundledDir}${file}`);
|
|
42
|
+
if (font !== undefined && (await covers(font, letters)))
|
|
43
|
+
return font;
|
|
44
|
+
}
|
|
45
|
+
for (const font of await deps.system())
|
|
46
|
+
if (await covers(font, letters))
|
|
47
|
+
return font;
|
|
48
|
+
throw new Error(`The caption font ${chosen.name} has no ${info.name} letters, and Slopify doesn't ship a font for ${info.name} (they are 10-20 MB). Upload a font that has them in Settings → Fonts (for example Noto Sans ${info.code === "ja" ? "JP" : info.code === "ko" ? "KR" : "SC"} from fonts.google.com), choose it in Edit project → Subtitles, then Try again.`);
|
|
49
|
+
}
|
|
50
|
+
// The same for text drawn bold (the shorts' captions): the chosen font's bold face when it has
|
|
51
|
+
// the letters, else the fallback, which the renderer emboldens.
|
|
52
|
+
export async function captionBoldFont(paths, fontId, language) {
|
|
53
|
+
if (language === undefined || language === "en")
|
|
54
|
+
return resolveBoldFont(paths, fontId);
|
|
55
|
+
const chosen = await resolveFont(paths, fontId);
|
|
56
|
+
const font = await captionFont(captionFontDeps(paths), fontId, language);
|
|
57
|
+
return font.path === chosen.path ? resolveBoldFont(paths, fontId) : font;
|
|
58
|
+
}
|
|
59
|
+
async function covers(font, letters) {
|
|
60
|
+
const bytes = await readFontFile(font.path);
|
|
61
|
+
if (bytes === undefined)
|
|
62
|
+
return false;
|
|
63
|
+
const found = mappedCodepoints(bytes, font.faceIndex, letters);
|
|
64
|
+
// A font whose cmap can't be read is taken at its word: it was chosen for this.
|
|
65
|
+
return found === undefined || found.size === letters.length;
|
|
66
|
+
}
|
|
67
|
+
async function fontFile(path) {
|
|
68
|
+
const bytes = await readFontFile(path);
|
|
69
|
+
const face = bytes === undefined ? undefined : readFontMetadata(bytes)?.[0];
|
|
70
|
+
if (face === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
return {
|
|
73
|
+
id: "default",
|
|
74
|
+
name: face.name,
|
|
75
|
+
family: face.family,
|
|
76
|
+
source: "bundled",
|
|
77
|
+
path,
|
|
78
|
+
extension: ".ttf",
|
|
79
|
+
assName: face.assName,
|
|
80
|
+
faceIndex: 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -260,3 +260,77 @@ function checksum(bytes) {
|
|
|
260
260
|
sum = (sum + bytes.readUInt32BE(at)) >>> 0;
|
|
261
261
|
return sum;
|
|
262
262
|
}
|
|
263
|
+
// Which of `codepoints` the face maps to a glyph, read from its Unicode cmap (format 4 or 12,
|
|
264
|
+
// the two every modern font carries). Undefined when the font has neither; the caller then
|
|
265
|
+
// cannot tell, and treats the font as covering what it was chosen for.
|
|
266
|
+
export function mappedCodepoints(content, index, codepoints) {
|
|
267
|
+
const bytes = Buffer.from(content.buffer, content.byteOffset, content.byteLength);
|
|
268
|
+
const face = directories(bytes)?.[index];
|
|
269
|
+
const table = face?.tables.get("cmap");
|
|
270
|
+
if (face === undefined || table === undefined || !validCmap(bytes, table))
|
|
271
|
+
return undefined;
|
|
272
|
+
const count = bytes.readUInt16BE(table.offset + 2);
|
|
273
|
+
let best;
|
|
274
|
+
for (let entry = 0; entry < count; entry += 1) {
|
|
275
|
+
const record = table.offset + 4 + entry * 8;
|
|
276
|
+
const platform = bytes.readUInt16BE(record);
|
|
277
|
+
const encoding = bytes.readUInt16BE(record + 2);
|
|
278
|
+
const at = table.offset + bytes.readUInt32BE(record + 4);
|
|
279
|
+
const format = bytes.readUInt16BE(at);
|
|
280
|
+
const unicode = platform === 0 || (platform === 3 && (encoding === 1 || encoding === 10));
|
|
281
|
+
if (!unicode || (format !== 4 && format !== 12))
|
|
282
|
+
continue;
|
|
283
|
+
if (best === undefined || format > best.format)
|
|
284
|
+
best = { at, format };
|
|
285
|
+
}
|
|
286
|
+
if (best === undefined)
|
|
287
|
+
return undefined;
|
|
288
|
+
const end = table.offset + table.length;
|
|
289
|
+
const found = new Set();
|
|
290
|
+
for (const codepoint of codepoints)
|
|
291
|
+
if (glyphOf(bytes, best.at, best.format, codepoint, end) !== 0)
|
|
292
|
+
found.add(codepoint);
|
|
293
|
+
return found;
|
|
294
|
+
}
|
|
295
|
+
function glyphOf(bytes, at, format, codepoint, end) {
|
|
296
|
+
if (format === 12) {
|
|
297
|
+
const groups = bytes.readUInt32BE(at + 12);
|
|
298
|
+
if (at + 16 + groups * 12 > end)
|
|
299
|
+
return 0;
|
|
300
|
+
for (let group = 0; group < groups; group += 1) {
|
|
301
|
+
const row = at + 16 + group * 12;
|
|
302
|
+
const first = bytes.readUInt32BE(row);
|
|
303
|
+
const last = bytes.readUInt32BE(row + 4);
|
|
304
|
+
if (codepoint >= first && codepoint <= last)
|
|
305
|
+
return bytes.readUInt32BE(row + 8) + (codepoint - first);
|
|
306
|
+
}
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
if (codepoint > 0xffff)
|
|
310
|
+
return 0;
|
|
311
|
+
const segments = bytes.readUInt16BE(at + 6) / 2;
|
|
312
|
+
const ends = at + 14;
|
|
313
|
+
const starts = ends + segments * 2 + 2;
|
|
314
|
+
const deltas = starts + segments * 2;
|
|
315
|
+
const ranges = deltas + segments * 2;
|
|
316
|
+
if (ranges + segments * 2 > end)
|
|
317
|
+
return 0;
|
|
318
|
+
for (let segment = 0; segment < segments; segment += 1) {
|
|
319
|
+
const last = bytes.readUInt16BE(ends + segment * 2);
|
|
320
|
+
if (codepoint > last)
|
|
321
|
+
continue;
|
|
322
|
+
const first = bytes.readUInt16BE(starts + segment * 2);
|
|
323
|
+
if (codepoint < first)
|
|
324
|
+
return 0;
|
|
325
|
+
const delta = bytes.readInt16BE(deltas + segment * 2);
|
|
326
|
+
const range = bytes.readUInt16BE(ranges + segment * 2);
|
|
327
|
+
if (range === 0)
|
|
328
|
+
return (codepoint + delta) & 0xffff;
|
|
329
|
+
const glyphAt = ranges + segment * 2 + range + (codepoint - first) * 2;
|
|
330
|
+
if (glyphAt + 2 > end)
|
|
331
|
+
return 0;
|
|
332
|
+
const glyph = bytes.readUInt16BE(glyphAt);
|
|
333
|
+
return glyph === 0 ? 0 : (glyph + delta) & 0xffff;
|
|
334
|
+
}
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
@@ -6,6 +6,7 @@ import { insertPiece, piecesOf, setPiece } from "../../kernel/runner/piece-repo.
|
|
|
6
6
|
import { projectById, setStageProgress } from "../admission/repo.js";
|
|
7
7
|
import { outputFileName, outputPath } from "../storage/layout.js";
|
|
8
8
|
import { insertOutput, outputsOf } from "../storage/repo.js";
|
|
9
|
+
import { imageCountsOf } from "./scale.js";
|
|
9
10
|
// What an image piece carries between runs: what was asked for, and the file it came back
|
|
10
11
|
// as once the provider answered. That is the whole of the resume.
|
|
11
12
|
const imagePayload = z.object({
|
|
@@ -28,13 +29,13 @@ export async function runImages(deps, context, providers) {
|
|
|
28
29
|
if (choice === undefined) {
|
|
29
30
|
// Admission refuses a run whose images are Generate without a provider and a model, so
|
|
30
31
|
// reaching here is a bug in admission rather than the user's.
|
|
31
|
-
throw new Error("No image model is set for this project. Choose one in Edit project → Providers, then
|
|
32
|
+
throw new Error("No image model is set for this project. Choose one in Edit project → Providers, then Try again.");
|
|
32
33
|
}
|
|
33
34
|
const pieces = plan(deps, context, project.config);
|
|
34
35
|
if (pieces.length === 0) {
|
|
35
36
|
// Admission makes an image source mandatory and puts the Number at one or more, so an
|
|
36
37
|
// empty plan is a bug upstream rather than a run with no pictures.
|
|
37
|
-
throw new Error("Slopify hit an internal error (no image prompt was selected).
|
|
38
|
+
throw new Error("Slopify hit an internal error (no image prompt was selected). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
39
|
}
|
|
39
40
|
// The run's own frame. The adapter turns it into whatever its provider spells the
|
|
40
41
|
// closest supported size, and the render crops whatever is left over.
|
|
@@ -73,14 +74,16 @@ function plan(deps, context, config) {
|
|
|
73
74
|
return existing;
|
|
74
75
|
}
|
|
75
76
|
const planned = [];
|
|
77
|
+
// Each prompt's Number, or more when the run scales its images (`scale.ts`).
|
|
78
|
+
const counts = imageCountsOf(config);
|
|
76
79
|
for (const [at, picked] of config.imagePrompts.entries()) {
|
|
77
80
|
// `slices/library/slots.ts` names the rendered body after the draft field that picked
|
|
78
81
|
// it, so the run carries the substituted text under this key.
|
|
79
82
|
const prompt = config.rendered[`imagePrompts.${String(at)}`];
|
|
80
83
|
if (prompt === undefined) {
|
|
81
|
-
throw new Error(`Slopify hit an internal error (the image prompt ${picked.name} was never filled in).
|
|
84
|
+
throw new Error(`Slopify hit an internal error (the image prompt ${picked.name} was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
82
85
|
}
|
|
83
|
-
for (let send = 1; send <= picked.number; send += 1) {
|
|
86
|
+
for (let send = 1; send <= (counts[at] ?? picked.number); send += 1) {
|
|
84
87
|
planned.push({
|
|
85
88
|
id: deps.ids.next(),
|
|
86
89
|
stageId: context.stage.id,
|
|
@@ -224,7 +227,7 @@ function report(deps, context, done, total) {
|
|
|
224
227
|
}
|
|
225
228
|
function payloadOf(piece) {
|
|
226
229
|
if (piece.payload === null) {
|
|
227
|
-
throw new Error("Slopify hit an internal error (a saved image step is empty).
|
|
230
|
+
throw new Error("Slopify hit an internal error (a saved image step is empty). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
228
231
|
}
|
|
229
232
|
return imagePayload.parse(JSON.parse(piece.payload));
|
|
230
233
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { imagesPerRunMax } from "../admission/rules.js";
|
|
2
|
+
// ceiling: one image an hour is the least that still scales; one every 15 seconds is the
|
|
3
|
+
// most, which is already as fast as the default Seconds per image shows them.
|
|
4
|
+
export const imagesPerHourMin = 1;
|
|
5
|
+
export const imagesPerHourMax = 240;
|
|
6
|
+
// "Every N minutes" spells the same range.
|
|
7
|
+
export const everyMinutesMin = 60 / imagesPerHourMax;
|
|
8
|
+
export const everyMinutesMax = 60 / imagesPerHourMin;
|
|
9
|
+
// ceiling: a hand-set run stops at `imagesPerRunMax` (60), which is one image every two
|
|
10
|
+
// minutes of a two-hour video. A scaled run may plan up to 240, four hours at one a minute:
|
|
11
|
+
// the images are drawn a few at a time under the image provider's own rate limits and the
|
|
12
|
+
// render joins its clips one at a time, so the count costs time and money, both of which the
|
|
13
|
+
// estimate shows before the run starts, rather than anything breaking. Past 240 the count is
|
|
14
|
+
// capped and Play says so.
|
|
15
|
+
export const scaledImagesMax = 240;
|
|
16
|
+
export const expectedWordsMax = 100000;
|
|
17
|
+
// Play's starting expected words (Review).
|
|
18
|
+
export const defaultExpectedWords = 1500;
|
|
19
|
+
// The estimate's own assumption (`estimate/requests.ts`).
|
|
20
|
+
export const spokenWordsPerMinute = 150;
|
|
21
|
+
// How many images a project's revision may hold: a scaled run's cap, else the hand-set one.
|
|
22
|
+
export function imagesPerVideoMax(config) {
|
|
23
|
+
return config.imageScale === undefined ? imagesPerRunMax : scaledImagesMax;
|
|
24
|
+
}
|
|
25
|
+
export function wordCount(text) {
|
|
26
|
+
return text.split(/\s+/).filter((word) => word !== "").length;
|
|
27
|
+
}
|
|
28
|
+
export function narrationMinutes(words) {
|
|
29
|
+
return words / spokenWordsPerMinute;
|
|
30
|
+
}
|
|
31
|
+
// Images per hour, rounded to four places so "every 7 minutes" is stored as a plain number
|
|
32
|
+
// that reads back as 7.
|
|
33
|
+
export function perHourFromMinutes(minutes) {
|
|
34
|
+
return Math.round((60 / minutes) * 10000) / 10000;
|
|
35
|
+
}
|
|
36
|
+
// The setting as Play's control spells it: "every N minutes" when N is a whole or a quarter
|
|
37
|
+
// minute, else "N per hour".
|
|
38
|
+
export function imageScaleForm(scale) {
|
|
39
|
+
const minutes = 60 / scale.perHour;
|
|
40
|
+
const quarters = Math.round(minutes * 4);
|
|
41
|
+
return Math.abs(minutes * 4 - quarters) < 0.001
|
|
42
|
+
? { every: "minutes", value: String(quarters / 4) }
|
|
43
|
+
: { every: "hour", value: String(scale.perHour) };
|
|
44
|
+
}
|
|
45
|
+
// What the length asks for, before the prompts' own Numbers are counted: one image per
|
|
46
|
+
// started stretch, capped at `scaledImagesMax`.
|
|
47
|
+
export function scaledTarget(scale) {
|
|
48
|
+
// The small epsilon keeps a length that is exactly a whole number of stretches (a rounded
|
|
49
|
+
// 60 / 7) from asking for one more.
|
|
50
|
+
const exact = (narrationMinutes(scale.words) * scale.perHour) / 60;
|
|
51
|
+
return Math.min(Math.max(Math.ceil(exact - 1e-9), 0), scaledImagesMax);
|
|
52
|
+
}
|
|
53
|
+
// Whether the run scales its images: set, and the images are generated from prompts.
|
|
54
|
+
export function scalesImages(draft) {
|
|
55
|
+
return draft.imageScale !== undefined && draft.sources.images === "generate";
|
|
56
|
+
}
|
|
57
|
+
// How many images each ticked prompt makes, in prompt order. Without the setting it is each
|
|
58
|
+
// prompt's Number, exactly as before it existed.
|
|
59
|
+
export function imageCountsOf(draft) {
|
|
60
|
+
const counts = draft.imagePrompts.map((prompt) => prompt.number);
|
|
61
|
+
if (!scalesImages(draft) || counts.length === 0)
|
|
62
|
+
return counts;
|
|
63
|
+
const floor = counts.reduce((sum, count) => sum + count, 0);
|
|
64
|
+
const extra = scaledTarget(draft.imageScale) - floor;
|
|
65
|
+
for (let at = 0; at < extra; at += 1) {
|
|
66
|
+
const index = at % counts.length;
|
|
67
|
+
counts[index] = (counts[index] ?? 0) + 1;
|
|
68
|
+
}
|
|
69
|
+
return counts;
|
|
70
|
+
}
|
|
71
|
+
// The images the run makes from its prompts.
|
|
72
|
+
export function plannedImageCount(draft) {
|
|
73
|
+
return imageCountsOf(draft).reduce((sum, count) => sum + count, 0);
|
|
74
|
+
}
|
|
75
|
+
// Admission's sentence for a stored setting out of range; Play's control says the same.
|
|
76
|
+
export function imageScaleProblem(scale) {
|
|
77
|
+
if (!Number.isFinite(scale.perHour) ||
|
|
78
|
+
scale.perHour < imagesPerHourMin ||
|
|
79
|
+
scale.perHour > imagesPerHourMax)
|
|
80
|
+
return `Enter between ${String(imagesPerHourMin)} and ${String(imagesPerHourMax)} images per hour (one every ${String(everyMinutesMin)} to ${String(everyMinutesMax)} minutes) under Images → More images for long videos.`;
|
|
81
|
+
if (!Number.isInteger(scale.words) || scale.words < 1 || scale.words > expectedWordsMax)
|
|
82
|
+
return `Enter the expected words (Review) as a whole number between 1 and ${String(expectedWordsMax)}; the image count for long videos is planned from it.`;
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// A word-level diff for History's side-by-side view. Browser-safe: the Library screen diffs the
|
|
2
|
+
// two versions it already holds. Words and the whitespace between them are the units, so a
|
|
3
|
+
// changed word is marked on its own and line breaks survive for the view to show.
|
|
4
|
+
// Past this many changed words the two texts are too different for a word diff to help, and
|
|
5
|
+
// the Myers trace would grow with it: the view shows the one removed and the other added.
|
|
6
|
+
const maxEdits = 4000;
|
|
7
|
+
export function tokens(text) {
|
|
8
|
+
return text.match(/\s+|[^\s]+/gu) ?? [];
|
|
9
|
+
}
|
|
10
|
+
// The parts of both texts in order. `same` parts belong to both sides, `removed` to the older
|
|
11
|
+
// text only and `added` to the newer only; neighbours of one kind are joined.
|
|
12
|
+
export function wordDiff(before, after) {
|
|
13
|
+
const a = tokens(before);
|
|
14
|
+
const b = tokens(after);
|
|
15
|
+
// The common head and tail are cut first: most edits touch a small part of a long prompt.
|
|
16
|
+
let head = 0;
|
|
17
|
+
while (head < a.length && head < b.length && a[head] === b[head])
|
|
18
|
+
head += 1;
|
|
19
|
+
let tail = 0;
|
|
20
|
+
while (tail < a.length - head &&
|
|
21
|
+
tail < b.length - head &&
|
|
22
|
+
a[a.length - 1 - tail] === b[b.length - 1 - tail])
|
|
23
|
+
tail += 1;
|
|
24
|
+
const parts = [];
|
|
25
|
+
push(parts, "same", a.slice(0, head).join(""));
|
|
26
|
+
const middleA = a.slice(head, a.length - tail);
|
|
27
|
+
const middleB = b.slice(head, b.length - tail);
|
|
28
|
+
for (const part of myers(middleA, middleB))
|
|
29
|
+
push(parts, part.op, part.text);
|
|
30
|
+
push(parts, "same", a.slice(a.length - tail).join(""));
|
|
31
|
+
return parts;
|
|
32
|
+
}
|
|
33
|
+
// One side of the side-by-side view: the older text keeps what was removed, the newer what
|
|
34
|
+
// was added.
|
|
35
|
+
export function sideOf(parts, side) {
|
|
36
|
+
const hidden = side === "before" ? "added" : "removed";
|
|
37
|
+
return parts.filter((part) => part.op !== hidden);
|
|
38
|
+
}
|
|
39
|
+
export function diffCounts(parts) {
|
|
40
|
+
const words = (op) => parts
|
|
41
|
+
.filter((part) => part.op === op)
|
|
42
|
+
.reduce((sum, part) => sum + tokens(part.text).filter((one) => one.trim() !== "").length, 0);
|
|
43
|
+
return { added: words("added"), removed: words("removed") };
|
|
44
|
+
}
|
|
45
|
+
function push(parts, op, text) {
|
|
46
|
+
if (text === "")
|
|
47
|
+
return;
|
|
48
|
+
const last = parts.at(-1);
|
|
49
|
+
if (last !== undefined && last.op === op)
|
|
50
|
+
parts[parts.length - 1] = { op, text: last.text + text };
|
|
51
|
+
else
|
|
52
|
+
parts.push({ op, text });
|
|
53
|
+
}
|
|
54
|
+
// Myers' O((N+M)D) shortest edit script, walked back from the end through the saved frontiers.
|
|
55
|
+
function myers(a, b) {
|
|
56
|
+
if (a.length === 0 && b.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
if (a.length === 0)
|
|
59
|
+
return [{ op: "added", text: b.join("") }];
|
|
60
|
+
if (b.length === 0)
|
|
61
|
+
return [{ op: "removed", text: a.join("") }];
|
|
62
|
+
const n = a.length;
|
|
63
|
+
const m = b.length;
|
|
64
|
+
const max = Math.min(n + m, maxEdits);
|
|
65
|
+
const offset = max + 1;
|
|
66
|
+
let v = new Int32Array(2 * max + 3);
|
|
67
|
+
const trace = [];
|
|
68
|
+
let found = false;
|
|
69
|
+
for (let d = 0; d <= max && !found; d += 1) {
|
|
70
|
+
trace.push(v.slice());
|
|
71
|
+
const next = v.slice();
|
|
72
|
+
for (let k = -d; k <= d; k += 2) {
|
|
73
|
+
const down = k === -d || (k !== d && (v[offset + k - 1] ?? 0) < (v[offset + k + 1] ?? 0));
|
|
74
|
+
let x = down ? (v[offset + k + 1] ?? 0) : (v[offset + k - 1] ?? 0) + 1;
|
|
75
|
+
let y = x - k;
|
|
76
|
+
while (x < n && y < m && a[x] === b[y]) {
|
|
77
|
+
x += 1;
|
|
78
|
+
y += 1;
|
|
79
|
+
}
|
|
80
|
+
next[offset + k] = x;
|
|
81
|
+
if (x >= n && y >= m) {
|
|
82
|
+
found = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
v = next;
|
|
87
|
+
}
|
|
88
|
+
if (!found)
|
|
89
|
+
return [
|
|
90
|
+
{ op: "removed", text: a.join("") },
|
|
91
|
+
{ op: "added", text: b.join("") },
|
|
92
|
+
];
|
|
93
|
+
// Backtrack: each frontier in `trace` is the state before step d.
|
|
94
|
+
const script = [];
|
|
95
|
+
let x = n;
|
|
96
|
+
let y = m;
|
|
97
|
+
for (let d = trace.length - 1; d >= 0; d -= 1) {
|
|
98
|
+
const before = trace[d] ?? new Int32Array(0);
|
|
99
|
+
const k = x - y;
|
|
100
|
+
const down = k === -d || (k !== d && (before[offset + k - 1] ?? 0) < (before[offset + k + 1] ?? 0));
|
|
101
|
+
const previousK = down ? k + 1 : k - 1;
|
|
102
|
+
const previousX = before[offset + previousK] ?? 0;
|
|
103
|
+
const previousY = previousX - previousK;
|
|
104
|
+
while (x > previousX && y > previousY) {
|
|
105
|
+
script.push({ op: "same", text: a[x - 1] ?? "" });
|
|
106
|
+
x -= 1;
|
|
107
|
+
y -= 1;
|
|
108
|
+
}
|
|
109
|
+
if (d > 0) {
|
|
110
|
+
if (down)
|
|
111
|
+
script.push({ op: "added", text: b[y - 1] ?? "" });
|
|
112
|
+
else
|
|
113
|
+
script.push({ op: "removed", text: a[x - 1] ?? "" });
|
|
114
|
+
x = previousX;
|
|
115
|
+
y = previousY;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const parts = [];
|
|
119
|
+
for (const part of script.reverse())
|
|
120
|
+
push(parts, part.op, part.text);
|
|
121
|
+
return parts;
|
|
122
|
+
}
|