@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
|
@@ -7,7 +7,8 @@ import { fontMaxBytes } from "../fonts/model.js";
|
|
|
7
7
|
// data/library.json settings, library, templates, schedules, drafts
|
|
8
8
|
// data/usage.json the Usage screen's event log
|
|
9
9
|
// data/projects/<id>.json one per project: every row it needs to open and keep going
|
|
10
|
-
// files/... the bytes: project folders, uploaded fonts, draft uploads
|
|
10
|
+
// files/... the bytes: project folders, uploaded fonts, draft uploads and
|
|
11
|
+
// the pictures kept in the database (cast, end screen)
|
|
11
12
|
// checksums.json a SHA-256 per file, written as the files streamed out
|
|
12
13
|
//
|
|
13
14
|
// Rows travel as the database holds them, table by table, with the schema version they fit;
|
|
@@ -24,6 +25,9 @@ export const projectMember = (id) => `data/projects/${id}.json`;
|
|
|
24
25
|
export const projectFileMember = (id, path) => `files/projects/${id}/${path}`;
|
|
25
26
|
export const fontMember = (name) => `files/fonts/${name}`;
|
|
26
27
|
export const stagedMember = (id) => `files/staging/${id}`;
|
|
28
|
+
// A picture from image_blobs, named by its SHA-256: the database keeps the bytes (cast
|
|
29
|
+
// pictures, the end screen image), so they travel as files rather than inside a JSON row.
|
|
30
|
+
export const imageMember = (sha256) => `files/images/${sha256}`;
|
|
27
31
|
// Every table a project's rows live in, parents before children. The queue is left out on
|
|
28
32
|
// purpose: a backup never carries a project that is waiting to run.
|
|
29
33
|
export const projectTables = [
|
|
@@ -49,6 +53,15 @@ export const projectTables = [
|
|
|
49
53
|
"review_checkpoints",
|
|
50
54
|
"review_checkpoint_approvals",
|
|
51
55
|
"project_recovery_requests",
|
|
56
|
+
"youtube_description_edits",
|
|
57
|
+
"project_uploads",
|
|
58
|
+
// Since 2.5.0. Left out on purpose: plan_limit_waits and plan_limit_waiters (an account's
|
|
59
|
+
// current wait, a lease of this install) and prompt_softening (a pending one-off request).
|
|
60
|
+
"review_verdicts",
|
|
61
|
+
"provider_usage",
|
|
62
|
+
"plan_limit_readings",
|
|
63
|
+
"project_channels",
|
|
64
|
+
"project_trash",
|
|
52
65
|
];
|
|
53
66
|
export const libraryTables = [
|
|
54
67
|
"settings",
|
|
@@ -56,14 +69,27 @@ export const libraryTables = [
|
|
|
56
69
|
"entries",
|
|
57
70
|
"voices",
|
|
58
71
|
"document_themes",
|
|
72
|
+
"narration_aliases",
|
|
59
73
|
"project_templates",
|
|
60
74
|
"project_template_revisions",
|
|
61
75
|
"schedules",
|
|
62
76
|
"schedule_runs",
|
|
77
|
+
"schedule_topics",
|
|
63
78
|
"staged_files",
|
|
64
79
|
"play_drafts",
|
|
65
80
|
"play_draft_attachments",
|
|
66
81
|
"project_template_instantiations",
|
|
82
|
+
// Since 2.5.0: prompt history, channels with their cast, episode memories and existing
|
|
83
|
+
// videos. image_blobs travel as files (`imageMember`), listed in the library part's `images`.
|
|
84
|
+
"library_versions",
|
|
85
|
+
"channels",
|
|
86
|
+
"cast_members",
|
|
87
|
+
"cast_images",
|
|
88
|
+
"episode_memories",
|
|
89
|
+
"channel_videos",
|
|
90
|
+
// Since 3.0.0: what topic generation, episode summaries and cast pictures cost, which
|
|
91
|
+
// belongs to a schedule or channel rather than a project.
|
|
92
|
+
"standalone_usage",
|
|
67
93
|
];
|
|
68
94
|
export const usageTables = ["telemetry_events"];
|
|
69
95
|
// Ceilings a well-formed backup of a real install stays far below. They bound what a hostile
|
|
@@ -118,6 +144,15 @@ export const manifestSchema = z
|
|
|
118
144
|
bytes: z.number().int().min(0),
|
|
119
145
|
})
|
|
120
146
|
.strict();
|
|
147
|
+
// A picture in image_blobs is at most this large (cast uploads are capped well below it).
|
|
148
|
+
export const backupMaxImageBytes = 64 * 1024 * 1024;
|
|
149
|
+
export const backupImage = z
|
|
150
|
+
.object({
|
|
151
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
152
|
+
mime: z.enum(["image/png", "image/jpeg"]),
|
|
153
|
+
bytes: z.number().int().min(1).max(backupMaxImageBytes),
|
|
154
|
+
})
|
|
155
|
+
.strict();
|
|
121
156
|
const fileEntry = z.object({ path: relativePath, bytes: z.number().int().min(0) }).strict();
|
|
122
157
|
export const libraryPartSchema = z
|
|
123
158
|
.object({
|
|
@@ -133,6 +168,8 @@ export const libraryPartSchema = z
|
|
|
133
168
|
staged: z
|
|
134
169
|
.array(z.object({ id: backupId, bytes: z.number().int().positive() }).strict())
|
|
135
170
|
.max(10_000),
|
|
171
|
+
// Absent in backups made before channels.
|
|
172
|
+
images: z.array(backupImage).max(100_000).optional(),
|
|
136
173
|
})
|
|
137
174
|
.strict();
|
|
138
175
|
export const usagePartSchema = z.object({ tables: tablesOf(usageTables) }).strict();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statfsSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statfsSync, } from "node:fs";
|
|
3
3
|
import { open } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
7
|
import { migrate, newestMigration } from "../../kernel/db/migrate.js";
|
|
8
8
|
import { transact } from "../../kernel/db/tx.js";
|
|
9
|
-
import {
|
|
9
|
+
import { projectByIdIncludingTrash } from "../admission/repo.js";
|
|
10
|
+
import { castOfChannel, channelById } from "../channels/repo.js";
|
|
10
11
|
import { documentThemeNameMax, documentThemeNameProblems } from "../document/model.js";
|
|
11
12
|
import { documentThemeSchema } from "../document/theme-schema.js";
|
|
12
13
|
import { nameMax } from "../library/model.js";
|
|
@@ -16,7 +17,7 @@ import { revisionById } from "../revisions/repo.js";
|
|
|
16
17
|
import { runsForSchedule, scheduleRows } from "../schedules/repo.js";
|
|
17
18
|
import { allTelemetryEvents } from "../telemetry/repo.js";
|
|
18
19
|
import { projectRows, rowsOf } from "./backup-export.js";
|
|
19
|
-
import { backupMaxJsonBytes, backupMaxManifestBytes, backupSchemaVersion, checksumsMember, checksumsSchema, fontMember, libraryMember, libraryPartSchema, libraryTables, manifestHead, manifestMember, manifestSchema, projectFileMember, projectMember, projectPartSchema, projectTables, stagedMember, usageMember, usagePartSchema, usageTables, } from "./backup-format.js";
|
|
20
|
+
import { backupMaxJsonBytes, backupMaxManifestBytes, backupSchemaVersion, checksumsMember, checksumsSchema, fontMember, imageMember, libraryMember, libraryPartSchema, libraryTables, manifestHead, manifestMember, manifestSchema, projectFileMember, projectMember, projectPartSchema, projectTables, stagedMember, usageMember, usagePartSchema, usageTables, } from "./backup-format.js";
|
|
20
21
|
import { outputPath, projectDir, stagingPath } from "./layout.js";
|
|
21
22
|
import { checkLibraryRows, exportableSettings, installedUploadedFontFiles, validFontContent, } from "./portable.js";
|
|
22
23
|
import { readTar, TarFormatError } from "./tar.js";
|
|
@@ -225,7 +226,10 @@ async function receive(body, file) {
|
|
|
225
226
|
finally {
|
|
226
227
|
await handle.close();
|
|
227
228
|
}
|
|
228
|
-
|
|
229
|
+
const digest = hash.digest("hex");
|
|
230
|
+
if (file.image !== undefined && digest !== file.image)
|
|
231
|
+
throw damaged(`the picture ${file.image} in it is damaged.`);
|
|
232
|
+
return digest;
|
|
229
233
|
}
|
|
230
234
|
// Loads every row into a scratch database at the schema the backup was written with, carries
|
|
231
235
|
// it forward with this build's migrations, and checks it with the same readers the app uses.
|
|
@@ -282,6 +286,16 @@ function prepare(deps, work, manifest, library, usage, parts, expected) {
|
|
|
282
286
|
font: { id, extension },
|
|
283
287
|
});
|
|
284
288
|
}
|
|
289
|
+
for (const image of library.images ?? []) {
|
|
290
|
+
const keep = deps.db.prepare("SELECT 1 FROM image_blobs WHERE sha256=?").get(image.sha256) === undefined;
|
|
291
|
+
expect(imageMember(image.sha256), {
|
|
292
|
+
bytes: image.bytes,
|
|
293
|
+
destination: keep ? join(work, "images", image.sha256) : undefined,
|
|
294
|
+
image: image.sha256,
|
|
295
|
+
});
|
|
296
|
+
if (keep)
|
|
297
|
+
needed += image.bytes;
|
|
298
|
+
}
|
|
285
299
|
for (const staged of library.staged) {
|
|
286
300
|
const keep = keptStaged.has(staged.id);
|
|
287
301
|
expect(stagedMember(staged.id), {
|
|
@@ -292,7 +306,7 @@ function prepare(deps, work, manifest, library, usage, parts, expected) {
|
|
|
292
306
|
needed += staged.bytes;
|
|
293
307
|
}
|
|
294
308
|
checkFreeSpace(deps.paths.dataDir, needed);
|
|
295
|
-
return { scratch, projects, skippedProjects, drafts };
|
|
309
|
+
return { scratch, projects, skippedProjects, drafts, images: library.images ?? [] };
|
|
296
310
|
}
|
|
297
311
|
catch (error) {
|
|
298
312
|
scratch.close();
|
|
@@ -319,12 +333,23 @@ function loadScratch(clock, manifest, library, usage, parts) {
|
|
|
319
333
|
if (known.size === 0 || Object.keys(row).some((column) => !known.has(column)))
|
|
320
334
|
throw damaged(`it has ${table} records that don't fit the Slopify version it names.`);
|
|
321
335
|
try {
|
|
322
|
-
|
|
336
|
+
// The default channel is already there: every install makes it (0032) with the same id.
|
|
337
|
+
insertRow(scratch, table, row, table === "channels" ? "INSERT OR REPLACE" : "INSERT");
|
|
323
338
|
}
|
|
324
339
|
catch {
|
|
325
340
|
throw damaged(`it has a ${table} record Slopify can't read.`);
|
|
326
341
|
}
|
|
327
342
|
};
|
|
343
|
+
// The pictures' bytes arrive after this, as files; a stand-in row lets their links check.
|
|
344
|
+
for (const image of library.images ?? [])
|
|
345
|
+
try {
|
|
346
|
+
scratch
|
|
347
|
+
.prepare("INSERT OR IGNORE INTO image_blobs(sha256,mime,bytes,created_at) VALUES (?,?,X'00','')")
|
|
348
|
+
.run(image.sha256, image.mime);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
throw damaged("it has pictures that don't fit the Slopify version it names.");
|
|
352
|
+
}
|
|
328
353
|
for (const table of libraryTables)
|
|
329
354
|
for (const row of library.tables[table] ?? [])
|
|
330
355
|
insert(table, row);
|
|
@@ -370,11 +395,20 @@ function loadScratch(clock, manifest, library, usage, parts) {
|
|
|
370
395
|
function checkScratch(deps, scratch, library, parts) {
|
|
371
396
|
const unreadable = (what) => damaged(`it has ${what} Slopify can't read.`);
|
|
372
397
|
try {
|
|
398
|
+
// Names are unique among the items outside the trash (0039); one in the trash is checked
|
|
399
|
+
// on its own.
|
|
400
|
+
const prompt = "SELECT id,kind,name,body,slots,updated_at FROM prompts";
|
|
401
|
+
const entry = "SELECT id,category,mode,name,body,slots,updated_at FROM entries";
|
|
373
402
|
checkLibraryRows({
|
|
374
|
-
prompts: rowsOf(scratch,
|
|
375
|
-
entries: rowsOf(scratch,
|
|
376
|
-
|
|
403
|
+
prompts: rowsOf(scratch, `${prompt} WHERE deleted_at IS NULL`),
|
|
404
|
+
entries: rowsOf(scratch, `${entry} WHERE deleted_at IS NULL`),
|
|
405
|
+
// A voice's languages are checked as the settings export reads them (`languages`).
|
|
406
|
+
voices: rowsOf(scratch, "SELECT id,provider,name,voice_id,languages_json FROM voices").map(({ languages_json: languages, ...voice }) => typeof languages === "string" ? { ...voice, languages: JSON.parse(languages) } : voice),
|
|
377
407
|
});
|
|
408
|
+
for (const row of rowsOf(scratch, `${prompt} WHERE deleted_at IS NOT NULL`))
|
|
409
|
+
checkLibraryRows({ prompts: [row], entries: [], voices: [] });
|
|
410
|
+
for (const row of rowsOf(scratch, `${entry} WHERE deleted_at IS NOT NULL`))
|
|
411
|
+
checkLibraryRows({ prompts: [], entries: [row], voices: [] });
|
|
378
412
|
}
|
|
379
413
|
catch {
|
|
380
414
|
throw unreadable("a prompt, intro, outro or voice");
|
|
@@ -401,6 +435,16 @@ function checkScratch(deps, scratch, library, parts) {
|
|
|
401
435
|
catch {
|
|
402
436
|
throw unreadable("a document theme");
|
|
403
437
|
}
|
|
438
|
+
try {
|
|
439
|
+
for (const row of rowsOf(scratch, "SELECT id FROM channels")) {
|
|
440
|
+
if (channelById(scratch, String(row.id)) === undefined)
|
|
441
|
+
throw new Error("channel");
|
|
442
|
+
castOfChannel(scratch, String(row.id));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
throw unreadable("a channel or cast member");
|
|
447
|
+
}
|
|
404
448
|
try {
|
|
405
449
|
for (const schedule of scheduleRows(scratch))
|
|
406
450
|
runsForSchedule(scratch, schedule.id);
|
|
@@ -428,7 +472,7 @@ function checkScratch(deps, scratch, library, parts) {
|
|
|
428
472
|
}
|
|
429
473
|
for (const project of parts) {
|
|
430
474
|
try {
|
|
431
|
-
if (
|
|
475
|
+
if (projectByIdIncludingTrash(scratch, project.id) === undefined)
|
|
432
476
|
throw new Error("project");
|
|
433
477
|
for (const row of rowsOf(scratch, "SELECT id FROM project_revisions WHERE project_id=?", project.id))
|
|
434
478
|
revisionById(scratch, project.id, String(row.id));
|
|
@@ -516,14 +560,14 @@ function commit(deps, work, manifest, prepared) {
|
|
|
516
560
|
}
|
|
517
561
|
const prompts = mergeNamed(db, scratch, {
|
|
518
562
|
table: "prompts",
|
|
519
|
-
sameName: "SELECT body FROM prompts WHERE kind=? AND lower(name)=lower(?)",
|
|
563
|
+
sameName: "SELECT body FROM prompts WHERE kind=? AND lower(name)=lower(?) AND deleted_at IS NULL",
|
|
520
564
|
key: (row) => [row.kind ?? null],
|
|
521
565
|
same: (row, existing) => existing.body === row.body,
|
|
522
566
|
max: nameMax,
|
|
523
567
|
});
|
|
524
568
|
const entries = mergeNamed(db, scratch, {
|
|
525
569
|
table: "entries",
|
|
526
|
-
sameName: "SELECT mode,body FROM entries WHERE category=? AND lower(name)=lower(?)",
|
|
570
|
+
sameName: "SELECT mode,body FROM entries WHERE category=? AND lower(name)=lower(?) AND deleted_at IS NULL",
|
|
527
571
|
key: (row) => [row.category ?? null],
|
|
528
572
|
same: (row, existing) => existing.mode === row.mode && existing.body === row.body,
|
|
529
573
|
max: nameMax,
|
|
@@ -535,6 +579,9 @@ function commit(deps, work, manifest, prepared) {
|
|
|
535
579
|
same: (row, existing) => existing.values_json === row.values_json,
|
|
536
580
|
max: documentThemeNameMax,
|
|
537
581
|
});
|
|
582
|
+
mergeVersions(db, scratch, "prompt", prompts.inserted);
|
|
583
|
+
mergeVersions(db, scratch, "entry", entries.inserted);
|
|
584
|
+
const channelCounts = mergeChannels(db, scratch, work, prepared.images, now);
|
|
538
585
|
let voicesAdded = 0;
|
|
539
586
|
let voicesSkipped = 0;
|
|
540
587
|
for (const row of rowsOf(scratch, "SELECT * FROM voices ORDER BY rowid")) {
|
|
@@ -546,6 +593,23 @@ function commit(deps, work, manifest, prepared) {
|
|
|
546
593
|
voicesAdded += 1;
|
|
547
594
|
}
|
|
548
595
|
}
|
|
596
|
+
// Library → Aliases: an alias for a written form this install already has is kept as
|
|
597
|
+
// it is here; the others are added after this install's own, in the backup's order.
|
|
598
|
+
const narrationAliases = { added: 0, renamed: 0, skipped: 0 };
|
|
599
|
+
let position = Number(db.prepare("SELECT coalesce(max(position),-1) AS n FROM narration_aliases").get()?.n ?? -1);
|
|
600
|
+
for (const row of rowsOf(scratch, "SELECT * FROM narration_aliases ORDER BY position,rowid")) {
|
|
601
|
+
const caseSensitive = row.case_sensitive === 1;
|
|
602
|
+
if (has("SELECT 1 FROM narration_aliases WHERE id=?", String(row.id)) ||
|
|
603
|
+
has(caseSensitive
|
|
604
|
+
? "SELECT 1 FROM narration_aliases WHERE case_sensitive=1 AND written=?"
|
|
605
|
+
: "SELECT 1 FROM narration_aliases WHERE case_sensitive=0 AND lower(written)=lower(?)", String(row.written)))
|
|
606
|
+
narrationAliases.skipped += 1;
|
|
607
|
+
else {
|
|
608
|
+
position += 1;
|
|
609
|
+
insertRow(db, "narration_aliases", { ...row, position });
|
|
610
|
+
narrationAliases.added += 1;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
549
613
|
const templates = { added: 0, renamed: 0, skipped: 0 };
|
|
550
614
|
for (const template of rowsOf(scratch, "SELECT * FROM project_templates ORDER BY rowid")) {
|
|
551
615
|
const id = String(template.id);
|
|
@@ -557,9 +621,11 @@ function commit(deps, work, manifest, prepared) {
|
|
|
557
621
|
let renamed = false;
|
|
558
622
|
for (const revision of rowsOf(scratch, "SELECT * FROM project_template_revisions WHERE template_id=? ORDER BY version", id)) {
|
|
559
623
|
let next = revision;
|
|
560
|
-
|
|
624
|
+
// Names clash only among templates outside the trash, like prompts'.
|
|
625
|
+
if (revision.version === template.head_version &&
|
|
626
|
+
typeof template.deleted_at !== "string") {
|
|
561
627
|
const name = String(revision.name);
|
|
562
|
-
const taken = (candidate) => has("SELECT 1 FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version WHERE t.id<>? AND lower(r.name)=lower(?)", id, candidate);
|
|
628
|
+
const taken = (candidate) => has("SELECT 1 FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version WHERE t.id<>? AND t.deleted_at IS NULL AND lower(r.name)=lower(?)", id, candidate);
|
|
563
629
|
if (taken(name)) {
|
|
564
630
|
next = { ...revision, name: importedName(name, 200, taken) };
|
|
565
631
|
renamed = true;
|
|
@@ -582,12 +648,19 @@ function commit(deps, work, manifest, prepared) {
|
|
|
582
648
|
// Two installs running one schedule would make every project twice. It comes in
|
|
583
649
|
// paused; Resume on the Schedules screen starts it here.
|
|
584
650
|
const active = schedule.status === "active";
|
|
585
|
-
insertRow(db, "schedules",
|
|
651
|
+
insertRow(db, "schedules",
|
|
652
|
+
// A generation the other install was running does not carry over.
|
|
653
|
+
active
|
|
654
|
+
? { ...schedule, status: "paused", updated_at: now, topics_generating_at: null }
|
|
655
|
+
: { ...schedule, topics_generating_at: null });
|
|
586
656
|
if (active)
|
|
587
657
|
schedules.paused += 1;
|
|
588
658
|
for (const run of rowsOf(scratch, "SELECT * FROM schedule_runs WHERE schedule_id=? ORDER BY rowid", String(schedule.id)))
|
|
589
659
|
if (!has("SELECT 1 FROM schedule_runs WHERE id=?", String(run.id)))
|
|
590
660
|
insertRow(db, "schedule_runs", run);
|
|
661
|
+
for (const topic of rowsOf(scratch, "SELECT * FROM schedule_topics WHERE schedule_id=? ORDER BY rowid", String(schedule.id)))
|
|
662
|
+
if (!has("SELECT 1 FROM schedule_topics WHERE id=?", String(topic.id)))
|
|
663
|
+
insertRow(db, "schedule_topics", topic);
|
|
591
664
|
schedules.added += 1;
|
|
592
665
|
}
|
|
593
666
|
const drafts = { added: 0, renamed: 0, skipped: 0 };
|
|
@@ -624,6 +697,10 @@ function commit(deps, work, manifest, prepared) {
|
|
|
624
697
|
fonts += 1;
|
|
625
698
|
}
|
|
626
699
|
const alreadyImported = has("SELECT 1 FROM backup_imports WHERE backup_id=?", manifest.backupId);
|
|
700
|
+
// Run cost made for a schedule or channel: a call already here, from an earlier import
|
|
701
|
+
// of the same backup, is kept once.
|
|
702
|
+
for (const row of rowsOf(scratch, "SELECT * FROM standalone_usage ORDER BY rowid"))
|
|
703
|
+
insertRow(db, "standalone_usage", row, "INSERT OR IGNORE");
|
|
627
704
|
let events = 0;
|
|
628
705
|
if (!alreadyImported)
|
|
629
706
|
for (const event of rowsOf(scratch, "SELECT * FROM telemetry_events ORDER BY rowid")) {
|
|
@@ -662,9 +739,11 @@ function commit(deps, work, manifest, prepared) {
|
|
|
662
739
|
appVersion: manifest.appVersion,
|
|
663
740
|
},
|
|
664
741
|
projects: { imported, skipped },
|
|
665
|
-
prompts,
|
|
666
|
-
entries,
|
|
742
|
+
prompts: counts(prompts),
|
|
743
|
+
entries: counts(entries),
|
|
744
|
+
...channelCounts,
|
|
667
745
|
documentThemes,
|
|
746
|
+
narrationAliases,
|
|
668
747
|
templates,
|
|
669
748
|
schedules,
|
|
670
749
|
voices: { added: voicesAdded, renamed: 0, skipped: voicesSkipped },
|
|
@@ -694,10 +773,15 @@ function commit(deps, work, manifest, prepared) {
|
|
|
694
773
|
// Library items whose name is unique per kind: same id is the same item and is skipped; the
|
|
695
774
|
// same name with the same content is skipped; the same name with other content comes in as
|
|
696
775
|
// "<name> (imported)".
|
|
776
|
+
//
|
|
777
|
+
// A prompt or intro/outro in the backup's trash comes in as it is, still in the trash: names
|
|
778
|
+
// only have to be unique among the items that are not (0039), and the name lookups here only
|
|
779
|
+
// see those.
|
|
697
780
|
function mergeNamed(db, scratch, rule) {
|
|
698
781
|
let added = 0;
|
|
699
782
|
let renamed = 0;
|
|
700
783
|
let skipped = 0;
|
|
784
|
+
const inserted = new Set();
|
|
701
785
|
for (const row of rowsOf(scratch, `SELECT * FROM ${rule.table} ORDER BY rowid`)) {
|
|
702
786
|
if (db.prepare(`SELECT 1 FROM ${rule.table} WHERE id=?`).get(String(row.id)) !== undefined) {
|
|
703
787
|
skipped += 1;
|
|
@@ -705,9 +789,11 @@ function mergeNamed(db, scratch, rule) {
|
|
|
705
789
|
}
|
|
706
790
|
const name = String(row.name);
|
|
707
791
|
const lookup = (candidate) => db.prepare(rule.sameName).get(...rule.key(row), candidate);
|
|
708
|
-
const
|
|
792
|
+
const trashed = typeof row.deleted_at === "string";
|
|
793
|
+
const existing = trashed ? undefined : lookup(name);
|
|
709
794
|
if (existing === undefined) {
|
|
710
795
|
insertRow(db, rule.table, row);
|
|
796
|
+
inserted.add(String(row.id));
|
|
711
797
|
added += 1;
|
|
712
798
|
}
|
|
713
799
|
else if (rule.same(row, existing)) {
|
|
@@ -718,10 +804,96 @@ function mergeNamed(db, scratch, rule) {
|
|
|
718
804
|
...row,
|
|
719
805
|
name: importedName(name, rule.max, (candidate) => lookup(candidate) !== undefined),
|
|
720
806
|
});
|
|
807
|
+
inserted.add(String(row.id));
|
|
721
808
|
renamed += 1;
|
|
722
809
|
}
|
|
723
810
|
}
|
|
724
|
-
return { added, renamed, skipped };
|
|
811
|
+
return { added, renamed, skipped, inserted };
|
|
812
|
+
}
|
|
813
|
+
// A prompt's or intro/outro's History comes with it when the item itself came in; an item
|
|
814
|
+
// that was already here keeps the history it has.
|
|
815
|
+
function mergeVersions(db, scratch, itemKind, inserted) {
|
|
816
|
+
for (const row of rowsOf(scratch, "SELECT * FROM library_versions WHERE item_kind=? ORDER BY item_id,version", itemKind))
|
|
817
|
+
if (inserted.has(String(row.item_id)))
|
|
818
|
+
insertRow(db, "library_versions", row, "INSERT OR IGNORE");
|
|
819
|
+
}
|
|
820
|
+
// The default channel has the same id on every install (0032), so the backup's default is
|
|
821
|
+
// always "already here". While this install's own default is still as it was made (never
|
|
822
|
+
// renamed or edited), it takes the backup's name, brand kit, brief and settings; otherwise it
|
|
823
|
+
// is kept. Any other channel whose id is here is skipped. Cast members, their pictures,
|
|
824
|
+
// episode memories and existing videos then come in by id into whichever channel is here.
|
|
825
|
+
function mergeChannels(db, scratch, work, images, now) {
|
|
826
|
+
const has = (sql, ...params) => db.prepare(sql).get(...params) !== undefined;
|
|
827
|
+
const channels = { added: 0, renamed: 0, skipped: 0 };
|
|
828
|
+
for (const row of rowsOf(scratch, "SELECT * FROM channels ORDER BY rowid")) {
|
|
829
|
+
const id = String(row.id);
|
|
830
|
+
const local = db
|
|
831
|
+
.prepare("SELECT is_default,version,brand_json,series_brief FROM channels WHERE id=?")
|
|
832
|
+
.get(id);
|
|
833
|
+
if (local === undefined) {
|
|
834
|
+
insertRow(db, "channels", { ...row, is_default: 0 });
|
|
835
|
+
channels.added += 1;
|
|
836
|
+
}
|
|
837
|
+
else if (local.is_default === 1 &&
|
|
838
|
+
row.is_default === 1 &&
|
|
839
|
+
local.version === 1 &&
|
|
840
|
+
local.brand_json === "{}" &&
|
|
841
|
+
local.series_brief === "" &&
|
|
842
|
+
!(row.version === 1 && row.brand_json === "{}" && row.series_brief === "")) {
|
|
843
|
+
const columns = Object.keys(row).filter((column) => column !== "id" && column !== "is_default");
|
|
844
|
+
db.prepare(`UPDATE channels SET ${columns.map((column) => `${column}=?`).join(",")} WHERE id=?`).run(...columns.map((column) => row[column] ?? null), id);
|
|
845
|
+
channels.added += 1;
|
|
846
|
+
}
|
|
847
|
+
else
|
|
848
|
+
channels.skipped += 1;
|
|
849
|
+
}
|
|
850
|
+
for (const image of images) {
|
|
851
|
+
const path = join(work, "images", image.sha256);
|
|
852
|
+
if (!existsSync(path))
|
|
853
|
+
continue;
|
|
854
|
+
db.prepare("INSERT INTO image_blobs(sha256,mime,bytes,created_at) VALUES (?,?,?,?) ON CONFLICT(sha256) DO NOTHING").run(image.sha256, image.mime, readFileSync(path), now);
|
|
855
|
+
}
|
|
856
|
+
const cast = { added: 0, renamed: 0, skipped: 0 };
|
|
857
|
+
for (const row of rowsOf(scratch, "SELECT * FROM cast_members ORDER BY rowid")) {
|
|
858
|
+
if (has("SELECT 1 FROM cast_members WHERE id=?", String(row.id)))
|
|
859
|
+
cast.skipped += 1;
|
|
860
|
+
else {
|
|
861
|
+
insertRow(db, "cast_members", row);
|
|
862
|
+
cast.added += 1;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
for (const row of rowsOf(scratch, "SELECT * FROM cast_images ORDER BY rowid"))
|
|
866
|
+
if (!has("SELECT 1 FROM cast_images WHERE id=?", String(row.id)) &&
|
|
867
|
+
has("SELECT 1 FROM cast_members WHERE id=?", String(row.member_id)) &&
|
|
868
|
+
(row.sha256 === null || has("SELECT 1 FROM image_blobs WHERE sha256=?", String(row.sha256))))
|
|
869
|
+
// A picture the other install was still drawing never finishes here.
|
|
870
|
+
insertRow(db, "cast_images", row.state === "generating"
|
|
871
|
+
? { ...row, state: "failed", error: "Stopped by the backup. Make it again." }
|
|
872
|
+
: row);
|
|
873
|
+
const episodeMemories = { added: 0, renamed: 0, skipped: 0 };
|
|
874
|
+
for (const row of rowsOf(scratch, "SELECT * FROM episode_memories ORDER BY rowid")) {
|
|
875
|
+
if (has("SELECT 1 FROM episode_memories WHERE id=?", String(row.id)) ||
|
|
876
|
+
has("SELECT 1 FROM episode_memories WHERE project_id=?", String(row.project_id)))
|
|
877
|
+
episodeMemories.skipped += 1;
|
|
878
|
+
else {
|
|
879
|
+
insertRow(db, "episode_memories", row);
|
|
880
|
+
episodeMemories.added += 1;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const channelVideos = { added: 0, renamed: 0, skipped: 0 };
|
|
884
|
+
for (const row of rowsOf(scratch, "SELECT * FROM channel_videos ORDER BY rowid")) {
|
|
885
|
+
const changes = has("SELECT 1 FROM channel_videos WHERE id=?", String(row.id))
|
|
886
|
+
? 0
|
|
887
|
+
: insertRow(db, "channel_videos", row, "INSERT OR IGNORE");
|
|
888
|
+
if (changes > 0)
|
|
889
|
+
channelVideos.added += 1;
|
|
890
|
+
else
|
|
891
|
+
channelVideos.skipped += 1;
|
|
892
|
+
}
|
|
893
|
+
return { channels, cast, episodeMemories, channelVideos };
|
|
894
|
+
}
|
|
895
|
+
function counts(value) {
|
|
896
|
+
return { added: value.added, renamed: value.renamed, skipped: value.skipped };
|
|
725
897
|
}
|
|
726
898
|
export function importedName(name, max, taken) {
|
|
727
899
|
for (let n = 1;; n += 1) {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { rmSync } from "node:fs";
|
|
2
2
|
import { derive } from "../../kernel/runner/graph.js";
|
|
3
|
-
import {
|
|
3
|
+
import { stagesOf } from "../admission/repo.js";
|
|
4
4
|
import { projectDir } from "./layout.js";
|
|
5
|
+
// A run in flight: a call the runner holds, or a stage running or waiting out a retry.
|
|
6
|
+
export function projectBusy(deps, projectId) {
|
|
7
|
+
return (deps.hasInflight?.(projectId) === true || derive(stagesOf(deps.db, projectId)) === "running");
|
|
8
|
+
}
|
|
5
9
|
export function deleteProject(deps, projectId) {
|
|
6
|
-
if (
|
|
10
|
+
if (deps.db.prepare("SELECT 1 FROM projects WHERE id = ?").get(projectId) === undefined) {
|
|
7
11
|
return { ok: false, reason: "no-project" };
|
|
8
12
|
}
|
|
9
|
-
if (deps
|
|
10
|
-
derive(stagesOf(deps.db, projectId)) === "running") {
|
|
13
|
+
if (projectBusy(deps, projectId)) {
|
|
11
14
|
return { ok: false, reason: "running" };
|
|
12
15
|
}
|
|
13
16
|
// Files first, rows second. A folder that would not go leaves the project listed; rows
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, posix, relative, resolve, win32 } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execute = promisify(execFile);
|
|
7
|
+
const execBytes = async (command, args) => (await execute(command, [...args], { timeout: 10_000, windowsHide: true, encoding: "buffer" }))
|
|
8
|
+
.stdout;
|
|
9
|
+
// Output is always decoded as UTF-8: the PowerShell command below switches its own output to
|
|
10
|
+
// UTF-8 first, so a Documents folder named with non-ASCII letters (a Cyrillic or accented
|
|
11
|
+
// OneDrive path) survives instead of arriving in the console codepage.
|
|
12
|
+
export function nodeDocumentsHost(exec = execBytes) {
|
|
13
|
+
return {
|
|
14
|
+
platform: process.platform,
|
|
15
|
+
env: process.env,
|
|
16
|
+
home: homedir(),
|
|
17
|
+
run: async (command, args) => new TextDecoder("utf-8").decode(await exec(command, args)),
|
|
18
|
+
read: (path) => readFile(path, "utf8").catch(() => undefined),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Forces PowerShell's stdout to UTF-8 before printing: by default it writes in the console's
|
|
22
|
+
// codepage (437, 850, 1252...), which mangles every non-ASCII letter in the path.
|
|
23
|
+
export const WINDOWS_DOCUMENTS_COMMAND = "[Console]::OutputEncoding = [Text.Encoding]::UTF8; [Environment]::GetFolderPath('MyDocuments')";
|
|
24
|
+
// The user's own Documents folder, the way the system itself names it: Windows asks for the
|
|
25
|
+
// known folder (which follows OneDrive and a folder moved in its Properties), Linux asks
|
|
26
|
+
// xdg-user-dir and then reads user-dirs.dirs, macOS has no setting for it. Never throws: the
|
|
27
|
+
// fallback is <home>/Documents. It isn't created here.
|
|
28
|
+
export async function documentsDir(host) {
|
|
29
|
+
if (host.platform === "win32") {
|
|
30
|
+
const known = await host
|
|
31
|
+
.run("powershell.exe", [
|
|
32
|
+
"-NoProfile",
|
|
33
|
+
"-NonInteractive",
|
|
34
|
+
"-Command",
|
|
35
|
+
WINDOWS_DOCUMENTS_COMMAND,
|
|
36
|
+
])
|
|
37
|
+
.then((out) => out.trim())
|
|
38
|
+
.catch(() => "");
|
|
39
|
+
if (usable(known, host.home, true))
|
|
40
|
+
return known;
|
|
41
|
+
return win32.join(host.env.USERPROFILE?.trim() || host.home, "Documents");
|
|
42
|
+
}
|
|
43
|
+
const fallback = posix.join(host.home, "Documents");
|
|
44
|
+
if (host.platform === "darwin")
|
|
45
|
+
return fallback;
|
|
46
|
+
const asked = await host
|
|
47
|
+
.run("xdg-user-dir", ["DOCUMENTS"])
|
|
48
|
+
.then((out) => out.trim())
|
|
49
|
+
.catch(() => "");
|
|
50
|
+
if (usable(asked, host.home))
|
|
51
|
+
return resolve(asked);
|
|
52
|
+
const configHome = host.env.XDG_CONFIG_HOME?.trim() || join(host.home, ".config");
|
|
53
|
+
const configured = userDirsDocuments((await host.read(join(configHome, "user-dirs.dirs"))) ?? "", host.home);
|
|
54
|
+
if (configured !== undefined && usable(configured, host.home))
|
|
55
|
+
return resolve(configured);
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
// XDG_DOCUMENTS_DIR from a user-dirs.dirs file: `XDG_DOCUMENTS_DIR="$HOME/Documents"`. The
|
|
59
|
+
// format allows only $HOME at the start or an absolute path.
|
|
60
|
+
export function userDirsDocuments(text, home) {
|
|
61
|
+
for (const line of text.split(/\r?\n/)) {
|
|
62
|
+
const match = /^\s*XDG_DOCUMENTS_DIR\s*=\s*"([^"]*)"\s*$/.exec(line);
|
|
63
|
+
if (match === null)
|
|
64
|
+
continue;
|
|
65
|
+
const value = match[1] ?? "";
|
|
66
|
+
if (value === "$HOME" || value.startsWith("$HOME/"))
|
|
67
|
+
return join(home, value.slice("$HOME".length));
|
|
68
|
+
if (isAbsolute(value))
|
|
69
|
+
return value;
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
// xdg-user-dir answers with the home folder itself when Documents isn't set (the spec's "not
|
|
75
|
+
// configured" value); a Documents folder that is the home folder would mix Slopify's folder
|
|
76
|
+
// with everything else, so that counts as unset too.
|
|
77
|
+
function usable(path, home, windows = false) {
|
|
78
|
+
if (path === "")
|
|
79
|
+
return false;
|
|
80
|
+
if (!windows && !isAbsolute(path))
|
|
81
|
+
return false;
|
|
82
|
+
if (windows && !/^(?:[a-zA-Z]:[\\/]|\\\\)/.test(path))
|
|
83
|
+
return false;
|
|
84
|
+
if (windows)
|
|
85
|
+
return win32.relative(win32.resolve(home), win32.resolve(path)) !== "";
|
|
86
|
+
return relative(resolve(home), resolve(path)) !== "";
|
|
87
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { extname } from "node:path";
|
|
3
3
|
import { zipSync } from "fflate";
|
|
4
|
+
import { effectiveDescription } from "../youtube/edits-repo.js";
|
|
4
5
|
import { assetOf } from "./asset-name.js";
|
|
5
6
|
import { outputPath } from "./layout.js";
|
|
6
7
|
import { outputsOf, projectTitle } from "./repo.js";
|
|
@@ -57,16 +58,47 @@ export function findDownload(deps, projectId, asset) {
|
|
|
57
58
|
if (stats === undefined) {
|
|
58
59
|
return { ok: false, reason: "missing-file" };
|
|
59
60
|
}
|
|
61
|
+
const shown = output.role === "youtube_description" || output.role === "youtube_tags"
|
|
62
|
+
? shownYoutubeText(deps, projectId, output.role)
|
|
63
|
+
: undefined;
|
|
60
64
|
return {
|
|
61
65
|
ok: true,
|
|
62
66
|
download: {
|
|
63
67
|
path,
|
|
64
68
|
filename: downloadName(slugOf(title), output),
|
|
65
|
-
bytes: stats,
|
|
69
|
+
bytes: shown?.byteLength ?? stats,
|
|
66
70
|
contentType: contentTypeOf(path),
|
|
71
|
+
...(shown === undefined ? {} : { text: shown }),
|
|
67
72
|
},
|
|
68
73
|
};
|
|
69
74
|
}
|
|
75
|
+
// The description and tags download as the project page shows and copies them, and as Prepare
|
|
76
|
+
// upload hands them on: the user's hand edits over the written text, chapters fitted, and the
|
|
77
|
+
// channel's and project's links filled in (`youtube/edits-repo.ts`). The file on disk stays
|
|
78
|
+
// the generated text, so regeneration can tell the user's edits from its own.
|
|
79
|
+
function shownYoutubeText(deps, projectId, role) {
|
|
80
|
+
const outputs = outputsOf(deps.db, projectId);
|
|
81
|
+
const read = (wanted) => {
|
|
82
|
+
const output = outputs.find((candidate) => candidate.role === wanted);
|
|
83
|
+
if (output === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
const path = outputPath(deps.paths, projectId, output.path);
|
|
86
|
+
return sizeOf(path) === undefined ? undefined : readFileSync(path, "utf8").trim();
|
|
87
|
+
};
|
|
88
|
+
const description = read("youtube_description");
|
|
89
|
+
if (description === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const video = outputs.find((output) => output.role === "video");
|
|
92
|
+
const shown = effectiveDescription(deps.db, projectId, {
|
|
93
|
+
description,
|
|
94
|
+
tags: read("youtube_tags") ?? "",
|
|
95
|
+
durationSeconds: video?.durationMs === null || video?.durationMs === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: video.durationMs / 1000,
|
|
98
|
+
});
|
|
99
|
+
const text = role === "youtube_description" ? shown.description : shown.tags;
|
|
100
|
+
return new TextEncoder().encode(`${text}\n`);
|
|
101
|
+
}
|
|
70
102
|
// "download all" images as `<title-slug>-images.zip`, thumbnail included.
|
|
71
103
|
export function imagesZip(deps, projectId) {
|
|
72
104
|
const title = projectTitle(deps.db, projectId);
|