@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
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
import { crc32 } from "node:zlib";
|
|
5
5
|
import { strFromU8, strToU8, Unzip, UnzipInflate, zipSync } from "fflate";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { transact } from "../../kernel/db/tx.js";
|
|
8
|
+
import { liveProject } from "../admission/repo.js";
|
|
8
9
|
import { silenceGapSecondsMax } from "../admission/rules.js";
|
|
9
10
|
import { detectSlots } from "../admission/substitute.js";
|
|
10
11
|
import { fontMaxBytes } from "../fonts/model.js";
|
|
11
12
|
import { readFontMetadata } from "../fonts/sfnt.js";
|
|
12
13
|
import { lintEntry, lintPrompt } from "../library/lint.js";
|
|
13
14
|
import { entryCategories, entryModes, promptKinds } from "../library/model.js";
|
|
15
|
+
import { photorealisticPromptsKey } from "../library/photorealistic.js";
|
|
14
16
|
import { listEntries, listPrompts } from "../library/repo.js";
|
|
15
17
|
import { notificationUrlKey } from "../notifications/settings.js";
|
|
18
|
+
import { patchNotesSeenKey } from "../patch-notes/seen.js";
|
|
16
19
|
import { projectTemplateSchema } from "../project-templates/schema.js";
|
|
17
20
|
import { cliPathMaxLength } from "../settings/cli-paths.js";
|
|
21
|
+
import { providerDefaultsKey, providerDefaultsSchema } from "../settings/first-run.js";
|
|
18
22
|
import { appearances, providerById, providerIds } from "../settings/model.js";
|
|
19
23
|
import { listVoices } from "../settings/repo.js";
|
|
20
|
-
import { voiceIdMax, voiceNameMax } from "../settings/voices.js";
|
|
24
|
+
import { realPersonVoicesKey, voiceIdMax, voiceNameMax } from "../settings/voices.js";
|
|
25
|
+
import { whatsNewSeenKey } from "../settings/whats-new.js";
|
|
26
|
+
import { studioPlaylistMax } from "../studio/model.js";
|
|
27
|
+
import { studioChannelPlaylistPrefix, studioFillQueuePrefix, studioPairingKey, studioPlaylistKey, studioRealFootageKey, } from "../studio/settings.js";
|
|
28
|
+
import { channelLinksKey } from "../youtube/edits-repo.js";
|
|
21
29
|
import { defaultBackupsDir, projectDir, stagingPath } from "./layout.js";
|
|
22
30
|
import { stageKinds } from "./model.js";
|
|
23
31
|
import { insertStagedFile, stagedFiles } from "./repo.js";
|
|
32
|
+
import { projectStorage } from "./trim.js";
|
|
24
33
|
const record = z.record(z.string(), z.string());
|
|
25
34
|
export const portableMaxArchiveBytes = 100 * 1024 * 1024;
|
|
26
35
|
export const portableMaxExpandedBytes = 128 * 1024 * 1024;
|
|
@@ -49,7 +58,18 @@ const libraryRow = z
|
|
|
49
58
|
})
|
|
50
59
|
.strict();
|
|
51
60
|
const voiceRow = z
|
|
52
|
-
.object({
|
|
61
|
+
.object({
|
|
62
|
+
id: z.string(),
|
|
63
|
+
provider: z.enum(providerIds),
|
|
64
|
+
name: z.string(),
|
|
65
|
+
voice_id: z.string(),
|
|
66
|
+
// Absent from backups made before voice languages, and on a voice whose languages are
|
|
67
|
+
// unknown.
|
|
68
|
+
languages: z
|
|
69
|
+
.array(z.string().regex(/^[a-z]{2,3}$/))
|
|
70
|
+
.max(60)
|
|
71
|
+
.optional(),
|
|
72
|
+
})
|
|
53
73
|
.strict();
|
|
54
74
|
const stagedMeta = z
|
|
55
75
|
.object({
|
|
@@ -86,6 +106,11 @@ const manifestSchema = z
|
|
|
86
106
|
staged: z.array(stagedMeta).max(portableMaxStagedFiles),
|
|
87
107
|
})
|
|
88
108
|
.strict();
|
|
109
|
+
// The settings-only .zip. Nothing in the app writes it any more: Export everything (the tar in
|
|
110
|
+
// `backup-export.ts`) replaced it in 2.5.0, and this stays so tests can make the .zip files
|
|
111
|
+
// older versions wrote. Channels and their cast are deliberately not in it: they arrived after
|
|
112
|
+
// the last version that could write a .zip, so no such file can hold one, and the full backup
|
|
113
|
+
// already carries them (with cast pictures) through `backup-format.ts`.
|
|
89
114
|
export function exportPortable(deps) {
|
|
90
115
|
const entries = {};
|
|
91
116
|
const stagedPlan = planStagedExport(deps);
|
|
@@ -127,9 +152,10 @@ export function exportPortable(deps) {
|
|
|
127
152
|
provider: voice.provider,
|
|
128
153
|
name: voice.name,
|
|
129
154
|
voice_id: voice.voiceId,
|
|
155
|
+
...(voice.languages === undefined ? {} : { languages: voice.languages }),
|
|
130
156
|
}));
|
|
131
157
|
const templates = deps.db
|
|
132
|
-
.prepare("SELECT t.id,r.version,r.name,t.created_at AS createdAt,r.created_at AS updatedAt,r.document_json AS document FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version ORDER BY t.id")
|
|
158
|
+
.prepare("SELECT t.id,r.version,r.name,t.created_at AS createdAt,r.created_at AS updatedAt,r.document_json AS document FROM project_templates t JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version WHERE t.deleted_at IS NULL ORDER BY t.id")
|
|
133
159
|
.all()
|
|
134
160
|
.map((row) => projectTemplateSchema.parse({ ...row, document: JSON.parse(String(row.document)) }));
|
|
135
161
|
const fonts = exportUploadedFonts(deps.paths, entries);
|
|
@@ -209,8 +235,8 @@ export function importPortable(deps, bytes) {
|
|
|
209
235
|
let voices = 0;
|
|
210
236
|
for (const row of manifest.voices) {
|
|
211
237
|
deps.db
|
|
212
|
-
.prepare("INSERT INTO voices(id,provider,name,voice_id) VALUES(
|
|
213
|
-
.run(row.id, row.provider, row.name, row.voice_id);
|
|
238
|
+
.prepare("INSERT INTO voices(id,provider,name,voice_id,languages_json) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET provider=excluded.provider,name=excluded.name,voice_id=excluded.voice_id,languages_json=excluded.languages_json")
|
|
239
|
+
.run(row.id, row.provider, row.name, row.voice_id, row.languages === undefined ? null : JSON.stringify(row.languages));
|
|
214
240
|
voices += 1;
|
|
215
241
|
}
|
|
216
242
|
let templateCount = 0;
|
|
@@ -324,17 +350,56 @@ const slotsColumn = z.array(z.string());
|
|
|
324
350
|
const storedCliPath = z.string().max(cliPathMaxLength).nullable();
|
|
325
351
|
const storedSilenceGap = z.number().int().min(0).max(silenceGapSecondsMax);
|
|
326
352
|
const storedAppearance = z.enum(appearances);
|
|
353
|
+
// Settings → General's Level the volume for new runs.
|
|
354
|
+
const storedLoudness = z
|
|
355
|
+
.object({ enabled: z.boolean(), videoLufs: z.number(), audioFilesLufs: z.number() })
|
|
356
|
+
.strict();
|
|
357
|
+
const storedPlaylist = z.string().max(studioPlaylistMax);
|
|
358
|
+
// Settings → Channel links, and the providers a fresh Play form starts with.
|
|
359
|
+
// The AI use marks: ticked voices, Image prompts and projects, by id.
|
|
360
|
+
const storedIds = z.array(z.string().max(200)).max(100_000);
|
|
361
|
+
// The channel page's "Keep only titles containing…" text, one per channel.
|
|
362
|
+
const importFilterPrefix = "channels.importFilter.";
|
|
363
|
+
const storedImportFilter = z.string().max(200);
|
|
364
|
+
const storedChannelLinks = z
|
|
365
|
+
.array(z.object({ name: z.string().max(200), url: z.string().max(2000) }).strict())
|
|
366
|
+
.max(200);
|
|
327
367
|
function portableSettings(settings) {
|
|
328
368
|
const portable = {};
|
|
329
369
|
for (const [key, value] of Object.entries(settings)) {
|
|
330
370
|
// The Notification URL stays on this machine: an ntfy topic in it is as good as a password.
|
|
331
|
-
|
|
371
|
+
// So does the Studio extension's pairing: its token reads every project's upload pack.
|
|
372
|
+
// Which "What's new" tour and which patch notes this install has closed are about this
|
|
373
|
+
// install, not the data.
|
|
374
|
+
if (key === "tutorial.session" ||
|
|
375
|
+
// Whether this install showed its first-run welcome belongs to this install.
|
|
376
|
+
key === "first-run.done" ||
|
|
377
|
+
key === notificationUrlKey ||
|
|
378
|
+
key === studioPairingKey ||
|
|
379
|
+
// What waits to be filled in Studio belongs to this install's pairing.
|
|
380
|
+
key.startsWith(studioFillQueuePrefix) ||
|
|
381
|
+
key === whatsNewSeenKey ||
|
|
382
|
+
key === patchNotesSeenKey)
|
|
332
383
|
continue;
|
|
333
384
|
const parsed = storedJson(value);
|
|
334
385
|
if (key === "silenceGapSeconds")
|
|
335
386
|
storedSilenceGap.parse(parsed);
|
|
336
387
|
else if (key === "appearance")
|
|
337
388
|
storedAppearance.parse(parsed);
|
|
389
|
+
else if (key === "loudness")
|
|
390
|
+
storedLoudness.parse(parsed);
|
|
391
|
+
else if (key === studioPlaylistKey || key.startsWith(studioChannelPlaylistPrefix))
|
|
392
|
+
storedPlaylist.parse(parsed);
|
|
393
|
+
else if (key === channelLinksKey)
|
|
394
|
+
storedChannelLinks.parse(parsed);
|
|
395
|
+
else if (key === realPersonVoicesKey ||
|
|
396
|
+
key === photorealisticPromptsKey ||
|
|
397
|
+
key === studioRealFootageKey)
|
|
398
|
+
storedIds.parse(parsed);
|
|
399
|
+
else if (key.startsWith(importFilterPrefix))
|
|
400
|
+
storedImportFilter.parse(parsed);
|
|
401
|
+
else if (key === providerDefaultsKey)
|
|
402
|
+
providerDefaultsSchema.parse(parsed);
|
|
338
403
|
else if (key.startsWith("cli.path.")) {
|
|
339
404
|
const provider = providerById(z.enum(providerIds).parse(key.slice("cli.path.".length)));
|
|
340
405
|
if (provider.auth !== "cli")
|
|
@@ -863,16 +928,24 @@ function planStagedImport(deps, archive, files) {
|
|
|
863
928
|
};
|
|
864
929
|
});
|
|
865
930
|
}
|
|
931
|
+
function inside(root, path) {
|
|
932
|
+
const r = relative(root, path);
|
|
933
|
+
return r === "" || (r !== ".." && !r.startsWith(`..${sep}`) && !isAbsolute(r));
|
|
934
|
+
}
|
|
866
935
|
export function storageBytes(paths) {
|
|
867
|
-
// Scheduled backups default to a folder inside the projects root
|
|
868
|
-
|
|
936
|
+
// Scheduled backups default to a folder inside the projects root on installs from before
|
|
937
|
+
// 3.0; they are not project files. Newer installs keep projects outside the data dir.
|
|
938
|
+
const backups = defaultBackupsDir(paths);
|
|
939
|
+
const projects = directoryBytes(paths.projects) -
|
|
940
|
+
(inside(paths.projects, backups) ? directoryBytes(backups) : 0);
|
|
869
941
|
const staging = directoryBytes(paths.staging);
|
|
870
|
-
|
|
942
|
+
const data = directoryBytes(paths.dataDir) + (inside(paths.dataDir, paths.projects) ? 0 : projects);
|
|
943
|
+
return { data, projects, staging };
|
|
871
944
|
}
|
|
872
945
|
export function storageUsage(deps) {
|
|
873
946
|
const totals = storageBytes(deps.paths);
|
|
874
947
|
const byProject = deps.db
|
|
875
|
-
.prepare(
|
|
948
|
+
.prepare(`SELECT id,title FROM projects WHERE ${liveProject()} ORDER BY created_at DESC, id DESC`)
|
|
876
949
|
.all()
|
|
877
950
|
.flatMap((row) => {
|
|
878
951
|
if (typeof row.id !== "string" || typeof row.title !== "string")
|
|
@@ -882,10 +955,19 @@ export function storageUsage(deps) {
|
|
|
882
955
|
id: row.id,
|
|
883
956
|
title: row.title,
|
|
884
957
|
bytes: directoryBytes(projectDir(deps.paths, row.id)),
|
|
958
|
+
...projectStorage(deps, row.id),
|
|
885
959
|
},
|
|
886
960
|
];
|
|
887
961
|
});
|
|
888
|
-
|
|
962
|
+
const trashed = deps.db
|
|
963
|
+
.prepare("SELECT project_id FROM project_trash ORDER BY project_id")
|
|
964
|
+
.all()
|
|
965
|
+
.flatMap((row) => (typeof row.project_id === "string" ? [row.project_id] : []));
|
|
966
|
+
const trash = {
|
|
967
|
+
projects: trashed.length,
|
|
968
|
+
bytes: trashed.reduce((sum, id) => sum + directoryBytes(projectDir(deps.paths, id)), 0),
|
|
969
|
+
};
|
|
970
|
+
return { ...totals, trash, byProject };
|
|
889
971
|
}
|
|
890
972
|
function directoryBytes(root) {
|
|
891
973
|
if (!existsSync(root))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readdirSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { join, relative, sep } from "node:path";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
3
|
import { backupsFolderName, outputPath, stagingPath } from "./layout.js";
|
|
4
4
|
import { stagedFiles as stagedRows } from "./repo.js";
|
|
5
5
|
import { stagedFileReferenced } from "./staging-refs.js";
|
|
@@ -31,12 +31,20 @@ export function reconcileStorage(db, paths) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
let orphanFiles = 0;
|
|
34
|
+
// A Projects folder outside the data dir (in Documents, from 3.0) is one the user browses,
|
|
35
|
+
// and the system drops its own files there (.DS_Store, desktop.ini). Only folders are
|
|
36
|
+
// Slopify's in it, so a loose file stays; hidden entries stay everywhere.
|
|
37
|
+
const userVisible = !within(paths.dataDir, paths.projects);
|
|
34
38
|
for (const entry of readdirSync(paths.projects, { withFileTypes: true })) {
|
|
35
39
|
const path = join(paths.projects, entry.name);
|
|
36
40
|
// The scheduled backups' default folder: its archives belong to slices/backups, which
|
|
37
41
|
// prunes them by its own rules.
|
|
38
42
|
if (entry.name === backupsFolderName && entry.isDirectory())
|
|
39
43
|
continue;
|
|
44
|
+
if (entry.name.startsWith(".") || osFiles.has(entry.name.toLowerCase()))
|
|
45
|
+
continue;
|
|
46
|
+
if (!entry.isDirectory() && userVisible)
|
|
47
|
+
continue;
|
|
40
48
|
if (!entry.isDirectory()) {
|
|
41
49
|
unlinkSync(path);
|
|
42
50
|
orphanFiles += 1;
|
|
@@ -119,6 +127,11 @@ function filesUnder(root) {
|
|
|
119
127
|
}
|
|
120
128
|
return files;
|
|
121
129
|
}
|
|
130
|
+
const osFiles = new Set(["desktop.ini", "thumbs.db"]);
|
|
131
|
+
function within(root, path) {
|
|
132
|
+
const inside = relative(root, path);
|
|
133
|
+
return (inside === "" || (inside !== ".." && !inside.startsWith(`..${sep}`) && !isAbsolute(inside)));
|
|
134
|
+
}
|
|
122
135
|
// outputs.path is stored with forward slashes; the filesystem may hand back backslashes.
|
|
123
136
|
function slashed(path) {
|
|
124
137
|
return path.split(sep).join("/");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { liveProject } from "../admission/repo.js";
|
|
2
3
|
import { outputRoles, stagedFileStates, stageKinds } from "./model.js";
|
|
3
4
|
import { metaSchema } from "./schema.js";
|
|
4
5
|
const outputRow = z.object({
|
|
@@ -58,7 +59,9 @@ export function deleteStagedFile(db, id) {
|
|
|
58
59
|
db.prepare("DELETE FROM staged_files WHERE id = ? AND NOT EXISTS (SELECT 1 FROM play_draft_attachments WHERE staged_file_id = ?)").run(id, id);
|
|
59
60
|
}
|
|
60
61
|
export function projectTitle(db, projectId) {
|
|
61
|
-
const row = db
|
|
62
|
+
const row = db
|
|
63
|
+
.prepare(`SELECT title FROM projects WHERE id = ? AND ${liveProject()}`)
|
|
64
|
+
.get(projectId);
|
|
62
65
|
if (row === undefined) {
|
|
63
66
|
return undefined;
|
|
64
67
|
}
|
|
@@ -10,11 +10,22 @@ export const metaSchema = z.object({
|
|
|
10
10
|
prompt: z.string().optional(),
|
|
11
11
|
index: z.number().optional(),
|
|
12
12
|
short: z.number().optional(),
|
|
13
|
+
format: z.enum(["16:9", "9:16"]).optional(),
|
|
13
14
|
sentences: z.tuple([z.number(), z.number()]).optional(),
|
|
14
15
|
warnings: z.array(z.string()).optional(),
|
|
15
16
|
provider: z.string().optional(),
|
|
16
17
|
model: z.string().optional(),
|
|
17
18
|
voice: z.string().optional(),
|
|
19
|
+
loudness: z
|
|
20
|
+
.object({
|
|
21
|
+
pieces: z.number(),
|
|
22
|
+
skipped: z.number(),
|
|
23
|
+
spreadBefore: z.number(),
|
|
24
|
+
spreadAfter: z.number(),
|
|
25
|
+
target: z.number(),
|
|
26
|
+
})
|
|
27
|
+
.optional(),
|
|
28
|
+
master: z.object({ target: z.number(), integrated: z.number(), truePeak: z.number() }).optional(),
|
|
18
29
|
});
|
|
19
30
|
export const outputSchema = z.object({
|
|
20
31
|
id: z.string(),
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { derive } from "../../kernel/runner/graph.js";
|
|
4
|
+
import { projectExists, projectPaused, stagesOf } from "../admission/repo.js";
|
|
5
|
+
import { backupsFolderName, projectDir } from "./layout.js";
|
|
6
|
+
import { pieceFile } from "./reconcile.js";
|
|
7
|
+
// Keep outputs only: a finished project drops the working files it was made from (images,
|
|
8
|
+
// narration parts, subtitle timing, render settings) and keeps what gets published. Two
|
|
9
|
+
// rules keep it safe. A file the user supplied is never removed, because nothing could make
|
|
10
|
+
// it again. And only files a project record names are removed: anything else in the folder
|
|
11
|
+
// is reconcile's business (`reconcile.ts`), which leaves the Backups folder alone.
|
|
12
|
+
// What a finished project is for: the files that get uploaded or read, from every revision,
|
|
13
|
+
// so an older video in History stays too.
|
|
14
|
+
export const outputRoles = [
|
|
15
|
+
"video",
|
|
16
|
+
"short_video",
|
|
17
|
+
"shorts",
|
|
18
|
+
"thumbnail",
|
|
19
|
+
"article_md",
|
|
20
|
+
"article_txt",
|
|
21
|
+
"sources",
|
|
22
|
+
"youtube_description",
|
|
23
|
+
"youtube_tags",
|
|
24
|
+
"document_pdf",
|
|
25
|
+
"audio_export",
|
|
26
|
+
"subtitles_srt",
|
|
27
|
+
"subtitles_vtt",
|
|
28
|
+
];
|
|
29
|
+
export function projectStorage(deps, projectId) {
|
|
30
|
+
const files = filesOf(deps.paths, projectId);
|
|
31
|
+
const kept = keptPaths(deps.db, projectId);
|
|
32
|
+
const outputs = outputPaths(deps.db, projectId);
|
|
33
|
+
const removable = removablePaths(deps.db, projectId, kept);
|
|
34
|
+
let outputsBytes = 0;
|
|
35
|
+
let workingBytes = 0;
|
|
36
|
+
let removableFiles = 0;
|
|
37
|
+
let removableBytes = 0;
|
|
38
|
+
for (const [path, bytes] of files) {
|
|
39
|
+
if (outputs.has(path))
|
|
40
|
+
outputsBytes += bytes;
|
|
41
|
+
else
|
|
42
|
+
workingBytes += bytes;
|
|
43
|
+
if (removable.has(path)) {
|
|
44
|
+
removableFiles += 1;
|
|
45
|
+
removableBytes += bytes;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
outputsBytes,
|
|
50
|
+
workingBytes,
|
|
51
|
+
removableFiles,
|
|
52
|
+
removableBytes,
|
|
53
|
+
finished: finished(deps, projectId),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function keepOutputsOnly(deps, projectId) {
|
|
57
|
+
if (!projectExists(deps.db, projectId))
|
|
58
|
+
return { ok: false, reason: "no-project" };
|
|
59
|
+
if (!finished(deps, projectId))
|
|
60
|
+
return { ok: false, reason: "not-finished" };
|
|
61
|
+
const files = filesOf(deps.paths, projectId);
|
|
62
|
+
const removable = removablePaths(deps.db, projectId, keptPaths(deps.db, projectId));
|
|
63
|
+
let count = 0;
|
|
64
|
+
let bytesFreed = 0;
|
|
65
|
+
for (const path of removable) {
|
|
66
|
+
const bytes = files.get(path);
|
|
67
|
+
if (bytes === undefined)
|
|
68
|
+
continue;
|
|
69
|
+
unlinkSync(join(projectDir(deps.paths, projectId), path));
|
|
70
|
+
count += 1;
|
|
71
|
+
bytesFreed += bytes;
|
|
72
|
+
}
|
|
73
|
+
return { ok: true, files: count, bytesFreed };
|
|
74
|
+
}
|
|
75
|
+
function finished(deps, projectId) {
|
|
76
|
+
if (deps.hasInflight?.(projectId) === true)
|
|
77
|
+
return false;
|
|
78
|
+
const state = derive(stagesOf(deps.db, projectId), projectPaused(deps.db, projectId));
|
|
79
|
+
if (state !== "done" && state !== "partial")
|
|
80
|
+
return false;
|
|
81
|
+
// Nothing admitted that could still start: a rebuild waiting to be picked up needs its inputs.
|
|
82
|
+
return (deps.db
|
|
83
|
+
.prepare("SELECT 1 FROM revision_work WHERE project_id=? AND (state='running' OR (state='pending' AND dispatch_state='allowed')) LIMIT 1")
|
|
84
|
+
.get(projectId) === undefined);
|
|
85
|
+
}
|
|
86
|
+
// Every file under the project folder, by project-relative path with forward slashes.
|
|
87
|
+
function filesOf(paths, projectId) {
|
|
88
|
+
const files = new Map();
|
|
89
|
+
if (projectId === backupsFolderName)
|
|
90
|
+
return files;
|
|
91
|
+
const root = projectDir(paths, projectId);
|
|
92
|
+
if (!existsSync(root))
|
|
93
|
+
return files;
|
|
94
|
+
for (const entry of readdirSync(root, { withFileTypes: true, recursive: true })) {
|
|
95
|
+
if (!entry.isFile())
|
|
96
|
+
continue;
|
|
97
|
+
const full = join(entry.parentPath, entry.name);
|
|
98
|
+
files.set(relative(root, full).split(sep).join("/"), statSync(full).size);
|
|
99
|
+
}
|
|
100
|
+
return files;
|
|
101
|
+
}
|
|
102
|
+
function outputPaths(db, projectId) {
|
|
103
|
+
const roles = outputRoles.map(() => "?").join(",");
|
|
104
|
+
return pathsOf(db
|
|
105
|
+
.prepare(`SELECT a.path FROM revision_outputs o JOIN project_assets a ON a.id=o.asset_id AND a.project_id=o.project_id
|
|
106
|
+
WHERE o.project_id=? AND json_extract(o.descriptor,'$.role') IN (${roles})
|
|
107
|
+
UNION SELECT path FROM outputs WHERE project_id=? AND role IN (${roles})`)
|
|
108
|
+
.all(projectId, ...outputRoles, projectId, ...outputRoles));
|
|
109
|
+
}
|
|
110
|
+
// The outputs, and every file the user supplied: an asset any saved revision or the project's
|
|
111
|
+
// settings name, or a legacy output row that came from an upload.
|
|
112
|
+
function keptPaths(db, projectId) {
|
|
113
|
+
const kept = new Set(outputPaths(db, projectId));
|
|
114
|
+
for (const path of pathsOf(db
|
|
115
|
+
.prepare(`SELECT a.path FROM project_assets a WHERE a.project_id=? AND (
|
|
116
|
+
EXISTS(SELECT 1 FROM project_revisions r WHERE r.project_id=a.project_id
|
|
117
|
+
AND (instr(r.content,a.id)>0 OR instr(r.config,a.id)>0))
|
|
118
|
+
OR EXISTS(SELECT 1 FROM projects p WHERE p.id=a.project_id AND instr(p.config,a.id)>0))
|
|
119
|
+
UNION SELECT path FROM outputs WHERE project_id=? AND original_filename IS NOT NULL`)
|
|
120
|
+
.all(projectId, projectId)))
|
|
121
|
+
kept.add(path);
|
|
122
|
+
return kept;
|
|
123
|
+
}
|
|
124
|
+
// The files a record names that are not kept: made by a step, so a step can make them again.
|
|
125
|
+
function removablePaths(db, projectId, kept) {
|
|
126
|
+
const named = new Set([
|
|
127
|
+
...pathsOf(db
|
|
128
|
+
.prepare("SELECT path FROM project_assets WHERE project_id=? UNION SELECT path FROM outputs WHERE project_id=?")
|
|
129
|
+
.all(projectId, projectId)),
|
|
130
|
+
]);
|
|
131
|
+
for (const row of db
|
|
132
|
+
.prepare("SELECT p.payload FROM stage_pieces p JOIN stages s ON s.id=p.stage_id WHERE s.project_id=? AND p.payload IS NOT NULL")
|
|
133
|
+
.all(projectId)) {
|
|
134
|
+
const file = pieceFile(row.payload);
|
|
135
|
+
if (file !== undefined)
|
|
136
|
+
named.add(file.split(sep).join("/"));
|
|
137
|
+
}
|
|
138
|
+
return new Set([...named].filter((path) => !kept.has(path)));
|
|
139
|
+
}
|
|
140
|
+
function pathsOf(rows) {
|
|
141
|
+
const paths = new Set();
|
|
142
|
+
for (const row of rows)
|
|
143
|
+
if (typeof row.path === "string")
|
|
144
|
+
paths.add(row.path.split("\\").join("/"));
|
|
145
|
+
return paths;
|
|
146
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// YouTube's "AI use" question (it was "Altered or synthetic content"), answered for every
|
|
2
|
+
// video and short of a project. Browser-safe: the upload pack carries the answer, the channel
|
|
3
|
+
// page names the setting.
|
|
4
|
+
//
|
|
5
|
+
// The question as Studio asks it (read on the live Details page, 2026-09-27), behind Show more:
|
|
6
|
+
// AI use — "Was AI used to generate or edit your content in any of the following ways?"
|
|
7
|
+
// 1. "Makes a real person appear to say or do something they didn't say or do"
|
|
8
|
+
// 2. "Alters footage of a real event or place"
|
|
9
|
+
// 3. "Generates a realistic-looking scene that didn't actually occur"
|
|
10
|
+
// Radios "Yes, AI was used" / "No, AI wasn't used"; "Selecting 'yes' adds a label to your
|
|
11
|
+
// content."
|
|
12
|
+
// YouTube's help page (https://support.google.com/youtube/answer/14328491) says the same:
|
|
13
|
+
// clearly unrealistic content (fantasy, animation), minor edits (colour, filters) and
|
|
14
|
+
// production help (AI for the script, thumbnail, title or captions) need no label.
|
|
15
|
+
//
|
|
16
|
+
// So the automatic answer is Yes only when one of the three cases applies, and No otherwise:
|
|
17
|
+
// 1. a saved voice marked "Imitates a real person" (Settings → Voices) narrates it: an AI
|
|
18
|
+
// voice cloned from, or made to sound like, a real person, so that person seems to say it;
|
|
19
|
+
// 2. uploaded clips the person marked as real footage (the project's Prepare upload) get the
|
|
20
|
+
// Look's atmosphere overlay (embers, dust or fog), which adds to a real scene what wasn't
|
|
21
|
+
// there. A colour grade, vignette or grain alone is one of YouTube's minor edits;
|
|
22
|
+
// 3. an AI image model draws its pictures from an Image prompt marked "Draws photorealistic
|
|
23
|
+
// pictures" (Library → Prompts). Stylised, painterly or illustrated images are not
|
|
24
|
+
// realistic-looking, and animating them doesn't make them so.
|
|
25
|
+
// An AI narrator that doesn't pose as a real person, AI-written text and an AI thumbnail are
|
|
26
|
+
// none of the three. Every mark is off until the person sets it. A channel can still set
|
|
27
|
+
// Always Yes or Always No (Channels → the channel → Brand → YouTube AI disclosure).
|
|
28
|
+
export const aiDisclosureSettings = ["auto", "yes", "no"];
|
|
29
|
+
export const aiDisclosureLabels = {
|
|
30
|
+
auto: "Automatic",
|
|
31
|
+
yes: "Always Yes",
|
|
32
|
+
no: "Always No",
|
|
33
|
+
};
|
|
34
|
+
// Studio's three cases, as it words them.
|
|
35
|
+
export const aiUseCases = [
|
|
36
|
+
"Makes a real person appear to say or do something they didn't say or do",
|
|
37
|
+
"Alters footage of a real event or place",
|
|
38
|
+
"Generates a realistic-looking scene that didn't actually occur",
|
|
39
|
+
];
|
|
40
|
+
const where = "Channels → the channel → Brand → YouTube AI disclosure";
|
|
41
|
+
export function aiDisclosureOf(input) {
|
|
42
|
+
if (input.setting === "yes")
|
|
43
|
+
return { altered: true, why: `This channel is set to Always Yes (${where}).` };
|
|
44
|
+
if (input.setting === "no")
|
|
45
|
+
return { altered: false, why: `This channel is set to Always No (${where}).` };
|
|
46
|
+
const reasons = [];
|
|
47
|
+
if (input.realPersonVoices.length > 0)
|
|
48
|
+
reasons.push(`the narration uses ${quotedList(input.realPersonVoices)}, marked in Settings → Voices as imitating a real person (YouTube's case 1: "${aiUseCases[0]}")`);
|
|
49
|
+
if (input.realFootage && input.footageOverlay !== undefined)
|
|
50
|
+
reasons.push(`the Look's ${input.footageOverlay} overlay is laid over uploaded clips marked as real footage (YouTube's case 2: "${aiUseCases[1]}")`);
|
|
51
|
+
if (input.photorealistic)
|
|
52
|
+
reasons.push(`an AI image model draws its pictures from an Image prompt marked photorealistic in Library → Prompts (YouTube's case 3: "${aiUseCases[2]}")`);
|
|
53
|
+
if (reasons.length > 0)
|
|
54
|
+
return {
|
|
55
|
+
altered: true,
|
|
56
|
+
why: `Yes because ${list(reasons)}. Selecting Yes adds YouTube's AI label.`,
|
|
57
|
+
};
|
|
58
|
+
const footage = input.realFootage && input.kind === "video"
|
|
59
|
+
? " The uploaded real footage only gets colour, vignette or grain, which YouTube counts as minor edits."
|
|
60
|
+
: "";
|
|
61
|
+
return {
|
|
62
|
+
altered: false,
|
|
63
|
+
why: `No because none of YouTube's three AI use cases applies: no narrating voice is marked as imitating a real person (Settings → Voices), no real footage is altered, and no AI pictures come from an Image prompt marked photorealistic (Library → Prompts). Stylised AI pictures and an AI narrator that doesn't pose as a real person don't need the label.${footage}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function quotedList(names) {
|
|
67
|
+
const quoted = names.map((name) => `"${name}"`);
|
|
68
|
+
return `the voice${names.length === 1 ? "" : "s"} ${list(quoted)}`;
|
|
69
|
+
}
|
|
70
|
+
function list(parts) {
|
|
71
|
+
if (parts.length <= 1)
|
|
72
|
+
return parts.join("");
|
|
73
|
+
return `${parts.slice(0, -1).join(", ")} and ${parts.at(-1) ?? ""}`;
|
|
74
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The upload pack: everything YouTube Studio asks for when a finished video (or one of its
|
|
2
|
+
// shorts) is uploaded by hand, in the order Studio asks for it. Slopify never uploads or
|
|
3
|
+
// publishes; the pack is copied into Studio by the person, or filled in by the Slopify Studio
|
|
4
|
+
// extension (`packages/extension`), and the person presses Publish.
|
|
5
|
+
// Browser-safe: the project page and the extension read these names too.
|
|
6
|
+
// YouTube's own limits on the upload dialog's fields.
|
|
7
|
+
export const studioTitleMax = 100;
|
|
8
|
+
export const studioPlaylistMax = 150;
|
|
9
|
+
// The one audience a Slopify video is uploaded with. Slopify's narrated videos are not made
|
|
10
|
+
// for children, and Studio refuses to continue until the question is answered.
|
|
11
|
+
export const studioAudience = "not_made_for_kids";
|
|
12
|
+
// Studio's upload page. It opens the upload dialog on the signed-in channel.
|
|
13
|
+
export const studioUploadUrl = "https://www.youtube.com/upload";
|
|
14
|
+
// The steps in Studio's order, as the Prepare upload view lists them.
|
|
15
|
+
export const studioSteps = [
|
|
16
|
+
"video",
|
|
17
|
+
"title",
|
|
18
|
+
"description",
|
|
19
|
+
"thumbnails",
|
|
20
|
+
"playlist",
|
|
21
|
+
"audience",
|
|
22
|
+
"altered",
|
|
23
|
+
"tags",
|
|
24
|
+
];
|
|
25
|
+
// Tags as Studio's field takes them: comma-separated.
|
|
26
|
+
export function tagsLine(tags) {
|
|
27
|
+
return tags.join(", ");
|
|
28
|
+
}
|
|
29
|
+
// The browsers the extension is built for, as its download names them.
|
|
30
|
+
export const studioExtensionBrowsers = ["chrome", "firefox"];
|