@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,406 @@
|
|
|
1
|
+
import { CliError } from "./errors";
|
|
2
|
+
import type { ProcessRunner } from "./io";
|
|
3
|
+
|
|
4
|
+
export interface DetectedInterval {
|
|
5
|
+
readonly endUs: number;
|
|
6
|
+
readonly startUs: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface OpenDetection {
|
|
10
|
+
readonly startUs: number;
|
|
11
|
+
durationUs?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function secondsToMicroseconds(value: string, label: string): number {
|
|
15
|
+
const seconds = Number(value);
|
|
16
|
+
const microseconds = Math.round(seconds * 1_000_000);
|
|
17
|
+
if (!Number.isFinite(seconds) || !Number.isSafeInteger(microseconds) || microseconds < 0) {
|
|
18
|
+
throw new CliError("invalid-data", `Invalid ${label} timestamp from FFmpeg: ${value}`);
|
|
19
|
+
}
|
|
20
|
+
return microseconds;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseDetections(
|
|
24
|
+
output: string,
|
|
25
|
+
labels: Readonly<{ readonly duration: RegExp; readonly end: RegExp; readonly start: RegExp }>,
|
|
26
|
+
mediaDurationUs?: number,
|
|
27
|
+
): readonly DetectedInterval[] {
|
|
28
|
+
const intervals: DetectedInterval[] = [];
|
|
29
|
+
let open: OpenDetection | undefined;
|
|
30
|
+
for (const line of output.split(/\r?\n/u)) {
|
|
31
|
+
const start = labels.start.exec(line);
|
|
32
|
+
if (start?.groups?.value !== undefined) {
|
|
33
|
+
open = { startUs: secondsToMicroseconds(start.groups.value, "start") };
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const duration = labels.duration.exec(line);
|
|
37
|
+
if (duration?.groups?.value !== undefined && open !== undefined) {
|
|
38
|
+
open.durationUs = secondsToMicroseconds(duration.groups.value, "duration");
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const end = labels.end.exec(line);
|
|
42
|
+
if (end?.groups?.value !== undefined && open !== undefined) {
|
|
43
|
+
const endUs = secondsToMicroseconds(end.groups.value, "end");
|
|
44
|
+
if (endUs >= open.startUs) intervals.push({ endUs, startUs: open.startUs });
|
|
45
|
+
open = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (open !== undefined) {
|
|
49
|
+
const endUs = open.durationUs === undefined
|
|
50
|
+
? mediaDurationUs
|
|
51
|
+
: open.startUs + open.durationUs;
|
|
52
|
+
if (endUs !== undefined && endUs >= open.startUs) intervals.push({ endUs, startUs: open.startUs });
|
|
53
|
+
}
|
|
54
|
+
return intervals;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const NUMBER = String.raw`(?<value>[0-9]+(?:\.[0-9]+)?)`;
|
|
58
|
+
|
|
59
|
+
export function parseFreezeDetectOutput(
|
|
60
|
+
output: string,
|
|
61
|
+
mediaDurationUs?: number,
|
|
62
|
+
): readonly DetectedInterval[] {
|
|
63
|
+
return parseDetections(output, {
|
|
64
|
+
duration: new RegExp(String.raw`(?:lavfi\.freezedetect\.)?freeze_duration\s*[:=]\s*${NUMBER}`, "u"),
|
|
65
|
+
end: new RegExp(String.raw`(?:lavfi\.freezedetect\.)?freeze_end\s*[:=]\s*${NUMBER}`, "u"),
|
|
66
|
+
start: new RegExp(String.raw`(?:lavfi\.freezedetect\.)?freeze_start\s*[:=]\s*${NUMBER}`, "u"),
|
|
67
|
+
}, mediaDurationUs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseSilenceDetectOutput(
|
|
71
|
+
output: string,
|
|
72
|
+
mediaDurationUs?: number,
|
|
73
|
+
): readonly DetectedInterval[] {
|
|
74
|
+
return parseDetections(output, {
|
|
75
|
+
duration: new RegExp(String.raw`silence_duration\s*[:=]\s*${NUMBER}`, "u"),
|
|
76
|
+
end: new RegExp(String.raw`silence_end\s*[:=]\s*${NUMBER}`, "u"),
|
|
77
|
+
start: new RegExp(String.raw`silence_start\s*[:=]\s*${NUMBER}`, "u"),
|
|
78
|
+
}, mediaDurationUs);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface MediaProbeSummary {
|
|
82
|
+
readonly durationUs: number;
|
|
83
|
+
readonly hasAudio: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface VisualMediaProbeSummary {
|
|
87
|
+
readonly audioEndUs: number | null;
|
|
88
|
+
readonly audioStartUs: number;
|
|
89
|
+
readonly audioStreamIndex: number | null;
|
|
90
|
+
readonly durationUs: number | null;
|
|
91
|
+
readonly hasAudio: boolean;
|
|
92
|
+
readonly pixelHeight: number;
|
|
93
|
+
readonly pixelWidth: number;
|
|
94
|
+
readonly videoStartUs: number;
|
|
95
|
+
readonly videoStreamIndex: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function signedSecondsToMicroseconds(value: string, label: string): number {
|
|
99
|
+
const seconds = Number(value);
|
|
100
|
+
const microseconds = Math.round(seconds * 1_000_000);
|
|
101
|
+
if (!Number.isFinite(seconds) || !Number.isSafeInteger(microseconds)) {
|
|
102
|
+
throw new CliError("invalid-data", `Invalid ${label} timestamp from FFprobe: ${value}`);
|
|
103
|
+
}
|
|
104
|
+
return microseconds;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function durationTagToMicroseconds(value: unknown, label: string): number | null {
|
|
108
|
+
if (typeof value !== "string") return null;
|
|
109
|
+
const match = /^(?<hours>[0-9]+):(?<minutes>[0-5][0-9]):(?<seconds>[0-5][0-9])(?:\.(?<fraction>[0-9]{1,9}))?$/u.exec(value);
|
|
110
|
+
if (match?.groups === undefined) {
|
|
111
|
+
throw new CliError("invalid-data", `Invalid ${label} duration tag from FFprobe: ${value}`);
|
|
112
|
+
}
|
|
113
|
+
const wholeSeconds = Number(match.groups.hours) * 3_600
|
|
114
|
+
+ Number(match.groups.minutes) * 60
|
|
115
|
+
+ Number(match.groups.seconds);
|
|
116
|
+
const fraction = match.groups.fraction ?? "";
|
|
117
|
+
const fractionalMicroseconds = Math.round(Number(`0.${fraction}`) * 1_000_000);
|
|
118
|
+
const microseconds = wholeSeconds * 1_000_000 + fractionalMicroseconds;
|
|
119
|
+
if (!Number.isSafeInteger(microseconds) || microseconds <= 0) {
|
|
120
|
+
throw new CliError("invalid-data", `Invalid ${label} duration tag from FFprobe: ${value}`);
|
|
121
|
+
}
|
|
122
|
+
return microseconds;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function streamDurationUs(stream: Readonly<Record<string, unknown>>, label: string): number | null {
|
|
126
|
+
if (typeof stream.duration === "string" && stream.duration !== "N/A") {
|
|
127
|
+
const durationUs = secondsToMicroseconds(stream.duration, `${label} duration`);
|
|
128
|
+
if (durationUs <= 0) throw new CliError("invalid-data", `FFprobe reported a non-positive ${label} duration.`);
|
|
129
|
+
return durationUs;
|
|
130
|
+
}
|
|
131
|
+
const tags = stream.tags;
|
|
132
|
+
if (tags === undefined) return null;
|
|
133
|
+
if (typeof tags !== "object" || tags === null || Array.isArray(tags)) {
|
|
134
|
+
throw new CliError("invalid-data", `FFprobe reported invalid ${label} stream tags.`);
|
|
135
|
+
}
|
|
136
|
+
const tagRecord = tags as Readonly<Record<string, unknown>>;
|
|
137
|
+
return durationTagToMicroseconds(tagRecord.DURATION ?? tagRecord.duration, label);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function probeStreamIndex(stream: Readonly<Record<string, unknown>>, label: string): number {
|
|
141
|
+
if (!Number.isSafeInteger(stream.index) || (stream.index as number) < 0) {
|
|
142
|
+
throw new CliError("invalid-data", `FFprobe ${label} stream omits a valid absolute index.`);
|
|
143
|
+
}
|
|
144
|
+
return stream.index as number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isAttachedPicture(stream: Readonly<Record<string, unknown>>): boolean {
|
|
148
|
+
const disposition = stream.disposition;
|
|
149
|
+
if (disposition === undefined) return false;
|
|
150
|
+
if (typeof disposition !== "object" || disposition === null || Array.isArray(disposition)) {
|
|
151
|
+
throw new CliError("invalid-data", "FFprobe video stream has an invalid disposition.");
|
|
152
|
+
}
|
|
153
|
+
const attached = (disposition as Readonly<Record<string, unknown>>).attached_pic;
|
|
154
|
+
if (attached === undefined || attached === false || attached === 0) return false;
|
|
155
|
+
if (attached === true || attached === 1) return true;
|
|
156
|
+
throw new CliError("invalid-data", "FFprobe video stream has an invalid attached-picture disposition.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function selectLowestIndexedStream(
|
|
160
|
+
streams: readonly Readonly<Record<string, unknown>>[],
|
|
161
|
+
type: "audio" | "video",
|
|
162
|
+
): { readonly index: number; readonly stream: Readonly<Record<string, unknown>> } | undefined {
|
|
163
|
+
return streams
|
|
164
|
+
.filter(stream => stream.codec_type === type && (type !== "video" || !isAttachedPicture(stream)))
|
|
165
|
+
.map(stream => ({ index: probeStreamIndex(stream, type), stream }))
|
|
166
|
+
.sort((left, right) => left.index - right.index)[0];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseMediaSummaryJson(stdout: string): MediaProbeSummary {
|
|
170
|
+
let value: unknown;
|
|
171
|
+
try {
|
|
172
|
+
value = JSON.parse(stdout);
|
|
173
|
+
} catch {
|
|
174
|
+
throw new CliError("invalid-data", "FFprobe duration output is not valid JSON.");
|
|
175
|
+
}
|
|
176
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
177
|
+
throw new CliError("invalid-data", "FFprobe duration output must be an object.");
|
|
178
|
+
}
|
|
179
|
+
const format = (value as Readonly<Record<string, unknown>>).format;
|
|
180
|
+
if (typeof format !== "object" || format === null || Array.isArray(format)) {
|
|
181
|
+
throw new CliError("invalid-data", "FFprobe duration output omits format.");
|
|
182
|
+
}
|
|
183
|
+
const duration = (format as Readonly<Record<string, unknown>>).duration;
|
|
184
|
+
if (typeof duration !== "string") {
|
|
185
|
+
throw new CliError("invalid-data", "FFprobe duration output omits duration.");
|
|
186
|
+
}
|
|
187
|
+
const streams = (value as Readonly<Record<string, unknown>>).streams;
|
|
188
|
+
if (streams !== undefined && !Array.isArray(streams)) {
|
|
189
|
+
throw new CliError("invalid-data", "FFprobe duration output has invalid streams.");
|
|
190
|
+
}
|
|
191
|
+
const hasAudio = Array.isArray(streams) && streams.some(stream => (
|
|
192
|
+
typeof stream === "object"
|
|
193
|
+
&& stream !== null
|
|
194
|
+
&& !Array.isArray(stream)
|
|
195
|
+
&& (stream as Readonly<Record<string, unknown>>).codec_type === "audio"
|
|
196
|
+
));
|
|
197
|
+
return { durationUs: secondsToMicroseconds(duration, "duration"), hasAudio };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function probeMediaSummary(
|
|
201
|
+
ffprobe: string,
|
|
202
|
+
runner: ProcessRunner,
|
|
203
|
+
path: string,
|
|
204
|
+
): Promise<MediaProbeSummary> {
|
|
205
|
+
const result = await runner.run([
|
|
206
|
+
ffprobe,
|
|
207
|
+
"-v", "error",
|
|
208
|
+
"-show_entries", "format=duration:stream=codec_type",
|
|
209
|
+
"-of", "json",
|
|
210
|
+
path,
|
|
211
|
+
], { maxOutputBytes: 32_000 });
|
|
212
|
+
if (result.exitCode !== 0) {
|
|
213
|
+
throw new CliError("subprocess", `FFprobe failed for ${path}: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
214
|
+
}
|
|
215
|
+
return parseMediaSummaryJson(result.stdout);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function probeVisualMediaSummary(
|
|
219
|
+
ffprobe: string,
|
|
220
|
+
runner: ProcessRunner,
|
|
221
|
+
path: string,
|
|
222
|
+
): Promise<VisualMediaProbeSummary> {
|
|
223
|
+
const result = await runner.run([
|
|
224
|
+
ffprobe,
|
|
225
|
+
"-v", "error",
|
|
226
|
+
"-show_entries", "format=duration,start_time:stream=index,codec_type,width,height,duration,start_time:stream_disposition=attached_pic:stream_tags=duration",
|
|
227
|
+
"-of", "json",
|
|
228
|
+
path,
|
|
229
|
+
], { maxOutputBytes: 32_000 });
|
|
230
|
+
if (result.exitCode !== 0) {
|
|
231
|
+
throw new CliError("subprocess", `FFprobe failed for ${path}: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
232
|
+
}
|
|
233
|
+
let value: unknown;
|
|
234
|
+
try {
|
|
235
|
+
value = JSON.parse(result.stdout);
|
|
236
|
+
} catch {
|
|
237
|
+
throw new CliError("invalid-data", "FFprobe visual-media output is not valid JSON.");
|
|
238
|
+
}
|
|
239
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
240
|
+
throw new CliError("invalid-data", "FFprobe visual-media output must be an object.");
|
|
241
|
+
}
|
|
242
|
+
const record = value as Readonly<Record<string, unknown>>;
|
|
243
|
+
if (!Array.isArray(record.streams)) {
|
|
244
|
+
throw new CliError("invalid-data", "FFprobe visual-media output omits streams.");
|
|
245
|
+
}
|
|
246
|
+
const streams = record.streams.filter((stream): stream is Readonly<Record<string, unknown>> => (
|
|
247
|
+
typeof stream === "object" && stream !== null && !Array.isArray(stream)
|
|
248
|
+
));
|
|
249
|
+
const selectedVideo = selectLowestIndexedStream(streams, "video");
|
|
250
|
+
if (selectedVideo === undefined) {
|
|
251
|
+
throw new CliError("invalid-data", "Overlay media has no non-attached visual video stream.");
|
|
252
|
+
}
|
|
253
|
+
const video = selectedVideo.stream;
|
|
254
|
+
const selectedAudio = selectLowestIndexedStream(streams, "audio");
|
|
255
|
+
const audio = selectedAudio?.stream;
|
|
256
|
+
const pixelWidth = video?.width;
|
|
257
|
+
const pixelHeight = video?.height;
|
|
258
|
+
if (
|
|
259
|
+
!Number.isSafeInteger(pixelWidth)
|
|
260
|
+
|| !Number.isSafeInteger(pixelHeight)
|
|
261
|
+
|| (pixelWidth as number) <= 0
|
|
262
|
+
|| (pixelHeight as number) <= 0
|
|
263
|
+
|| (pixelWidth as number) > 16_384
|
|
264
|
+
|| (pixelHeight as number) > 16_384
|
|
265
|
+
|| (pixelWidth as number) * (pixelHeight as number) > 134_217_728
|
|
266
|
+
) {
|
|
267
|
+
throw new CliError("invalid-data", "Overlay intrinsic dimensions exceed the 16384-pixel or 128-megapixel safety limit.");
|
|
268
|
+
}
|
|
269
|
+
const format = record.format;
|
|
270
|
+
const formatDuration = typeof format === "object" && format !== null && !Array.isArray(format)
|
|
271
|
+
? (format as Readonly<Record<string, unknown>>).duration
|
|
272
|
+
: undefined;
|
|
273
|
+
const startTime = video?.start_time;
|
|
274
|
+
const formatStartTime = typeof format === "object" && format !== null && !Array.isArray(format)
|
|
275
|
+
? (format as Readonly<Record<string, unknown>>).start_time
|
|
276
|
+
: undefined;
|
|
277
|
+
const hasAudio = selectedAudio !== undefined;
|
|
278
|
+
const formatStartUs = typeof formatStartTime === "string" && formatStartTime !== "N/A"
|
|
279
|
+
? signedSecondsToMicroseconds(formatStartTime, "format start")
|
|
280
|
+
: 0;
|
|
281
|
+
const videoStartUs = typeof startTime === "string" && startTime !== "N/A"
|
|
282
|
+
? signedSecondsToMicroseconds(startTime, "video start") - formatStartUs
|
|
283
|
+
: 0;
|
|
284
|
+
const audioStartUs = typeof audio?.start_time === "string" && audio.start_time !== "N/A"
|
|
285
|
+
? signedSecondsToMicroseconds(audio.start_time, "audio start") - formatStartUs
|
|
286
|
+
: videoStartUs;
|
|
287
|
+
const audioDurationUs = audio === undefined ? null : streamDurationUs(audio, "audio");
|
|
288
|
+
const formatDurationUs = typeof formatDuration === "string" && formatDuration !== "N/A"
|
|
289
|
+
? secondsToMicroseconds(formatDuration, "format duration")
|
|
290
|
+
: null;
|
|
291
|
+
const normalizedFormatEndUs = formatDurationUs !== null && formatDurationUs > 0
|
|
292
|
+
? formatDurationUs
|
|
293
|
+
: null;
|
|
294
|
+
const audioEndUs = !hasAudio
|
|
295
|
+
? null
|
|
296
|
+
: audioDurationUs === null
|
|
297
|
+
? normalizedFormatEndUs
|
|
298
|
+
: audioStartUs + audioDurationUs;
|
|
299
|
+
if (audioEndUs !== null && (!Number.isSafeInteger(audioEndUs) || audioEndUs <= audioStartUs)) {
|
|
300
|
+
throw new CliError("invalid-data", "FFprobe reported an invalid audio stream end timestamp.");
|
|
301
|
+
}
|
|
302
|
+
const explicitVideoDurationUs = streamDurationUs(video, "video");
|
|
303
|
+
const formatBoundedVideoDurationUs = normalizedFormatEndUs === null
|
|
304
|
+
? null
|
|
305
|
+
: normalizedFormatEndUs - videoStartUs;
|
|
306
|
+
const durationUs = explicitVideoDurationUs
|
|
307
|
+
?? (formatBoundedVideoDurationUs !== null && Number.isSafeInteger(formatBoundedVideoDurationUs) && formatBoundedVideoDurationUs > 0
|
|
308
|
+
? formatBoundedVideoDurationUs
|
|
309
|
+
: null);
|
|
310
|
+
return {
|
|
311
|
+
audioEndUs,
|
|
312
|
+
audioStartUs,
|
|
313
|
+
audioStreamIndex: selectedAudio?.index ?? null,
|
|
314
|
+
durationUs,
|
|
315
|
+
hasAudio,
|
|
316
|
+
pixelHeight: pixelHeight as number,
|
|
317
|
+
pixelWidth: pixelWidth as number,
|
|
318
|
+
videoStartUs,
|
|
319
|
+
videoStreamIndex: selectedVideo.index,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function probeMediaDuration(
|
|
324
|
+
ffprobe: string,
|
|
325
|
+
runner: ProcessRunner,
|
|
326
|
+
path: string,
|
|
327
|
+
): Promise<number> {
|
|
328
|
+
return (await probeMediaSummary(ffprobe, runner, path)).durationUs;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface MediaAnalyzerOptions {
|
|
332
|
+
readonly ffmpeg: string;
|
|
333
|
+
readonly ffprobe: string;
|
|
334
|
+
readonly runner: ProcessRunner;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export class FfmpegInactivityAnalyzer {
|
|
338
|
+
readonly #ffmpeg: string;
|
|
339
|
+
readonly #ffprobe: string;
|
|
340
|
+
readonly #runner: ProcessRunner;
|
|
341
|
+
|
|
342
|
+
constructor(options: MediaAnalyzerOptions) {
|
|
343
|
+
this.#ffmpeg = options.ffmpeg;
|
|
344
|
+
this.#ffprobe = options.ffprobe;
|
|
345
|
+
this.#runner = options.runner;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async duration(path: string): Promise<number> {
|
|
349
|
+
return await probeMediaDuration(this.#ffprobe, this.#runner, path);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async freeze(
|
|
353
|
+
path: string,
|
|
354
|
+
streamIndex: number,
|
|
355
|
+
minDurationUs: number,
|
|
356
|
+
motionThreshold: number,
|
|
357
|
+
sourceOffsetUs = 0,
|
|
358
|
+
): Promise<readonly DetectedInterval[]> {
|
|
359
|
+
const durationUs = await this.duration(path);
|
|
360
|
+
const result = await this.#runner.run([
|
|
361
|
+
this.#ffmpeg,
|
|
362
|
+
"-hide_banner",
|
|
363
|
+
"-nostdin",
|
|
364
|
+
"-i", path,
|
|
365
|
+
"-map", `0:${streamIndex}`,
|
|
366
|
+
"-vf", `freezedetect=n=${motionThreshold}:d=${minDurationUs / 1_000_000},metadata=mode=print`,
|
|
367
|
+
"-an",
|
|
368
|
+
"-f", "null",
|
|
369
|
+
"-",
|
|
370
|
+
], { maxOutputBytes: 1_000_000 });
|
|
371
|
+
if (result.exitCode !== 0) {
|
|
372
|
+
throw new CliError("subprocess", `FFmpeg freeze analysis failed for ${path}: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
373
|
+
}
|
|
374
|
+
return parseFreezeDetectOutput(`${result.stdout}\n${result.stderr}`, durationUs).map(({ startUs, endUs }) => ({
|
|
375
|
+
endUs: endUs + sourceOffsetUs,
|
|
376
|
+
startUs: startUs + sourceOffsetUs,
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async silence(
|
|
381
|
+
path: string,
|
|
382
|
+
streamIndex: number,
|
|
383
|
+
minDurationUs: number,
|
|
384
|
+
sourceOffsetUs = 0,
|
|
385
|
+
): Promise<readonly DetectedInterval[]> {
|
|
386
|
+
const durationUs = await this.duration(path);
|
|
387
|
+
const result = await this.#runner.run([
|
|
388
|
+
this.#ffmpeg,
|
|
389
|
+
"-hide_banner",
|
|
390
|
+
"-nostdin",
|
|
391
|
+
"-i", path,
|
|
392
|
+
"-map", `0:${streamIndex}`,
|
|
393
|
+
"-af", `silencedetect=noise=-45dB:d=${minDurationUs / 1_000_000},ametadata=mode=print`,
|
|
394
|
+
"-vn",
|
|
395
|
+
"-f", "null",
|
|
396
|
+
"-",
|
|
397
|
+
], { maxOutputBytes: 1_000_000 });
|
|
398
|
+
if (result.exitCode !== 0) {
|
|
399
|
+
throw new CliError("subprocess", `FFmpeg silence analysis failed for ${path}: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
400
|
+
}
|
|
401
|
+
return parseSilenceDetectOutput(`${result.stdout}\n${result.stderr}`, durationUs).map(({ startUs, endUs }) => ({
|
|
402
|
+
endUs: endUs + sourceOffsetUs,
|
|
403
|
+
startUs: startUs + sourceOffsetUs,
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
}
|