@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,287 @@
|
|
|
1
|
+
import { createReadStream, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { zValidator } from "@hono/zod-validator";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { projectById } from "../../slices/admission/repo.js";
|
|
8
|
+
import { channelById } from "../../slices/channels/repo.js";
|
|
9
|
+
import { findDownload } from "../../slices/storage/downloads.js";
|
|
10
|
+
import { studioPlaylistMax } from "../../slices/studio/model.js";
|
|
11
|
+
import { packItem, uploadPack } from "../../slices/studio/pack.js";
|
|
12
|
+
import { enqueueFill, readFillQueue, removeFill, } from "../../slices/studio/queue.js";
|
|
13
|
+
import { bearerToken, isExtensionOrigin, pairStudioExtension, readChannelPlaylists, readStudioPlaylist, resetStudioPairing, saveRealFootage, saveStudioPlaylist, studioPairing, studioPlaylistProblem, studioRequestAllowed, } from "../../slices/studio/settings.js";
|
|
14
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
15
|
+
const id = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(64)
|
|
19
|
+
.regex(/^[0-9A-Za-z_-]+$/);
|
|
20
|
+
const projectParam = z.object({ projectId: id });
|
|
21
|
+
const fileParam = z.object({
|
|
22
|
+
projectId: id,
|
|
23
|
+
asset: z
|
|
24
|
+
.string()
|
|
25
|
+
.max(64)
|
|
26
|
+
.regex(/^[a-z0-9-]+$/),
|
|
27
|
+
});
|
|
28
|
+
const chooseBody = z.object({ short: z.number().int().min(1).max(99).optional() });
|
|
29
|
+
const queueItemBody = z.object({
|
|
30
|
+
projectId: id,
|
|
31
|
+
short: z.number().int().min(1).max(99).nullable().optional(),
|
|
32
|
+
});
|
|
33
|
+
const playlistBody = z.object({
|
|
34
|
+
playlist: z.string().max(studioPlaylistMax * 2),
|
|
35
|
+
// A channel's own playlist; absent is the default every other channel uses.
|
|
36
|
+
channelId: id.optional(),
|
|
37
|
+
});
|
|
38
|
+
const realFootageBody = z.object({ realFootage: z.boolean() });
|
|
39
|
+
// The extension builds the app ships (`scripts/copy-extension.mjs`), by browser.
|
|
40
|
+
const extensionFiles = {
|
|
41
|
+
"chrome.zip": "slopify-studio-chrome.zip",
|
|
42
|
+
"firefox.zip": "slopify-studio-firefox.zip",
|
|
43
|
+
};
|
|
44
|
+
const extensionParam = z.object({ file: z.enum(["chrome.zip", "firefox.zip"]) });
|
|
45
|
+
// The Studio routes. The ones under `/ext` are the only routes of the app a page from another
|
|
46
|
+
// origin may read, and only the paired browser extension: every request carries the pairing
|
|
47
|
+
// token, and the CORS headers name the paired extension's origin, never a web page's and never
|
|
48
|
+
// `*`. The rest are the Slopify page's own, refused from any other origin.
|
|
49
|
+
export function studioRoutes(deps) {
|
|
50
|
+
// What waits to be filled, with each project's title; entries whose project is gone are left
|
|
51
|
+
// out (the extension drops them when it reaches them).
|
|
52
|
+
const queueView = (entries) => entries.flatMap((entry) => {
|
|
53
|
+
const project = projectById(deps.db, entry.projectId);
|
|
54
|
+
return project === undefined ? [] : [{ ...entry, projectTitle: project.title }];
|
|
55
|
+
});
|
|
56
|
+
const samePage = (c) => {
|
|
57
|
+
const origin = c.req.header("origin");
|
|
58
|
+
if (origin === undefined || origin === new URL(c.req.url).origin)
|
|
59
|
+
return undefined;
|
|
60
|
+
return problem(c, {
|
|
61
|
+
status: 403,
|
|
62
|
+
title: titleOf(403),
|
|
63
|
+
detail: "For your safety, YouTube Studio settings can only be changed from the Slopify page itself. Open Slopify and try again there.",
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
// The CORS headers for the extension: its own origin, echoed only when it is allowed.
|
|
67
|
+
const allowOrigin = (c, pairing) => {
|
|
68
|
+
const origin = c.req.header("origin");
|
|
69
|
+
const paired = studioPairing(deps.db).origin;
|
|
70
|
+
c.header("Vary", "Origin");
|
|
71
|
+
if (!isExtensionOrigin(origin))
|
|
72
|
+
return;
|
|
73
|
+
if (origin === paired || pairing) {
|
|
74
|
+
c.header("Access-Control-Allow-Origin", origin);
|
|
75
|
+
c.header("Access-Control-Allow-Headers", "authorization, content-type");
|
|
76
|
+
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
77
|
+
c.header("Access-Control-Max-Age", "600");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const refused = (c) => problem(c, {
|
|
81
|
+
status: 401,
|
|
82
|
+
title: "Unauthorized",
|
|
83
|
+
detail: "The Slopify Studio extension isn't paired with this Slopify. Copy the pairing token from Slopify's Settings → YouTube Studio into the extension's options and press Pair.",
|
|
84
|
+
});
|
|
85
|
+
return (new Hono()
|
|
86
|
+
.get("/settings", (c) => {
|
|
87
|
+
// The token is read only by the Slopify page, never by another origin's.
|
|
88
|
+
const denied = samePage(c);
|
|
89
|
+
if (denied !== undefined)
|
|
90
|
+
return denied;
|
|
91
|
+
return c.json({
|
|
92
|
+
playlist: readStudioPlaylist(deps.db),
|
|
93
|
+
channelPlaylists: readChannelPlaylists(deps.db),
|
|
94
|
+
pairing: studioPairing(deps.db),
|
|
95
|
+
});
|
|
96
|
+
})
|
|
97
|
+
.put("/settings/playlist", zValidator("json", playlistBody, onInvalid), (c) => {
|
|
98
|
+
const denied = samePage(c);
|
|
99
|
+
if (denied !== undefined)
|
|
100
|
+
return denied;
|
|
101
|
+
const { playlist: raw, channelId } = c.req.valid("json");
|
|
102
|
+
if (channelId !== undefined && channelById(deps.db, channelId) === undefined)
|
|
103
|
+
return problem(c, {
|
|
104
|
+
status: 404,
|
|
105
|
+
title: titleOf(404),
|
|
106
|
+
detail: "The playlist wasn't saved: that channel no longer exists. Reload Settings → YouTube Studio and pick the channel again.",
|
|
107
|
+
});
|
|
108
|
+
const invalid = studioPlaylistProblem(raw);
|
|
109
|
+
if (invalid !== undefined)
|
|
110
|
+
return problem(c, {
|
|
111
|
+
status: 400,
|
|
112
|
+
title: titleOf(400),
|
|
113
|
+
detail: invalid,
|
|
114
|
+
extensions: { fields: [{ field: "playlist", message: invalid }] },
|
|
115
|
+
});
|
|
116
|
+
return c.json({
|
|
117
|
+
playlist: saveStudioPlaylist(deps.db, raw, channelId),
|
|
118
|
+
channelId: channelId ?? null,
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
.post("/settings/pairing", (c) => {
|
|
122
|
+
const denied = samePage(c);
|
|
123
|
+
if (denied !== undefined)
|
|
124
|
+
return denied;
|
|
125
|
+
return c.json({ pairing: resetStudioPairing(deps.db) });
|
|
126
|
+
})
|
|
127
|
+
.get("/packs/:projectId", zValidator("param", projectParam, onInvalid), (c) => {
|
|
128
|
+
const result = uploadPack(deps, c.req.valid("param").projectId);
|
|
129
|
+
if (!result.ok)
|
|
130
|
+
return unknownProject(c);
|
|
131
|
+
return c.json(result.pack);
|
|
132
|
+
})
|
|
133
|
+
// Prepare upload's AI use tick: the project's uploaded clips are real footage.
|
|
134
|
+
.put("/packs/:projectId/real-footage", zValidator("param", projectParam, onInvalid), zValidator("json", realFootageBody, onInvalid), (c) => {
|
|
135
|
+
const denied = samePage(c);
|
|
136
|
+
if (denied !== undefined)
|
|
137
|
+
return denied;
|
|
138
|
+
const { projectId } = c.req.valid("param");
|
|
139
|
+
if (!uploadPack(deps, projectId).ok)
|
|
140
|
+
return unknownProject(c);
|
|
141
|
+
saveRealFootage(deps.db, projectId, c.req.valid("json").realFootage);
|
|
142
|
+
const result = uploadPack(deps, projectId);
|
|
143
|
+
return result.ok ? c.json(result.pack) : unknownProject(c);
|
|
144
|
+
})
|
|
145
|
+
.post("/packs/:projectId/choose", zValidator("param", projectParam, onInvalid), zValidator("json", chooseBody, onInvalid), (c) => {
|
|
146
|
+
const denied = samePage(c);
|
|
147
|
+
if (denied !== undefined)
|
|
148
|
+
return denied;
|
|
149
|
+
const { projectId } = c.req.valid("param");
|
|
150
|
+
const { short } = c.req.valid("json");
|
|
151
|
+
const result = uploadPack(deps, projectId);
|
|
152
|
+
if (!result.ok)
|
|
153
|
+
return unknownProject(c);
|
|
154
|
+
if (packItem(result.pack, short) === undefined)
|
|
155
|
+
return problem(c, {
|
|
156
|
+
status: 404,
|
|
157
|
+
title: titleOf(404),
|
|
158
|
+
detail: `This project has no short ${String(short)}. Reload the project page and press Prepare upload again.`,
|
|
159
|
+
});
|
|
160
|
+
const queue = enqueueFill(deps.db, projectId, short ?? null, deps.clock.now());
|
|
161
|
+
return c.json({ chosen: { projectId, short: short ?? null }, queue: queueView(queue) });
|
|
162
|
+
})
|
|
163
|
+
// What waits for the extension, oldest first: each new upload dialog takes the first.
|
|
164
|
+
.get("/queue", (c) => {
|
|
165
|
+
const denied = samePage(c);
|
|
166
|
+
if (denied !== undefined)
|
|
167
|
+
return denied;
|
|
168
|
+
return c.json({ queue: queueView(readFillQueue(deps.db, deps.clock.now())) });
|
|
169
|
+
})
|
|
170
|
+
.post("/queue/remove", zValidator("json", queueItemBody, onInvalid), (c) => {
|
|
171
|
+
const denied = samePage(c);
|
|
172
|
+
if (denied !== undefined)
|
|
173
|
+
return denied;
|
|
174
|
+
const { projectId, short } = c.req.valid("json");
|
|
175
|
+
return c.json({
|
|
176
|
+
queue: queueView(removeFill(deps.db, projectId, short ?? null, deps.clock.now())),
|
|
177
|
+
});
|
|
178
|
+
})
|
|
179
|
+
// The extension's own zips, for Settings' and Prepare upload's Download.
|
|
180
|
+
.get("/extension/:file", zValidator("param", extensionParam, onInvalid), (c) => {
|
|
181
|
+
const { file } = c.req.valid("param");
|
|
182
|
+
const name = extensionFiles[file] ?? "";
|
|
183
|
+
const path = deps.extensionDist === undefined ? undefined : join(deps.extensionDist, name);
|
|
184
|
+
const stat = path === undefined ? undefined : statSync(path, { throwIfNoEntry: false });
|
|
185
|
+
if (path === undefined || stat === undefined || !stat.isFile())
|
|
186
|
+
return problem(c, {
|
|
187
|
+
status: 404,
|
|
188
|
+
title: titleOf(404),
|
|
189
|
+
detail: "The Slopify Studio extension isn't included in this copy of Slopify, so there is nothing to download. Update or reinstall Slopify; when running from the repository, run `npm run build` at its root, which builds the extension too.",
|
|
190
|
+
});
|
|
191
|
+
return c.body(Readable.toWeb(createReadStream(path)), 200, {
|
|
192
|
+
"content-type": "application/zip",
|
|
193
|
+
"content-length": String(stat.size),
|
|
194
|
+
"content-disposition": `attachment; filename="${name}"`,
|
|
195
|
+
"x-content-type-options": "nosniff",
|
|
196
|
+
});
|
|
197
|
+
})
|
|
198
|
+
// The extension's routes.
|
|
199
|
+
.options("/ext/pair", (c) => {
|
|
200
|
+
allowOrigin(c, true);
|
|
201
|
+
return c.body(null, 204);
|
|
202
|
+
})
|
|
203
|
+
.options("/ext/*", (c) => {
|
|
204
|
+
allowOrigin(c, false);
|
|
205
|
+
return c.body(null, 204);
|
|
206
|
+
})
|
|
207
|
+
.post("/ext/pair", (c) => {
|
|
208
|
+
allowOrigin(c, true);
|
|
209
|
+
const result = pairStudioExtension(deps.db, bearerToken(c.req.header("authorization")), c.req.header("origin"), deps.clock.now());
|
|
210
|
+
if (!result.ok)
|
|
211
|
+
return result.reason === "bad-token"
|
|
212
|
+
? refused(c)
|
|
213
|
+
: problem(c, {
|
|
214
|
+
status: 403,
|
|
215
|
+
title: titleOf(403),
|
|
216
|
+
detail: "Only the Slopify Studio browser extension can pair with Slopify. Install it (see Settings → YouTube Studio) and pair from its options page.",
|
|
217
|
+
});
|
|
218
|
+
// The origin is paired now; the response carries its headers.
|
|
219
|
+
allowOrigin(c, false);
|
|
220
|
+
return c.json({ paired: true, origin: result.pairing.origin });
|
|
221
|
+
})
|
|
222
|
+
.get("/ext/pack", (c) => {
|
|
223
|
+
allowOrigin(c, false);
|
|
224
|
+
if (!studioRequestAllowed(deps.db, bearerToken(c.req.header("authorization")), c.req.header("origin")))
|
|
225
|
+
return refused(c);
|
|
226
|
+
// The first waiting item that can still be filled; one whose project or short is gone
|
|
227
|
+
// leaves the queue.
|
|
228
|
+
const now = deps.clock.now();
|
|
229
|
+
let gone = 0;
|
|
230
|
+
for (const entry of readFillQueue(deps.db, now)) {
|
|
231
|
+
const result = uploadPack(deps, entry.projectId);
|
|
232
|
+
const item = result.ok ? packItem(result.pack, entry.short ?? undefined) : undefined;
|
|
233
|
+
if (result.ok && item !== undefined) {
|
|
234
|
+
const waiting = readFillQueue(deps.db, now).length;
|
|
235
|
+
return c.json({ pack: result.pack, item, waiting });
|
|
236
|
+
}
|
|
237
|
+
removeFill(deps.db, entry.projectId, entry.short, now);
|
|
238
|
+
gone += 1;
|
|
239
|
+
}
|
|
240
|
+
return problem(c, {
|
|
241
|
+
status: 404,
|
|
242
|
+
title: titleOf(404),
|
|
243
|
+
detail: gone > 0
|
|
244
|
+
? "The project chosen to fill in is gone. In Slopify, open the project again, press Prepare upload, then Fill in YouTube Studio."
|
|
245
|
+
: "Nothing is waiting to be filled in. In Slopify, open the project, press Prepare upload, then Fill in YouTube Studio.",
|
|
246
|
+
});
|
|
247
|
+
})
|
|
248
|
+
// The extension filled an item: it leaves the queue, so the next upload dialog takes the
|
|
249
|
+
// next one.
|
|
250
|
+
.post("/ext/filled", zValidator("json", queueItemBody, onInvalid), (c) => {
|
|
251
|
+
allowOrigin(c, false);
|
|
252
|
+
if (!studioRequestAllowed(deps.db, bearerToken(c.req.header("authorization")), c.req.header("origin")))
|
|
253
|
+
return refused(c);
|
|
254
|
+
const { projectId, short } = c.req.valid("json");
|
|
255
|
+
const left = removeFill(deps.db, projectId, short ?? null, deps.clock.now());
|
|
256
|
+
return c.json({ waiting: left.length });
|
|
257
|
+
})
|
|
258
|
+
.get("/ext/files/:projectId/:asset", zValidator("param", fileParam, onInvalid), (c) => {
|
|
259
|
+
allowOrigin(c, false);
|
|
260
|
+
if (!studioRequestAllowed(deps.db, bearerToken(c.req.header("authorization")), c.req.header("origin")))
|
|
261
|
+
return refused(c);
|
|
262
|
+
const { projectId, asset } = c.req.valid("param");
|
|
263
|
+
// Only the thumbnails of a pack: the extension needs nothing else from the disk.
|
|
264
|
+
const result = uploadPack(deps, projectId);
|
|
265
|
+
const listed = result.ok &&
|
|
266
|
+
result.pack.items.some((item) => item.thumbnails.some((thumbnail) => thumbnail.asset === asset));
|
|
267
|
+
const found = listed ? findDownload(deps, projectId, asset) : undefined;
|
|
268
|
+
if (found === undefined || !found.ok)
|
|
269
|
+
return problem(c, {
|
|
270
|
+
status: 404,
|
|
271
|
+
title: titleOf(404),
|
|
272
|
+
detail: "That thumbnail isn't in the project's upload pack any more. Reload the project in Slopify and press Fill in YouTube Studio again.",
|
|
273
|
+
});
|
|
274
|
+
return c.body(Readable.toWeb(createReadStream(found.download.path)), 200, {
|
|
275
|
+
"content-type": found.download.contentType,
|
|
276
|
+
"content-length": String(found.download.bytes),
|
|
277
|
+
"x-content-type-options": "nosniff",
|
|
278
|
+
});
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
function unknownProject(c) {
|
|
282
|
+
return problem(c, {
|
|
283
|
+
status: 404,
|
|
284
|
+
title: titleOf(404),
|
|
285
|
+
detail: "This project no longer exists. Go back to Projects and open it again.",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { StylePreviewError } from "../../slices/style-preview/render.js";
|
|
5
|
+
import { stylePreviewRequestSchema } from "../../slices/style-preview/schema.js";
|
|
6
|
+
import { fileResponse } from "./byte-range.js";
|
|
7
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
8
|
+
export { byteRange } from "./byte-range.js";
|
|
9
|
+
// POST renders (or finds) the preview for a set of caption and Look settings and answers where
|
|
10
|
+
// to play it; GET streams the mp4, with byte ranges so the browser's player can seek.
|
|
11
|
+
const fileParam = z.object({ file: z.string().regex(/^[a-f0-9]{64}\.mp4$/) });
|
|
12
|
+
export function stylePreviewRoutes(deps) {
|
|
13
|
+
return new Hono()
|
|
14
|
+
.post("/", zValidator("json", stylePreviewRequestSchema, onInvalid), async (c) => {
|
|
15
|
+
const previews = deps.stylePreviews;
|
|
16
|
+
if (previews === undefined)
|
|
17
|
+
return unavailable(c);
|
|
18
|
+
try {
|
|
19
|
+
const result = await previews.render(c.req.valid("json"));
|
|
20
|
+
c.header("Cache-Control", "no-store");
|
|
21
|
+
return c.json({
|
|
22
|
+
hash: result.hash,
|
|
23
|
+
url: `/api/style-preview/${result.hash}.mp4?v=${String(result.version)}`,
|
|
24
|
+
cached: result.cached,
|
|
25
|
+
seconds: result.seconds,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!(error instanceof StylePreviewError))
|
|
30
|
+
throw error;
|
|
31
|
+
const status = error.reason === "font-missing" ? 409 : error.reason === "ffmpeg-missing" ? 503 : 500;
|
|
32
|
+
return problem(c, {
|
|
33
|
+
status,
|
|
34
|
+
title: titleOf(status),
|
|
35
|
+
detail: error.message,
|
|
36
|
+
extensions: { reason: error.reason },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.get("/:file", zValidator("param", fileParam, onInvalid), (c) => {
|
|
41
|
+
const saved = deps.stylePreviews?.file(c.req.valid("param").file.slice(0, 64));
|
|
42
|
+
if (saved === undefined)
|
|
43
|
+
return problem(c, {
|
|
44
|
+
status: 404,
|
|
45
|
+
title: titleOf(404),
|
|
46
|
+
detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press Render again to make it.",
|
|
47
|
+
});
|
|
48
|
+
return fileResponse(c, saved.path, saved.bytes, {
|
|
49
|
+
"content-type": "video/mp4",
|
|
50
|
+
"cache-control": "no-cache",
|
|
51
|
+
"x-content-type-options": "nosniff",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function unavailable(c) {
|
|
56
|
+
return problem(c, {
|
|
57
|
+
status: 503,
|
|
58
|
+
title: titleOf(503),
|
|
59
|
+
detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press Render again.",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -16,7 +16,7 @@ function revisionRequired(c) {
|
|
|
16
16
|
return problem(c, {
|
|
17
17
|
status: 409,
|
|
18
18
|
title: titleOf(409),
|
|
19
|
-
detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use
|
|
19
|
+
detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Choose what to remake (in the project's More menu).",
|
|
20
20
|
extensions: { reason: "revision-required" },
|
|
21
21
|
});
|
|
22
22
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { withProjectControl } from "../../slices/control/lock.js";
|
|
5
|
+
import { trashKinds } from "../../slices/trash/model.js";
|
|
6
|
+
import { deleteNow, listTrash, restoreItem } from "../../slices/trash/service.js";
|
|
7
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
8
|
+
const itemParam = z.object({
|
|
9
|
+
kind: z.enum(trashKinds),
|
|
10
|
+
id: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(64)
|
|
14
|
+
.regex(/^[0-9A-Za-z_-]+$/),
|
|
15
|
+
});
|
|
16
|
+
const status = {
|
|
17
|
+
"not-found": 404,
|
|
18
|
+
running: 409,
|
|
19
|
+
files: 500,
|
|
20
|
+
"template-in-trash": 409,
|
|
21
|
+
"template-gone": 409,
|
|
22
|
+
};
|
|
23
|
+
const details = {
|
|
24
|
+
"not-found": "This item is no longer in the trash: it was restored or removed for good. Reload Settings → Trash to see what is left.",
|
|
25
|
+
running: "This project is still running. Use Cancel run on the project page first, then try again.",
|
|
26
|
+
files: "Some of this project's files could not be removed. Close any program using files in the project folder, then press Delete now again.",
|
|
27
|
+
"template-in-trash": "This schedule's template is in the trash too. Restore the template first (it is listed here), then restore the schedule.",
|
|
28
|
+
"template-gone": "This schedule's template was removed for good, so the schedule has nothing to run. It cannot be restored; Delete now removes it from the trash.",
|
|
29
|
+
};
|
|
30
|
+
function refused(c, reason, detail) {
|
|
31
|
+
return problem(c, {
|
|
32
|
+
status: status[reason],
|
|
33
|
+
title: titleOf(status[reason]),
|
|
34
|
+
detail: detail === undefined ? details[reason] : `${details[reason]} (${detail})`,
|
|
35
|
+
extensions: { reason },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Settings → Trash: the list, Restore and Delete now. The daily purge runs from main.ts.
|
|
39
|
+
export function trashRoutes(deps) {
|
|
40
|
+
const trash = {
|
|
41
|
+
db: deps.db,
|
|
42
|
+
paths: deps.paths,
|
|
43
|
+
clock: deps.clock,
|
|
44
|
+
log: deps.log,
|
|
45
|
+
hasInflight: deps.runner.hasInflight,
|
|
46
|
+
};
|
|
47
|
+
return new Hono()
|
|
48
|
+
.get("/", (c) => c.json({ items: listTrash(trash) }))
|
|
49
|
+
.post("/:kind/:id/restore", zValidator("param", itemParam, onInvalid), (c) => {
|
|
50
|
+
const { kind, id } = c.req.valid("param");
|
|
51
|
+
const result = restoreItem(trash, kind, id);
|
|
52
|
+
if (!result.ok)
|
|
53
|
+
return refused(c, result.reason);
|
|
54
|
+
// A restored project picks up where it was: unfinished work goes on, a queued batch
|
|
55
|
+
// item is back in its place.
|
|
56
|
+
if (kind === "project")
|
|
57
|
+
deps.runner.tick(id);
|
|
58
|
+
return c.json({ restored: result.value });
|
|
59
|
+
})
|
|
60
|
+
.delete("/:kind/:id", zValidator("param", itemParam, onInvalid), async (c) => {
|
|
61
|
+
const { kind, id } = c.req.valid("param");
|
|
62
|
+
const result = kind === "project"
|
|
63
|
+
? await withProjectControl(deps.db, id, () => deleteNow(trash, kind, id))
|
|
64
|
+
: deleteNow(trash, kind, id);
|
|
65
|
+
if (!result.ok)
|
|
66
|
+
return refused(c, result.reason, result.detail);
|
|
67
|
+
return c.body(null, 204);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { tutorialPageIdPattern } from "../../slices/tutorials/anchors.js";
|
|
3
|
+
import { bundledTutorialsDir, loadTutorials, searchTutorials, TutorialsMissing, } from "../../slices/tutorials/library.js";
|
|
4
|
+
import { problem, titleOf } from "./problem.js";
|
|
5
|
+
// A build without its tutorials says so in plain words; anything else is the app's error handler's.
|
|
6
|
+
function missing(c, error) {
|
|
7
|
+
if (!(error instanceof TutorialsMissing))
|
|
8
|
+
throw error;
|
|
9
|
+
return problem(c, { status: 500, title: titleOf(500), detail: error.message });
|
|
10
|
+
}
|
|
11
|
+
// Help → Tutorials: the sidebar and page list, a search across every page, and one page's
|
|
12
|
+
// Markdown. The pages ship with the app, so they are read once and kept.
|
|
13
|
+
// Preserve Hono's inferred route types for the web client.
|
|
14
|
+
export function tutorialPagesRoutes(deps) {
|
|
15
|
+
const dir = deps.tutorialsDir ?? bundledTutorialsDir();
|
|
16
|
+
let book;
|
|
17
|
+
const read = () => {
|
|
18
|
+
book ??= loadTutorials(dir).catch((error) => {
|
|
19
|
+
book = undefined;
|
|
20
|
+
throw error;
|
|
21
|
+
});
|
|
22
|
+
return book;
|
|
23
|
+
};
|
|
24
|
+
return new Hono()
|
|
25
|
+
.get("/", async (c) => {
|
|
26
|
+
try {
|
|
27
|
+
return c.json((await read()).index);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return missing(c, error);
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.get("/search", async (c) => {
|
|
34
|
+
const query = (c.req.query("q") ?? "").slice(0, 200);
|
|
35
|
+
try {
|
|
36
|
+
return c.json({ hits: searchTutorials(await read(), query) });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return missing(c, error);
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.get("/:page", async (c) => {
|
|
43
|
+
const id = c.req.param("page");
|
|
44
|
+
let markdown;
|
|
45
|
+
try {
|
|
46
|
+
markdown = tutorialPageIdPattern.test(id) ? (await read()).pages.get(id) : undefined;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return missing(c, error);
|
|
50
|
+
}
|
|
51
|
+
if (markdown === undefined)
|
|
52
|
+
return problem(c, {
|
|
53
|
+
status: 404,
|
|
54
|
+
title: titleOf(404),
|
|
55
|
+
detail: `There is no tutorial called ${id} in this version of Slopify. Pick one from the list on Help → Tutorials, or search there.`,
|
|
56
|
+
});
|
|
57
|
+
return c.body(markdown, 200, { "content-type": "text/markdown; charset=utf-8" });
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/edge/http/update.js
CHANGED
|
@@ -10,7 +10,7 @@ export function updateRoutes(deps) {
|
|
|
10
10
|
status: "idle",
|
|
11
11
|
blockedReason: "This copy of Slopify cannot update itself from the app. Update it the way you installed it, for example by running the launcher again.",
|
|
12
12
|
};
|
|
13
|
-
return new Hono()
|
|
13
|
+
return (new Hono()
|
|
14
14
|
.get("/ready", (c) => {
|
|
15
15
|
c.header("Cache-Control", "no-store");
|
|
16
16
|
if (!deps.updater?.ready(c.req.header("X-Slopify-Update-Token") ?? ""))
|
|
@@ -31,6 +31,23 @@ export function updateRoutes(deps) {
|
|
|
31
31
|
return c.json(deps.updater === undefined
|
|
32
32
|
? unavailable
|
|
33
33
|
: await deps.updater.check(c.req.query("refresh") === "1"));
|
|
34
|
+
})
|
|
35
|
+
// Drops an update that is waiting for running work to finish.
|
|
36
|
+
.delete("/", async (c) => {
|
|
37
|
+
const origin = c.req.header("Origin");
|
|
38
|
+
if (origin !== undefined && origin !== new URL(c.req.url).origin)
|
|
39
|
+
return problem(c, {
|
|
40
|
+
status: 403,
|
|
41
|
+
title: titleOf(403),
|
|
42
|
+
detail: "For your safety, updates can only be changed from the Slopify page itself. Open Slopify and try again there.",
|
|
43
|
+
});
|
|
44
|
+
if (deps.updater?.cancelWaiting() !== true)
|
|
45
|
+
return problem(c, {
|
|
46
|
+
status: 409,
|
|
47
|
+
title: titleOf(409),
|
|
48
|
+
detail: "No update is waiting to install. Reload the page to see the current update status.",
|
|
49
|
+
});
|
|
50
|
+
return c.json(await deps.updater.check());
|
|
34
51
|
})
|
|
35
52
|
.post("/", async (c) => {
|
|
36
53
|
const origin = c.req.header("Origin");
|
|
@@ -41,7 +58,11 @@ export function updateRoutes(deps) {
|
|
|
41
58
|
detail: "For your safety, updates can only be started from the Slopify page itself. Open Slopify and try again there.",
|
|
42
59
|
});
|
|
43
60
|
if (deps.updater === undefined)
|
|
44
|
-
return problem(c, {
|
|
61
|
+
return problem(c, {
|
|
62
|
+
status: 409,
|
|
63
|
+
title: titleOf(409),
|
|
64
|
+
detail: unavailable.blockedReason,
|
|
65
|
+
});
|
|
45
66
|
const result = await deps.updater.start();
|
|
46
67
|
if (result.ok)
|
|
47
68
|
return c.json(result.info, 202);
|
|
@@ -53,5 +74,5 @@ export function updateRoutes(deps) {
|
|
|
53
74
|
result.info.error ??
|
|
54
75
|
"You already have the latest version of Slopify.",
|
|
55
76
|
});
|
|
56
|
-
});
|
|
77
|
+
}));
|
|
57
78
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { markPatchNotesSeen } from "../../slices/patch-notes/seen.js";
|
|
3
|
+
import { dismissWhatsNew, readWhatsNew } from "../../slices/settings/whats-new.js";
|
|
4
|
+
// Preserve Hono's inferred route types for the web client.
|
|
5
|
+
export function whatsNewRoutes(deps) {
|
|
6
|
+
return (new Hono()
|
|
7
|
+
.get("/", (c) => c.json(readWhatsNew(deps.db, deps.version)))
|
|
8
|
+
// The tour stands in for the patch notes on a major update (its last step links to
|
|
9
|
+
// them), so closing it also closes this version's notes: never two popups in a row.
|
|
10
|
+
.post("/seen", (c) => {
|
|
11
|
+
const view = dismissWhatsNew(deps.db, deps.version);
|
|
12
|
+
markPatchNotesSeen(deps.db, deps.version);
|
|
13
|
+
return c.json(view);
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
5
|
+
import { projectExists } from "../../slices/admission/repo.js";
|
|
6
|
+
import { projectChannelId } from "../../slices/channels/repo.js";
|
|
7
|
+
import { descriptionFields, withEdit, withoutEdit } from "../../slices/youtube/edits.js";
|
|
8
|
+
import { readChannelLinksFor, readDescriptionEdits, writeDescriptionEdits, } from "../../slices/youtube/edits-repo.js";
|
|
9
|
+
import { descriptionMaxCharacters } from "../../slices/youtube/model.js";
|
|
10
|
+
import { channelLinksProblem } from "../../slices/youtube/placeholders.js";
|
|
11
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
12
|
+
import { channelLinksBody } from "./settings.js";
|
|
13
|
+
const idParam = z.object({ id: z.string().min(1).max(64) });
|
|
14
|
+
const fieldParam = idParam.extend({ field: z.enum(descriptionFields) });
|
|
15
|
+
// A field is at most a description long; the bound keeps a pasted novel off the table, and
|
|
16
|
+
// YouTube's own limit is shown beside the field rather than refused here.
|
|
17
|
+
const fieldBody = z.object({
|
|
18
|
+
text: z.string().max(descriptionMaxCharacters * 4),
|
|
19
|
+
base: z.string().max(descriptionMaxCharacters * 4),
|
|
20
|
+
});
|
|
21
|
+
const gone = "This project no longer exists. Go back to Projects to pick another.";
|
|
22
|
+
// The project page's hand edits to the YouTube description (`slices/youtube/edits.ts`). The
|
|
23
|
+
// server keeps them; the page applies the merge rules to the text it shows.
|
|
24
|
+
export function youtubeEditRoutes(deps) {
|
|
25
|
+
const exists = (id) => projectExists(deps.db, id);
|
|
26
|
+
const missing = (c) => problem(c, { status: 404, title: titleOf(404), detail: gone });
|
|
27
|
+
const now = () => deps.clock.now().toISOString();
|
|
28
|
+
return (new Hono()
|
|
29
|
+
.get("/:id/youtube-edits", zValidator("param", idParam, onInvalid), (c) => {
|
|
30
|
+
const { id } = c.req.valid("param");
|
|
31
|
+
return exists(id) ? c.json(readDescriptionEdits(deps.db, id)) : missing(c);
|
|
32
|
+
})
|
|
33
|
+
// The links of the project's channel, which its placeholders fill from after its own.
|
|
34
|
+
.get("/:id/channel-links", zValidator("param", idParam, onInvalid), (c) => {
|
|
35
|
+
const { id } = c.req.valid("param");
|
|
36
|
+
if (!exists(id))
|
|
37
|
+
return missing(c);
|
|
38
|
+
const channelId = projectChannelId(deps.db, id);
|
|
39
|
+
return c.json({ channelId, links: readChannelLinksFor(deps.db, channelId) });
|
|
40
|
+
})
|
|
41
|
+
// Saves the user's text for one field with the generated text it was edited from; text
|
|
42
|
+
// equal to that generated text clears the edit. "Keep mine" is this call with the new
|
|
43
|
+
// generated text as the base.
|
|
44
|
+
.put("/:id/youtube-edits/fields/:field", zValidator("param", fieldParam, onInvalid), zValidator("json", fieldBody, onInvalid), (c) => {
|
|
45
|
+
const { id, field } = c.req.valid("param");
|
|
46
|
+
if (!exists(id))
|
|
47
|
+
return missing(c);
|
|
48
|
+
const { text, base } = c.req.valid("json");
|
|
49
|
+
const saved = transact(deps.db, () => {
|
|
50
|
+
const current = readDescriptionEdits(deps.db, id);
|
|
51
|
+
const next = { ...current, fields: withEdit(current.fields, field, text, base) };
|
|
52
|
+
writeDescriptionEdits(deps.db, id, next, now());
|
|
53
|
+
return next;
|
|
54
|
+
});
|
|
55
|
+
return c.json(saved);
|
|
56
|
+
})
|
|
57
|
+
// "Use it": the field follows the generated text again.
|
|
58
|
+
.delete("/:id/youtube-edits/fields/:field", zValidator("param", fieldParam, onInvalid), (c) => {
|
|
59
|
+
const { id, field } = c.req.valid("param");
|
|
60
|
+
if (!exists(id))
|
|
61
|
+
return missing(c);
|
|
62
|
+
const saved = transact(deps.db, () => {
|
|
63
|
+
const current = readDescriptionEdits(deps.db, id);
|
|
64
|
+
const next = { ...current, fields: withoutEdit(current.fields, field) };
|
|
65
|
+
writeDescriptionEdits(deps.db, id, next, now());
|
|
66
|
+
return next;
|
|
67
|
+
});
|
|
68
|
+
return c.json(saved);
|
|
69
|
+
})
|
|
70
|
+
// The project's own links, such as its "Previous video"; they win over its channel's list.
|
|
71
|
+
.put("/:id/youtube-edits/links", zValidator("param", idParam, onInvalid), zValidator("json", channelLinksBody, onInvalid), (c) => {
|
|
72
|
+
const { id } = c.req.valid("param");
|
|
73
|
+
if (!exists(id))
|
|
74
|
+
return missing(c);
|
|
75
|
+
const links = c.req
|
|
76
|
+
.valid("json")
|
|
77
|
+
.links.map((link) => ({ name: link.name.trim(), url: link.url.trim() }));
|
|
78
|
+
const refused = channelLinksProblem(links);
|
|
79
|
+
if (refused !== undefined)
|
|
80
|
+
return problem(c, {
|
|
81
|
+
status: 400,
|
|
82
|
+
title: titleOf(400),
|
|
83
|
+
detail: `This project's links weren't saved: ${refused} Then press Save links under YouTube on the Video section.`,
|
|
84
|
+
});
|
|
85
|
+
const saved = transact(deps.db, () => {
|
|
86
|
+
const next = { ...readDescriptionEdits(deps.db, id), links };
|
|
87
|
+
writeDescriptionEdits(deps.db, id, next, now());
|
|
88
|
+
return next;
|
|
89
|
+
});
|
|
90
|
+
return c.json(saved);
|
|
91
|
+
}));
|
|
92
|
+
}
|