@gentbajko/slopify 2.5.0 → 3.0.1
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 +24 -41
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +31 -0
- 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 +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +85 -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.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 +12 -7
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/gemini.js +206 -0
- 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/pcm-mp3.js +62 -0
- package/dist/adapters/tts/system.js +147 -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 +38 -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/assets/style-preview/narration.mp3 +0 -0
- package/dist/assets/style-preview/tall-1.jpg +0 -0
- package/dist/assets/style-preview/tall-2.jpg +0 -0
- package/dist/assets/style-preview/tall-3.jpg +0 -0
- package/dist/assets/style-preview/wide-1.jpg +0 -0
- package/dist/assets/style-preview/wide-2.jpg +0 -0
- package/dist/assets/style-preview/wide-3.jpg +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +17 -14
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +2 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +118 -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 +72 -0
- package/dist/edge/autostart/service.js +90 -0
- package/dist/edge/cli-args.js +71 -0
- package/dist/edge/cli.js +64 -63
- 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 +160 -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 +3 -1
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +38 -11
- 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/byte-range.js +42 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/diagnostics.js +1 -0
- package/dist/edge/http/draft-files.js +2 -4
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +11 -7
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +29 -5
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +212 -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/project-create.js +87 -0
- package/dist/edge/http/project-templates.js +19 -2
- package/dist/edge/http/projects.js +37 -100
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +119 -3
- package/dist/edge/http/reviews.js +131 -0
- package/dist/edge/http/revision-files.js +3 -5
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +115 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +43 -2
- package/dist/edge/http/studio.js +287 -0
- package/dist/edge/http/style-preview.js +61 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/tutorials.js +59 -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 +92 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/gemini-login.js +83 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/host-cli/status.js +4 -2
- 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/db/migrations/0041-cast-hosts.sql +3 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/model.js +10 -5
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/ports/system-speech.js +203 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +40 -5
- package/dist/kernel/runner/index.js +39 -9
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +142 -7
- 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 +277 -16
- package/dist/model-catalog.js +2 -0
- 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/3.0.1.md +105 -0
- package/dist/patch-notes/index.json +32 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +80 -2
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +42 -6
- 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/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -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 +221 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +152 -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 +5 -2
- package/dist/slices/control/providers.js +4 -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 +109 -28
- package/dist/slices/estimate/requests.js +68 -2
- package/dist/slices/eta/history.js +56 -0
- package/dist/slices/eta/model.js +63 -0
- package/dist/slices/eta/view.js +25 -0
- 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 +7 -1
- 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 +185 -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 +47 -3
- package/dist/slices/notifications/rules.js +48 -3
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +72 -0
- package/dist/slices/onboarding/full-video.js +81 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +85 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +144 -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 +127 -8
- package/dist/slices/play-drafts/readiness.js +4 -1
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +59 -6
- package/dist/slices/project-templates/next-chapter.js +94 -0
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/schema.js +7 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +18 -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 +46 -3
- 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 +8 -4
- 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 +67 -8
- package/dist/slices/rebuild/recipe-input-schema.js +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -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 +15 -1
- 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 +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- 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 +125 -0
- package/dist/slices/rebuild/runtime-actions.js +5 -0
- 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 +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- 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 +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +267 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +44 -17
- package/dist/slices/rebuild/runtime-store.js +62 -11
- 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 +3 -3
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- 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 +118 -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 +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- 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 +256 -0
- package/dist/slices/run-cost/pricing.js +95 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +222 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +68 -17
- package/dist/slices/schedules/schema.js +179 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +128 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +523 -0
- package/dist/slices/settings/first-run.js +106 -0
- package/dist/slices/settings/health.js +247 -0
- package/dist/slices/settings/key-guides.js +138 -0
- package/dist/slices/settings/key-test.js +93 -0
- package/dist/slices/settings/keys.js +10 -5
- package/dist/slices/settings/model-reach.js +52 -0
- package/dist/slices/settings/model.js +55 -1
- package/dist/slices/settings/models.js +5 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/readiness.js +14 -2
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/tutorial-schema.js +5 -0
- 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 +63 -13
- 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 +87 -0
- package/dist/slices/storage/downloads.js +33 -1
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +94 -12
- package/dist/slices/storage/reconcile.js +14 -1
- 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 +30 -0
- package/dist/slices/studio/pack.js +211 -0
- package/dist/slices/studio/queue.js +69 -0
- package/dist/slices/studio/settings.js +170 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/narration.js +33 -0
- package/dist/slices/style-preview/render.js +148 -0
- package/dist/slices/style-preview/schema.js +59 -0
- package/dist/slices/style-preview/service.js +92 -0
- package/dist/slices/style-preview/settings.js +139 -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/tutorials/anchors.js +57 -0
- package/dist/slices/tutorials/library.js +185 -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 +87 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +74 -0
- package/dist/slices/voices/grouping.js +59 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +218 -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 +84 -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/tutorials/AI-CLIs.md +146 -0
- package/dist/tutorials/Backups.md +121 -0
- package/dist/tutorials/Calendar.md +103 -0
- package/dist/tutorials/Cast-Library.md +112 -0
- package/dist/tutorials/Channels.md +148 -0
- package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
- package/dist/tutorials/Docker.md +171 -0
- package/dist/tutorials/Document-Themes.md +207 -0
- package/dist/tutorials/Editing-a-Project.md +204 -0
- package/dist/tutorials/FAQ.md +134 -0
- package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
- package/dist/tutorials/Home-and-Projects.md +174 -0
- package/dist/tutorials/Home.md +119 -0
- package/dist/tutorials/Install.md +154 -0
- package/dist/tutorials/Intros-and-Outros.md +86 -0
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
- package/dist/tutorials/Library-Overview.md +75 -0
- package/dist/tutorials/Models.md +133 -0
- package/dist/tutorials/Multiple-Voices.md +161 -0
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
- package/dist/tutorials/Notifications.md +86 -0
- package/dist/tutorials/Other-Languages.md +110 -0
- package/dist/tutorials/PDF-Documents.md +100 -0
- package/dist/tutorials/Play-Images.md +135 -0
- package/dist/tutorials/Play-Narration.md +167 -0
- package/dist/tutorials/Play-Outputs.md +109 -0
- package/dist/tutorials/Play-Overview.md +216 -0
- package/dist/tutorials/Play-Title-and-Article.md +135 -0
- package/dist/tutorials/Play-Video-and-Style.md +143 -0
- package/dist/tutorials/Project-Page.md +228 -0
- package/dist/tutorials/Prompts.md +136 -0
- package/dist/tutorials/Providers-and-Keys.md +290 -0
- package/dist/tutorials/Publishing-to-YouTube.md +135 -0
- package/dist/tutorials/Recovery-and-Retries.md +141 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
- package/dist/tutorials/Schedules.md +154 -0
- package/dist/tutorials/Settings-Reference.md +258 -0
- package/dist/tutorials/Shorts.md +116 -0
- package/dist/tutorials/Start-at-Login.md +126 -0
- package/dist/tutorials/Studio-Extension.md +146 -0
- package/dist/tutorials/Templates.md +146 -0
- package/dist/tutorials/Trash.md +87 -0
- package/dist/tutorials/Troubleshooting.md +149 -0
- package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
- package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
- package/dist/tutorials/Video-Editing.md +135 -0
- package/dist/tutorials/Where-Your-Files-Live.md +149 -0
- package/dist/tutorials/YouTube-Description.md +132 -0
- package/dist/tutorials/Your-First-Short.md +125 -0
- package/dist/tutorials/_Footer.md +1 -0
- package/dist/tutorials/_Sidebar.md +69 -0
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-DBLSfqA-.css +1 -0
- package/dist/web/assets/index-DFdIhmm0.js +307 -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-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- 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-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { remark } from "remark";
|
|
2
|
+
import remarkGfm from "remark-gfm";
|
|
3
|
+
// The article as it will be spoken, block by block, for "Describe tables and figures in the
|
|
4
|
+
// narration". Flattening the Markdown (`article/plain.ts`) keeps the words of prose but has
|
|
5
|
+
// nothing to say about a table, a picture, an equation or a program: it drops them or reads
|
|
6
|
+
// their bare cells. Here the document is walked instead, and every block a listener cannot
|
|
7
|
+
// follow read aloud is kept whole for the text model to describe in a sentence or four
|
|
8
|
+
// (`narration/describe.ts`); everything else becomes plain spoken text.
|
|
9
|
+
//
|
|
10
|
+
// Deterministic: the same Markdown always gives the same blocks, so a description is cached by
|
|
11
|
+
// its block and an unchanged article never asks for one again. The article's end matter (the
|
|
12
|
+
// sources and the pronunciation glossary) is cut off before this runs (`article/split.ts`), as
|
|
13
|
+
// it is for the flattened text, so neither is ever narrated.
|
|
14
|
+
export const describedKinds = ["table", "figure", "diagram", "math", "code"];
|
|
15
|
+
// Private-use characters around an equation's number while the document is parsed, so an
|
|
16
|
+
// underscore or asterisk inside an equation is never read as emphasis.
|
|
17
|
+
const open = "\uE000";
|
|
18
|
+
const close = "\uE001";
|
|
19
|
+
const placeholder = /\uE000(\d+)\uE001/g;
|
|
20
|
+
export function narrationBlocks(markdown, options = {}) {
|
|
21
|
+
const { text: protectedText, math } = protectMath(markdown);
|
|
22
|
+
const tree = remark().use(remarkGfm).parse(protectedText);
|
|
23
|
+
const walker = new Walker(protectedText, math, options);
|
|
24
|
+
walker.blocks(tree.children);
|
|
25
|
+
return walker.out;
|
|
26
|
+
}
|
|
27
|
+
export function describedBlocks(blocks) {
|
|
28
|
+
return blocks.filter((block) => block.kind !== "text");
|
|
29
|
+
}
|
|
30
|
+
// The narration: the text blocks as they are and each described block as its spoken passage,
|
|
31
|
+
// one paragraph each, in the flattened text's own shape. Null while any passage is unknown.
|
|
32
|
+
export function spokenNarration(blocks, passage) {
|
|
33
|
+
const paragraphs = [];
|
|
34
|
+
for (const block of blocks) {
|
|
35
|
+
const text = block.kind === "text" ? block.text : passage(block);
|
|
36
|
+
if (text === null)
|
|
37
|
+
return null;
|
|
38
|
+
const clean = text.trim();
|
|
39
|
+
if (clean !== "")
|
|
40
|
+
paragraphs.push(clean);
|
|
41
|
+
}
|
|
42
|
+
return paragraphs.length === 0 ? "" : `${paragraphs.join("\n\n")}\n`;
|
|
43
|
+
}
|
|
44
|
+
function protectMath(markdown) {
|
|
45
|
+
// Code keeps its dollars: a shell variable is not an equation.
|
|
46
|
+
const code = [];
|
|
47
|
+
const visit = (node) => {
|
|
48
|
+
if ((node.type === "code" || node.type === "inlineCode") && node.position) {
|
|
49
|
+
code.push([node.position.start.offset ?? 0, node.position.end.offset ?? 0]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if ("children" in node)
|
|
53
|
+
for (const child of node.children)
|
|
54
|
+
visit(child);
|
|
55
|
+
};
|
|
56
|
+
visit(remark().use(remarkGfm).parse(markdown));
|
|
57
|
+
const math = [];
|
|
58
|
+
const pattern = /\$\$([\s\S]+?)\$\$|\\\[([\s\S]+?)\\\]|\\\(([\s\S]+?)\\\)|(?<![\\$\w])\$(?![\s$])([^$\n]+?)(?<![\s\\])\$(?![\w$])/g;
|
|
59
|
+
const text = markdown.replace(pattern, (written, a, b, c, d, at) => {
|
|
60
|
+
if (code.some(([from, to]) => at < to && at + written.length > from))
|
|
61
|
+
return written;
|
|
62
|
+
const tex = String(a ?? b ?? c ?? d ?? "").trim();
|
|
63
|
+
math.push({ tex, written, display: a !== undefined || b !== undefined });
|
|
64
|
+
return `${open}${String(math.length - 1)}${close}`;
|
|
65
|
+
});
|
|
66
|
+
return { text, math };
|
|
67
|
+
}
|
|
68
|
+
class Walker {
|
|
69
|
+
markdown;
|
|
70
|
+
math;
|
|
71
|
+
options;
|
|
72
|
+
out = [];
|
|
73
|
+
section = null;
|
|
74
|
+
described = 0;
|
|
75
|
+
constructor(markdown, math, options) {
|
|
76
|
+
this.markdown = markdown;
|
|
77
|
+
this.math = math;
|
|
78
|
+
this.options = options;
|
|
79
|
+
}
|
|
80
|
+
blocks(nodes) {
|
|
81
|
+
for (let at = 0; at < nodes.length; at++) {
|
|
82
|
+
const node = nodes[at];
|
|
83
|
+
if (node === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
switch (node.type) {
|
|
86
|
+
case "heading": {
|
|
87
|
+
const text = this.spoken(node.children).replace(placeholder, (_all, n) => {
|
|
88
|
+
const tex = this.math[Number(n)]?.tex ?? "";
|
|
89
|
+
return readable(tex) ?? tex;
|
|
90
|
+
});
|
|
91
|
+
this.section = text.trim() || this.section;
|
|
92
|
+
this.text(text);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "paragraph": {
|
|
96
|
+
if (node.children.some((child) => child.type === "image")) {
|
|
97
|
+
// A caption or legend written under the picture belongs to it.
|
|
98
|
+
const captions = [];
|
|
99
|
+
while (captions.length < 2) {
|
|
100
|
+
const next = nodes[at + captions.length + 1];
|
|
101
|
+
if (next?.type !== "paragraph" || !isCaption(next, this.markdown))
|
|
102
|
+
break;
|
|
103
|
+
captions.push(next);
|
|
104
|
+
}
|
|
105
|
+
at += captions.length;
|
|
106
|
+
this.figure(node, captions);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
this.paragraph(node);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "list":
|
|
113
|
+
this.list(node);
|
|
114
|
+
break;
|
|
115
|
+
case "blockquote":
|
|
116
|
+
this.quote(node);
|
|
117
|
+
break;
|
|
118
|
+
case "table":
|
|
119
|
+
this.describe("table", this.source(node));
|
|
120
|
+
break;
|
|
121
|
+
case "code":
|
|
122
|
+
this.code(node);
|
|
123
|
+
break;
|
|
124
|
+
case "html":
|
|
125
|
+
this.html(node);
|
|
126
|
+
break;
|
|
127
|
+
// A footnote's text, a link definition, a rule and front matter are never spoken.
|
|
128
|
+
default:
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
text(text) {
|
|
134
|
+
const clean = tidy(text);
|
|
135
|
+
if (clean !== "")
|
|
136
|
+
this.out.push({ kind: "text", text: clean });
|
|
137
|
+
}
|
|
138
|
+
describe(kind, source, extra = {}) {
|
|
139
|
+
this.described += 1;
|
|
140
|
+
this.out.push({
|
|
141
|
+
kind,
|
|
142
|
+
index: this.described,
|
|
143
|
+
source: source.trim(),
|
|
144
|
+
section: this.section,
|
|
145
|
+
...(extra.image === undefined ? {} : { image: extra.image }),
|
|
146
|
+
...(extra.lang === undefined ? {} : { lang: extra.lang }),
|
|
147
|
+
...(extra.inline === undefined ? {} : { inline: extra.inline }),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
paragraph(node) {
|
|
151
|
+
const raw = this.spoken(node.children);
|
|
152
|
+
const only = /^\s*\uE000(\d+)\uE001\s*$/.exec(raw);
|
|
153
|
+
const alone = only === null ? undefined : this.math[Number(only[1])];
|
|
154
|
+
if (alone !== undefined) {
|
|
155
|
+
this.describe("math", alone.written);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const spans = [...raw.matchAll(placeholder)].map((match) => this.math[Number(match[1])]);
|
|
159
|
+
if (spans.some((span) => span === undefined || span.display || readable(span.tex) === null)) {
|
|
160
|
+
// Said in words by the model, the rest of the paragraph with it.
|
|
161
|
+
this.describe("math", this.source(node), { inline: true });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.text(raw.replace(placeholder, (_all, n) => readable(this.math[Number(n)]?.tex ?? "") ?? ""));
|
|
165
|
+
}
|
|
166
|
+
figure(node, captions) {
|
|
167
|
+
const image = node.children.find((child) => child.type === "image");
|
|
168
|
+
const lines = [];
|
|
169
|
+
for (const child of node.children)
|
|
170
|
+
if (child.type === "image" && child.alt?.trim())
|
|
171
|
+
lines.push(`Alt text: ${child.alt.trim()}`);
|
|
172
|
+
const beside = tidy(this.restore(this.spoken(node.children.filter((child) => child.type !== "image"))));
|
|
173
|
+
if (beside !== "")
|
|
174
|
+
lines.push(`Caption: ${beside}`);
|
|
175
|
+
for (const caption of captions) {
|
|
176
|
+
const text = tidy(this.restore(this.spoken(caption.children)));
|
|
177
|
+
if (text !== "")
|
|
178
|
+
lines.push(`Caption: ${text}`);
|
|
179
|
+
}
|
|
180
|
+
this.describe("figure", lines.join("\n") || "A picture with no caption.", {
|
|
181
|
+
...(image?.url ? { image: image.url } : {}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
html(node) {
|
|
185
|
+
// An article pasted from the web may carry its figures as HTML.
|
|
186
|
+
const img = /<img\b[^>]*>/i.exec(node.value);
|
|
187
|
+
if (img === null)
|
|
188
|
+
return;
|
|
189
|
+
const attribute = (name) => new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)')`, "i")
|
|
190
|
+
.exec(img[0])
|
|
191
|
+
?.slice(2)
|
|
192
|
+
.find(Boolean);
|
|
193
|
+
const caption = /<figcaption[^>]*>([\s\S]*?)<\/figcaption>/i.exec(node.value)?.[1];
|
|
194
|
+
const lines = [
|
|
195
|
+
...(attribute("alt")?.trim() ? [`Alt text: ${attribute("alt")?.trim() ?? ""}`] : []),
|
|
196
|
+
...(caption?.trim() ? [`Caption: ${tidy(caption.replace(/<[^>]+>/g, " "))}`] : []),
|
|
197
|
+
];
|
|
198
|
+
const src = attribute("src");
|
|
199
|
+
this.describe("figure", lines.join("\n") || "A picture with no caption.", {
|
|
200
|
+
...(src ? { image: src } : {}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
code(node) {
|
|
204
|
+
const lang = node.lang?.trim().toLowerCase() ?? "";
|
|
205
|
+
const source = this.restore(this.source(node));
|
|
206
|
+
const named = lang === "" ? {} : { lang };
|
|
207
|
+
if (lang === "mermaid" ||
|
|
208
|
+
(["", "text", "txt", "ascii", "plain"].includes(lang) && isDrawing(node.value)))
|
|
209
|
+
this.describe("diagram", source, named);
|
|
210
|
+
else if (["math", "latex", "tex", "katex"].includes(lang))
|
|
211
|
+
this.describe("math", source);
|
|
212
|
+
else if (this.options.code !== "skip")
|
|
213
|
+
this.describe("code", source, named);
|
|
214
|
+
}
|
|
215
|
+
quote(node) {
|
|
216
|
+
const start = this.out.length;
|
|
217
|
+
this.blocks(node.children);
|
|
218
|
+
// The quoted words are spoken in quotation marks, which a voice reads as a quote.
|
|
219
|
+
for (let at = start; at < this.out.length; at++) {
|
|
220
|
+
const block = this.out[at];
|
|
221
|
+
if (block?.kind === "text")
|
|
222
|
+
this.out[at] = { kind: "text", text: `“${block.text}”` };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
list(node) {
|
|
226
|
+
const items = [];
|
|
227
|
+
const flush = () => {
|
|
228
|
+
if (items.length > 0)
|
|
229
|
+
this.text(this.sentences(items.splice(0), node.ordered === true));
|
|
230
|
+
};
|
|
231
|
+
for (const item of node.children) {
|
|
232
|
+
const words = [];
|
|
233
|
+
for (const child of item.children) {
|
|
234
|
+
if (child.type === "paragraph" && !child.children.some((one) => one.type === "image")) {
|
|
235
|
+
const raw = this.spoken(child.children);
|
|
236
|
+
if (!raw.includes(open)) {
|
|
237
|
+
words.push(raw);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// A table, a picture or a nested list inside an item is its own block, after the
|
|
242
|
+
// item's words.
|
|
243
|
+
if (words.length > 0)
|
|
244
|
+
items.push(tidy(words.splice(0).join(" ")));
|
|
245
|
+
flush();
|
|
246
|
+
this.blocks([child]);
|
|
247
|
+
}
|
|
248
|
+
if (words.length > 0)
|
|
249
|
+
items.push(tidy(words.join(" ")));
|
|
250
|
+
}
|
|
251
|
+
flush();
|
|
252
|
+
}
|
|
253
|
+
sentences(items, ordered) {
|
|
254
|
+
const english = this.options.language === undefined || this.options.language === "en";
|
|
255
|
+
const kept = items.filter((item) => item !== "");
|
|
256
|
+
if (!english || kept.length < 2)
|
|
257
|
+
return kept.map(sentence).join(" ");
|
|
258
|
+
if (ordered)
|
|
259
|
+
return kept
|
|
260
|
+
.map((item, at) => sentence(`${at === 0 ? "First" : at === kept.length - 1 ? "Finally" : "Then"}, ${item}`))
|
|
261
|
+
.join(" ");
|
|
262
|
+
// Short items read as one sentence, "a, b and c"; longer ones one sentence each.
|
|
263
|
+
if (kept.every((item) => item.split(/\s+/).length <= 5 && !/[.!?;:,—–]/.test(item))) {
|
|
264
|
+
const joined = `${kept.slice(0, -1).join(", ")} and ${kept.at(-1) ?? ""}`;
|
|
265
|
+
return sentence(joined.charAt(0).toUpperCase() + joined.slice(1));
|
|
266
|
+
}
|
|
267
|
+
return kept.map(sentence).join(" ");
|
|
268
|
+
}
|
|
269
|
+
// The words of inline Markdown: link text without its address, no footnote markers, no
|
|
270
|
+
// bare URLs. Equations stay as placeholders for the caller to decide on.
|
|
271
|
+
spoken(nodes) {
|
|
272
|
+
return nodes.map((node) => this.inline(node)).join("");
|
|
273
|
+
}
|
|
274
|
+
inline(node) {
|
|
275
|
+
switch (node.type) {
|
|
276
|
+
case "text":
|
|
277
|
+
case "inlineCode":
|
|
278
|
+
return node.value;
|
|
279
|
+
case "emphasis":
|
|
280
|
+
case "strong":
|
|
281
|
+
case "delete":
|
|
282
|
+
return this.spoken(node.children);
|
|
283
|
+
case "link": {
|
|
284
|
+
const text = this.spoken(node.children);
|
|
285
|
+
return isAddress(text, node.url) ? "" : text;
|
|
286
|
+
}
|
|
287
|
+
case "linkReference": {
|
|
288
|
+
const text = this.spoken(node.children);
|
|
289
|
+
return /^\s*\^?\d+\s*$/.test(text) ? "" : text;
|
|
290
|
+
}
|
|
291
|
+
case "image":
|
|
292
|
+
case "imageReference":
|
|
293
|
+
return node.alt ?? "";
|
|
294
|
+
case "break":
|
|
295
|
+
return "\n";
|
|
296
|
+
default:
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
source(node) {
|
|
301
|
+
const from = node.position?.start.offset;
|
|
302
|
+
const to = node.position?.end.offset;
|
|
303
|
+
return from === undefined || to === undefined
|
|
304
|
+
? ""
|
|
305
|
+
: this.restore(this.markdown.slice(from, to));
|
|
306
|
+
}
|
|
307
|
+
restore(text) {
|
|
308
|
+
return text.replace(placeholder, (_all, n) => this.math[Number(n)]?.written ?? "");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function isCaption(node, markdown) {
|
|
312
|
+
if (node.children.length === 1 && node.children[0]?.type === "emphasis")
|
|
313
|
+
return true;
|
|
314
|
+
const from = node.position?.start.offset ?? 0;
|
|
315
|
+
const head = markdown.slice(from, from + 40).replace(/^[*_\s]+/, "");
|
|
316
|
+
return /^(figure|fig\.|chart|graph|diagram|image|legend|caption|source)\b/i.test(head);
|
|
317
|
+
}
|
|
318
|
+
// An ASCII or box-drawing chart in a plain fence: box characters, or lines that are mostly
|
|
319
|
+
// rules and bars.
|
|
320
|
+
function isDrawing(value) {
|
|
321
|
+
if (/[─│┌┐└┘├┤┬┴┼═║╔╗╚╝▲▼►◄█░▒▓]/.test(value))
|
|
322
|
+
return true;
|
|
323
|
+
const lines = value.split("\n").filter((line) => line.trim() !== "");
|
|
324
|
+
return (lines.filter((line) => /[-+|*]{3,}|[|+].*[|+]/.test(line)).length >=
|
|
325
|
+
Math.max(3, lines.length / 2));
|
|
326
|
+
}
|
|
327
|
+
// A link whose text is its own address, or any bare URL: nothing a listener can use.
|
|
328
|
+
function isAddress(text, url) {
|
|
329
|
+
const clean = text.trim();
|
|
330
|
+
return clean === "" || clean === url || /^(https?:\/\/|www\.|mailto:)/i.test(clean);
|
|
331
|
+
}
|
|
332
|
+
const greek = /\\(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Phi|Psi|Omega)\b/g;
|
|
333
|
+
// A short inline symbol read as it is written ("$T$" is "T", "$\alpha$" is "alpha"), or null
|
|
334
|
+
// when it is a formula to be said in words.
|
|
335
|
+
function readable(tex) {
|
|
336
|
+
const plain = tex.replace(greek, (_all, name) => name.toLowerCase()).trim();
|
|
337
|
+
return plain.length <= 20 && /^[\p{L}\p{N}\s.,%]*$/u.test(plain) ? plain : null;
|
|
338
|
+
}
|
|
339
|
+
function sentence(text) {
|
|
340
|
+
const clean = text.trim();
|
|
341
|
+
return clean === "" || /[.!?…:;]["”’)]?$/.test(clean) ? clean : `${clean}.`;
|
|
342
|
+
}
|
|
343
|
+
// Footnote markers and bare addresses out, then the spacing they leave behind.
|
|
344
|
+
function tidy(text) {
|
|
345
|
+
return text
|
|
346
|
+
.replace(/\s?\[(?:\^?[\w-]+)(?:\s*[,–-]\s*\^?\d+)*\](?!\()/g, (marker) => /\[\^|\[\d/.test(marker) ? "" : marker)
|
|
347
|
+
.replace(/\s?\b(?:https?:\/\/|www\.)[^\s)]+/g, "")
|
|
348
|
+
.replace(/[ \t]+([.,;:!?])/g, "$1")
|
|
349
|
+
.replace(/[ \t]{2,}/g, " ")
|
|
350
|
+
.replace(/\(\s*\)/g, "")
|
|
351
|
+
.trim();
|
|
352
|
+
}
|
|
@@ -20,9 +20,20 @@ export function chunkNarration(text, chunking) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
// What "N words" counts: runs of non-space. Exported because it is half of the rule -
|
|
23
|
-
// a test that asserts a chunk fits in N has to count the same way the cut did.
|
|
23
|
+
// a test that asserts a chunk fits in N has to count the same way the cut did. Chinese,
|
|
24
|
+
// Japanese, Thai and the other scripts written without spaces are counted by the platform's
|
|
25
|
+
// own word breaker instead, or a whole paragraph would be one "word"; text without them
|
|
26
|
+
// (every English narration) is counted exactly as before.
|
|
27
|
+
const unspaced = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
|
|
24
28
|
export function wordsIn(text) {
|
|
25
|
-
|
|
29
|
+
if (!unspaced.test(text))
|
|
30
|
+
return text.split(/\s+/).filter((word) => word !== "").length;
|
|
31
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
|
|
32
|
+
let count = 0;
|
|
33
|
+
for (const part of segmenter.segment(text))
|
|
34
|
+
if (part.isWordLike === true)
|
|
35
|
+
count += 1;
|
|
36
|
+
return count;
|
|
26
37
|
}
|
|
27
38
|
export function sameChunking(left, right) {
|
|
28
39
|
const a = left ?? defaultChunking;
|
|
@@ -52,11 +63,12 @@ function sentenceRuns(text, budget, measure) {
|
|
|
52
63
|
// The provider-specific planning layer applies any hard request limit afterwards.
|
|
53
64
|
return nonEmpty(chunks);
|
|
54
65
|
}
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
66
|
+
// `Intl.Segmenter` is the platform's own sentence breaker (ICU ships with Node), so no
|
|
67
|
+
// dependency and no regex full of abbreviations. Its sentence rules are Unicode's (UAX #29)
|
|
68
|
+
// for every language: "。", "!", "।", "؟" and "¿…?" end or open sentences whatever the
|
|
69
|
+
// locale, so the project language needs no locale here, and "en" keeps it the same on every
|
|
70
|
+
// machine (the default locale varies).
|
|
71
|
+
export function* sentences(text) {
|
|
60
72
|
// Built per call rather than held at module scope: the standards forbid a module-level
|
|
61
73
|
// singleton, and constructing one costs microseconds against a call that just read an
|
|
62
74
|
// article off disk.
|
|
@@ -46,10 +46,10 @@ export async function joinNarration(deps, input) {
|
|
|
46
46
|
const first = input.files[0];
|
|
47
47
|
if (first === undefined) {
|
|
48
48
|
// The stage refuses an empty narration before it gets here, so this is a bug.
|
|
49
|
-
throw new Error("Slopify hit an internal error (there is no narration audio to join).
|
|
49
|
+
throw new Error("Slopify hit an internal error (there is no narration audio to join). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
50
50
|
}
|
|
51
51
|
mkdirSync(dirname(input.output), { recursive: true, mode: 0o700 });
|
|
52
|
-
if (input.files.length === 1) {
|
|
52
|
+
if (input.files.length === 1 && input.reencode !== true) {
|
|
53
53
|
// Whole-text mode, and any run whose article is one paragraph: there is nothing to
|
|
54
54
|
// join, and re-encoding the only chunk would lose quality for no reason.
|
|
55
55
|
input.signal.throwIfAborted();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { withLanguage } from "../../kernel/ports/languages.js";
|
|
2
|
+
const instructions = {
|
|
3
|
+
table: "Describe this table for a listener in 1–4 sentences in the narration's language and tone; give the pattern and the notable values, not every cell; never read it row by row.",
|
|
4
|
+
figure: "Describe this figure for a listener in 1–3 sentences in the narration's language and tone, from its caption, alt text and legend (and the attached picture, when there is one): say what it shows and what to take from it. Never say that a figure shows something without saying what; explain colours or symbols only through what they stand for.",
|
|
5
|
+
diagram: "Describe this diagram or chart for a listener in 1–3 sentences in the narration's language and tone: what it connects or compares and what it tells us. Never read labels, arrows or axis ticks one by one.",
|
|
6
|
+
math: "Say what this equation means for a listener in 1–3 sentences in the narration's language and tone: how the quantities depend on each other (for example, “evaporation grows with temperature and falls with humidity”), not symbol by symbol. A single short symbol may be read as it is.",
|
|
7
|
+
code: "Summarise what this code does for a listener in one or two sentences in the narration's language and tone, naming the language when it helps (for example, “a Rust function that carves river channels by lowering the terrain along the flow”). Never read code, symbols or names of variables aloud.",
|
|
8
|
+
};
|
|
9
|
+
const inlineMath = "Rewrite this paragraph so it can be read aloud, in the narration's language and tone: keep its words, but say each formula's meaning in words rather than symbol by symbol. A single short symbol may be read as it is.";
|
|
10
|
+
const answerRules = "Answer with the spoken passage only: plain sentences a narrator reads out, no Markdown, no lists, no headings, no quotation marks around the whole answer, no preamble such as “Here is”.";
|
|
11
|
+
export function describeInstruction(block) {
|
|
12
|
+
return block.kind === "math" && block.inline === true ? inlineMath : instructions[block.kind];
|
|
13
|
+
}
|
|
14
|
+
export function describeMessages(block, style, language) {
|
|
15
|
+
const guidance = style.trim();
|
|
16
|
+
const system = [
|
|
17
|
+
describeInstruction(block),
|
|
18
|
+
answerRules,
|
|
19
|
+
...(guidance === ""
|
|
20
|
+
? []
|
|
21
|
+
: [
|
|
22
|
+
`Style guidance for the narration (follow its tone; ignore anything about formats or cues):\n${guidance}`,
|
|
23
|
+
]),
|
|
24
|
+
].join("\n\n");
|
|
25
|
+
const user = [
|
|
26
|
+
...(block.section === null ? [] : [`Section: ${block.section}`]),
|
|
27
|
+
...(block.lang === undefined ? [] : [`Language of the code: ${block.lang}`]),
|
|
28
|
+
block.source,
|
|
29
|
+
].join("\n\n");
|
|
30
|
+
return withLanguage([
|
|
31
|
+
{ role: "system", content: system },
|
|
32
|
+
{ role: "user", content: user },
|
|
33
|
+
], language);
|
|
34
|
+
}
|
|
35
|
+
// The passage as the narration says it: one paragraph, no Markdown a voice would read out.
|
|
36
|
+
export function spokenPassage(answer) {
|
|
37
|
+
return answer
|
|
38
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
39
|
+
.replace(/^\s*(?:[-*+]|\d+[.)]|#{1,6})\s+/gm, "")
|
|
40
|
+
.replace(/[*_`#|]/g, "")
|
|
41
|
+
.replace(/\s+/g, " ")
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(/^["“](.*)["”]$/s, "$1")
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Pauses between sentences. Some voices (Inworld's TTS-2 among them) run one sentence into the
|
|
2
|
+
// next with barely a breath, so a narration sounds rushed. With a minimum set, the join finds
|
|
3
|
+
// the quiet after each sentence in every narration piece and lengthens it to at least that
|
|
4
|
+
// long by adding silence in the middle of it (`pauses.ts`): an existing pause is never
|
|
5
|
+
// shortened and a word is never cut. It happens in the join, before the word timing reads the
|
|
6
|
+
// narration, so the captions, the word-by-word highlights, the cuts, the shorts' clips, the
|
|
7
|
+
// chapters and the M4B's marks are timed on the paced narration and stay in step with it.
|
|
8
|
+
//
|
|
9
|
+
// A paragraph's minimum is separate and 0 (the sentence minimum only) unless set: the pause
|
|
10
|
+
// between paragraphs is mostly the narration prompt's and the voice's to make.
|
|
11
|
+
//
|
|
12
|
+
// Browser-safe: Play and Edit project read these limits. A run without either has neither on
|
|
13
|
+
// its config, which is what every project saved before them was, and the joins keep their
|
|
14
|
+
// fingerprints then.
|
|
15
|
+
// ceiling: a quarter second reads brisk, 0.35 to 0.45 natural or documentary, 0.5 to 0.8 an
|
|
16
|
+
// audiobook's relaxed pace, and above 0.8 sleep content; beyond 2 s it stops sounding like a
|
|
17
|
+
// pause between sentences.
|
|
18
|
+
export const defaultSentencePauseSeconds = 0.4;
|
|
19
|
+
export const pauseSecondsMax = 2;
|
|
20
|
+
export const pauseSecondsStep = 0.05;
|
|
21
|
+
// What a run's config asks for; undefined when neither minimum is set.
|
|
22
|
+
export function pausesOf(config) {
|
|
23
|
+
const sentenceSeconds = config.sentencePauseSeconds ?? 0;
|
|
24
|
+
const paragraphSeconds = config.paragraphPauseSeconds ?? 0;
|
|
25
|
+
return sentenceSeconds > 0 || paragraphSeconds > 0
|
|
26
|
+
? { sentenceSeconds, paragraphSeconds }
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
export function pauseProblem(value, name) {
|
|
30
|
+
const steps = value / pauseSecondsStep;
|
|
31
|
+
return Number.isFinite(value) &&
|
|
32
|
+
value >= 0 &&
|
|
33
|
+
value <= pauseSecondsMax &&
|
|
34
|
+
Math.abs(steps - Math.round(steps)) < 1e-6
|
|
35
|
+
? undefined
|
|
36
|
+
: `The pause between ${name} must be between 0 and ${String(pauseSecondsMax)} seconds, in steps of ${String(pauseSecondsStep)}. Change it under Play → Video and style, or Edit project → Pauses and volume.`;
|
|
37
|
+
}
|
|
38
|
+
export function pauseFields(config) {
|
|
39
|
+
const fields = [];
|
|
40
|
+
if (config.sentencePauseSeconds !== undefined) {
|
|
41
|
+
const problem = pauseProblem(config.sentencePauseSeconds, "sentences");
|
|
42
|
+
if (problem !== undefined)
|
|
43
|
+
fields.push({ field: "sentencePauseSeconds", message: problem });
|
|
44
|
+
}
|
|
45
|
+
if (config.paragraphPauseSeconds !== undefined) {
|
|
46
|
+
const problem = pauseProblem(config.paragraphPauseSeconds, "paragraphs");
|
|
47
|
+
if (problem !== undefined)
|
|
48
|
+
fields.push({ field: "paragraphPauseSeconds", message: problem });
|
|
49
|
+
}
|
|
50
|
+
return fields;
|
|
51
|
+
}
|
|
52
|
+
// Play's typed pauses as a run's config holds them: absent text is the default (the sentence
|
|
53
|
+
// default, no paragraph minimum), a value of 0 is left off, and text that is not a number is
|
|
54
|
+
// NaN, which the rule refuses in place.
|
|
55
|
+
export function pausesOfForm(form) {
|
|
56
|
+
const typed = (raw, fallback) => raw === undefined || raw.trim() === "" ? fallback : Number(raw.trim().replace(",", "."));
|
|
57
|
+
const sentence = typed(form.sentencePause, defaultSentencePauseSeconds);
|
|
58
|
+
const paragraph = typed(form.paragraphPause, 0);
|
|
59
|
+
return {
|
|
60
|
+
...(sentence === 0 ? {} : { sentencePauseSeconds: sentence }),
|
|
61
|
+
...(paragraph === 0 ? {} : { paragraphPauseSeconds: paragraph }),
|
|
62
|
+
};
|
|
63
|
+
}
|