cozyclay 2.1.1 → 3.0.0
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/CHANGELOG.md +60 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +150 -2624
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +8 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
package/tools/ardy/footage.mjs
DELETED
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* footage.mjs — the bridge's footage-fetch side: platform pages (YouTube,
|
|
3
|
-
* Vimeo, …) serve a player, not a file, and block cross-origin reads, so the
|
|
4
|
-
* BROWSER can never ingest them. This process can: yt-dlp downloads the
|
|
5
|
-
* video stream, ffmpeg locks it to a constant rate (platform video is often
|
|
6
|
-
* VFR — frame numbers would drift against seconds) no higher than extraction
|
|
7
|
-
* can use, and the result is served back over loopback where the ordinary
|
|
8
|
-
* ingest takes over.
|
|
9
|
-
*
|
|
10
|
-
* Same safety posture as motion delivery: served paths come only from an
|
|
11
|
-
* allowlist this process populated, a client disconnect kills the child
|
|
12
|
-
* process group, and every failure is a NAMED reason the UI can act on.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
16
|
-
import { randomBytes } from "node:crypto";
|
|
17
|
-
import { createReadStream, existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
18
|
-
import { dirname, join, sep } from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { killGroup, track } from "./runners/proc.mjs";
|
|
21
|
-
import { createPrivateArtifactDir, removePrivateArtifactDir } from "./artifacts.mjs";
|
|
22
|
-
|
|
23
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
|
-
const FOOTAGE_DIR = join(HERE, "out", "footage");
|
|
25
|
-
// Normalisation locks the clip to a CONSTANT rate and keeps the source's own
|
|
26
|
-
// rate up to the extraction ceiling below: platform video is often VFR (frame
|
|
27
|
-
// numbers drift against seconds), but resampling 30 → 24 would drop every 5th
|
|
28
|
-
// frame and turn smooth motion into a 4-step-1-jump judder that extraction
|
|
29
|
-
// reads as real velocity. The take is retimed to the 24 fps timeline AFTER
|
|
30
|
-
// extraction, in rotation space.
|
|
31
|
-
const FOOTAGE_FPS_FALLBACK = 30;
|
|
32
|
-
const FOOTAGE_FPS_MIN = 10;
|
|
33
|
-
const FOOTAGE_FPS_MAX = 60;
|
|
34
|
-
// The ceiling extraction is allowed to see. SAM-3D-Body solves every frame
|
|
35
|
-
// INDEPENDENTLY and errs a little each time, so twice the rate feeds twice the
|
|
36
|
-
// estimation noise per second while the smoothing windows behind it are
|
|
37
|
-
// counted in FRAMES and therefore span half as much time. Measured on two
|
|
38
|
-
// takes of the same session: a 60 fps / 1632-frame extract trembled 3.36 and
|
|
39
|
-
// 3.54 cm at the feet, a 30 fps / 270-frame one 0.60 and 0.72 cm — five times
|
|
40
|
-
// the wobble, plus double the GPU cost, for frames a 24 fps timeline can
|
|
41
|
-
// never show. Both intake paths clamp here; this is the single edit that
|
|
42
|
-
// moves the ceiling.
|
|
43
|
-
export const EXTRACT_FPS_MAX = 30;
|
|
44
|
-
const FOOTAGE_MAX_S = 900; // 15 min: previs reference, not an archive mirror
|
|
45
|
-
const FOOTAGE_MAX_HEIGHT = 1080;
|
|
46
|
-
const FOOTAGE_ALLOWLIST_MAX = 16; // newest downloads only; evicted ids 404
|
|
47
|
-
const FOOTAGE_ID = /^[0-9]+-[0-9a-f]{6}$/;
|
|
48
|
-
// Prefer the Homebrew binaries over whatever shadows them on PATH: a stale
|
|
49
|
-
// pip-installed yt-dlp is the classic cause of "The page needs to be
|
|
50
|
-
// reloaded" refusals from YouTube, and it sorts FIRST in a default PATH.
|
|
51
|
-
function firstExisting(override, candidates, fallback) {
|
|
52
|
-
if (override) return override;
|
|
53
|
-
for (const candidate of candidates) if (existsSync(candidate)) return candidate;
|
|
54
|
-
return fallback;
|
|
55
|
-
}
|
|
56
|
-
const YTDLP = firstExisting(process.env.CCLAY_YTDLP, ["/opt/homebrew/bin/yt-dlp", "/usr/local/bin/yt-dlp"], "yt-dlp");
|
|
57
|
-
const FFMPEG = firstExisting(process.env.CCLAY_FFMPEG, ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg"], "ffmpeg");
|
|
58
|
-
const FFPROBE = firstExisting(process.env.CCLAY_FFPROBE, ["/opt/homebrew/bin/ffprobe", "/usr/local/bin/ffprobe"], "ffprobe");
|
|
59
|
-
// The default (web) client serves full resolution but hits YouTube's
|
|
60
|
-
// "confirm you're not a bot" wall on music/mix content; the android client
|
|
61
|
-
// always passes but is capped around 360p. So every yt-dlp call goes web
|
|
62
|
-
// first and falls back to android ONLY on that specific refusal — quality
|
|
63
|
-
// when possible, availability when not.
|
|
64
|
-
const YTDLP_BASE = ["--no-playlist", "--no-warnings"];
|
|
65
|
-
const YTDLP_BOTFIX = ["--extractor-args", "youtube:player_client=android"];
|
|
66
|
-
// Two ways YouTube refuses the web client: the bot wall up front, or a 403
|
|
67
|
-
// on the media URLs partway through a download. Both mean "switch client".
|
|
68
|
-
const WEB_CLIENT_REFUSED = /confirm you.re not a bot|HTTP Error 403/i;
|
|
69
|
-
|
|
70
|
-
const footageAllowlist = new Map(); // id -> absolute mp4 path under FOOTAGE_DIR
|
|
71
|
-
|
|
72
|
-
/** `[download] 42.3% of ...` -> 0.423; null for any other line. */
|
|
73
|
-
export function parseDownloadRatio(line) {
|
|
74
|
-
const match = /\[download\]\s+([\d.]+)%/.exec(line);
|
|
75
|
-
if (!match) return null;
|
|
76
|
-
const percent = Number(match[1]);
|
|
77
|
-
if (!Number.isFinite(percent)) return null;
|
|
78
|
-
return Math.min(1, Math.max(0, percent / 100));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** ffmpeg's `... time=00:00:12.34 ...` against the clip length -> 0..1. */
|
|
82
|
-
export function parseEncodeRatio(line, durationS) {
|
|
83
|
-
if (!(durationS > 0)) return null;
|
|
84
|
-
const match = /time=(\d+):(\d\d):(\d\d(?:\.\d+)?)/.exec(line);
|
|
85
|
-
if (!match) return null;
|
|
86
|
-
const seconds = Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]);
|
|
87
|
-
if (!Number.isFinite(seconds)) return null;
|
|
88
|
-
return Math.min(1, Math.max(0, seconds / durationS));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** yt-dlp's probe line `"123.45\t29.97\tSome title"` -> { durationS, fps,
|
|
92
|
-
* title }. A missing/absurd rate falls back to 30 and is clamped to a sane
|
|
93
|
-
* band — the ffmpeg pass hard-locks whatever rate is returned here. */
|
|
94
|
-
export function parseProbeLine(line) {
|
|
95
|
-
const parts = line.split("\t");
|
|
96
|
-
if (parts.length < 3) return null;
|
|
97
|
-
const durationS = Number(parts[0]);
|
|
98
|
-
const rawFps = Number(parts[1]);
|
|
99
|
-
const title = parts.slice(2).join("\t").trim();
|
|
100
|
-
if (!Number.isFinite(durationS) || durationS <= 0) return null;
|
|
101
|
-
const fps = Number.isFinite(rawFps) && rawFps > 0
|
|
102
|
-
? Math.min(FOOTAGE_FPS_MAX, Math.max(FOOTAGE_FPS_MIN, Math.round(rawFps)))
|
|
103
|
-
: FOOTAGE_FPS_FALLBACK;
|
|
104
|
-
return { durationS, fps, title };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* The rate a source of `sourceFps` may be fed to extraction at. Never
|
|
109
|
-
* upsamples — 24 stays 24, 25 stays 25 — because inventing frames invents
|
|
110
|
-
* motion. Above the ceiling it prefers an INTEGER divisor of the source
|
|
111
|
-
* (60→30, 50→25, 48→24, 120→30): dropping every n-th frame keeps the
|
|
112
|
-
* surviving frames exactly as they were shot, while landing on a foreign
|
|
113
|
-
* rate has to duplicate frames at uneven intervals and reads as judder. A
|
|
114
|
-
* rate with no divisor that lands in range (35, 45, …) takes the ceiling
|
|
115
|
-
* itself, and an unreadable rate takes it too — the ceiling is the safe
|
|
116
|
-
* answer, never the source's word.
|
|
117
|
-
*/
|
|
118
|
-
export function capExtractFps(sourceFps) {
|
|
119
|
-
const fps = Math.round(Number(sourceFps));
|
|
120
|
-
if (!Number.isFinite(fps) || fps <= 0) return EXTRACT_FPS_MAX;
|
|
121
|
-
if (fps <= EXTRACT_FPS_MAX) return fps;
|
|
122
|
-
const divisor = Math.ceil(fps / EXTRACT_FPS_MAX);
|
|
123
|
-
return fps % divisor === 0 ? fps / divisor : EXTRACT_FPS_MAX;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* The one ffmpeg pass both intake paths use: a CONSTANT `fps`, no audio, a
|
|
128
|
-
* faststart mp4. Nothing here touches presentation timestamps as a whole —
|
|
129
|
-
* `fps=N` only drops or repeats frames, so the clip comes out the same
|
|
130
|
-
* length and the same speed it went in.
|
|
131
|
-
*/
|
|
132
|
-
export function normalizeArgs(inputPath, outputPath, fps) {
|
|
133
|
-
return [
|
|
134
|
-
"-y", "-i", inputPath,
|
|
135
|
-
"-vf", `fps=${fps}`,
|
|
136
|
-
"-an",
|
|
137
|
-
"-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
|
|
138
|
-
"-movflags", "+faststart",
|
|
139
|
-
outputPath,
|
|
140
|
-
];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** ffprobe's `key=num/den` block -> { fps, durationS }, either field null when
|
|
144
|
-
* the file would not say. avg_frame_rate is frames ÷ length over the whole
|
|
145
|
-
* stream, which is what extraction actually meets; r_frame_rate is only the
|
|
146
|
-
* container's base tick and reads as 90000 on some VFR muxes, so it is the
|
|
147
|
-
* fallback, not the answer. */
|
|
148
|
-
export function parseProbeStreamInfo(text) {
|
|
149
|
-
const source = typeof text === "string" ? text : "";
|
|
150
|
-
const ratio = (key) => {
|
|
151
|
-
const match = new RegExp(`^${key}=([\\d.]+)(?:/([\\d.]+))?$`, "m").exec(source);
|
|
152
|
-
if (!match) return null;
|
|
153
|
-
const value = match[2] === undefined ? Number(match[1]) : Number(match[1]) / Number(match[2]);
|
|
154
|
-
return Number.isFinite(value) && value > 0 ? value : null;
|
|
155
|
-
};
|
|
156
|
-
return { fps: ratio("avg_frame_rate") ?? ratio("r_frame_rate"), durationS: ratio("duration") };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function runCollect(command, args, { children, timeoutMs, onLine }) {
|
|
160
|
-
return new Promise((resolvePromise, reject) => {
|
|
161
|
-
const child = spawn(command, args, { detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
162
|
-
children.add(child);
|
|
163
|
-
track(child);
|
|
164
|
-
let out = "";
|
|
165
|
-
let err = "";
|
|
166
|
-
let buffered = "";
|
|
167
|
-
const timer = timeoutMs
|
|
168
|
-
? setTimeout(() => {
|
|
169
|
-
killGroup(child);
|
|
170
|
-
reject(new Error("footage-timeout"));
|
|
171
|
-
}, timeoutMs)
|
|
172
|
-
: null;
|
|
173
|
-
const feed = (text) => {
|
|
174
|
-
buffered += text;
|
|
175
|
-
// yt-dlp redraws progress with \r, ffmpeg logs stats to stderr the
|
|
176
|
-
// same way; both count as line breaks for the progress parsers.
|
|
177
|
-
const lines = buffered.split(/[\r\n]/);
|
|
178
|
-
buffered = lines.pop() ?? "";
|
|
179
|
-
for (const line of lines) if (line) onLine?.(line);
|
|
180
|
-
};
|
|
181
|
-
child.stdout.on("data", (chunk) => {
|
|
182
|
-
out += chunk;
|
|
183
|
-
feed(String(chunk));
|
|
184
|
-
});
|
|
185
|
-
child.stderr.on("data", (chunk) => {
|
|
186
|
-
err += chunk;
|
|
187
|
-
feed(String(chunk));
|
|
188
|
-
});
|
|
189
|
-
child.on("error", (error) => {
|
|
190
|
-
if (timer) clearTimeout(timer);
|
|
191
|
-
children.delete(child);
|
|
192
|
-
reject(new Error(`spawn ${command}: ${error.message}`));
|
|
193
|
-
});
|
|
194
|
-
child.on("close", (code) => {
|
|
195
|
-
if (timer) clearTimeout(timer);
|
|
196
|
-
children.delete(child);
|
|
197
|
-
if (buffered) onLine?.(buffered);
|
|
198
|
-
if (code === 0) resolvePromise({ out, err });
|
|
199
|
-
else reject(new Error(err.split("\n").filter(Boolean).pop() || `${command} exited ${code}`));
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** yt-dlp with the web client, retried via the android client only when
|
|
205
|
-
* YouTube's bot wall is the stated reason for the failure. */
|
|
206
|
-
async function runYtdlp(args, options) {
|
|
207
|
-
try {
|
|
208
|
-
return await runCollect(YTDLP, [...YTDLP_BASE, ...args], options);
|
|
209
|
-
} catch (error) {
|
|
210
|
-
if (!WEB_CLIENT_REFUSED.test(error?.message ?? "")) throw error;
|
|
211
|
-
console.error("[bridge] youtube refused the web client; retrying with the android client (≤360p)");
|
|
212
|
-
return runCollect(YTDLP, [...YTDLP_BASE, ...YTDLP_BOTFIX, ...args], options);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function cleanup(paths) {
|
|
217
|
-
for (const path of paths) {
|
|
218
|
-
try {
|
|
219
|
-
rmSync(path, { force: true });
|
|
220
|
-
} catch {
|
|
221
|
-
/* best effort */
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Bring a local video down to EXTRACT_FPS_MAX, if it is over it. Resolves to
|
|
228
|
-
* `{ path, fps, capped }`: `path` is the INPUT untouched whenever the source
|
|
229
|
-
* is already at or under the ceiling, so a bridge download — normalized at
|
|
230
|
-
* this same ceiling on the way in — is never re-encoded a second time for
|
|
231
|
-
* nothing. `fps` is what the file at `path` now runs at, i.e. what extraction
|
|
232
|
-
* will see; it is null only when ffprobe would not name a rate, in which case
|
|
233
|
-
* the clip goes through as it is rather than be refused over a probe hiccup.
|
|
234
|
-
* The ffmpeg failure itself throws, for the caller to name.
|
|
235
|
-
*/
|
|
236
|
-
export async function conformToExtractFps(inputPath, outputPath, { children = new Set(), timeoutMs = 600000, onCap, onProgress } = {}) {
|
|
237
|
-
let probed = { fps: null, durationS: null };
|
|
238
|
-
try {
|
|
239
|
-
const { out } = await runCollect(
|
|
240
|
-
FFPROBE,
|
|
241
|
-
[
|
|
242
|
-
"-v", "error", "-select_streams", "v:0",
|
|
243
|
-
"-show_entries", "stream=avg_frame_rate,r_frame_rate",
|
|
244
|
-
"-show_entries", "format=duration",
|
|
245
|
-
"-of", "default=noprint_wrappers=1",
|
|
246
|
-
inputPath,
|
|
247
|
-
],
|
|
248
|
-
{ children, timeoutMs: 60000 }
|
|
249
|
-
);
|
|
250
|
-
probed = parseProbeStreamInfo(out);
|
|
251
|
-
} catch (err) {
|
|
252
|
-
console.error(`[bridge] fps probe failed for ${inputPath}: ${err.message}`);
|
|
253
|
-
}
|
|
254
|
-
if (probed.fps === null) return { path: inputPath, fps: null, capped: false };
|
|
255
|
-
const targetFps = capExtractFps(probed.fps);
|
|
256
|
-
if (targetFps >= Math.round(probed.fps)) return { path: inputPath, fps: targetFps, capped: false };
|
|
257
|
-
onCap?.(targetFps);
|
|
258
|
-
await runCollect(FFMPEG, normalizeArgs(inputPath, outputPath, targetFps), {
|
|
259
|
-
children,
|
|
260
|
-
timeoutMs,
|
|
261
|
-
onLine: (line) => {
|
|
262
|
-
const ratio = parseEncodeRatio(line, probed.durationS ?? 0);
|
|
263
|
-
if (ratio !== null) onProgress?.(ratio);
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
return { path: outputPath, fps: targetFps, capped: true };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* POST /ardy/footage — body {url}, answer ndjson:
|
|
271
|
-
* {event:"status", message} stage announcements
|
|
272
|
-
* {event:"progress", stage, ratio} download / normalize movement
|
|
273
|
-
* {event:"done", url, title, durationS, fps}
|
|
274
|
-
* {event:"error", message} message is a NAMED reason
|
|
275
|
-
*/
|
|
276
|
-
export async function handleFootage(req, res, readBody) {
|
|
277
|
-
let body;
|
|
278
|
-
try {
|
|
279
|
-
body = JSON.parse(await readBody(req));
|
|
280
|
-
} catch (err) {
|
|
281
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
282
|
-
res.end(`${JSON.stringify({ ok: false, reason: err.message })}\n`);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const url = typeof body?.url === "string" ? body.url.trim() : "";
|
|
286
|
-
if (!/^https?:\/\/\S{1,2000}$/.test(url)) {
|
|
287
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
288
|
-
res.end(`${JSON.stringify({ ok: false, reason: "footage-url-invalid" })}\n`);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
res.writeHead(200, { "Content-Type": "application/x-ndjson", "Cache-Control": "no-store" });
|
|
293
|
-
const send = (obj) => {
|
|
294
|
-
if (res.writableEnded) return;
|
|
295
|
-
try {
|
|
296
|
-
res.write(`${JSON.stringify(obj)}\n`);
|
|
297
|
-
} catch {
|
|
298
|
-
/* socket gone */
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
let cleanupArtifacts = () => {};
|
|
302
|
-
const fail = (message) => {
|
|
303
|
-
send({ event: "error", message });
|
|
304
|
-
res.end();
|
|
305
|
-
cleanupArtifacts();
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const children = new Set();
|
|
309
|
-
res.on("close", () => {
|
|
310
|
-
if (!res.writableEnded) {
|
|
311
|
-
console.error(`[bridge] client disconnected mid-footage; killing ${children.size} child group(s)`);
|
|
312
|
-
for (const child of children) killGroup(child);
|
|
313
|
-
children.clear();
|
|
314
|
-
cleanupArtifacts();
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const stamp = `${Date.now()}-${randomBytes(3).toString("hex")}`;
|
|
319
|
-
const artifactDir = createPrivateArtifactDir(FOOTAGE_DIR, "footage");
|
|
320
|
-
const rawPrefix = "raw";
|
|
321
|
-
const outPath = join(artifactDir, "normalized.mp4");
|
|
322
|
-
cleanupArtifacts = () => removePrivateArtifactDir(artifactDir);
|
|
323
|
-
|
|
324
|
-
// 1) probe: refuse lives (no duration) and long videos before any bytes.
|
|
325
|
-
send({ event: "status", message: "probing" });
|
|
326
|
-
let probe;
|
|
327
|
-
try {
|
|
328
|
-
const { out } = await runYtdlp(
|
|
329
|
-
["--print", "%(duration)s\t%(fps)s\t%(title)s", url],
|
|
330
|
-
{ children, timeoutMs: 60000 }
|
|
331
|
-
);
|
|
332
|
-
probe = parseProbeLine(out.split("\n").find((line) => line.includes("\t")) ?? "");
|
|
333
|
-
} catch (err) {
|
|
334
|
-
console.error(`[bridge] footage probe failed: ${err.message}`);
|
|
335
|
-
fail("footage-probe-failed");
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
if (!probe) {
|
|
339
|
-
fail("footage-live-unsupported");
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (probe.durationS > FOOTAGE_MAX_S) {
|
|
343
|
-
fail("footage-too-long");
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// 2) download the video stream only (audio never reaches the ingest).
|
|
348
|
-
send({ event: "status", message: "downloading" });
|
|
349
|
-
try {
|
|
350
|
-
await runYtdlp(
|
|
351
|
-
[
|
|
352
|
-
"--newline",
|
|
353
|
-
"-f", `bv*[height<=${FOOTAGE_MAX_HEIGHT}]/b[height<=${FOOTAGE_MAX_HEIGHT}]/b`,
|
|
354
|
-
"-o", join(artifactDir, `${rawPrefix}.%(ext)s`),
|
|
355
|
-
url,
|
|
356
|
-
],
|
|
357
|
-
{
|
|
358
|
-
children,
|
|
359
|
-
timeoutMs: 10 * 60 * 1000,
|
|
360
|
-
onLine: (line) => {
|
|
361
|
-
const ratio = parseDownloadRatio(line);
|
|
362
|
-
if (ratio !== null) send({ event: "progress", stage: "download", ratio });
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
);
|
|
366
|
-
} catch (err) {
|
|
367
|
-
console.error(`[bridge] footage download failed: ${err.message}`);
|
|
368
|
-
cleanupArtifacts();
|
|
369
|
-
fail("footage-download-failed");
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
const rawName = readdirSync(artifactDir).find((f) => f.startsWith(rawPrefix));
|
|
373
|
-
if (!rawName) {
|
|
374
|
-
fail("footage-download-failed");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const rawPath = join(artifactDir, rawName);
|
|
378
|
-
|
|
379
|
-
// 3) lock to a CONSTANT rate at the source's own fps, capped at the rate
|
|
380
|
-
// extraction can use: VFR platform video would otherwise make "frame 385"
|
|
381
|
-
// a different instant than the timeline believes, and resampling to a
|
|
382
|
-
// foreign rate would judder the motion. Everything downstream — the done
|
|
383
|
-
// event below, the browser probe, the retime — is told THIS number, so the
|
|
384
|
-
// clip's declared rate is the rate SAM will actually meet.
|
|
385
|
-
const targetFps = capExtractFps(probe.fps);
|
|
386
|
-
send({ event: "status", message: "normalizing" });
|
|
387
|
-
try {
|
|
388
|
-
await runCollect(
|
|
389
|
-
FFMPEG,
|
|
390
|
-
normalizeArgs(rawPath, outPath, targetFps),
|
|
391
|
-
{
|
|
392
|
-
children,
|
|
393
|
-
timeoutMs: 10 * 60 * 1000,
|
|
394
|
-
onLine: (line) => {
|
|
395
|
-
const ratio = parseEncodeRatio(line, probe.durationS);
|
|
396
|
-
if (ratio !== null) send({ event: "progress", stage: "normalize", ratio });
|
|
397
|
-
},
|
|
398
|
-
}
|
|
399
|
-
);
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error(`[bridge] footage normalize failed: ${err.message}`);
|
|
402
|
-
cleanupArtifacts();
|
|
403
|
-
fail("footage-normalize-failed");
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
cleanup([rawPath]);
|
|
407
|
-
|
|
408
|
-
footageAllowlist.set(stamp, outPath);
|
|
409
|
-
if (footageAllowlist.size > FOOTAGE_ALLOWLIST_MAX) {
|
|
410
|
-
const oldest = footageAllowlist.keys().next().value;
|
|
411
|
-
removePrivateArtifactDir(dirname(footageAllowlist.get(oldest)));
|
|
412
|
-
footageAllowlist.delete(oldest);
|
|
413
|
-
}
|
|
414
|
-
send({
|
|
415
|
-
event: "done",
|
|
416
|
-
url: `/ardy/footage/${stamp}.mp4`,
|
|
417
|
-
footage: stamp,
|
|
418
|
-
title: probe.title,
|
|
419
|
-
durationS: probe.durationS,
|
|
420
|
-
fps: targetFps,
|
|
421
|
-
});
|
|
422
|
-
res.end();
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** Absolute path for an allowlisted footage id, or null — the extract route
|
|
426
|
-
* reuses a bridge-downloaded clip without a second trip through the browser. */
|
|
427
|
-
export function footagePath(id) {
|
|
428
|
-
if (typeof id !== "string" || !FOOTAGE_ID.test(id)) return null;
|
|
429
|
-
return footageAllowlist.get(id) ?? null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** GET /ardy/footage/<id>.mp4 — allowlist only, exactly like motion delivery. */
|
|
433
|
-
export function serveFootage(req, res, pathname) {
|
|
434
|
-
const sendJson = (status, obj) => {
|
|
435
|
-
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
436
|
-
res.end(`${JSON.stringify(obj)}\n`);
|
|
437
|
-
};
|
|
438
|
-
const match = /^\/ardy\/footage\/([^/]+)\.mp4$/.exec(pathname);
|
|
439
|
-
if (!match || !FOOTAGE_ID.test(match[1]) || !footageAllowlist.has(match[1])) {
|
|
440
|
-
sendJson(404, { ok: false, reason: `unknown or expired footage "${match?.[1] ?? pathname}"` });
|
|
441
|
-
return 404;
|
|
442
|
-
}
|
|
443
|
-
const absPath = footageAllowlist.get(match[1]);
|
|
444
|
-
if (!absPath.startsWith(`${FOOTAGE_DIR}${sep}`)) {
|
|
445
|
-
sendJson(404, { ok: false, reason: "footage path is outside the footage directory" });
|
|
446
|
-
return 404;
|
|
447
|
-
}
|
|
448
|
-
let size;
|
|
449
|
-
try {
|
|
450
|
-
size = statSync(absPath).size;
|
|
451
|
-
} catch {
|
|
452
|
-
sendJson(404, { ok: false, reason: "footage is no longer on disk" });
|
|
453
|
-
return 404;
|
|
454
|
-
}
|
|
455
|
-
res.writeHead(200, {
|
|
456
|
-
"Content-Type": "video/mp4",
|
|
457
|
-
"Content-Length": size,
|
|
458
|
-
"Cache-Control": "no-store",
|
|
459
|
-
});
|
|
460
|
-
createReadStream(absPath)
|
|
461
|
-
.on("error", (err) => {
|
|
462
|
-
console.error(`[bridge] error streaming ${absPath}: ${err.message}`);
|
|
463
|
-
res.destroy();
|
|
464
|
-
})
|
|
465
|
-
.pipe(res);
|
|
466
|
-
return 200;
|
|
467
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/** Root-only floor safety for an accepted GVHMR descent correction.
|
|
2
|
-
* Measures both shipped character skins with the same playback transforms
|
|
3
|
-
* as Studio. It never runs AutoPhysics, writes IK keys or changes rotations.
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
|
|
8
|
-
import { applyMotionFrame } from "../../src/ardy/playback.js";
|
|
9
|
-
import { createSurfaceSampler } from "../../src/ardy/physics-surface.js";
|
|
10
|
-
|
|
11
|
-
const SITES = [
|
|
12
|
-
{ id: "leftFoot", bone: "LeftFoot", kind: "foot", match: /Left(Foot|Toe)/ },
|
|
13
|
-
{ id: "rightFoot", bone: "RightFoot", kind: "foot", match: /Right(Foot|Toe)/ },
|
|
14
|
-
{ id: "leftHand", bone: "LeftHand", kind: "hand", match: /LeftHand/ },
|
|
15
|
-
{ id: "rightHand", bone: "RightHand", kind: "hand", match: /RightHand/ },
|
|
16
|
-
];
|
|
17
|
-
function shiftFrame(motion, f, delta) {
|
|
18
|
-
motion.rootPos[f * 3 + 1] += delta;
|
|
19
|
-
for (let j = 0; j < 27; j++) motion.posedJoints[(f * 27 + j) * 3 + 1] += delta;
|
|
20
|
-
}
|
|
21
|
-
function loadRig(model) {
|
|
22
|
-
const path = ["public", "dist"].map(dir => fileURLToPath(new URL(`../../${dir}/models/${model}.fbx`, import.meta.url))).find(existsSync);
|
|
23
|
-
if (!path) throw new Error(`extract-trajectory-floor-model-missing: ${model}`);
|
|
24
|
-
const bytes = readFileSync(path);
|
|
25
|
-
const rig = new FBXLoader().parse(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), "");
|
|
26
|
-
rig.scale.setScalar(.01); rig.updateMatrixWorld(true);
|
|
27
|
-
return rig;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function guardTrajectoryFloor(motion, events = []) {
|
|
31
|
-
if (!events.length) return { motion, diagnostics: { status: "not-needed", changedFrames: 0 } };
|
|
32
|
-
const began = performance.now();
|
|
33
|
-
// Export/retarget grounding uses skeletal foot points, not skin thickness.
|
|
34
|
-
// Once this pass owns a corrected take, protect its whole surface timeline
|
|
35
|
-
// (including the initial standing frames), not only the final descent.
|
|
36
|
-
const start = 0;
|
|
37
|
-
const descentStart = Math.max(0, Math.min(...events.map(e => e.start)));
|
|
38
|
-
const lifts = new Float64Array(motion.frames);
|
|
39
|
-
const models = ["x-bot-tpose", "y-bot-tpose"];
|
|
40
|
-
const measurements = [];
|
|
41
|
-
for (const model of models) {
|
|
42
|
-
const rig = loadRig(model), sample = createSurfaceSampler(rig, SITES);
|
|
43
|
-
applyMotionFrame(rig, motion, start);
|
|
44
|
-
const y = sample().pelvis.position.y;
|
|
45
|
-
const probe = { ...motion, rootPos: motion.rootPos.slice(), posedJoints: motion.posedJoints.slice() };
|
|
46
|
-
shiftFrame(probe, start, 1); applyMotionFrame(rig, probe, start);
|
|
47
|
-
const scale = sample().pelvis.position.y - y;
|
|
48
|
-
if (!(scale > .5 && scale < 2)) throw new Error("extract-trajectory-floor-invalid-scale");
|
|
49
|
-
const floor = new Float64Array(motion.frames);
|
|
50
|
-
for (let f = start; f < motion.frames; f++) {
|
|
51
|
-
applyMotionFrame(rig, motion, f);
|
|
52
|
-
floor[f] = Math.min(...Object.values(sample()).map(p => p.floor));
|
|
53
|
-
lifts[f] = Math.max(lifts[f], (0.002 - floor[f]) / scale, 0);
|
|
54
|
-
}
|
|
55
|
-
measurements.push({ model, scale, floor });
|
|
56
|
-
// These are private measurement rigs, not cached live user characters.
|
|
57
|
-
rig.traverse(o => { o.geometry?.dispose(); if (Array.isArray(o.material)) o.material.forEach(m => m.dispose()); else o.material?.dispose(); });
|
|
58
|
-
}
|
|
59
|
-
// A camera-scaled endpoint is not yet calibrated to the target skin.
|
|
60
|
-
// Adjust its landing datum from measured contact clearance, distributing
|
|
61
|
-
// that change over the observed descent instead of abruptly lifting the
|
|
62
|
-
// body at impact. Only raise an endpoint that penetrates; never assume a
|
|
63
|
-
// stationary landing on an elevated object must be lowered to the floor.
|
|
64
|
-
const datum = new Float64Array(motion.frames);
|
|
65
|
-
const median = values => { const a = [...values].sort((x, y) => x - y); return a[Math.floor(a.length / 2)]; };
|
|
66
|
-
for (const event of events) {
|
|
67
|
-
if (event.endpointSource !== "observed-plateau") continue;
|
|
68
|
-
const { start: from, landing, anchor } = event;
|
|
69
|
-
const lift = median(lifts.slice(landing, anchor + 1));
|
|
70
|
-
if (lift > .5) throw new Error("extract-trajectory-endpoint-clearance-too-large");
|
|
71
|
-
const firstY = motion.rootPos[from * 3 + 1];
|
|
72
|
-
const lastY = median(Array.from({ length: anchor - landing + 1 }, (_, i) => motion.rootPos[(landing + i) * 3 + 1]));
|
|
73
|
-
for (let f = from; f < motion.frames; f++) {
|
|
74
|
-
const progress = f >= landing ? 1 : Math.max(0, Math.min(1, (firstY - motion.rootPos[f * 3 + 1]) / Math.max(.01, firstY - lastY)));
|
|
75
|
-
datum[f] = Math.max(datum[f], lift * progress);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const residual = lifts.map((v, f) => Math.max(0, v - datum[f]));
|
|
79
|
-
if (Math.max(...residual) > .25) throw new Error("extract-trajectory-floor-correction-too-large");
|
|
80
|
-
// A conservative smooth upper envelope cannot reintroduce penetration.
|
|
81
|
-
const radius = Math.max(1, Math.round(motion.fps * .1));
|
|
82
|
-
// Once landed, use each pose's actual clearance instead of keeping the
|
|
83
|
-
// median datum as a permanent cushion (which can itself leave a hover).
|
|
84
|
-
const desired = lifts.slice();
|
|
85
|
-
for (const event of events) if (event.endpointSource === "observed-plateau") {
|
|
86
|
-
for (let f = event.start; f < event.landing; f++) desired[f] = Math.max(desired[f], datum[f]);
|
|
87
|
-
}
|
|
88
|
-
const safe = desired.slice();
|
|
89
|
-
for (let f = start; f < motion.frames; f++) for (let j = Math.max(start, f - radius); j <= Math.min(motion.frames - 1, f + radius); j++) {
|
|
90
|
-
const t = Math.abs(f - j) / (radius + 1), fade = 1 - t * t * (3 - 2 * t);
|
|
91
|
-
safe[f] = Math.max(safe[f], desired[j] * fade);
|
|
92
|
-
}
|
|
93
|
-
const corrected = { ...motion, rootPos: motion.rootPos.slice(), posedJoints: motion.posedJoints.slice() };
|
|
94
|
-
for (let f = start; f < motion.frames; f++) if (safe[f]) shiftFrame(corrected, f, safe[f]);
|
|
95
|
-
const diagnostics = { status: "verified", start, descentStart, changedFrames: [...safe].filter(v => v > 1e-6).length,
|
|
96
|
-
maxLiftM: Math.max(...safe), endpointDatumLiftM: Math.max(...datum), seconds: (performance.now() - began) / 1000,
|
|
97
|
-
models: measurements.map(({ model, scale, floor }) => ({ model,
|
|
98
|
-
minimumBeforeM: Math.min(...floor.slice(start)),
|
|
99
|
-
minimumAfterM: Math.min(...floor.slice(start).map((v, i) => v + safe[start + i] * scale)),
|
|
100
|
-
})), };
|
|
101
|
-
return { motion: corrected, diagnostics };
|
|
102
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readNpz } from "../kimodo/read-npz.mjs";
|
|
3
|
-
import { motionArraysToNpzMembers, writeNpz } from "./npz.mjs";
|
|
4
|
-
import { smplToCskel27Motion } from "./smpl-cskel27.mjs";
|
|
5
|
-
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
const input = args[0], output = args[1];
|
|
8
|
-
let boneScale = "derive";
|
|
9
|
-
for (let i = 2; i < args.length; i += 1) {
|
|
10
|
-
if (args[i] !== "--bone-scale" || i + 1 >= args.length) { console.error("usage: node tools/ardy/gvhmr-to-cskel27.mjs <gvhmr.npz> <motion.npz> [--bone-scale derive|number]"); process.exit(2); }
|
|
11
|
-
const value = args[++i];
|
|
12
|
-
if (value !== "derive") { boneScale = Number(value); if (!(Number.isFinite(boneScale) && boneScale > 0)) { console.error("--bone-scale must be derive or a positive number"); process.exit(2); } }
|
|
13
|
-
}
|
|
14
|
-
if (!input || !output) {
|
|
15
|
-
console.error("usage: node tools/ardy/gvhmr-to-cskel27.mjs <gvhmr.npz> <motion.npz> [--bone-scale derive|number]");
|
|
16
|
-
process.exit(2);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const motion = smplToCskel27Motion(readNpz(input), { boneScale });
|
|
20
|
-
writeNpz(output, motionArraysToNpzMembers(motion));
|
|
21
|
-
console.log(JSON.stringify({ output, frames: motion.frames, fps: motion.fps, boneScale }));
|