cozyclay 2.1.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +43 -116
- package/bin/agent/agent-runner.mjs +14 -15
- 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/session-store.mjs +2 -2
- package/bin/agent/studio-context-budget.mjs +93 -0
- 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-BWCqdgWA.js +1 -0
- package/dist/assets/app-DfTCxaKk.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-BxKXh07s.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
- package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-D78eiKTa.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 +151 -2625
- package/src/agent-progress.js +80 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +12 -2
- 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 +10 -8
- 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 +214 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +55 -13
- 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 +53 -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 +47 -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 +60 -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 +79 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +113 -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 +197 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +177 -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 +144 -0
- package/src/motion3/compiler/validate-parts.js +118 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +74 -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 +177 -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 +204 -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/people.js +82 -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 +125 -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 +195 -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 +44 -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/people-checks.js +37 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +228 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +129 -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 +113 -0
- package/src/motion3/schema/types.js +149 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/settle.js +136 -0
- package/src/motion3/skills/basic.js +83 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/flip.js +200 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/impact.js +109 -0
- package/src/motion3/skills/index.js +85 -0
- package/src/motion3/skills/info.js +80 -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/qa-label.js +25 -0
- 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 +16 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +47 -77
- package/src/studio-app-binding.js +27 -47
- 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 +15 -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 +109 -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 +9 -125
- 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
|
@@ -1,664 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
I2V_MOTION_STILL_OUTPUT, I2V_MOTION_SHOT_ASPECT, I2V_MOTION_MIN_DURATION,
|
|
3
|
-
waitForMotionJob, submitMotionJob, buildH3MotionPrompt,
|
|
4
|
-
} from "../../i2v-motion-client.js";
|
|
5
|
-
import * as THREE from "three";
|
|
6
|
-
import { isKo, ko } from "../../locale.js";
|
|
7
|
-
import {
|
|
8
|
-
isPlatformPageUrl,
|
|
9
|
-
normalizeSourceUrl,
|
|
10
|
-
sourceLabel,
|
|
11
|
-
requestBridgeFootage,
|
|
12
|
-
fetchFootageBlob,
|
|
13
|
-
probeFootage,
|
|
14
|
-
requestBridgeExtract,
|
|
15
|
-
} from "../../multimodel-ingest.js";
|
|
16
|
-
import { characterScaleFor, loadMotionFromUrl } from "../../ardy/npz.js";
|
|
17
|
-
import { autoRoofDrop, applyAutoFall } from "../../ardy/root-drop.js";
|
|
18
|
-
import { takeAnchor, createCharacterEntry, isProxyFigure } from "../../scenes.js";
|
|
19
|
-
import { kindRefusal } from "../../character-kind.js";
|
|
20
|
-
import { retimeMotion } from "../../ardy/retime.js";
|
|
21
|
-
import { createMotionEdit } from "../../ardy/motion-edit.js";
|
|
22
|
-
import { DEFAULT_POSE } from "../../poses.js";
|
|
23
|
-
import { startMotionRequest } from "../../analytics.js";
|
|
24
|
-
import { MULTIMODEL_REASONS, nextCharacterId, TIMELINE_FPS } from "../../app-stage.jsx";
|
|
25
|
-
|
|
26
|
-
export function createMotionVideoCapture({
|
|
27
|
-
appContext,
|
|
28
|
-
bridge,
|
|
29
|
-
i2vMotion,
|
|
30
|
-
setI2vMotion,
|
|
31
|
-
multiModelUrl,
|
|
32
|
-
multiModelSource,
|
|
33
|
-
multiModelFootage,
|
|
34
|
-
multiModelExtract,
|
|
35
|
-
setMultiModelUrl,
|
|
36
|
-
setMultiModelSource,
|
|
37
|
-
setMultiModelStatus,
|
|
38
|
-
setMultiModelStage,
|
|
39
|
-
setMultiModelProgress,
|
|
40
|
-
setMultiModelFootage,
|
|
41
|
-
setMultiModelError,
|
|
42
|
-
setMultiModelTake,
|
|
43
|
-
setMultiModelExtract,
|
|
44
|
-
setMultiModelExtractProgress,
|
|
45
|
-
setMultiModelExtractError,
|
|
46
|
-
loadMotion,
|
|
47
|
-
authoredSupportDescriptors,
|
|
48
|
-
applyAuthoredSupportRise,
|
|
49
|
-
}) {
|
|
50
|
-
/* ---------------------------- video capture ----------------------------
|
|
51
|
-
* Footage in, takes out. The ingest downloads and decodes a source so the
|
|
52
|
-
* timeline is sized by what was actually read; extraction then turns it
|
|
53
|
-
* into one take per tracked performer. Take 0 belongs to the ACTIVE
|
|
54
|
-
* character's layer, the rest are landed on their own cast members. */
|
|
55
|
-
// A picked file is already local bytes: no download stage, straight to the
|
|
56
|
-
// probe that produces the timeline numbers.
|
|
57
|
-
function chooseMultiModelFile(event) {
|
|
58
|
-
const file = event.target.files?.[0] ?? null;
|
|
59
|
-
if (!file) return;
|
|
60
|
-
setMultiModelUrl("");
|
|
61
|
-
ingestFootage({ kind: "file", name: file.name, blob: file, bytes: file.size });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function pasteMultiModelUrl() {
|
|
65
|
-
try {
|
|
66
|
-
const text = await navigator.clipboard.readText();
|
|
67
|
-
if (!text.trim()) return;
|
|
68
|
-
setMultiModelUrl(text.trim());
|
|
69
|
-
setMultiModelStatus("idle");
|
|
70
|
-
} catch {
|
|
71
|
-
appContext.notify(isKo ? "클립보드를 읽지 못했어요 — 직접 붙여넣어 주세요" : "Clipboard is unavailable — paste into the field directly");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function useMultiModelUrl() {
|
|
76
|
-
const raw = multiModelUrl.trim();
|
|
77
|
-
// A platform page (YouTube, Vimeo, …) is never browser-fetchable, but
|
|
78
|
-
// the dev bridge can fetch it server-side. With the bridge up the
|
|
79
|
-
// address goes there; without it the named refusal below stands.
|
|
80
|
-
if (isPlatformPageUrl(raw) && bridge?.ok) {
|
|
81
|
-
ingestPlatformFootage(raw);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const normalized = normalizeSourceUrl(raw);
|
|
85
|
-
if (!normalized.ok) {
|
|
86
|
-
setMultiModelStatus("error");
|
|
87
|
-
setMultiModelStage("error");
|
|
88
|
-
setMultiModelError(MULTIMODEL_REASONS[normalized.reason]?.[isKo ? 1 : 0] ?? normalized.reason);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
ingestFootage({ kind: "url", name: sourceLabel(normalized.url), url: normalized.url });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Platform page → bridge download (yt-dlp + normalize) → the local
|
|
95
|
-
* /ardy/footage/… address rides the ordinary ingest unchanged. */
|
|
96
|
-
async function ingestPlatformFootage(pageUrl) {
|
|
97
|
-
const run = appContext.shared.multiModelRunRef.current + 1;
|
|
98
|
-
appContext.shared.multiModelRunRef.current = run;
|
|
99
|
-
const live = () => appContext.shared.multiModelRunRef.current === run;
|
|
100
|
-
setMultiModelSource({ kind: "url", name: sourceLabel(pageUrl), url: pageUrl });
|
|
101
|
-
setMultiModelFootage(null);
|
|
102
|
-
setMultiModelError("");
|
|
103
|
-
setMultiModelStatus("busy");
|
|
104
|
-
setMultiModelStage("fetching");
|
|
105
|
-
setMultiModelProgress(null);
|
|
106
|
-
setMultiModelTake(null);
|
|
107
|
-
setMultiModelExtract("idle");
|
|
108
|
-
setMultiModelExtractProgress(null);
|
|
109
|
-
setMultiModelExtractError("");
|
|
110
|
-
try {
|
|
111
|
-
const footage = await requestBridgeFootage(pageUrl, {
|
|
112
|
-
onProgress: ({ ratio }) => {
|
|
113
|
-
if (live()) setMultiModelProgress(Number.isFinite(ratio) ? ratio : null);
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
if (!live()) return;
|
|
117
|
-
ingestFootage({ kind: "url", name: footage.title || sourceLabel(pageUrl), url: footage.url, fps: footage.fps, bridgeId: footage.footage ?? null });
|
|
118
|
-
} catch (error) {
|
|
119
|
-
if (!live()) return;
|
|
120
|
-
const code = error?.message ?? String(error);
|
|
121
|
-
setMultiModelStage("error");
|
|
122
|
-
setMultiModelStatus("error");
|
|
123
|
-
setMultiModelProgress(null);
|
|
124
|
-
setMultiModelError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Download (when remote), decode, measure, then size the timeline from what
|
|
129
|
-
* was actually read. Each run carries a token so a slow first source can
|
|
130
|
-
* never land its numbers after a second one replaced it. */
|
|
131
|
-
async function ingestFootage(source, commandContext = null) {
|
|
132
|
-
const run = appContext.shared.multiModelRunRef.current + 1;
|
|
133
|
-
appContext.shared.multiModelRunRef.current = run;
|
|
134
|
-
const live = () => appContext.shared.multiModelRunRef.current === run;
|
|
135
|
-
setMultiModelSource(source);
|
|
136
|
-
setMultiModelFootage(null);
|
|
137
|
-
setMultiModelError("");
|
|
138
|
-
setMultiModelStatus("busy");
|
|
139
|
-
setMultiModelProgress(null);
|
|
140
|
-
// A take baked from the PREVIOUS clip must not read as this one's
|
|
141
|
-
// result, so the extraction state resets with the source.
|
|
142
|
-
setMultiModelTake(null);
|
|
143
|
-
setMultiModelExtract("idle");
|
|
144
|
-
setMultiModelExtractProgress(null);
|
|
145
|
-
setMultiModelExtractError("");
|
|
146
|
-
try {
|
|
147
|
-
let blob = source.blob ?? null;
|
|
148
|
-
let bytes = source.bytes ?? 0;
|
|
149
|
-
if (!blob) {
|
|
150
|
-
setMultiModelStage("fetching");
|
|
151
|
-
const downloaded = await fetchFootageBlob(source.url, {
|
|
152
|
-
onProgress: ({ ratio }) => {
|
|
153
|
-
if (live()) setMultiModelProgress(ratio);
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
if (!live()) return;
|
|
157
|
-
blob = downloaded.blob;
|
|
158
|
-
bytes = downloaded.bytes;
|
|
159
|
-
}
|
|
160
|
-
setMultiModelStage("probing");
|
|
161
|
-
if (appContext.shared.multiModelObjectUrlRef.current) URL.revokeObjectURL(appContext.shared.multiModelObjectUrlRef.current);
|
|
162
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
163
|
-
appContext.shared.multiModelObjectUrlRef.current = objectUrl;
|
|
164
|
-
const probed = await probeFootage(objectUrl, {
|
|
165
|
-
createVideo: () => document.createElement("video"),
|
|
166
|
-
// The bridge normalized the clip and DECLARED its rate; a probe
|
|
167
|
-
// that re-guesses it would overrule a measurement with a guess.
|
|
168
|
-
knownFps: Number.isFinite(source.fps) ? source.fps : null,
|
|
169
|
-
});
|
|
170
|
-
if (!live()) return;
|
|
171
|
-
commandContext?.check();
|
|
172
|
-
const footage = { ...probed, bytes, objectUrl, blob, bridgeId: source.bridgeId ?? null };
|
|
173
|
-
setMultiModelFootage(footage);
|
|
174
|
-
setMultiModelStage("ready");
|
|
175
|
-
setMultiModelStatus("ready");
|
|
176
|
-
setMultiModelProgress(1);
|
|
177
|
-
// The timeline is the point: the playhead now spans the footage that
|
|
178
|
-
// was actually decoded, at the rate that was actually measured.
|
|
179
|
-
appContext.shared.setTlFps(footage.fps);
|
|
180
|
-
appContext.shared.setTlFrameCount(footage.frames);
|
|
181
|
-
appContext.shared.setTlFrame(0);
|
|
182
|
-
appContext.shared.setTlPlaying(false);
|
|
183
|
-
appContext.notify((isKo, ko) => isKo
|
|
184
|
-
? `${source.name} 인제스트됨 — ${footage.frames}프레임 @ ${footage.fps} fps`
|
|
185
|
-
: `Ingested ${source.name} — ${footage.frames} frames @ ${footage.fps} fps`);
|
|
186
|
-
return footage;
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (commandContext && (commandContext.signal.aborted || error.code === "STALE_TARGET")) throw error;
|
|
189
|
-
if (!live()) return;
|
|
190
|
-
const code = error?.message ?? String(error);
|
|
191
|
-
setMultiModelStage("error");
|
|
192
|
-
setMultiModelStatus("error");
|
|
193
|
-
setMultiModelProgress(null);
|
|
194
|
-
setMultiModelError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Extract motion from the ingested footage. With the bridge up this goes
|
|
199
|
-
* to the GPU box (GVHMR: whole-clip temporal context, real 3D body
|
|
200
|
-
* prior — previs-grade). If the bridge is unavailable or is configured for
|
|
201
|
-
* another backend, extraction stops with a named error. */
|
|
202
|
-
async function extractMultiModelMotion() {
|
|
203
|
-
if (!bridge?.ok) {
|
|
204
|
-
setMultiModelExtract("error");
|
|
205
|
-
setMultiModelExtractError(MULTIMODEL_REASONS["extract-bridge-required"]?.[isKo ? 1 : 0] ?? "extract-bridge-required");
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (bridge.extractionBackend !== "gvhmr") {
|
|
209
|
-
setMultiModelExtract("error");
|
|
210
|
-
setMultiModelExtractError(MULTIMODEL_REASONS["extract-backend-unsupported"]?.[isKo ? 1 : 0] ?? "extract-backend-unsupported");
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
return extractMultiModelMotionGpu();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function extractMultiModelMotionGpu() {
|
|
217
|
-
const footage = multiModelFootage;
|
|
218
|
-
if (!footage || multiModelExtract === "running") return;
|
|
219
|
-
if (isProxyFigure(appContext.shared.activeChar)) {
|
|
220
|
-
setMultiModelExtract("error");
|
|
221
|
-
setMultiModelExtractError(kindRefusal("mocap", isKo));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const run = appContext.shared.multiModelRunRef.current;
|
|
225
|
-
const live = () => appContext.shared.multiModelRunRef.current === run;
|
|
226
|
-
setMultiModelExtract("running");
|
|
227
|
-
setMultiModelExtractProgress(null);
|
|
228
|
-
setMultiModelExtractError("");
|
|
229
|
-
setMultiModelTake(null);
|
|
230
|
-
try {
|
|
231
|
-
const done = await requestBridgeExtract(
|
|
232
|
-
footage.bridgeId ? { footage: footage.bridgeId } : footage.blob,
|
|
233
|
-
{
|
|
234
|
-
onProgress: ({ ratio }) => {
|
|
235
|
-
if (live()) setMultiModelExtractProgress(Number.isFinite(ratio) ? ratio : null);
|
|
236
|
-
},
|
|
237
|
-
}
|
|
238
|
-
);
|
|
239
|
-
if (!live()) return;
|
|
240
|
-
if (done.quality && done.quality.pass === false) {
|
|
241
|
-
const failed = Array.isArray(done.quality.checks)
|
|
242
|
-
? done.quality.checks.filter((check) => check && check.pass === false).map((check) => check.name).join(", ")
|
|
243
|
-
: "quality";
|
|
244
|
-
appContext.notify(isKo
|
|
245
|
-
? `모캡 품질 경고: ${failed || "검증 실패"} — 결과는 로드하지만 보정이 필요합니다`
|
|
246
|
-
: `Mocap quality warning: ${failed || "validation failed"} — loaded for review, correction required`);
|
|
247
|
-
}
|
|
248
|
-
// One take per tracked performer. An older bridge sends a single
|
|
249
|
-
// motionUrl and no list; that is the same thing with one entry.
|
|
250
|
-
const takes = Array.isArray(done.takes) && done.takes.length
|
|
251
|
-
? done.takes
|
|
252
|
-
: [{ motionUrl: done.motionUrl, personScale: done.personScale, offsetX: 0, offsetZ: 0 }];
|
|
253
|
-
const label = multiModelSource?.name ?? "extracted take";
|
|
254
|
-
// The cast can change while the GPU works, so the destination is read
|
|
255
|
-
// now, once, and every take is placed against THIS character.
|
|
256
|
-
const active = appContext.live.characters.find((entry) => entry.id === appContext.shared.activeChar.id) ?? appContext.shared.activeChar;
|
|
257
|
-
// Take 0 arrives as an ordinary motion npz; loadMotion decodes,
|
|
258
|
-
// retimes to the 24 fps timeline, snapshots the rig baseline and
|
|
259
|
-
// applies the stature stored in the take itself — the body matches
|
|
260
|
-
// the FILMED person because the file carries the measurement, not
|
|
261
|
-
// because this handler remembered to re-apply it afterwards.
|
|
262
|
-
const fallbackScale = Number.isFinite(takes[0].personScale) ? takes[0].personScale : done.personScale;
|
|
263
|
-
let personScale = await loadMotion(takes[0].motionUrl ?? done.motionUrl, label, active.rot, null, active.id, null, { personScaleFallback: fallbackScale });
|
|
264
|
-
if (!live()) return;
|
|
265
|
-
// loadMotion reports the stature it applied — 1 for a take that
|
|
266
|
-
// stores none, nothing at all if the load failed. A failed load is
|
|
267
|
-
// not a finished extraction: say so with the named reason instead
|
|
268
|
-
// of a receipt for a take nobody can play.
|
|
269
|
-
if (!Number.isFinite(personScale)) throw new Error("extract-convert-failed");
|
|
270
|
-
// Only a take that stores NO stature gets the fallback for an npz
|
|
271
|
-
// that predates person_scale (or an older bridge): the response
|
|
272
|
-
// still carries the estimate, clamped the same way, because a bad
|
|
273
|
-
// leg estimate must never produce a giant or a gnome.
|
|
274
|
-
const declared = Number.isFinite(takes[0].personScale) ? takes[0].personScale : done.personScale;
|
|
275
|
-
if (personScale === 1 && Number.isFinite(declared)) {
|
|
276
|
-
personScale = characterScaleFor(null, declared);
|
|
277
|
-
}
|
|
278
|
-
// The clip itself is session-only; this reference is what a save
|
|
279
|
-
// keeps and restoreMotionRefs re-fetches on the next session.
|
|
280
|
-
const leadRef = {
|
|
281
|
-
url: takes[0].motionUrl ?? done.motionUrl,
|
|
282
|
-
prompt: label,
|
|
283
|
-
rotationDeg: active.rot,
|
|
284
|
-
anchorX: active.x,
|
|
285
|
-
anchorZ: active.z,
|
|
286
|
-
};
|
|
287
|
-
if (!appContext.storeDomain('motion')) appContext.shared.publishStudioCharacters((list) => list.map((entry) => entry.id === active.id
|
|
288
|
-
? { ...entry, scale: personScale, motionRef: leadRef }
|
|
289
|
-
: entry));
|
|
290
|
-
const placed = await deliverExtraTakes(takes.slice(1), active, label);
|
|
291
|
-
if (!live()) return;
|
|
292
|
-
const persons = 1 + placed;
|
|
293
|
-
setMultiModelTake({ frames: done.frames, fps: done.fps, gpu: true, personScale, persons, trajectory: done.performance?.trajectory, segmentation: done.segmentation ?? done.performance?.segmentation ?? takes[0]?.segmentation ?? null, quality: done.quality ?? takes[0]?.quality ?? null });
|
|
294
|
-
setMultiModelExtract("done");
|
|
295
|
-
appContext.notify(isKo
|
|
296
|
-
? `GPU 모션 추출됨 — ${done.frames}프레임 @ ${done.fps} fps${persons > 1 ? ` · ${persons}명` : ""} · 인물 스케일 ×${personScale.toFixed(2)}`
|
|
297
|
-
: `GPU motion extracted — ${done.frames} frames @ ${done.fps} fps${persons > 1 ? ` · ${persons} performers` : ""} · person scale ×${personScale.toFixed(2)}`);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (!live()) return;
|
|
300
|
-
const code = error?.message ?? String(error);
|
|
301
|
-
setMultiModelExtract("error");
|
|
302
|
-
setMultiModelExtractError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function deliverExtraTakes(extras, active, label) {
|
|
307
|
-
const decoded = await Promise.all(extras.map(async (take, index) => {
|
|
308
|
-
if (typeof take?.motionUrl !== "string" || !take.motionUrl) return null;
|
|
309
|
-
try {
|
|
310
|
-
// Inbound boundary, exactly like every other clip: decode, then
|
|
311
|
-
// retime onto the production clock before anything counts frames.
|
|
312
|
-
const anchor = takeAnchor(active, take.offsetX, take.offsetZ);
|
|
313
|
-
const clip = retimeMotion(await loadMotionFromUrl(take.motionUrl), TIMELINE_FPS);
|
|
314
|
-
const scale = characterScaleFor(clip, take.personScale);
|
|
315
|
-
const raised = applyAuthoredSupportRise(clip, { ...anchor, y: active.y ?? 0 }, active.rot, scale);
|
|
316
|
-
const staging = autoRoofDrop(
|
|
317
|
-
raised,
|
|
318
|
-
{ x: anchor.x, z: anchor.z, y: active.y ?? 0, rotationDeg: active.rot },
|
|
319
|
-
authoredSupportDescriptors(),
|
|
320
|
-
{ worldScale: scale },
|
|
321
|
-
);
|
|
322
|
-
const stagedClip = staging ? applyAutoFall(raised, staging, { worldScale: scale }) : raised;
|
|
323
|
-
return {
|
|
324
|
-
url: take.motionUrl,
|
|
325
|
-
prompt: `${label} · ${index + 2}`,
|
|
326
|
-
rotationDeg: active.rot,
|
|
327
|
-
anchor,
|
|
328
|
-
// A second performer is a DIFFERENT body: their take carries
|
|
329
|
-
// their own stature, and the response estimate is only the
|
|
330
|
-
// fallback for an npz that stores none.
|
|
331
|
-
scale: characterScaleFor(clip, take.personScale),
|
|
332
|
-
clip: stagedClip,
|
|
333
|
-
};
|
|
334
|
-
} catch {
|
|
335
|
-
return null; // one unreadable take never voids the others
|
|
336
|
-
}
|
|
337
|
-
}));
|
|
338
|
-
const usable = decoded.filter(Boolean);
|
|
339
|
-
if (!usable.length) return 0;
|
|
340
|
-
const owned = appContext.storeDomain('motion');
|
|
341
|
-
// Plan against the cast as it stands, so ids are decided once and the
|
|
342
|
-
// full-take map can be seeded with them.
|
|
343
|
-
const list = appContext.live.characters;
|
|
344
|
-
const taken = new Set([active.id]);
|
|
345
|
-
let idPool = list;
|
|
346
|
-
const assignments = usable.map((take) => {
|
|
347
|
-
// Reuse a visible cast member with no clip of its own before adding
|
|
348
|
-
// another body to the set.
|
|
349
|
-
const reuse = list.find((entry) => !entry.hidden && !isProxyFigure(entry) && !taken.has(entry.id) && !entry.sessionMotion && !entry.motionRef);
|
|
350
|
-
const id = reuse ? reuse.id : nextCharacterId(idPool);
|
|
351
|
-
if (!reuse) idPool = [...idPool, { id }];
|
|
352
|
-
taken.add(id);
|
|
353
|
-
return {
|
|
354
|
-
id,
|
|
355
|
-
spawn: !reuse,
|
|
356
|
-
patch: {
|
|
357
|
-
hidden: false,
|
|
358
|
-
x: take.anchor.x,
|
|
359
|
-
z: take.anchor.z,
|
|
360
|
-
rot: take.rotationDeg,
|
|
361
|
-
scale: take.scale,
|
|
362
|
-
motionRef: {
|
|
363
|
-
url: take.url,
|
|
364
|
-
prompt: take.prompt,
|
|
365
|
-
rotationDeg: take.rotationDeg,
|
|
366
|
-
anchorX: take.anchor.x,
|
|
367
|
-
anchorZ: take.anchor.z,
|
|
368
|
-
},
|
|
369
|
-
sessionMotion: {
|
|
370
|
-
...take.clip,
|
|
371
|
-
url: take.url,
|
|
372
|
-
prompt: take.prompt,
|
|
373
|
-
anchorX: take.anchor.x,
|
|
374
|
-
anchorZ: take.anchor.z,
|
|
375
|
-
anchorFrame: 0,
|
|
376
|
-
rotationDeg: take.rotationDeg,
|
|
377
|
-
editSegments: createMotionEdit(take.clip.frames),
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
};
|
|
381
|
-
});
|
|
382
|
-
const place = current => {
|
|
383
|
-
let next = current;
|
|
384
|
-
for (const { id, spawn, patch } of assignments) {
|
|
385
|
-
next = spawn && !next.some((entry) => entry.id === id)
|
|
386
|
-
? [...next, { ...createCharacterEntry({ id, model: active.model, pose: DEFAULT_POSE, subject: "a person" }, next.length), ...patch }]
|
|
387
|
-
: next.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));
|
|
388
|
-
}
|
|
389
|
-
return next;
|
|
390
|
-
};
|
|
391
|
-
if (owned) owned.runPrepared(active.id, () => {
|
|
392
|
-
owned.castWrite(place);
|
|
393
|
-
for (const { id, patch } of assignments) { owned.replace(id, patch.sessionMotion); owned.persistTake(patch.sessionMotion, patch.motionRef); }
|
|
394
|
-
return { affectedIds: assignments.map(row => row.id) };
|
|
395
|
-
});
|
|
396
|
-
else {
|
|
397
|
-
appContext.shared.publishStudioCharacters(place);
|
|
398
|
-
// Their takes are trimmable the moment they become the active layer.
|
|
399
|
-
for (const { id, patch } of assignments) appContext.shared.motionFullRef.current.set(id, patch.sessionMotion);
|
|
400
|
-
}
|
|
401
|
-
return assignments.length;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function captureI2vStill() {
|
|
405
|
-
// H3 480P renders 832x480. The still is captured at exactly that canvas
|
|
406
|
-
// (x2) regardless of the Studio's shot ratio; markI2vPose also switches
|
|
407
|
-
// the viewport to the matching ratio so what the user framed is what
|
|
408
|
-
// gets sent.
|
|
409
|
-
const captured = appContext.shared.captureLiveFraming({ output: I2V_MOTION_STILL_OUTPUT });
|
|
410
|
-
if (!captured?.dataUrl?.startsWith("data:image/")) throw new Error(ko("The shot renderer is not ready.", "렌더러가 준비되지 않았어요."));
|
|
411
|
-
if (captured.width !== I2V_MOTION_STILL_OUTPUT.width || captured.height !== I2V_MOTION_STILL_OUTPUT.height) {
|
|
412
|
-
throw new Error(ko(`The H3 480P reference must be captured at ${I2V_MOTION_STILL_OUTPUT.width}×${I2V_MOTION_STILL_OUTPUT.height}.`, `H3 480P 참조 캡처는 ${I2V_MOTION_STILL_OUTPUT.width}×${I2V_MOTION_STILL_OUTPUT.height}이어야 해요.`));
|
|
413
|
-
}
|
|
414
|
-
// H3 must see the same complete subject in both endpoints. A clipped
|
|
415
|
-
// foot or head makes the model invent the missing geometry during the
|
|
416
|
-
// transition, which is exactly the bad motion this flow is meant to avoid.
|
|
417
|
-
const rig = appContext.live.state.rigs?.[appContext.shared.activeChar.id];
|
|
418
|
-
const cam = appContext.shared.shotCamRef.current;
|
|
419
|
-
if (!rig || !cam) throw new Error(ko("The full-body frame is not ready yet. Wait a moment and try the reference capture again.", "전신 프레임을 확인할 수 없어 참조를 캡처할 수 없어요. 잠시 후 다시 시도하세요."));
|
|
420
|
-
rig.updateWorldMatrix(true, true);
|
|
421
|
-
cam.updateMatrixWorld(true);
|
|
422
|
-
const bounds = new THREE.Box3().setFromObject(rig);
|
|
423
|
-
const corners = [
|
|
424
|
-
new THREE.Vector3(bounds.min.x, bounds.min.y, bounds.min.z),
|
|
425
|
-
new THREE.Vector3(bounds.min.x, bounds.min.y, bounds.max.z),
|
|
426
|
-
new THREE.Vector3(bounds.min.x, bounds.max.y, bounds.min.z),
|
|
427
|
-
new THREE.Vector3(bounds.min.x, bounds.max.y, bounds.max.z),
|
|
428
|
-
new THREE.Vector3(bounds.max.x, bounds.min.y, bounds.min.z),
|
|
429
|
-
new THREE.Vector3(bounds.max.x, bounds.min.y, bounds.max.z),
|
|
430
|
-
new THREE.Vector3(bounds.max.x, bounds.max.y, bounds.min.z),
|
|
431
|
-
new THREE.Vector3(bounds.max.x, bounds.max.y, bounds.max.z),
|
|
432
|
-
].map((corner) => corner.project(cam));
|
|
433
|
-
const margin = 0.94;
|
|
434
|
-
const clipped = corners.some((corner) =>
|
|
435
|
-
corner.z < -1 || corner.z > 1 || Math.abs(corner.x) > margin || Math.abs(corner.y) > margin
|
|
436
|
-
);
|
|
437
|
-
if (clipped) {
|
|
438
|
-
throw new Error(ko(
|
|
439
|
-
"The full character is not inside the A/B reference. In the shot view, pull the camera back until the head and both feet are visible, then capture again.",
|
|
440
|
-
"A/B 참조에 캐릭터 전신이 다 안 들어왔어요. 샷 시점에서 머리와 양발이 화면 안에 들어오도록 카메라를 뒤로 빼고 다시 캡처하세요."
|
|
441
|
-
));
|
|
442
|
-
}
|
|
443
|
-
if (!appContext.shared.i2vMotionSegmentationReady || !Array.isArray(captured.partColours) || captured.partColours.length === 0) {
|
|
444
|
-
throw new Error(ko("Enable View → Body part colours → Shaded before capturing an A/B reference.", "A/B 참조는 View에서 부위 색상 → 음영을 켜야 캡처할 수 있어요."));
|
|
445
|
-
}
|
|
446
|
-
return { ...captured, framing: appContext.shared.captureCurrentFraming() };
|
|
447
|
-
}
|
|
448
|
-
/** Put the viewport on the Fal canvas and hand the fly controls to the
|
|
449
|
-
* shot camera, so the user composes the A/B reference on exactly the
|
|
450
|
-
* 832x480 frame the clip will have. Idempotent; capture does not need it. */
|
|
451
|
-
function enterI2vFraming() {
|
|
452
|
-
appContext.shared.runStudioAction("stage.setFilmback", { shotAspect: I2V_MOTION_SHOT_ASPECT });
|
|
453
|
-
if (!appContext.shared.lookThroughShot) appContext.shared.enterShotLook();
|
|
454
|
-
}
|
|
455
|
-
function markI2vPose(slot) {
|
|
456
|
-
try {
|
|
457
|
-
if (slot === "b" && i2vMotion.a && framingDistance(i2vMotion.a.framing, appContext.shared.captureCurrentFraming()) > 0.001) {
|
|
458
|
-
throw new Error(ko("The camera moved between A and B. Capture both refs with the same camera framing.", "A와 B 사이에서 카메라가 이동했어요. 같은 카메라 프레이밍으로 다시 캡처하세요."));
|
|
459
|
-
}
|
|
460
|
-
const still = captureI2vStill();
|
|
461
|
-
// The capture is already on the Fal canvas; make the viewport agree so
|
|
462
|
-
// the user sees the frame that was just sent.
|
|
463
|
-
appContext.shared.runStudioAction("stage.setFilmback", { shotAspect: I2V_MOTION_SHOT_ASPECT });
|
|
464
|
-
setI2vMotion((current) => ({ ...current, [slot]: still, status: "idle", error: "" }));
|
|
465
|
-
if (slot === "a") appContext.shared.setI2vMotionCameraUnlocked(false);
|
|
466
|
-
appContext.notify(isKo ? `포즈 ${slot.toUpperCase()} 캡처됨 · ${still.width}×${still.height}` : `Pose ${slot.toUpperCase()} captured · ${still.width}×${still.height}`);
|
|
467
|
-
} catch (error) {
|
|
468
|
-
setI2vMotion((current) => ({ ...current, error: error.message, status: "error" }));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
function clearI2vPose(slot) {
|
|
472
|
-
setI2vMotion((current) => ({ ...current, [slot]: null, status: "idle", error: "", job: null }));
|
|
473
|
-
if (slot === "a") appContext.shared.setI2vMotionCameraUnlocked(false);
|
|
474
|
-
}
|
|
475
|
-
function clearI2vMotion() {
|
|
476
|
-
setI2vMotion({ a: null, b: null, job: null, status: "idle", error: "", instruction: "", promptOverride: "", duration: I2V_MOTION_MIN_DURATION, dailyRemaining: null });
|
|
477
|
-
appContext.shared.setI2vMotionCameraUnlocked(false);
|
|
478
|
-
}
|
|
479
|
-
function restoreI2vCamera() {
|
|
480
|
-
const framing = i2vMotion.a?.framing;
|
|
481
|
-
const camera = appContext.shared.shotCamRef.current;
|
|
482
|
-
if (!framing || !camera) return;
|
|
483
|
-
camera.position.set(framing.pos.x, framing.pos.y, framing.pos.z);
|
|
484
|
-
camera.rotation.order = "YXZ";
|
|
485
|
-
camera.rotation.set(framing.pitch, framing.yaw, 0);
|
|
486
|
-
camera.fov = framing.fovDeg;
|
|
487
|
-
camera.updateProjectionMatrix();
|
|
488
|
-
appContext.shared.look.current.yaw = framing.yaw;
|
|
489
|
-
appContext.shared.look.current.pitch = framing.pitch;
|
|
490
|
-
appContext.shared.shotCameraPosRef.current = { ...framing.pos };
|
|
491
|
-
appContext.shared.setCameraPos({ ...framing.pos });
|
|
492
|
-
appContext.shared.setFovDeg(framing.fovDeg);
|
|
493
|
-
appContext.shared.setI2vMotionCameraUnlocked(false);
|
|
494
|
-
setI2vMotion((current) => ({ ...current, error: "", status: "idle" }));
|
|
495
|
-
appContext.notify(isKo ? "A 캡처 카메라로 복원했어요." : "Restored the camera used for A.");
|
|
496
|
-
}
|
|
497
|
-
function framingDistance(a, b) {
|
|
498
|
-
if (!a || !b) return Infinity;
|
|
499
|
-
return Math.max(
|
|
500
|
-
Math.abs(a.pos.x - b.pos.x), Math.abs(a.pos.y - b.pos.y), Math.abs(a.pos.z - b.pos.z),
|
|
501
|
-
Math.abs(a.yaw - b.yaw), Math.abs(a.pitch - b.pitch), Math.abs(a.fovDeg - b.fovDeg),
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
/** The AI motion card's lock line: AI video motion is not enabled for this account. */
|
|
505
|
-
function showI2vMotionLock() {
|
|
506
|
-
setI2vMotion((current) => ({ ...current, error: ko("AI motion generation is locked during QA.", "AI 모션 생성은 QA 중 잠겨 있어요."), status: "error" }));
|
|
507
|
-
}
|
|
508
|
-
/** The AI motion card shows every failure itself. For motion.generateFromVideo the
|
|
509
|
-
* answer says what happened: `{ failed }` with the reason in English, or the
|
|
510
|
-
* finished job, the footage it was ingested as (null when ingest failed) and
|
|
511
|
-
* the account's daily generations left. */
|
|
512
|
-
async function generateI2vMotion(kind = "interpolate", instructionOverride = null, commandContext = null) {
|
|
513
|
-
// One explicit request on the motion:* contract (#466). While the route is
|
|
514
|
-
// gated, the lock itself is the demand signal. Telemetry never decides.
|
|
515
|
-
const motionRequest = startMotionRequest({
|
|
516
|
-
surface: !commandContext ? "fal_card" : commandContext.origin === "mcp" || commandContext.origin === "live" ? "mcp" : "agent",
|
|
517
|
-
input_mode: kind === "interpolate" ? "a_to_b" : "still",
|
|
518
|
-
});
|
|
519
|
-
if (!appContext.shared.i2vMotionEnabled) {
|
|
520
|
-
motionRequest.block("locked");
|
|
521
|
-
showI2vMotionLock();
|
|
522
|
-
return { failed: "AI video motion (Fal) is not enabled for this account." };
|
|
523
|
-
}
|
|
524
|
-
let source = i2vMotion;
|
|
525
|
-
if (kind === "act" && !source.a) {
|
|
526
|
-
try { source = { ...source, a: captureI2vStill() }; setI2vMotion((current) => ({ ...current, a: source.a })); }
|
|
527
|
-
catch (error) {
|
|
528
|
-
motionRequest.block("missing_input");
|
|
529
|
-
setI2vMotion((current) => ({ ...current, error: error.message, status: "error" }));
|
|
530
|
-
return { failed: "Could not capture the character's pose frame: the full body must be inside the shot frame, shaded part colours must be on (view.setPartColours { mode: \"shaded\" }), and the renderer and rig must be ready." };
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (kind === "interpolate" && (!source.a || !source.b)) {
|
|
534
|
-
motionRequest.block("missing_input");
|
|
535
|
-
setI2vMotion((current) => ({ ...current, error: ko("Capture both A and B poses first.", "A와 B 포즈를 먼저 캡처하세요."), status: "error" }));
|
|
536
|
-
return { failed: "Capture both A and B poses first." };
|
|
537
|
-
}
|
|
538
|
-
if (!source.a?.partColours || (kind === "interpolate" && !source.b?.partColours)) {
|
|
539
|
-
motionRequest.block("missing_input");
|
|
540
|
-
setI2vMotion((current) => ({ ...current, error: ko("Recapture A/B refs with shaded body-part segmentation enabled.", "색 세그멘테이션이 포함된 음영 A/B 참조를 다시 캡처하세요."), status: "error" }));
|
|
541
|
-
return { failed: "The captured pose frame has no shaded body-part segmentation; the user must recapture it in the Fal card with shaded part colours on." };
|
|
542
|
-
}
|
|
543
|
-
if (kind === "interpolate" && framingDistance(source.a.framing, source.b.framing) > 0.001) {
|
|
544
|
-
motionRequest.block("missing_input");
|
|
545
|
-
setI2vMotion((current) => ({ ...current, error: ko("The camera changed between A and B. Capture both poses with the same camera.", "A와 B 사이에서 카메라가 바뀌었어요. 같은 카메라로 다시 캡처하세요."), status: "error" }));
|
|
546
|
-
return { failed: "The camera changed between poses A and B; capture both with the same camera." };
|
|
547
|
-
}
|
|
548
|
-
// A hand-edited prompt wins verbatim; otherwise build from the description.
|
|
549
|
-
// Interpolate now honours the description too (#380): the bare pose
|
|
550
|
-
// difference lets the model invent the transition and produces junk.
|
|
551
|
-
const description = instructionOverride || source.instruction || "";
|
|
552
|
-
const prompt = source.promptOverride?.trim()
|
|
553
|
-
? source.promptOverride.trim()
|
|
554
|
-
: buildH3MotionPrompt(description || (kind === "interpolate" ? "" : "Make the character perform the requested action."), { interpolate: kind === "interpolate" });
|
|
555
|
-
setI2vMotion((current) => ({ ...current, status: "submitting", error: "", job: null }));
|
|
556
|
-
motionRequest.pass("fal");
|
|
557
|
-
motionRequest.start();
|
|
558
|
-
try {
|
|
559
|
-
const fetchImpl = commandContext ? (url, options) => fetch(url, { ...options, signal: commandContext.signal }) : undefined;
|
|
560
|
-
const submitted = await submitMotionJob({
|
|
561
|
-
kind,
|
|
562
|
-
stillA: source.a?.dataUrl,
|
|
563
|
-
stillB: source.b?.dataUrl,
|
|
564
|
-
still: source.a?.dataUrl,
|
|
565
|
-
prompt,
|
|
566
|
-
duration: source.duration ?? I2V_MOTION_MIN_DURATION,
|
|
567
|
-
}, fetchImpl);
|
|
568
|
-
const id = submitted?.job?.id;
|
|
569
|
-
if (!id) throw Object.assign(new Error(ko("The server did not return a motion job ID.", "생성 작업 ID를 받지 못했어요.")), { reason: "The motion server did not return a job id." });
|
|
570
|
-
setI2vMotion((current) => ({ ...current, status: "queued", job: submitted.job, dailyRemaining: submitted.dailyRemaining }));
|
|
571
|
-
const finished = await waitForMotionJob(id, {
|
|
572
|
-
fetchImpl,
|
|
573
|
-
onUpdate: (job) => setI2vMotion((current) => ({ ...current, job, status: job?.status ?? current.status })),
|
|
574
|
-
});
|
|
575
|
-
commandContext?.check();
|
|
576
|
-
const job = finished?.job;
|
|
577
|
-
if (job?.status !== "done") throw Object.assign(new Error(job?.error || ko("AI motion generation failed.", "AI 모션 생성에 실패했어요.")), job?.error ? {} : { reason: "The AI video generation failed." });
|
|
578
|
-
motionRequest.succeed();
|
|
579
|
-
setI2vMotion((current) => ({ ...current, job, status: "done", dailyRemaining: finished.dailyRemaining }));
|
|
580
|
-
let footage = null;
|
|
581
|
-
if (job.video?.url) {
|
|
582
|
-
const motionSource = { kind: "url", url: job.video.url, name: `Fal H3 Max Turbo · ${job.resolution}` };
|
|
583
|
-
setMultiModelSource(motionSource);
|
|
584
|
-
// Put the completed clip through the same probe/ingest path as a
|
|
585
|
-
// manually supplied URL so GVHMR sees measured fps, duration and
|
|
586
|
-
// a ready extraction card without another generation request.
|
|
587
|
-
footage = (await ingestFootage(motionSource, commandContext)) ?? null;
|
|
588
|
-
appContext.shared.setResult({
|
|
589
|
-
mode: "video",
|
|
590
|
-
modelLabel: "Fal H3 Max Turbo",
|
|
591
|
-
prompt,
|
|
592
|
-
frame: source.a?.dataUrl ?? null,
|
|
593
|
-
videoUrl: job.video.url,
|
|
594
|
-
motion: {
|
|
595
|
-
videoUrl: job.video.url,
|
|
596
|
-
resolution: job.resolution,
|
|
597
|
-
width: job.width,
|
|
598
|
-
height: job.height,
|
|
599
|
-
fps: job.fps,
|
|
600
|
-
duration: job.resultDuration ?? job.duration,
|
|
601
|
-
cost: job.cost,
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
appContext.shared.setResultOpen(true);
|
|
605
|
-
// Applied = the clip is in the Studio, ready for GVHMR extraction.
|
|
606
|
-
motionRequest.apply();
|
|
607
|
-
// The result modal and the studio modal are both z-30; never stack them.
|
|
608
|
-
appContext.shared.setI2vMotionStudioOpen(false);
|
|
609
|
-
appContext.notify((isKo, ko) => isKo ? "AI 영상이 준비됐어요 · 추출 패널에서 GVHMR을 실행하세요" : "AI video is ready · run GVHMR from the extraction panel");
|
|
610
|
-
}
|
|
611
|
-
return { job, footage, dailyRemaining: finished.dailyRemaining ?? null };
|
|
612
|
-
} catch (error) {
|
|
613
|
-
const cancelled = Boolean(commandContext && (commandContext.signal.aborted || error?.code === "STALE_TARGET"));
|
|
614
|
-
// 429 is the account's daily cap (workers/api daily_cap).
|
|
615
|
-
motionRequest.fail(error, cancelled ? "aborted" : error?.status === 429 ? "quota" : "generation_failed");
|
|
616
|
-
if (cancelled) throw error;
|
|
617
|
-
setI2vMotion((current) => ({ ...current, status: "error", error: error.message || String(error) }));
|
|
618
|
-
return { failed: error.reason ?? `The AI video generation failed: ${error.message || error}` };
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
/** Why the chip cannot start an AI video motion now, as the user reads it:
|
|
622
|
-
* one already running, or none left today. The lock has its own AI motion card line. */
|
|
623
|
-
function i2vMotionUnavailable() {
|
|
624
|
-
if (!["idle", "done", "error", "failed"].includes(i2vMotion.status)) return ko("A generation is already running", "이미 생성이 돌고 있어요");
|
|
625
|
-
if (i2vMotion.dailyRemaining === 0) return ko("No AI video motion generations left today", "오늘 남은 AI 영상 모션 생성이 없어요");
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
function generateI2vMotionFromUi(instruction) {
|
|
629
|
-
if (!appContext.shared.i2vMotionEnabled) {
|
|
630
|
-
// Locked, the action's availability check refuses before
|
|
631
|
-
// generateI2vMotion runs, so the chip records its own lock (#466).
|
|
632
|
-
startMotionRequest({ surface: "agent", input_mode: "still" }).block("locked");
|
|
633
|
-
showI2vMotionLock();
|
|
634
|
-
} else {
|
|
635
|
-
// The chip clears the typed instruction when clicked, so a refusal says why.
|
|
636
|
-
const reason = i2vMotionUnavailable();
|
|
637
|
-
if (reason) { appContext.notify(reason); return null; }
|
|
638
|
-
}
|
|
639
|
-
return appContext.shared.runStudioAction("motion.generateFromVideo", { instruction });
|
|
640
|
-
}
|
|
641
|
-
function updateI2vMotionQuota(dailyRemaining) { setI2vMotion((current) => ({ ...current, dailyRemaining })); }
|
|
642
|
-
return {
|
|
643
|
-
chooseMultiModelFile,
|
|
644
|
-
pasteMultiModelUrl,
|
|
645
|
-
useMultiModelUrl,
|
|
646
|
-
ingestPlatformFootage,
|
|
647
|
-
ingestFootage,
|
|
648
|
-
extractMultiModelMotion,
|
|
649
|
-
extractMultiModelMotionGpu,
|
|
650
|
-
deliverExtraTakes,
|
|
651
|
-
captureI2vStill,
|
|
652
|
-
enterI2vFraming,
|
|
653
|
-
markI2vPose,
|
|
654
|
-
clearI2vPose,
|
|
655
|
-
clearI2vMotion,
|
|
656
|
-
restoreI2vCamera,
|
|
657
|
-
framingDistance,
|
|
658
|
-
showI2vMotionLock,
|
|
659
|
-
generateI2vMotion,
|
|
660
|
-
i2vMotionUnavailable,
|
|
661
|
-
generateI2vMotionFromUi,
|
|
662
|
-
updateI2vMotionQuota,
|
|
663
|
-
};
|
|
664
|
-
}
|