@hraness/slopcamera 3.2.4
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 +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProjectEditPlanV1Schema,
|
|
3
|
+
ProjectCameraKeyframeSchema,
|
|
4
|
+
RecordingEventV1Schema,
|
|
5
|
+
RecordingManifestV1Schema,
|
|
6
|
+
RenderEffectsSchema,
|
|
7
|
+
VideoProjectV1Schema,
|
|
8
|
+
type Point,
|
|
9
|
+
type ProjectCameraKeyframe,
|
|
10
|
+
type ProjectCameraSegment,
|
|
11
|
+
type ProjectEditPlanV1,
|
|
12
|
+
type ProjectPlacementV1,
|
|
13
|
+
type ProjectRenderPlanV1,
|
|
14
|
+
type RecordingEventV1,
|
|
15
|
+
type RecordingManifestV1,
|
|
16
|
+
type Rect,
|
|
17
|
+
type RenderEffects,
|
|
18
|
+
type SourceInterval,
|
|
19
|
+
type VideoProjectV1,
|
|
20
|
+
type ZoomOperation,
|
|
21
|
+
} from "../contracts";
|
|
22
|
+
import {
|
|
23
|
+
deriveTypingSpans,
|
|
24
|
+
findCursorAtTime,
|
|
25
|
+
findFocusedInputAtTime,
|
|
26
|
+
findWindowAtTime,
|
|
27
|
+
smoothAndDownsampleCursor,
|
|
28
|
+
} from "./events";
|
|
29
|
+
import {
|
|
30
|
+
assertProjectCameraSpatialLayerGeometry,
|
|
31
|
+
buildProjectCameraSpatialIndex,
|
|
32
|
+
evaluateProjectCameraSpatialViewportAt,
|
|
33
|
+
projectCameraSpatialLayer,
|
|
34
|
+
projectCameraSpatialLayerAtOutputTime,
|
|
35
|
+
type ProjectCameraSpatialIndex,
|
|
36
|
+
} from "./project-camera-spatial";
|
|
37
|
+
import {
|
|
38
|
+
assetToProjectUs,
|
|
39
|
+
mapAssetIntervalToProjectSlices,
|
|
40
|
+
mapProjectIntervalToOutputSlices,
|
|
41
|
+
projectToAssetUs,
|
|
42
|
+
projectToOutputUs,
|
|
43
|
+
type ProjectOutputTimeMap,
|
|
44
|
+
} from "./project-time";
|
|
45
|
+
|
|
46
|
+
export interface ProjectMetadataContext {
|
|
47
|
+
readonly events: readonly RecordingEventV1[];
|
|
48
|
+
readonly manifest: RecordingManifestV1;
|
|
49
|
+
readonly placementId: ProjectPlacementV1["placementId"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CompiledProjectMetadata {
|
|
53
|
+
readonly cameraKeyframes: ProjectRenderPlanV1["cameraKeyframes"];
|
|
54
|
+
readonly effects: ProjectRenderPlanV1["effects"];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type EnabledVideoPresentation = Extract<
|
|
58
|
+
ProjectPlacementV1["video"][number]["presentation"],
|
|
59
|
+
{ readonly enabled: true }
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
export interface ProjectMetadataSourceLayer {
|
|
63
|
+
readonly display: RecordingManifestV1["sources"]["displays"][number];
|
|
64
|
+
readonly presentation: EnabledVideoPresentation;
|
|
65
|
+
readonly stream: Extract<
|
|
66
|
+
VideoProjectV1["assets"][number]["streams"][number],
|
|
67
|
+
{ readonly kind: "video" }
|
|
68
|
+
>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ResolvedProjectMetadataSourceLayer
|
|
72
|
+
extends ProjectMetadataSourceLayer {
|
|
73
|
+
readonly track: Extract<
|
|
74
|
+
RecordingManifestV1["tracks"][number],
|
|
75
|
+
{ readonly kind: "display-video" }
|
|
76
|
+
>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface PixelLayout {
|
|
80
|
+
readonly height: number;
|
|
81
|
+
readonly width: number;
|
|
82
|
+
readonly x: number;
|
|
83
|
+
readonly y: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface DisplayLayer extends ProjectMetadataSourceLayer {
|
|
87
|
+
readonly layout: PixelLayout;
|
|
88
|
+
readonly placement: ProjectPlacementV1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ValidatedMetadataContext {
|
|
92
|
+
readonly events: readonly RecordingEventV1[];
|
|
93
|
+
readonly layers: ReadonlyMap<string, DisplayLayer>;
|
|
94
|
+
readonly manifest: RecordingManifestV1;
|
|
95
|
+
readonly placement: ProjectPlacementV1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface AssetOutputSlice {
|
|
99
|
+
readonly asset: SourceInterval;
|
|
100
|
+
readonly output: SourceInterval;
|
|
101
|
+
readonly project: SourceInterval;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
105
|
+
return Math.min(maximum, Math.max(minimum, value));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function pointInRect(point: Point, rect: Rect): boolean {
|
|
109
|
+
return point.x >= rect.x && point.x <= rect.x + rect.width
|
|
110
|
+
&& point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function intersectRect(left: Rect, right: Rect): Rect | null {
|
|
114
|
+
const x = Math.max(left.x, right.x);
|
|
115
|
+
const y = Math.max(left.y, right.y);
|
|
116
|
+
const rightEdge = Math.min(left.x + left.width, right.x + right.width);
|
|
117
|
+
const bottomEdge = Math.min(left.y + left.height, right.y + right.height);
|
|
118
|
+
return rightEdge > x && bottomEdge > y
|
|
119
|
+
? { height: bottomEdge - y, width: rightEdge - x, x, y }
|
|
120
|
+
: null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function layoutPixels(
|
|
124
|
+
presentation: EnabledVideoPresentation,
|
|
125
|
+
outputWidth: number,
|
|
126
|
+
outputHeight: number,
|
|
127
|
+
): PixelLayout {
|
|
128
|
+
const value = presentation.layout.kind === "output-pixels"
|
|
129
|
+
? presentation.layout
|
|
130
|
+
: {
|
|
131
|
+
height: presentation.layout.height * outputHeight,
|
|
132
|
+
width: presentation.layout.width * outputWidth,
|
|
133
|
+
x: presentation.layout.x * outputWidth,
|
|
134
|
+
y: presentation.layout.y * outputHeight,
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
height: Math.max(1, Math.round(value.height)),
|
|
138
|
+
width: Math.max(1, Math.round(value.width)),
|
|
139
|
+
x: Math.round(value.x),
|
|
140
|
+
y: Math.round(value.y),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function streamIdForTrack(trackId: string): string {
|
|
145
|
+
return `stream_${trackId.slice("track_".length)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolves the one immutable recording track that owns a display layer in a
|
|
150
|
+
* project placement. A display-only zoom cannot safely persist a hidden track
|
|
151
|
+
* choice, so multiple eligible tracks are rejected instead of picking one by
|
|
152
|
+
* array order.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveProjectMetadataSourceLayer(
|
|
155
|
+
manifest: RecordingManifestV1,
|
|
156
|
+
asset: VideoProjectV1["assets"][number],
|
|
157
|
+
placement: ProjectPlacementV1,
|
|
158
|
+
displayId: string,
|
|
159
|
+
): ResolvedProjectMetadataSourceLayer | null {
|
|
160
|
+
if (asset.source.kind !== "recording") return null;
|
|
161
|
+
const display = manifest.sources.displays.find(
|
|
162
|
+
candidate => candidate.displayId === displayId,
|
|
163
|
+
);
|
|
164
|
+
if (display === undefined) return null;
|
|
165
|
+
const candidates: ResolvedProjectMetadataSourceLayer[] = [];
|
|
166
|
+
for (const track of manifest.tracks) {
|
|
167
|
+
if (
|
|
168
|
+
!track.enabled
|
|
169
|
+
|| track.kind !== "display-video"
|
|
170
|
+
|| track.source.displayId !== displayId
|
|
171
|
+
|| !asset.source.trackIds.includes(track.trackId)
|
|
172
|
+
) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const streamId = streamIdForTrack(track.trackId);
|
|
176
|
+
const stream = asset.streams.find(
|
|
177
|
+
candidate => candidate.streamId === streamId,
|
|
178
|
+
);
|
|
179
|
+
const configured = placement.video.find(
|
|
180
|
+
candidate => candidate.streamId === streamId,
|
|
181
|
+
);
|
|
182
|
+
if (
|
|
183
|
+
stream?.kind !== "video"
|
|
184
|
+
|| configured?.presentation.enabled !== true
|
|
185
|
+
) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
candidates.push({
|
|
189
|
+
display,
|
|
190
|
+
presentation: configured.presentation,
|
|
191
|
+
stream,
|
|
192
|
+
track,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (candidates.length > 1) {
|
|
196
|
+
throw new TypeError(
|
|
197
|
+
`Display ${displayId} resolves to multiple enabled recording video layers.`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return candidates[0] ?? null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function requiredMetadataDisplayIds(
|
|
204
|
+
plan: ProjectEditPlanV1,
|
|
205
|
+
placementId: ProjectPlacementV1["placementId"],
|
|
206
|
+
events: readonly RecordingEventV1[],
|
|
207
|
+
manifest: RecordingManifestV1,
|
|
208
|
+
): ReadonlySet<string> {
|
|
209
|
+
const required = new Set(
|
|
210
|
+
plan.zooms
|
|
211
|
+
.filter(zoom => zoom.placementId === placementId)
|
|
212
|
+
.map(zoom => zoom.operation.displayId),
|
|
213
|
+
);
|
|
214
|
+
if (plan.effects.metadataPlacementId !== placementId) return required;
|
|
215
|
+
if (plan.effects.cursor.enabled) {
|
|
216
|
+
for (const event of events) {
|
|
217
|
+
if (event.type === "cursor.sample") required.add(event.displayId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (plan.effects.clicks.enabled) {
|
|
221
|
+
for (const event of events) {
|
|
222
|
+
if (event.type === "mouse.click") required.add(event.displayId);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!plan.effects.typedText.enabled) return required;
|
|
226
|
+
const typingWindowIds = new Set<string>();
|
|
227
|
+
const typingBounds: Rect[] = [];
|
|
228
|
+
for (const event of events) {
|
|
229
|
+
if (event.type === "typing.input") {
|
|
230
|
+
typingWindowIds.add(event.input.windowId);
|
|
231
|
+
typingBounds.push(event.input.bounds);
|
|
232
|
+
}
|
|
233
|
+
if (event.type === "focus.changed" && event.target.kind !== "none") {
|
|
234
|
+
typingWindowIds.add(event.target.windowId);
|
|
235
|
+
typingBounds.push(event.target.bounds);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const event of events) {
|
|
239
|
+
if (event.type === "window.snapshot") {
|
|
240
|
+
for (const window of event.windows) {
|
|
241
|
+
if (typingWindowIds.has(window.windowId)) {
|
|
242
|
+
required.add(window.displayId);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
event.type === "window.changed"
|
|
248
|
+
&& event.change.kind !== "destroyed"
|
|
249
|
+
&& typingWindowIds.has(event.change.window.windowId)
|
|
250
|
+
) {
|
|
251
|
+
required.add(event.change.window.displayId);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const bounds of typingBounds) {
|
|
255
|
+
for (const display of manifest.sources.displays) {
|
|
256
|
+
if (intersectRect(bounds, display.bounds) !== null) {
|
|
257
|
+
required.add(display.displayId);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return required;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function validateContext(
|
|
265
|
+
project: VideoProjectV1,
|
|
266
|
+
plan: ProjectEditPlanV1,
|
|
267
|
+
input: ProjectMetadataContext,
|
|
268
|
+
outputWidth: number,
|
|
269
|
+
outputHeight: number,
|
|
270
|
+
): ValidatedMetadataContext {
|
|
271
|
+
const manifest = RecordingManifestV1Schema.parse(input.manifest);
|
|
272
|
+
const events = input.events.map(event => RecordingEventV1Schema.parse(event));
|
|
273
|
+
const placement = project.placements.find(candidate => candidate.placementId === input.placementId);
|
|
274
|
+
if (placement === undefined) throw new TypeError(`Metadata context references unknown placement ${input.placementId}.`);
|
|
275
|
+
const asset = project.assets.find(candidate => candidate.assetId === placement.assetId);
|
|
276
|
+
if (asset?.source.kind !== "recording" || asset.source.recordingId !== manifest.recordingId) {
|
|
277
|
+
throw new TypeError(`Placement ${placement.placementId} is not backed by recording ${manifest.recordingId}.`);
|
|
278
|
+
}
|
|
279
|
+
const layers = new Map<string, DisplayLayer>();
|
|
280
|
+
for (const displayId of requiredMetadataDisplayIds(
|
|
281
|
+
plan,
|
|
282
|
+
placement.placementId,
|
|
283
|
+
events,
|
|
284
|
+
manifest,
|
|
285
|
+
)) {
|
|
286
|
+
const source = resolveProjectMetadataSourceLayer(
|
|
287
|
+
manifest,
|
|
288
|
+
asset,
|
|
289
|
+
placement,
|
|
290
|
+
displayId,
|
|
291
|
+
);
|
|
292
|
+
if (source === null) continue;
|
|
293
|
+
layers.set(displayId, {
|
|
294
|
+
...source,
|
|
295
|
+
layout: layoutPixels(source.presentation, outputWidth, outputHeight),
|
|
296
|
+
placement,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return { events, layers, manifest, placement };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function sourceCropGeometry(layer: ProjectMetadataSourceLayer): {
|
|
303
|
+
readonly height: number;
|
|
304
|
+
readonly left: number;
|
|
305
|
+
readonly top: number;
|
|
306
|
+
readonly width: number;
|
|
307
|
+
} {
|
|
308
|
+
const crop = layer.presentation.crop;
|
|
309
|
+
return {
|
|
310
|
+
height: layer.stream.pixelHeight * (
|
|
311
|
+
crop.kind === "none" ? 1 : 1 - crop.top - crop.bottom
|
|
312
|
+
),
|
|
313
|
+
left: crop.kind === "none" ? 0 : crop.left * layer.stream.pixelWidth,
|
|
314
|
+
top: crop.kind === "none" ? 0 : crop.top * layer.stream.pixelHeight,
|
|
315
|
+
width: layer.stream.pixelWidth * (
|
|
316
|
+
crop.kind === "none" ? 1 : 1 - crop.left - crop.right
|
|
317
|
+
),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function visibleRawPoint(
|
|
322
|
+
point: Point,
|
|
323
|
+
layer: ProjectMetadataSourceLayer,
|
|
324
|
+
): Point {
|
|
325
|
+
if (!pointInRect(point, layer.display.bounds)) {
|
|
326
|
+
throw new TypeError(`Metadata point is outside display ${layer.display.displayId}.`);
|
|
327
|
+
}
|
|
328
|
+
const raw = {
|
|
329
|
+
x: (point.x - layer.display.bounds.x) * layer.display.scaleFactor,
|
|
330
|
+
y: (point.y - layer.display.bounds.y) * layer.display.scaleFactor,
|
|
331
|
+
};
|
|
332
|
+
const geometry = sourceCropGeometry(layer);
|
|
333
|
+
if (
|
|
334
|
+
raw.x < geometry.left
|
|
335
|
+
|| raw.x > geometry.left + geometry.width
|
|
336
|
+
|| raw.y < geometry.top
|
|
337
|
+
|| raw.y > geometry.top + geometry.height
|
|
338
|
+
) {
|
|
339
|
+
throw new TypeError(`Metadata point is cropped out of display ${layer.display.displayId}.`);
|
|
340
|
+
}
|
|
341
|
+
return raw;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function visibleRawRect(
|
|
345
|
+
rect: Rect,
|
|
346
|
+
layer: ProjectMetadataSourceLayer,
|
|
347
|
+
): Rect {
|
|
348
|
+
const clippedToDisplay = intersectRect(rect, layer.display.bounds);
|
|
349
|
+
if (clippedToDisplay === null) {
|
|
350
|
+
throw new TypeError(`Metadata rectangle misses display ${layer.display.displayId}.`);
|
|
351
|
+
}
|
|
352
|
+
const raw = {
|
|
353
|
+
height: clippedToDisplay.height * layer.display.scaleFactor,
|
|
354
|
+
width: clippedToDisplay.width * layer.display.scaleFactor,
|
|
355
|
+
x: (clippedToDisplay.x - layer.display.bounds.x) * layer.display.scaleFactor,
|
|
356
|
+
y: (clippedToDisplay.y - layer.display.bounds.y) * layer.display.scaleFactor,
|
|
357
|
+
};
|
|
358
|
+
const geometry = sourceCropGeometry(layer);
|
|
359
|
+
const visible = intersectRect(raw, {
|
|
360
|
+
height: geometry.height,
|
|
361
|
+
width: geometry.width,
|
|
362
|
+
x: geometry.left,
|
|
363
|
+
y: geometry.top,
|
|
364
|
+
});
|
|
365
|
+
if (visible === null) {
|
|
366
|
+
throw new TypeError(`Metadata rectangle is cropped out of ${layer.display.displayId}.`);
|
|
367
|
+
}
|
|
368
|
+
return visible;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Rejects static zoom targets that cannot be resolved from the selected
|
|
373
|
+
* display layer. Render planning calls the same source-visibility helpers,
|
|
374
|
+
* so committing an edit cannot defer a known display/crop failure to render.
|
|
375
|
+
*/
|
|
376
|
+
export function assertStaticProjectZoomTargetVisible(
|
|
377
|
+
target: Extract<
|
|
378
|
+
ZoomOperation["target"],
|
|
379
|
+
{ readonly kind: "point" | "rect" }
|
|
380
|
+
>,
|
|
381
|
+
layer: ProjectMetadataSourceLayer,
|
|
382
|
+
): void {
|
|
383
|
+
if (target.kind === "point") {
|
|
384
|
+
visibleRawPoint(target.point, layer);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
visibleRawRect(target.rect, layer);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function cropGeometry(layer: DisplayLayer): {
|
|
391
|
+
readonly height: number;
|
|
392
|
+
readonly left: number;
|
|
393
|
+
readonly scaleX: number;
|
|
394
|
+
readonly scaleY: number;
|
|
395
|
+
readonly top: number;
|
|
396
|
+
readonly translatedX: number;
|
|
397
|
+
readonly translatedY: number;
|
|
398
|
+
readonly width: number;
|
|
399
|
+
} {
|
|
400
|
+
const { height, left, top, width } = sourceCropGeometry(layer);
|
|
401
|
+
if (layer.presentation.fit === "fill") {
|
|
402
|
+
return {
|
|
403
|
+
height,
|
|
404
|
+
left,
|
|
405
|
+
scaleX: layer.layout.width / width,
|
|
406
|
+
scaleY: layer.layout.height / height,
|
|
407
|
+
top,
|
|
408
|
+
translatedX: 0,
|
|
409
|
+
translatedY: 0,
|
|
410
|
+
width,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const scale = layer.presentation.fit === "contain"
|
|
414
|
+
? Math.min(layer.layout.width / width, layer.layout.height / height)
|
|
415
|
+
: Math.max(layer.layout.width / width, layer.layout.height / height);
|
|
416
|
+
return {
|
|
417
|
+
height,
|
|
418
|
+
left,
|
|
419
|
+
scaleX: scale,
|
|
420
|
+
scaleY: scale,
|
|
421
|
+
top,
|
|
422
|
+
translatedX: (layer.layout.width - width * scale) / 2,
|
|
423
|
+
translatedY: (layer.layout.height - height * scale) / 2,
|
|
424
|
+
width,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function globalPointToLayer(point: Point, layer: DisplayLayer): Point {
|
|
429
|
+
const raw = visibleRawPoint(point, layer);
|
|
430
|
+
const geometry = cropGeometry(layer);
|
|
431
|
+
const mapped = {
|
|
432
|
+
x: (raw.x - geometry.left) * geometry.scaleX + geometry.translatedX,
|
|
433
|
+
y: (raw.y - geometry.top) * geometry.scaleY + geometry.translatedY,
|
|
434
|
+
};
|
|
435
|
+
if (!pointInRect(mapped, { height: layer.layout.height, width: layer.layout.width, x: 0, y: 0 })) {
|
|
436
|
+
throw new TypeError(`Metadata point is outside the visible layer for ${layer.display.displayId}.`);
|
|
437
|
+
}
|
|
438
|
+
return mapped;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function globalRectToLayer(rect: Rect, layer: DisplayLayer): Rect {
|
|
442
|
+
const visibleRaw = visibleRawRect(rect, layer);
|
|
443
|
+
const geometry = cropGeometry(layer);
|
|
444
|
+
const mapped = {
|
|
445
|
+
height: visibleRaw.height * geometry.scaleY,
|
|
446
|
+
width: visibleRaw.width * geometry.scaleX,
|
|
447
|
+
x: (visibleRaw.x - geometry.left) * geometry.scaleX + geometry.translatedX,
|
|
448
|
+
y: (visibleRaw.y - geometry.top) * geometry.scaleY + geometry.translatedY,
|
|
449
|
+
};
|
|
450
|
+
const clippedToLayer = intersectRect(mapped, { height: layer.layout.height, width: layer.layout.width, x: 0, y: 0 });
|
|
451
|
+
if (clippedToLayer === null) throw new TypeError(`Metadata rectangle is outside ${layer.display.displayId}'s layer.`);
|
|
452
|
+
return clippedToLayer;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function targetViewport(target: Rect, scale: number, layer: DisplayLayer, paddingPx = 0): Rect {
|
|
456
|
+
const aspect = layer.layout.width / layer.layout.height;
|
|
457
|
+
const requestedWidth = layer.layout.width / scale;
|
|
458
|
+
const paddedWidth = Math.min(layer.layout.width, target.width + paddingPx * 2);
|
|
459
|
+
const paddedHeight = Math.min(layer.layout.height, target.height + paddingPx * 2);
|
|
460
|
+
const width = Math.min(layer.layout.width, Math.max(requestedWidth, paddedWidth, paddedHeight * aspect));
|
|
461
|
+
const height = width / aspect;
|
|
462
|
+
return {
|
|
463
|
+
height,
|
|
464
|
+
width,
|
|
465
|
+
x: clamp(target.x + target.width / 2 - width / 2, 0, layer.layout.width - width),
|
|
466
|
+
y: clamp(target.y + target.height / 2 - height / 2, 0, layer.layout.height - height),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function resolveZoomTarget(
|
|
471
|
+
operation: ZoomOperation,
|
|
472
|
+
assetTimeUs: number,
|
|
473
|
+
scale: number,
|
|
474
|
+
context: ValidatedMetadataContext,
|
|
475
|
+
layer: DisplayLayer,
|
|
476
|
+
): Rect {
|
|
477
|
+
const target = operation.target;
|
|
478
|
+
if (target.kind === "rect") return targetViewport(globalRectToLayer(target.rect, layer), scale, layer);
|
|
479
|
+
if (target.kind === "point") {
|
|
480
|
+
const point = globalPointToLayer(target.point, layer);
|
|
481
|
+
return targetViewport({ height: 0, width: 0, ...point }, scale, layer);
|
|
482
|
+
}
|
|
483
|
+
if (target.kind === "cursor") {
|
|
484
|
+
const cursor = findCursorAtTime(
|
|
485
|
+
context.events.filter(event => event.type !== "cursor.sample" || event.displayId === layer.display.displayId),
|
|
486
|
+
assetTimeUs,
|
|
487
|
+
target.sampling,
|
|
488
|
+
);
|
|
489
|
+
if (cursor !== null && cursor.visible) {
|
|
490
|
+
const point = globalPointToLayer(cursor.position, layer);
|
|
491
|
+
return targetViewport({ height: 0, width: 0, ...point }, scale, layer);
|
|
492
|
+
}
|
|
493
|
+
} else if (target.kind === "window") {
|
|
494
|
+
const window = findWindowAtTime(context.events, target.selector, assetTimeUs);
|
|
495
|
+
if (window !== null && window.displayId === layer.display.displayId) {
|
|
496
|
+
return targetViewport(globalRectToLayer(window.bounds, layer), scale, layer, target.paddingPx);
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
const input = findFocusedInputAtTime(context.events, assetTimeUs);
|
|
500
|
+
if (input !== null) {
|
|
501
|
+
return targetViewport(globalRectToLayer(input.bounds, layer), scale, layer, target.paddingPx);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
throw new TypeError(`Cannot resolve ${target.kind} zoom target at asset time ${assetTimeUs}.`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function zoomScaleAt(projectTimeUs: number, zoom: ZoomOperation): number {
|
|
508
|
+
const enterEndUs = zoom.range.startUs + zoom.enterDurationUs;
|
|
509
|
+
const exitStartUs = zoom.range.endUs - zoom.exitDurationUs;
|
|
510
|
+
if (zoom.enterDurationUs > 0 && projectTimeUs < enterEndUs) {
|
|
511
|
+
return 1 + (zoom.scale - 1) * ((projectTimeUs - zoom.range.startUs) / zoom.enterDurationUs);
|
|
512
|
+
}
|
|
513
|
+
if (zoom.exitDurationUs > 0 && projectTimeUs > exitStartUs) {
|
|
514
|
+
return 1 + (zoom.scale - 1) * ((zoom.range.endUs - projectTimeUs) / zoom.exitDurationUs);
|
|
515
|
+
}
|
|
516
|
+
return zoom.scale;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function zoomProjectTimes(part: SourceInterval, zoom: ZoomOperation): readonly number[] {
|
|
520
|
+
return [...new Set([
|
|
521
|
+
part.startUs,
|
|
522
|
+
zoom.range.startUs + zoom.enterDurationUs,
|
|
523
|
+
zoom.range.endUs - zoom.exitDurationUs,
|
|
524
|
+
part.endUs,
|
|
525
|
+
].filter(timeUs => timeUs >= part.startUs && timeUs <= part.endUs))]
|
|
526
|
+
.sort((left, right) => left - right);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function compileCameraKeyframes(
|
|
530
|
+
plan: ProjectEditPlanV1,
|
|
531
|
+
contexts: ReadonlyMap<string, ValidatedMetadataContext>,
|
|
532
|
+
outputMap: ProjectOutputTimeMap,
|
|
533
|
+
): readonly ProjectCameraKeyframe[] {
|
|
534
|
+
const keyframes = new Map<string, ProjectCameraKeyframe>();
|
|
535
|
+
for (const configured of plan.zooms) {
|
|
536
|
+
const context = contexts.get(configured.placementId);
|
|
537
|
+
if (context === undefined) {
|
|
538
|
+
throw new TypeError(`Zoom ${configured.operation.zoomId} requires metadata for ${configured.placementId}.`);
|
|
539
|
+
}
|
|
540
|
+
const layer = context.layers.get(configured.operation.displayId);
|
|
541
|
+
if (layer === undefined) {
|
|
542
|
+
throw new TypeError(`Zoom ${configured.operation.zoomId} selects an unavailable display layer.`);
|
|
543
|
+
}
|
|
544
|
+
for (const slice of mapProjectIntervalToOutputSlices(outputMap, configured.operation.range)) {
|
|
545
|
+
for (const projectTimeUs of zoomProjectTimes(slice.project, configured.operation)) {
|
|
546
|
+
const outputTimeUs = projectToOutputUs(outputMap, projectTimeUs);
|
|
547
|
+
const assetTimeUs = projectToAssetUs(context.placement.sync, projectTimeUs);
|
|
548
|
+
if (outputTimeUs === null || assetTimeUs === null) {
|
|
549
|
+
throw new TypeError(`Zoom ${configured.operation.zoomId} cannot map its project clock.`);
|
|
550
|
+
}
|
|
551
|
+
const viewport = resolveZoomTarget(
|
|
552
|
+
configured.operation,
|
|
553
|
+
assetTimeUs,
|
|
554
|
+
zoomScaleAt(projectTimeUs, configured.operation),
|
|
555
|
+
context,
|
|
556
|
+
layer,
|
|
557
|
+
);
|
|
558
|
+
const keyframe = {
|
|
559
|
+
displayId: layer.display.displayId,
|
|
560
|
+
easing: configured.operation.easing,
|
|
561
|
+
layerPixelHeight: layer.layout.height,
|
|
562
|
+
layerPixelWidth: layer.layout.width,
|
|
563
|
+
outputTimeUs,
|
|
564
|
+
placementId: context.placement.placementId,
|
|
565
|
+
scale: layer.layout.width / viewport.width,
|
|
566
|
+
sourceTimeUs: assetTimeUs,
|
|
567
|
+
streamId: layer.stream.streamId,
|
|
568
|
+
viewport,
|
|
569
|
+
zoomId: configured.operation.zoomId,
|
|
570
|
+
} satisfies ProjectCameraKeyframe;
|
|
571
|
+
keyframes.set(
|
|
572
|
+
`${keyframe.placementId}:${keyframe.streamId}:${keyframe.zoomId}:${keyframe.outputTimeUs}`,
|
|
573
|
+
keyframe,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return [...keyframes.values()].sort((left, right) => (
|
|
579
|
+
left.outputTimeUs - right.outputTimeUs
|
|
580
|
+
|| left.placementId.localeCompare(right.placementId)
|
|
581
|
+
|| left.streamId.localeCompare(right.streamId)
|
|
582
|
+
|| left.zoomId.localeCompare(right.zoomId)
|
|
583
|
+
));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function cameraViewportAt(
|
|
587
|
+
layer: DisplayLayer,
|
|
588
|
+
outputTimeUs: number,
|
|
589
|
+
camera: ProjectCameraSpatialIndex,
|
|
590
|
+
): Rect {
|
|
591
|
+
return evaluateProjectCameraSpatialViewportAt(
|
|
592
|
+
projectCameraSpatialLayerAtOutputTime(
|
|
593
|
+
projectCameraSpatialLayer(
|
|
594
|
+
camera,
|
|
595
|
+
layer.placement.placementId,
|
|
596
|
+
layer.stream.streamId,
|
|
597
|
+
),
|
|
598
|
+
outputTimeUs,
|
|
599
|
+
),
|
|
600
|
+
{
|
|
601
|
+
outputTimeUs,
|
|
602
|
+
pixelHeight: layer.layout.height,
|
|
603
|
+
pixelWidth: layer.layout.width,
|
|
604
|
+
},
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function layerPointToOutput(
|
|
609
|
+
point: Point,
|
|
610
|
+
layer: DisplayLayer,
|
|
611
|
+
outputTimeUs: number,
|
|
612
|
+
camera: ProjectCameraSpatialIndex,
|
|
613
|
+
): Point {
|
|
614
|
+
const viewport = cameraViewportAt(
|
|
615
|
+
layer,
|
|
616
|
+
outputTimeUs,
|
|
617
|
+
camera,
|
|
618
|
+
);
|
|
619
|
+
return {
|
|
620
|
+
x: layer.layout.x + (point.x - viewport.x) * layer.layout.width / viewport.width,
|
|
621
|
+
y: layer.layout.y + (point.y - viewport.y) * layer.layout.height / viewport.height,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function layerRectToOutput(
|
|
626
|
+
rect: Rect,
|
|
627
|
+
layer: DisplayLayer,
|
|
628
|
+
outputTimeUs: number,
|
|
629
|
+
camera: ProjectCameraSpatialIndex,
|
|
630
|
+
): Rect {
|
|
631
|
+
const topLeft = layerPointToOutput(rect, layer, outputTimeUs, camera);
|
|
632
|
+
const viewport = cameraViewportAt(
|
|
633
|
+
layer,
|
|
634
|
+
outputTimeUs,
|
|
635
|
+
camera,
|
|
636
|
+
);
|
|
637
|
+
return {
|
|
638
|
+
height: rect.height * layer.layout.height / viewport.height,
|
|
639
|
+
width: rect.width * layer.layout.width / viewport.width,
|
|
640
|
+
x: topLeft.x,
|
|
641
|
+
y: topLeft.y,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function outputTimeForAsset(
|
|
646
|
+
placement: ProjectPlacementV1,
|
|
647
|
+
outputMap: ProjectOutputTimeMap,
|
|
648
|
+
assetTimeUs: number,
|
|
649
|
+
): number | null {
|
|
650
|
+
const projectTimeUs = assetToProjectUs(placement.sync, assetTimeUs);
|
|
651
|
+
return projectTimeUs === null ? null : projectToOutputUs(outputMap, projectTimeUs);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function assetOutputSlices(
|
|
655
|
+
placement: ProjectPlacementV1,
|
|
656
|
+
outputMap: ProjectOutputTimeMap,
|
|
657
|
+
assetRange: SourceInterval,
|
|
658
|
+
): readonly AssetOutputSlice[] {
|
|
659
|
+
return mapAssetIntervalToProjectSlices(placement, assetRange).flatMap(mapped => (
|
|
660
|
+
mapProjectIntervalToOutputSlices(outputMap, mapped.project).flatMap((slice) => {
|
|
661
|
+
const startUs = projectToAssetUs(placement.sync, slice.project.startUs);
|
|
662
|
+
const endUs = projectToAssetUs(placement.sync, slice.project.endUs);
|
|
663
|
+
return startUs === null || endUs === null || endUs <= startUs
|
|
664
|
+
? []
|
|
665
|
+
: [{ asset: { endUs, startUs }, output: slice.output, project: slice.project }];
|
|
666
|
+
})
|
|
667
|
+
));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function contextLayerForRect(
|
|
671
|
+
context: ValidatedMetadataContext,
|
|
672
|
+
rect: Rect,
|
|
673
|
+
sourceTimeUs: number,
|
|
674
|
+
windowId?: string,
|
|
675
|
+
): DisplayLayer | null {
|
|
676
|
+
if (windowId !== undefined) {
|
|
677
|
+
const window = findWindowAtTime(context.events, { kind: "window-id", windowId }, sourceTimeUs);
|
|
678
|
+
if (window !== null) return context.layers.get(window.displayId) ?? null;
|
|
679
|
+
}
|
|
680
|
+
return [...context.layers.values()].find(layer => intersectRect(rect, layer.display.bounds) !== null) ?? null;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function compileEffects(
|
|
684
|
+
plan: ProjectEditPlanV1,
|
|
685
|
+
context: ValidatedMetadataContext | null,
|
|
686
|
+
outputMap: ProjectOutputTimeMap,
|
|
687
|
+
camera: ProjectCameraSpatialIndex,
|
|
688
|
+
frameRate: number,
|
|
689
|
+
): RenderEffects {
|
|
690
|
+
const disabledResult: RenderEffects = {
|
|
691
|
+
clickCues: [],
|
|
692
|
+
clicks: plan.effects.clicks,
|
|
693
|
+
cursor: plan.effects.cursor,
|
|
694
|
+
cursorSamples: [],
|
|
695
|
+
keystrokes: plan.effects.keystrokes,
|
|
696
|
+
keystrokeCues: [],
|
|
697
|
+
typedText: plan.effects.typedText,
|
|
698
|
+
typingSpans: [],
|
|
699
|
+
};
|
|
700
|
+
const enabled = plan.effects.clicks.enabled
|
|
701
|
+
|| plan.effects.cursor.enabled
|
|
702
|
+
|| plan.effects.keystrokes.enabled
|
|
703
|
+
|| plan.effects.typedText.enabled;
|
|
704
|
+
if (!enabled) return disabledResult;
|
|
705
|
+
if (context === null) throw new TypeError("Enabled project metadata effects require a recording-backed placement.");
|
|
706
|
+
|
|
707
|
+
const cursorSamples = plan.effects.cursor.enabled
|
|
708
|
+
? smoothAndDownsampleCursor(context.events, {
|
|
709
|
+
minDistancePx: 0.5,
|
|
710
|
+
minIntervalUs: Math.max(1, Math.round(1_000_000 / frameRate)),
|
|
711
|
+
strength: plan.effects.cursor.smoothing.algorithm === "none"
|
|
712
|
+
? 0
|
|
713
|
+
: plan.effects.cursor.smoothing.strength,
|
|
714
|
+
}).flatMap((sample) => {
|
|
715
|
+
const outputTimeUs = outputTimeForAsset(context.placement, outputMap, sample.sourceTimeUs);
|
|
716
|
+
const layer = context.layers.get(sample.displayId);
|
|
717
|
+
if (outputTimeUs === null || layer === undefined) return [];
|
|
718
|
+
try {
|
|
719
|
+
const local = globalPointToLayer(sample.position, layer);
|
|
720
|
+
return [{
|
|
721
|
+
coordinateSpace: "output-pixels" as const,
|
|
722
|
+
displayId: sample.displayId,
|
|
723
|
+
outputTimeUs,
|
|
724
|
+
position: layerPointToOutput(local, layer, outputTimeUs, camera),
|
|
725
|
+
sourceTimeUs: sample.sourceTimeUs,
|
|
726
|
+
visible: sample.visible,
|
|
727
|
+
}];
|
|
728
|
+
} catch {
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
})
|
|
732
|
+
: [];
|
|
733
|
+
|
|
734
|
+
const clickCues = plan.effects.clicks.enabled
|
|
735
|
+
? context.events.flatMap((event) => {
|
|
736
|
+
if (event.type !== "mouse.click") return [];
|
|
737
|
+
const outputTimeUs = outputTimeForAsset(context.placement, outputMap, event.sourceTimeUs);
|
|
738
|
+
const layer = context.layers.get(event.displayId);
|
|
739
|
+
if (outputTimeUs === null || layer === undefined) return [];
|
|
740
|
+
try {
|
|
741
|
+
const local = globalPointToLayer(event.position, layer);
|
|
742
|
+
return [{
|
|
743
|
+
button: event.button,
|
|
744
|
+
clickCount: event.clickCount,
|
|
745
|
+
coordinateSpace: "output-pixels" as const,
|
|
746
|
+
displayId: event.displayId,
|
|
747
|
+
outputTimeUs,
|
|
748
|
+
phase: event.phase,
|
|
749
|
+
position: layerPointToOutput(local, layer, outputTimeUs, camera),
|
|
750
|
+
sourceTimeUs: event.sourceTimeUs,
|
|
751
|
+
}];
|
|
752
|
+
} catch {
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
})
|
|
756
|
+
: [];
|
|
757
|
+
|
|
758
|
+
const keystrokeCues = plan.effects.keystrokes.enabled
|
|
759
|
+
? context.events.flatMap((event) => {
|
|
760
|
+
if (event.type !== "key.activity") return [];
|
|
761
|
+
const outputTimeUs = outputTimeForAsset(context.placement, outputMap, event.sourceTimeUs);
|
|
762
|
+
if (outputTimeUs === null) return [];
|
|
763
|
+
const activity = event.activity.kind === "printable"
|
|
764
|
+
? { kind: "printable" as const, token: "[PRINTABLE]" as const }
|
|
765
|
+
: event.activity.kind === "shortcut"
|
|
766
|
+
? { keyCode: event.activity.keyCode, kind: "shortcut" as const, modifiers: event.activity.modifiers }
|
|
767
|
+
: { control: event.activity.control, kind: "control" as const, modifiers: event.activity.modifiers };
|
|
768
|
+
return [{
|
|
769
|
+
activity,
|
|
770
|
+
outputTimeUs,
|
|
771
|
+
phase: event.activity.phase,
|
|
772
|
+
repeat: event.activity.repeat,
|
|
773
|
+
sourceTimeUs: event.sourceTimeUs,
|
|
774
|
+
}];
|
|
775
|
+
})
|
|
776
|
+
: [];
|
|
777
|
+
|
|
778
|
+
const typingSpans: RenderEffects["typingSpans"][number][] = [];
|
|
779
|
+
if (plan.effects.typedText.enabled) {
|
|
780
|
+
for (const span of deriveTypingSpans(context.events, plan.effects.typedText.idleTimeoutUs)) {
|
|
781
|
+
const clipped = {
|
|
782
|
+
endUs: Math.min(span.endUs, context.placement.assetRange.endUs),
|
|
783
|
+
startUs: Math.max(span.startUs, context.placement.assetRange.startUs),
|
|
784
|
+
};
|
|
785
|
+
if (clipped.endUs <= clipped.startUs) continue;
|
|
786
|
+
for (const slice of assetOutputSlices(context.placement, outputMap, clipped)) {
|
|
787
|
+
const layer = contextLayerForRect(context, span.bounds, slice.asset.startUs, span.windowId);
|
|
788
|
+
if (layer === null) continue;
|
|
789
|
+
let renderedBounds: Rect;
|
|
790
|
+
try {
|
|
791
|
+
renderedBounds = layerRectToOutput(
|
|
792
|
+
globalRectToLayer(span.bounds, layer),
|
|
793
|
+
layer,
|
|
794
|
+
slice.output.startUs,
|
|
795
|
+
camera,
|
|
796
|
+
);
|
|
797
|
+
} catch {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (span.secure) {
|
|
801
|
+
typingSpans.push({
|
|
802
|
+
bounds: renderedBounds,
|
|
803
|
+
endOutputUs: slice.output.endUs,
|
|
804
|
+
endSourceUs: slice.asset.endUs,
|
|
805
|
+
fieldId: "[REDACTED]",
|
|
806
|
+
secure: true,
|
|
807
|
+
startOutputUs: slice.output.startUs,
|
|
808
|
+
startSourceUs: slice.asset.startUs,
|
|
809
|
+
state: "hidden",
|
|
810
|
+
windowId: span.windowId,
|
|
811
|
+
});
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const preceding = [...span.updates].reverse().find(update => update.sourceTimeUs <= slice.asset.startUs);
|
|
815
|
+
const selected = [
|
|
816
|
+
...(preceding === undefined ? [] : [{ ...preceding, sourceTimeUs: slice.asset.startUs }]),
|
|
817
|
+
...span.updates.filter(update => (
|
|
818
|
+
update.sourceTimeUs >= slice.asset.startUs && update.sourceTimeUs < slice.asset.endUs
|
|
819
|
+
)),
|
|
820
|
+
];
|
|
821
|
+
const unique = new Map<number, typeof selected[number]>();
|
|
822
|
+
for (const update of selected) unique.set(update.sourceTimeUs, update);
|
|
823
|
+
const updates = [...unique.values()].flatMap((update) => {
|
|
824
|
+
const outputTimeUs = outputTimeForAsset(context.placement, outputMap, update.sourceTimeUs);
|
|
825
|
+
const updateLayer = contextLayerForRect(context, update.bounds, update.sourceTimeUs, span.windowId) ?? layer;
|
|
826
|
+
if (outputTimeUs === null) return [];
|
|
827
|
+
try {
|
|
828
|
+
return [{
|
|
829
|
+
bounds: layerRectToOutput(
|
|
830
|
+
globalRectToLayer(update.bounds, updateLayer),
|
|
831
|
+
updateLayer,
|
|
832
|
+
outputTimeUs,
|
|
833
|
+
camera,
|
|
834
|
+
),
|
|
835
|
+
coordinateSpace: "output-pixels" as const,
|
|
836
|
+
outputTimeUs,
|
|
837
|
+
sourceTimeUs: update.sourceTimeUs,
|
|
838
|
+
text: update.text,
|
|
839
|
+
}];
|
|
840
|
+
} catch {
|
|
841
|
+
return [];
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
if (updates.length > 0) {
|
|
845
|
+
typingSpans.push({
|
|
846
|
+
endOutputUs: slice.output.endUs,
|
|
847
|
+
endSourceUs: slice.asset.endUs,
|
|
848
|
+
fieldId: span.fieldId,
|
|
849
|
+
secure: false,
|
|
850
|
+
startOutputUs: slice.output.startUs,
|
|
851
|
+
startSourceUs: slice.asset.startUs,
|
|
852
|
+
updates,
|
|
853
|
+
windowId: span.windowId,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return {
|
|
861
|
+
...disabledResult,
|
|
862
|
+
clickCues,
|
|
863
|
+
cursorSamples,
|
|
864
|
+
keystrokeCues,
|
|
865
|
+
typingSpans,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export function compileProjectMetadata(
|
|
870
|
+
projectInput: VideoProjectV1,
|
|
871
|
+
planInput: ProjectEditPlanV1,
|
|
872
|
+
outputMap: ProjectOutputTimeMap,
|
|
873
|
+
inputs: readonly ProjectMetadataContext[],
|
|
874
|
+
settings: { readonly frameRate: number; readonly pixelHeight: number; readonly pixelWidth: number },
|
|
875
|
+
cameraSegments: readonly ProjectCameraSegment[] = [],
|
|
876
|
+
): CompiledProjectMetadata {
|
|
877
|
+
const project = VideoProjectV1Schema.parse(projectInput);
|
|
878
|
+
const plan = ProjectEditPlanV1Schema.parse(planInput);
|
|
879
|
+
const contexts = new Map<string, ValidatedMetadataContext>();
|
|
880
|
+
for (const input of inputs) {
|
|
881
|
+
if (contexts.has(input.placementId)) throw new TypeError(`Duplicate metadata context for ${input.placementId}.`);
|
|
882
|
+
contexts.set(
|
|
883
|
+
input.placementId,
|
|
884
|
+
validateContext(
|
|
885
|
+
project,
|
|
886
|
+
plan,
|
|
887
|
+
input,
|
|
888
|
+
settings.pixelWidth,
|
|
889
|
+
settings.pixelHeight,
|
|
890
|
+
),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
const cameraKeyframes = compileCameraKeyframes(plan, contexts, outputMap);
|
|
894
|
+
const camera = buildProjectCameraSpatialIndex({
|
|
895
|
+
cameraKeyframes,
|
|
896
|
+
cameraSegments,
|
|
897
|
+
});
|
|
898
|
+
for (const context of contexts.values()) {
|
|
899
|
+
for (const layer of context.layers.values()) {
|
|
900
|
+
const spatialLayer = projectCameraSpatialLayer(
|
|
901
|
+
camera,
|
|
902
|
+
layer.placement.placementId,
|
|
903
|
+
layer.stream.streamId,
|
|
904
|
+
);
|
|
905
|
+
assertProjectCameraSpatialLayerGeometry(
|
|
906
|
+
spatialLayer,
|
|
907
|
+
layer.layout.width,
|
|
908
|
+
layer.layout.height,
|
|
909
|
+
`${layer.placement.placementId}:${layer.stream.streamId}`,
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const effectContext = plan.effects.metadataPlacementId === null
|
|
914
|
+
? null
|
|
915
|
+
: contexts.get(plan.effects.metadataPlacementId) ?? null;
|
|
916
|
+
const effects = compileEffects(
|
|
917
|
+
plan,
|
|
918
|
+
effectContext,
|
|
919
|
+
outputMap,
|
|
920
|
+
camera,
|
|
921
|
+
settings.frameRate,
|
|
922
|
+
);
|
|
923
|
+
return {
|
|
924
|
+
cameraKeyframes: ProjectCameraKeyframeSchema.array().max(100_000).parse(cameraKeyframes),
|
|
925
|
+
effects: RenderEffectsSchema.parse(effects),
|
|
926
|
+
};
|
|
927
|
+
}
|