@bitmagic/cli 0.1.60-dev.0 → 0.1.60-dev.2
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/README.md +60 -4
- package/dist/cli.d.ts +36 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/forge.js +15 -25
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +28 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/import.d.ts +75 -0
- package/dist/commands/import.js +338 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.js +1 -45
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/verify.js +17 -29
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/animation-preview-page.js +1 -1
- package/dist/editor/motion-panel.d.ts +3 -3
- package/dist/editor/motion-panel.js +272 -2
- package/dist/editor/motion-panel.js.map +1 -1
- package/dist/editor/motion-sink.d.ts +10 -2
- package/dist/editor/motion-sink.js +27 -2
- package/dist/editor/motion-sink.js.map +1 -1
- package/dist/editor/server.js +100 -1
- package/dist/editor/server.js.map +1 -1
- package/dist/generate/animation-from-video.d.ts +12 -0
- package/dist/generate/animation-from-video.js +15 -6
- package/dist/generate/animation-from-video.js.map +1 -1
- package/dist/generate/trim-analysis.d.ts +22 -0
- package/dist/generate/trim-analysis.js +31 -0
- package/dist/generate/trim-analysis.js.map +1 -0
- package/dist/local-port.d.ts +23 -4
- package/dist/local-port.js +44 -13
- package/dist/local-port.js.map +1 -1
- package/dist/scaffold/export-zip.d.ts +48 -0
- package/dist/scaffold/export-zip.js +167 -0
- package/dist/scaffold/export-zip.js.map +1 -0
- package/dist/scaffold/import-project.d.ts +34 -0
- package/dist/scaffold/import-project.js +108 -0
- package/dist/scaffold/import-project.js.map +1 -0
- package/dist/scaffold/project-files.js +9 -1
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +34 -0
- package/dist/scaffold/project.js +53 -22
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/rehost-assets.d.ts +63 -0
- package/dist/scaffold/rehost-assets.js +270 -0
- package/dist/scaffold/rehost-assets.js.map +1 -0
- package/dist/scaffold/scaffold-run.d.ts +17 -0
- package/dist/scaffold/scaffold-run.js +57 -0
- package/dist/scaffold/scaffold-run.js.map +1 -0
- package/dist/theme/hud-theme-catalog.d.ts +0 -1
- package/dist/theme/hud-theme-catalog.js +9 -6
- package/dist/theme/hud-theme-catalog.js.map +1 -1
- package/dist/theme/theme-io.js +2 -1
- package/dist/theme/theme-io.js.map +1 -1
- package/dist/verify/browser.js +87 -18
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +12 -0
- package/dist/verify/classify.js +24 -3
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/start.d.ts +23 -3
- package/dist/verify/start.js +13 -1
- package/dist/verify/start.js.map +1 -1
- package/dist/verify/wait-for-server.d.ts +11 -0
- package/dist/verify/wait-for-server.js +29 -1
- package/dist/verify/wait-for-server.js.map +1 -1
- package/package.json +7 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -97,7 +97,7 @@ ${BITMAGIC_TOKENS_CSS}
|
|
|
97
97
|
<video id="srcvideo" controls muted playsinline></video>
|
|
98
98
|
<div id="cut-veil"><span id="cut-veil-label"></span></div>
|
|
99
99
|
</div>
|
|
100
|
-
<div id="trimbar" title="The hatched ends were
|
|
100
|
+
<div id="trimbar" title="The hatched ends were trimmed before the motion was made">
|
|
101
101
|
<div id="cut-start" class="seg cut"><span class="seg-label"></span></div><div id="kept" class="seg kept"><span class="seg-label"></span></div><div id="cut-end" class="seg cut"><span class="seg-label"></span></div>
|
|
102
102
|
<div id="playhead"></div>
|
|
103
103
|
</div>
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* Same template-literal rules as the host: **no backticks**, no regex literals starting with a
|
|
12
12
|
* dot-escape, every colour a `--bm-*` token — `shell-page.test.ts` enforces all three.
|
|
13
13
|
*/
|
|
14
|
-
export declare const MOTION_PANEL_CSS = "\n /* Level with the other dialogs: each opens from its own control and they never stack. */\n #motion-overlay { z-index: 10; }\n #motion-dialog { width: min(520px, 94vw); padding: 20px; }\n #motion-dialog .bm-label { display: block; margin: 14px 0 6px; }\n #motion-stage { position: relative; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted); }\n #motion-preview { width: 100%; height: 100%; object-fit: contain; background: #000000; display: block; }\n #motion-preview.mirror { transform: scaleX(-1); }\n #motion-timer { position: absolute; top: 10px; left: 10px; display: none; padding: 3px 10px;\n border-radius: 999px; background: var(--bm-pink); color: #ffffff; font-size: 12px;\n font-family: var(--bm-font-mono); }\n #motion-timer.show { display: inline-block; }\n #motion-countdown { position: absolute; inset: 0; display: none; flex-direction: column; align-items: center;\n justify-content: center; gap: 6px; background: var(--bm-scrim); pointer-events: none; }\n #motion-countdown.show { display: flex; }\n #motion-countdown-number { font-size: 96px; font-weight: 700; line-height: 1; color: #ffffff; }\n #motion-countdown-hint { font-size: 13px; color: #ffffff; opacity: 0.85; }\n #motion-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;\n text-align: center; padding: 20px; font-size: 13px; color: var(--bm-text-muted); }\n #motion-hint.hidden { display: none; }\n #motion-sources { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }\n #motion-file { display: none; }\n #motion-guidance { margin: 10px 0 0; font-size: 12px; color: var(--bm-text-muted); }\n #motion-options { display: grid; grid-template-columns: 1fr auto; gap: 10px 12px; align-items: end; }\n #motion-name, #motion-slot { width: 100%; }\n .motion-check { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 13px; cursor: pointer; }\n .motion-check input { accent-color: var(--bm-aqua); }\n #motion-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }\n #motion-note { display: none; margin-top: 12px; padding: 12px; border-radius: 6px; font-size: 12px;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted);\n color: var(--bm-text-muted); }\n #motion-note.show { display: block; }\n #motion-note code { display: block; margin-top: 6px; color: var(--bm-aqua);\n font-family: var(--bm-font-mono); word-break: break-all; }\n";
|
|
15
|
-
export declare const MOTION_PANEL_HTML = " <div id=\"motion-overlay\" class=\"bm-overlay\">\n <div id=\"motion-dialog\" class=\"bm-dialog\">\n <h2>Record a motion</h2>\n <p>Perform the move on camera, or pick a video. Uthana turns it into an animation clip for\n this game's characters. Costs sparks.</p>\n <div id=\"motion-stage\">\n <video id=\"motion-preview\" playsinline muted></video>\n <span id=\"motion-timer\">0:00</span>\n <div id=\"motion-countdown\"><span id=\"motion-countdown-number\">3</span><span id=\"motion-countdown-hint\">Get in frame</span></div>\n <div id=\"motion-hint\">Whole body in frame, one person, plain background, good light.\n 2–60 seconds.</div>\n </div>\n <div id=\"motion-sources\">\n <button id=\"motion-record\" class=\"bm-pill bm-pill--primary\" type=\"button\">Record with webcam</button>\n <button id=\"motion-stop\" class=\"bm-pill\" type=\"button\" hidden>Stop</button>\n <button id=\"motion-pick\" class=\"bm-pill\" type=\"button\">Choose a video…</button>\n <button id=\"motion-retake\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Start over</button>\n <input id=\"motion-file\" type=\"file\" accept=\".mp4,.mov,.avi,.webm,video/mp4,video/quicktime,video/webm\">\n </div>\n <p id=\"motion-guidance\"></p>\n <div id=\"motion-options\">\n <div>\n <label class=\"bm-label\" for=\"motion-name\">Name</label>\n <input id=\"motion-name\" class=\"bm-input\" type=\"text\" placeholder=\"e.g. Victory dance\" maxlength=\"80\">\n </div>\n <div>\n <label class=\"bm-label\" for=\"motion-slot\">Use as</label>\n <select id=\"motion-slot\" class=\"bm-input\">\n <option value=\"\">One-shot clip</option>\n <option value=\"idle\">Idle</option>\n <option value=\"walk\">Walk</option>\n <option value=\"run\">Run</option>\n </select>\n </div>\n </div>\n <label class=\"motion-check\"><input id=\"motion-loop\" type=\"checkbox\"> Loops</label>\n <div id=\"motion-actions\">\n <button id=\"motion-close\" class=\"bm-pill bm-pill--ghost\" type=\"button\">Close</button>\n <button id=\"motion-submit\" class=\"bm-pill bm-pill--primary\" type=\"button\" disabled>Make the animation</button>\n </div>\n <div id=\"motion-note\">\n <span id=\"motion-note-text\"></span>\n <code id=\"motion-command\"></code>\n </div>\n </div>\n </div>\n";
|
|
14
|
+
export declare const MOTION_PANEL_CSS = "\n /* Level with the other dialogs: each opens from its own control and they never stack. */\n #motion-overlay { z-index: 10; }\n #motion-dialog { width: min(520px, 94vw); padding: 20px; }\n #motion-dialog .bm-label { display: block; margin: 14px 0 6px; }\n #motion-stage { position: relative; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted); }\n #motion-preview { width: 100%; height: 100%; object-fit: contain; background: #000000; display: block; }\n #motion-preview.mirror { transform: scaleX(-1); }\n #motion-timer { position: absolute; top: 10px; left: 10px; display: none; padding: 3px 10px;\n border-radius: 999px; background: var(--bm-pink); color: #ffffff; font-size: 12px;\n font-family: var(--bm-font-mono); }\n #motion-timer.show { display: inline-block; }\n #motion-countdown { position: absolute; inset: 0; display: none; flex-direction: column; align-items: center;\n justify-content: center; gap: 6px; background: var(--bm-scrim); pointer-events: none; }\n #motion-countdown.show { display: flex; }\n #motion-countdown-number { font-size: 96px; font-weight: 700; line-height: 1; color: #ffffff; }\n #motion-countdown-hint { font-size: 13px; color: #ffffff; opacity: 0.85; }\n #motion-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;\n text-align: center; padding: 20px; font-size: 13px; color: var(--bm-text-muted); }\n #motion-hint.hidden { display: none; }\n #motion-trim { display: none; margin-top: 10px; }\n #motion-trim.show { display: block; }\n #motion-trimbar { position: relative; height: 14px; border-radius: 7px; background: var(--bm-surface-alt);\n touch-action: none; }\n #motion-trim-kept { position: absolute; top: 0; bottom: 0; border-radius: 7px; background: var(--bm-aqua); }\n #motion-trim-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--bm-pink);\n pointer-events: none; }\n .motion-trim-handle { position: absolute; top: 50%; width: 14px; height: 22px;\n transform: translate(-50%, -50%); border-radius: 5px; background: var(--bm-text);\n cursor: ew-resize; touch-action: none; }\n #motion-trim-row { display: flex; align-items: center; justify-content: space-between; gap: 8px;\n margin-top: 6px; min-height: 24px; }\n #motion-trim-note { font-size: 11px; color: var(--bm-text-muted); }\n #motion-trim-actions { display: flex; gap: 6px; }\n #motion-sources { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }\n #motion-file { display: none; }\n #motion-guidance { margin: 10px 0 0; font-size: 12px; color: var(--bm-text-muted); }\n #motion-options { display: grid; grid-template-columns: 1fr auto; gap: 10px 12px; align-items: end; }\n #motion-name, #motion-slot { width: 100%; }\n .motion-check { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 13px; cursor: pointer; }\n .motion-check input { accent-color: var(--bm-aqua); }\n #motion-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }\n #motion-note { display: none; margin-top: 12px; padding: 12px; border-radius: 6px; font-size: 12px;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted);\n color: var(--bm-text-muted); }\n #motion-note.show { display: block; }\n #motion-note code { display: block; margin-top: 6px; color: var(--bm-aqua);\n font-family: var(--bm-font-mono); word-break: break-all; }\n";
|
|
15
|
+
export declare const MOTION_PANEL_HTML = " <div id=\"motion-overlay\" class=\"bm-overlay\">\n <div id=\"motion-dialog\" class=\"bm-dialog\">\n <h2>Record a motion</h2>\n <p>Perform the move on camera, or pick a video. Uthana turns it into an animation clip for\n this game's characters. Costs sparks.</p>\n <div id=\"motion-stage\">\n <video id=\"motion-preview\" playsinline muted></video>\n <span id=\"motion-timer\">0:00</span>\n <div id=\"motion-countdown\"><span id=\"motion-countdown-number\">3</span><span id=\"motion-countdown-hint\">Get in frame</span></div>\n <div id=\"motion-hint\">Whole body in frame, one person, plain background, good light.\n 2–60 seconds.</div>\n </div>\n <div id=\"motion-trim\">\n <div id=\"motion-trimbar\" title=\"Drag the handles to choose what the animation is made from\">\n <div id=\"motion-trim-kept\"></div>\n <div id=\"motion-trim-playhead\"></div>\n <div id=\"motion-trim-start\" class=\"motion-trim-handle\"></div>\n <div id=\"motion-trim-end\" class=\"motion-trim-handle\"></div>\n </div>\n <div id=\"motion-trim-row\">\n <span id=\"motion-trim-note\"></span>\n <span id=\"motion-trim-actions\">\n <button id=\"motion-trim-preview\" class=\"bm-pill\" type=\"button\">▶ Play</button>\n <button id=\"motion-trim-full\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Use full clip</button>\n </span>\n </div>\n </div>\n <div id=\"motion-sources\">\n <button id=\"motion-record\" class=\"bm-pill bm-pill--primary\" type=\"button\">Record with webcam</button>\n <button id=\"motion-stop\" class=\"bm-pill\" type=\"button\" hidden>Stop</button>\n <button id=\"motion-pick\" class=\"bm-pill\" type=\"button\">Choose a video…</button>\n <button id=\"motion-retake\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Start over</button>\n <input id=\"motion-file\" type=\"file\" accept=\".mp4,.mov,.avi,.webm,video/mp4,video/quicktime,video/webm\">\n </div>\n <p id=\"motion-guidance\"></p>\n <div id=\"motion-options\">\n <div>\n <label class=\"bm-label\" for=\"motion-name\">Name</label>\n <input id=\"motion-name\" class=\"bm-input\" type=\"text\" placeholder=\"e.g. Victory dance\" maxlength=\"80\">\n </div>\n <div>\n <label class=\"bm-label\" for=\"motion-slot\">Use as</label>\n <select id=\"motion-slot\" class=\"bm-input\">\n <option value=\"\">One-shot clip</option>\n <option value=\"idle\">Idle</option>\n <option value=\"walk\">Walk</option>\n <option value=\"run\">Run</option>\n </select>\n </div>\n </div>\n <label class=\"motion-check\"><input id=\"motion-loop\" type=\"checkbox\"> Loops</label>\n <div id=\"motion-actions\">\n <button id=\"motion-close\" class=\"bm-pill bm-pill--ghost\" type=\"button\">Close</button>\n <button id=\"motion-submit\" class=\"bm-pill bm-pill--primary\" type=\"button\" disabled>Make the animation</button>\n </div>\n <div id=\"motion-note\">\n <span id=\"motion-note-text\"></span>\n <code id=\"motion-command\"></code>\n </div>\n </div>\n </div>\n";
|
|
16
16
|
/**
|
|
17
17
|
* Behaviour, spliced into the shell's IIFE. Runs after the shell's own helpers are defined, so
|
|
18
18
|
* `setStatus`, `start`, `autoReload`, `banner` and `frame` are in scope.
|
|
19
19
|
*/
|
|
20
|
-
export declare const MOTION_PANEL_JS = "\n // ---- Record motion: webcam or file -> Uthana video-to-motion -> assets[] ----------------------\n var motionToggle = document.getElementById('motion');\n var motionOverlay = document.getElementById('motion-overlay');\n var motionPreview = document.getElementById('motion-preview');\n var motionTimer = document.getElementById('motion-timer');\n var motionCountdown = document.getElementById('motion-countdown');\n var motionCountdownNumber = document.getElementById('motion-countdown-number');\n /** Seconds shown on the live preview before the recorder starts, so the first frames are the pose. */\n var MOTION_COUNTDOWN_S = 3;\n var motionCountdownToken = 0;\n var motionHint = document.getElementById('motion-hint');\n var motionRecordBtn = document.getElementById('motion-record');\n var motionStopBtn = document.getElementById('motion-stop');\n var motionPickBtn = document.getElementById('motion-pick');\n var motionRetakeBtn = document.getElementById('motion-retake');\n var motionFile = document.getElementById('motion-file');\n var motionGuidance = document.getElementById('motion-guidance');\n var motionName = document.getElementById('motion-name');\n var motionSlot = document.getElementById('motion-slot');\n var motionLoop = document.getElementById('motion-loop');\n var motionSubmit = document.getElementById('motion-submit');\n var motionNote = document.getElementById('motion-note');\n var MOTION_MIN_S = 2;\n var MOTION_MAX_S = 60;\n var MOTION_MAX_BYTES = 200 * 1024 * 1024;\n var motionBlob = null;\n // Whether motionBlob came from the webcam recorder (auto-trim applies) or a picked file (it does not).\n var motionFromRecording = false;\n var motionSeconds = 0;\n var motionStream = null;\n var motionRecorder = null;\n var motionChunks = [];\n var motionTickTimer = null;\n var motionStartedAt = 0;\n var motionObjectUrl = null;\n\n function motionSupported() {\n return !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia\n && typeof MediaRecorder !== 'undefined');\n }\n\n /** mp4 first (Safari), then the WebM the rest produce; asset-forger re-encodes either. */\n function pickMotionMime() {\n var candidates = ['video/mp4', 'video/webm;codecs=vp9', 'video/webm'];\n for (var i = 0; i < candidates.length; i++) {\n if (MediaRecorder.isTypeSupported(candidates[i])) return candidates[i];\n }\n return '';\n }\n\n function motionCameraError(error) {\n var name = error && error.name;\n if (name === 'NotAllowedError' || name === 'SecurityError') return 'Camera access was refused. Allow it in the address bar, or choose a video file.';\n if (name === 'NotFoundError' || name === 'OverconstrainedError') return 'No camera found. Choose a video file instead.';\n if (name === 'NotReadableError') return 'The camera is in use by another app. Close it, or choose a video file.';\n return 'Could not start the camera: ' + ((error && error.message) || 'unknown error');\n }\n\n function formatMotionClock(seconds) {\n var s = Math.max(0, Math.floor(seconds));\n var rest = s % 60;\n return Math.floor(s / 60) + ':' + (rest < 10 ? '0' : '') + rest;\n }\n\n function showMotionNote(text, command) {\n document.getElementById('motion-note-text').textContent = text;\n document.getElementById('motion-command').textContent = command || '';\n motionNote.classList.add('show');\n }\n\n function releaseMotionObjectUrl() {\n if (motionObjectUrl) { URL.revokeObjectURL(motionObjectUrl); motionObjectUrl = null; }\n }\n\n /** Stop the recorder if one is still live \u2014 it may already have stopped itself. */\n function discardMotionRecorder() {\n if (motionRecorder && motionRecorder.state !== 'inactive') {\n try { motionRecorder.stop(); } catch (error) { /* already stopped */ }\n }\n }\n\n function stopMotionStream() {\n if (motionStream) {\n motionStream.getTracks().forEach(function (track) { track.stop(); });\n motionStream = null;\n }\n if (motionTickTimer) { clearInterval(motionTickTimer); motionTickTimer = null; }\n motionCountdownToken++;\n motionCountdown.classList.remove('show');\n motionTimer.classList.remove('show');\n motionPreview.classList.remove('mirror');\n }\n\n function setMotionButtons(state) {\n motionRecordBtn.hidden = state !== 'idle';\n motionPickBtn.hidden = state !== 'idle';\n motionStopBtn.hidden = state !== 'recording';\n motionRetakeBtn.hidden = state !== 'ready';\n motionSubmit.disabled = state !== 'ready';\n }\n\n function resetMotionPanel() {\n stopMotionStream();\n discardMotionRecorder();\n motionRecorder = null;\n motionChunks = [];\n motionBlob = null;\n motionFromRecording = false;\n motionSeconds = 0;\n releaseMotionObjectUrl();\n motionPreview.srcObject = null;\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.muted = true;\n motionHint.classList.remove('hidden');\n var supported = motionSupported();\n motionGuidance.textContent = supported ? '' : 'Webcam recording needs a secure page with a camera; choose a video file instead.';\n motionRecordBtn.disabled = !supported;\n motionNote.classList.remove('show');\n setMotionButtons('idle');\n }\n\n function openMotionPanel() {\n resetMotionPanel();\n motionOverlay.classList.add('show');\n motionName.focus();\n }\n\n /** Returns whether it was open \u2014 the Escape handler's contract, like closeAssetDetail. */\n function closeMotionPanel() {\n if (!motionOverlay.classList.contains('show')) return false;\n stopMotionStream();\n discardMotionRecorder();\n motionOverlay.classList.remove('show');\n frame.focus();\n return true;\n }\n\n /** The clip is in hand: show it back, and say whether it is within Uthana's 2-60 s window. */\n function acceptMotionClip(blob, seconds) {\n motionBlob = blob;\n motionSeconds = seconds;\n releaseMotionObjectUrl();\n motionObjectUrl = URL.createObjectURL(blob);\n motionPreview.srcObject = null;\n motionPreview.src = motionObjectUrl;\n motionPreview.controls = true;\n motionPreview.muted = true;\n motionHint.classList.add('hidden');\n var note;\n if (blob.size > MOTION_MAX_BYTES) note = 'That video is over 200 MB. Trim it to the seconds that matter.';\n else if (seconds > 0 && seconds < MOTION_MIN_S) note = 'Too short: Uthana needs at least ' + MOTION_MIN_S + ' seconds.';\n else if (seconds > MOTION_MAX_S) note = 'Too long: Uthana takes at most ' + MOTION_MAX_S + ' seconds. Trim it, or record again.';\n var ok = !note;\n motionGuidance.textContent = ok\n ? (seconds > 0 ? formatMotionClock(seconds) + ' of video ready.' : 'Video ready.')\n : note;\n setMotionButtons('ready');\n motionSubmit.disabled = !ok;\n }\n\n async function startMotionRecording() {\n if (!motionSupported()) return;\n motionNote.classList.remove('show');\n try {\n motionStream = await navigator.mediaDevices.getUserMedia({\n video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' }, audio: false\n });\n } catch (error) {\n motionGuidance.textContent = motionCameraError(error);\n return;\n }\n releaseMotionObjectUrl();\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.srcObject = motionStream;\n motionPreview.classList.add('mirror');\n motionHint.classList.add('hidden');\n try { await motionPreview.play(); } catch (error) { /* autoplay refused; the stream still shows on interaction */ }\n var mime = pickMotionMime();\n try {\n motionRecorder = mime ? new MediaRecorder(motionStream, { mimeType: mime }) : new MediaRecorder(motionStream);\n } catch (error) {\n stopMotionStream();\n motionGuidance.textContent = 'This browser cannot record video. Choose a video file instead.';\n return;\n }\n // Count down on the live preview; Stop during the count aborts without recording.\n var token = ++motionCountdownToken;\n setMotionButtons('recording');\n motionGuidance.textContent = 'Get in frame \u2014 recording starts after the count.';\n motionCountdown.classList.add('show');\n for (var left = MOTION_COUNTDOWN_S; left > 0; left--) {\n motionCountdownNumber.textContent = String(left);\n await new Promise(function (resolve) { setTimeout(resolve, 1000); });\n if (token !== motionCountdownToken) return;\n }\n motionCountdown.classList.remove('show');\n motionChunks = [];\n motionRecorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) motionChunks.push(event.data); };\n motionRecorder.onstop = function () {\n var seconds = (Date.now() - motionStartedAt) / 1000;\n var type = motionRecorder.mimeType || mime || 'video/webm';\n stopMotionStream();\n motionFromRecording = true;\n acceptMotionClip(new Blob(motionChunks, { type: type }), seconds);\n };\n motionStartedAt = Date.now();\n motionRecorder.start(1000);\n motionTimer.textContent = '0:00';\n motionTimer.classList.add('show');\n motionGuidance.textContent = 'Recording. Stop when the move is done (auto-stops at ' + MOTION_MAX_S + ' s).';\n motionTickTimer = setInterval(function () {\n var elapsed = (Date.now() - motionStartedAt) / 1000;\n motionTimer.textContent = formatMotionClock(elapsed);\n if (elapsed >= MOTION_MAX_S) stopMotionRecording();\n }, 250);\n }\n\n function stopMotionRecording() {\n if (motionRecorder && motionRecorder.state === 'recording') { motionRecorder.stop(); return; }\n // Stop pressed during the countdown: nothing was recorded, back to the start.\n if (motionCountdown.classList.contains('show')) resetMotionPanel();\n }\n\n function pickMotionFile(file) {\n if (!file) return;\n // Read the duration off the file itself; a picked video knows its length, a fresh WebM often\n // does not (Infinity), so 0 means \"unknown\" and the server's ffprobe is the judge.\n var probe = document.createElement('video');\n probe.preload = 'metadata';\n var url = URL.createObjectURL(file);\n var settled = false;\n var finish = function (seconds) {\n if (settled) return;\n settled = true;\n URL.revokeObjectURL(url);\n motionFromRecording = false;\n acceptMotionClip(file, seconds);\n };\n probe.onloadedmetadata = function () { finish(isFinite(probe.duration) ? probe.duration : 0); };\n probe.onerror = function () { finish(0); };\n probe.src = url;\n setTimeout(function () { finish(0); }, 3000);\n }\n\n var motionPollTimer = null;\n function pollMotionJob() {\n if (motionPollTimer) clearTimeout(motionPollTimer);\n var tick = async function () {\n var job;\n try {\n job = (await (await fetch('/api/editor/motion-status')).json()).job;\n } catch (error) {\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (!job) return;\n if (job.status === 'running') {\n showMotionNote(job.message, '');\n setStatus('saving', 'Making the animation\u2026');\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (job.status === 'failed') {\n showMotionNote('Could not make the animation: ' + job.message, '');\n setStatus('error', 'Animation failed');\n setMotionButtons('ready');\n return;\n }\n showMotionNote(job.message + ' Reloading to show it.', '');\n setStatus('saved', 'Animation added');\n setMotionButtons('ready');\n if (autoReload && !voxelSessionActive && !voxelSessionPending) start();\n else banner.classList.add('show');\n };\n void tick();\n }\n\n async function submitMotion() {\n if (!motionBlob) return;\n var name = motionName.value.trim();\n if (name === '') { motionName.focus(); return; }\n var query = '?name=' + encodeURIComponent(name)\n + '&slot=' + encodeURIComponent(motionSlot.value)\n + '&loop=' + (motionLoop.checked ? '1' : '0')\n + '&autoTrim=' + (motionFromRecording ? '1' : '0');\n motionSubmit.disabled = true;\n showMotionNote('Uploading the video\u2026', '');\n var response, result;\n try {\n response = await fetch('/api/editor/motion-video' + query, {\n method: 'POST',\n headers: { 'Content-Type': motionBlob.type || 'video/webm' },\n body: motionBlob\n });\n result = await response.json();\n } catch (error) {\n showMotionNote('Could not send the video to the sidecar.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (!response.ok || result.ok !== true) {\n showMotionNote(result.error || 'The sidecar refused the video.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (result.running !== true) {\n // The one case the sidecar cannot run itself: nobody is logged in.\n showMotionNote(result.reason || '', result.command || '');\n setStatus('saved', 'Video saved');\n return;\n }\n showMotionNote(result.note || 'Making the animation \u2014 this takes a few minutes. You can keep editing.', '');\n setStatus('saving', 'Making the animation\u2026');\n pollMotionJob();\n }\n\n motionToggle.addEventListener('click', openMotionPanel);\n document.getElementById('motion-close').addEventListener('click', function () { closeMotionPanel(); });\n motionOverlay.addEventListener('click', function (event) {\n if (event.target === motionOverlay) closeMotionPanel();\n });\n motionRecordBtn.addEventListener('click', function () { void startMotionRecording(); });\n motionStopBtn.addEventListener('click', stopMotionRecording);\n motionRetakeBtn.addEventListener('click', resetMotionPanel);\n motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });\n motionFile.addEventListener('change', function () { pickMotionFile(motionFile.files && motionFile.files[0]); });\n motionSubmit.addEventListener('click', function () { void submitMotion(); });\n";
|
|
20
|
+
export declare const MOTION_PANEL_JS = "\n // ---- Record motion: webcam or file -> Uthana video-to-motion -> assets[] ----------------------\n var motionToggle = document.getElementById('motion');\n var motionOverlay = document.getElementById('motion-overlay');\n var motionPreview = document.getElementById('motion-preview');\n var motionTimer = document.getElementById('motion-timer');\n var motionCountdown = document.getElementById('motion-countdown');\n var motionCountdownNumber = document.getElementById('motion-countdown-number');\n /** Seconds shown on the live preview before the recorder starts, so the first frames are the pose. */\n var MOTION_COUNTDOWN_S = 3;\n var motionCountdownToken = 0;\n var motionHint = document.getElementById('motion-hint');\n var motionRecordBtn = document.getElementById('motion-record');\n var motionStopBtn = document.getElementById('motion-stop');\n var motionPickBtn = document.getElementById('motion-pick');\n var motionRetakeBtn = document.getElementById('motion-retake');\n var motionFile = document.getElementById('motion-file');\n var motionGuidance = document.getElementById('motion-guidance');\n var motionName = document.getElementById('motion-name');\n var motionSlot = document.getElementById('motion-slot');\n var motionLoop = document.getElementById('motion-loop');\n var motionSubmit = document.getElementById('motion-submit');\n var motionNote = document.getElementById('motion-note');\n var MOTION_MIN_S = 2;\n var MOTION_MAX_S = 60;\n var MOTION_MAX_BYTES = 200 * 1024 * 1024;\n var motionBlob = null;\n // Whether motionBlob came from the webcam recorder (auto-trim applies) or a picked file (it does not).\n var motionFromRecording = false;\n var motionSeconds = 0;\n var motionStream = null;\n var motionRecorder = null;\n var motionChunks = [];\n var motionTickTimer = null;\n var motionStartedAt = 0;\n var motionObjectUrl = null;\n // ---- Trim window: seeded by the sidecar's trim analysis, adjusted on the handles ------------\n var motionTrimWrap = document.getElementById('motion-trim');\n var motionTrimBar = document.getElementById('motion-trimbar');\n var motionTrimKept = document.getElementById('motion-trim-kept');\n var motionTrimPlayhead = document.getElementById('motion-trim-playhead');\n var motionTrimStartHandle = document.getElementById('motion-trim-start');\n var motionTrimEndHandle = document.getElementById('motion-trim-end');\n var motionTrimNote = document.getElementById('motion-trim-note');\n var motionTrimFullBtn = document.getElementById('motion-trim-full');\n var motionTrimPreviewBtn = document.getElementById('motion-trim-preview');\n // True while \"Preview the kept range\" is playing trimStart -> trimEnd.\n var motionPreviewingTrim = false;\n var MOTION_TRIM_MIN_KEPT_S = 2;\n var motionTrimDuration = 0;\n var motionTrimStart = 0;\n var motionTrimEnd = 0;\n var motionPendingId = null;\n var motionAnalysisState = 'idle';\n var motionAnalysisToken = 0;\n var motionAnalyzePollTimer = null;\n\n function motionHasTrim() {\n return motionTrimDuration > 0 && motionTrimEnd > motionTrimStart\n && (motionTrimStart >= 0.1 || motionTrimDuration - motionTrimEnd >= 0.1);\n }\n\n function renderMotionTrim() {\n if (motionTrimDuration <= 0) { motionTrimWrap.classList.remove('show'); return; }\n motionTrimWrap.classList.add('show');\n var pct = function (seconds) {\n return Math.min(Math.max(seconds / motionTrimDuration, 0), 1) * 100 + '%';\n };\n motionTrimKept.style.left = pct(motionTrimStart);\n motionTrimKept.style.width = Math.max(0, (motionTrimEnd - motionTrimStart) / motionTrimDuration) * 100 + '%';\n motionTrimStartHandle.style.left = pct(motionTrimStart);\n motionTrimEndHandle.style.left = pct(motionTrimEnd);\n motionTrimFullBtn.hidden = !motionHasTrim();\n motionTrimPreviewBtn.textContent = motionPreviewingTrim\n ? '\u23F9 Stop'\n : (motionHasTrim() ? '\u25B6 Preview the kept range' : '\u25B6 Play');\n var note;\n if (motionAnalysisState === 'running') {\n note = 'Analyzing the clip for a good trim\u2026';\n } else if (!motionHasTrim()) {\n note = 'Using the full clip.';\n } else {\n var parts = [];\n if (motionTrimStart >= 0.1) parts.push(motionTrimStart.toFixed(1) + 's from the start');\n if (motionTrimDuration - motionTrimEnd >= 0.1) parts.push((motionTrimDuration - motionTrimEnd).toFixed(1) + 's from the end');\n note = 'Trimming ' + parts.join(' and ') + '.';\n }\n motionTrimNote.textContent = note;\n }\n\n function resetMotionTrim() {\n motionPreviewingTrim = false;\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n motionPendingId = null;\n motionAnalysisState = 'idle';\n motionTrimDuration = 0;\n motionTrimStart = 0;\n motionTrimEnd = 0;\n motionTrimWrap.classList.remove('show');\n }\n\n function seedMotionTrim(durationSeconds) {\n if (!(durationSeconds > 0)) return;\n motionTrimDuration = durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = durationSeconds;\n // Review playback is the trim: the Play/Preview button below drives the\n // video, and a native full-clip scrubber under a trim bar read as \"it\n // plays the whole video anyway\". Controls stay native only while the\n // duration is unknown and the bar cannot render.\n motionPreview.controls = false;\n renderMotionTrim();\n }\n\n function applyMotionSuggestion(suggestion) {\n if (!suggestion) { renderMotionTrim(); return; }\n if (suggestion.durationSeconds > 0) {\n motionTrimDuration = suggestion.durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = suggestion.durationSeconds;\n }\n if (suggestion.confidence === 'high'\n && typeof suggestion.suggestedStartSeconds === 'number'\n && typeof suggestion.suggestedEndSeconds === 'number'\n && suggestion.suggestedEndSeconds > suggestion.suggestedStartSeconds) {\n motionTrimStart = suggestion.suggestedStartSeconds;\n motionTrimEnd = Math.min(suggestion.suggestedEndSeconds, motionTrimDuration || suggestion.suggestedEndSeconds);\n }\n renderMotionTrim();\n }\n\n function beginMotionTrimDrag(which, event) {\n if (motionTrimDuration <= 0) return;\n var handle = event.currentTarget;\n handle.setPointerCapture(event.pointerId);\n var move = function (e) {\n var rect = motionTrimBar.getBoundingClientRect();\n if (rect.width === 0) return;\n var at = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1) * motionTrimDuration;\n if (which === 'start') motionTrimStart = Math.min(at, motionTrimEnd - MOTION_TRIM_MIN_KEPT_S);\n else motionTrimEnd = Math.max(at, motionTrimStart + MOTION_TRIM_MIN_KEPT_S);\n // Show the exact frame the cut lands on while the handle moves.\n motionPreview.pause();\n motionPreviewingTrim = false;\n motionPreview.currentTime = which === 'start' ? motionTrimStart : motionTrimEnd;\n renderMotionTrim();\n };\n var up = function () {\n handle.removeEventListener('pointermove', move);\n handle.removeEventListener('pointerup', up);\n handle.removeEventListener('pointercancel', up);\n };\n handle.addEventListener('pointermove', move);\n handle.addEventListener('pointerup', up);\n handle.addEventListener('pointercancel', up);\n event.preventDefault();\n }\n\n function pollMotionAnalysis(token) {\n var tick = async function () {\n if (token !== motionAnalysisToken) return;\n var payload;\n try {\n payload = await (await fetch('/api/editor/motion-analyze-status')).json();\n } catch (error) {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n if (token !== motionAnalysisToken) return;\n if (payload && payload.status === 'done') {\n motionAnalysisState = 'done';\n applyMotionSuggestion(payload.suggestion);\n return;\n }\n if (payload && payload.status === 'running') {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n // failed / unavailable / none: no suggestion, the full-range handles stand.\n motionAnalysisState = 'failed';\n renderMotionTrim();\n };\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n }\n\n /**\n * Hand the clip to the sidecar ahead of submission: it uploads once and runs the unbilled trim\n * analysis while the creator reviews. Degraded, never blocking \u2014 any failure just leaves the\n * handles at full range, and Submit works throughout.\n */\n async function startMotionAnalysis(blob) {\n var token = ++motionAnalysisToken;\n motionPendingId = null;\n motionAnalysisState = 'running';\n renderMotionTrim();\n var result = null;\n try {\n var response = await fetch('/api/editor/motion-analyze', {\n method: 'POST',\n headers: { 'Content-Type': blob.type || 'video/webm' },\n body: blob\n });\n result = await response.json();\n } catch (error) { /* the sidecar said nothing; treat as unavailable */ }\n if (token !== motionAnalysisToken) return;\n if (!result || result.ok !== true || result.analysing !== true) {\n motionAnalysisState = 'idle';\n renderMotionTrim();\n return;\n }\n motionPendingId = result.pendingId || null;\n pollMotionAnalysis(token);\n }\n\n function motionSupported() {\n return !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia\n && typeof MediaRecorder !== 'undefined');\n }\n\n /** mp4 first (Safari), then the WebM the rest produce; asset-forger re-encodes either. */\n function pickMotionMime() {\n var candidates = ['video/mp4', 'video/webm;codecs=vp9', 'video/webm'];\n for (var i = 0; i < candidates.length; i++) {\n if (MediaRecorder.isTypeSupported(candidates[i])) return candidates[i];\n }\n return '';\n }\n\n function motionCameraError(error) {\n var name = error && error.name;\n if (name === 'NotAllowedError' || name === 'SecurityError') return 'Camera access was refused. Allow it in the address bar, or choose a video file.';\n if (name === 'NotFoundError' || name === 'OverconstrainedError') return 'No camera found. Choose a video file instead.';\n if (name === 'NotReadableError') return 'The camera is in use by another app. Close it, or choose a video file.';\n return 'Could not start the camera: ' + ((error && error.message) || 'unknown error');\n }\n\n function formatMotionClock(seconds) {\n var s = Math.max(0, Math.floor(seconds));\n var rest = s % 60;\n return Math.floor(s / 60) + ':' + (rest < 10 ? '0' : '') + rest;\n }\n\n function showMotionNote(text, command) {\n document.getElementById('motion-note-text').textContent = text;\n document.getElementById('motion-command').textContent = command || '';\n motionNote.classList.add('show');\n }\n\n function releaseMotionObjectUrl() {\n if (motionObjectUrl) { URL.revokeObjectURL(motionObjectUrl); motionObjectUrl = null; }\n }\n\n /** Stop the recorder if one is still live \u2014 it may already have stopped itself. */\n function discardMotionRecorder() {\n if (motionRecorder && motionRecorder.state !== 'inactive') {\n try { motionRecorder.stop(); } catch (error) { /* already stopped */ }\n }\n }\n\n function stopMotionStream() {\n if (motionStream) {\n motionStream.getTracks().forEach(function (track) { track.stop(); });\n motionStream = null;\n }\n if (motionTickTimer) { clearInterval(motionTickTimer); motionTickTimer = null; }\n motionCountdownToken++;\n motionCountdown.classList.remove('show');\n motionTimer.classList.remove('show');\n motionPreview.classList.remove('mirror');\n }\n\n function setMotionButtons(state) {\n motionRecordBtn.hidden = state !== 'idle';\n motionPickBtn.hidden = state !== 'idle';\n motionStopBtn.hidden = state !== 'recording';\n motionRetakeBtn.hidden = state !== 'ready';\n motionSubmit.disabled = state !== 'ready';\n }\n\n function resetMotionPanel() {\n resetMotionTrim();\n stopMotionStream();\n discardMotionRecorder();\n motionRecorder = null;\n motionChunks = [];\n motionBlob = null;\n motionFromRecording = false;\n motionSeconds = 0;\n releaseMotionObjectUrl();\n motionPreview.srcObject = null;\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.muted = true;\n motionHint.classList.remove('hidden');\n var supported = motionSupported();\n motionGuidance.textContent = supported ? '' : 'Webcam recording needs a secure page with a camera; choose a video file instead.';\n motionRecordBtn.disabled = !supported;\n motionNote.classList.remove('show');\n setMotionButtons('idle');\n }\n\n function openMotionPanel() {\n resetMotionPanel();\n motionOverlay.classList.add('show');\n motionName.focus();\n }\n\n /** Returns whether it was open \u2014 the Escape handler's contract, like closeAssetDetail. */\n function closeMotionPanel() {\n if (!motionOverlay.classList.contains('show')) return false;\n stopMotionStream();\n discardMotionRecorder();\n motionOverlay.classList.remove('show');\n frame.focus();\n return true;\n }\n\n /** The clip is in hand: show it back, and say whether it is within Uthana's 2-60 s window. */\n function acceptMotionClip(blob, seconds) {\n motionBlob = blob;\n motionSeconds = seconds;\n releaseMotionObjectUrl();\n motionObjectUrl = URL.createObjectURL(blob);\n motionPreview.srcObject = null;\n motionPreview.src = motionObjectUrl;\n motionPreview.controls = true;\n motionPreview.muted = true;\n motionHint.classList.add('hidden');\n var note;\n if (blob.size > MOTION_MAX_BYTES) note = 'That video is over 200 MB. Trim it to the seconds that matter.';\n else if (seconds > 0 && seconds < MOTION_MIN_S) note = 'Too short: Uthana needs at least ' + MOTION_MIN_S + ' seconds.';\n else if (seconds > MOTION_MAX_S) note = 'Too long: Uthana takes at most ' + MOTION_MAX_S + ' seconds. Trim it, or record again.';\n var ok = !note;\n motionGuidance.textContent = ok\n ? (seconds > 0 ? formatMotionClock(seconds) + ' of video ready.' : 'Video ready.')\n : note;\n setMotionButtons('ready');\n motionSubmit.disabled = !ok;\n resetMotionTrim();\n if (ok) {\n seedMotionTrim(seconds);\n // A fresh WebM often reports no duration; the preview's metadata is the fallback scale.\n motionPreview.onloadedmetadata = function () {\n if (motionTrimDuration === 0 && isFinite(motionPreview.duration) && motionPreview.duration > 0) {\n seedMotionTrim(motionPreview.duration);\n }\n };\n void startMotionAnalysis(blob);\n }\n }\n\n async function startMotionRecording() {\n if (!motionSupported()) return;\n motionNote.classList.remove('show');\n try {\n motionStream = await navigator.mediaDevices.getUserMedia({\n video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' }, audio: false\n });\n } catch (error) {\n motionGuidance.textContent = motionCameraError(error);\n return;\n }\n releaseMotionObjectUrl();\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.srcObject = motionStream;\n motionPreview.classList.add('mirror');\n motionHint.classList.add('hidden');\n try { await motionPreview.play(); } catch (error) { /* autoplay refused; the stream still shows on interaction */ }\n var mime = pickMotionMime();\n try {\n motionRecorder = mime ? new MediaRecorder(motionStream, { mimeType: mime }) : new MediaRecorder(motionStream);\n } catch (error) {\n stopMotionStream();\n motionGuidance.textContent = 'This browser cannot record video. Choose a video file instead.';\n return;\n }\n // Count down on the live preview; Stop during the count aborts without recording.\n var token = ++motionCountdownToken;\n setMotionButtons('recording');\n motionGuidance.textContent = 'Get in frame \u2014 recording starts after the count.';\n motionCountdown.classList.add('show');\n for (var left = MOTION_COUNTDOWN_S; left > 0; left--) {\n motionCountdownNumber.textContent = String(left);\n await new Promise(function (resolve) { setTimeout(resolve, 1000); });\n if (token !== motionCountdownToken) return;\n }\n motionCountdown.classList.remove('show');\n motionChunks = [];\n motionRecorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) motionChunks.push(event.data); };\n motionRecorder.onstop = function () {\n var seconds = (Date.now() - motionStartedAt) / 1000;\n var type = motionRecorder.mimeType || mime || 'video/webm';\n stopMotionStream();\n motionFromRecording = true;\n acceptMotionClip(new Blob(motionChunks, { type: type }), seconds);\n };\n motionStartedAt = Date.now();\n motionRecorder.start(1000);\n motionTimer.textContent = '0:00';\n motionTimer.classList.add('show');\n motionGuidance.textContent = 'Recording. Stop when the move is done (auto-stops at ' + MOTION_MAX_S + ' s).';\n motionTickTimer = setInterval(function () {\n var elapsed = (Date.now() - motionStartedAt) / 1000;\n motionTimer.textContent = formatMotionClock(elapsed);\n if (elapsed >= MOTION_MAX_S) stopMotionRecording();\n }, 250);\n }\n\n function stopMotionRecording() {\n if (motionRecorder && motionRecorder.state === 'recording') { motionRecorder.stop(); return; }\n // Stop pressed during the countdown: nothing was recorded, back to the start.\n if (motionCountdown.classList.contains('show')) resetMotionPanel();\n }\n\n function pickMotionFile(file) {\n if (!file) return;\n // Read the duration off the file itself; a picked video knows its length, a fresh WebM often\n // does not (Infinity), so 0 means \"unknown\" and the server's ffprobe is the judge.\n var probe = document.createElement('video');\n probe.preload = 'metadata';\n var url = URL.createObjectURL(file);\n var settled = false;\n var finish = function (seconds) {\n if (settled) return;\n settled = true;\n URL.revokeObjectURL(url);\n motionFromRecording = false;\n acceptMotionClip(file, seconds);\n };\n probe.onloadedmetadata = function () { finish(isFinite(probe.duration) ? probe.duration : 0); };\n probe.onerror = function () { finish(0); };\n probe.src = url;\n setTimeout(function () { finish(0); }, 3000);\n }\n\n var motionPollTimer = null;\n function pollMotionJob() {\n if (motionPollTimer) clearTimeout(motionPollTimer);\n var tick = async function () {\n var job;\n try {\n job = (await (await fetch('/api/editor/motion-status')).json()).job;\n } catch (error) {\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (!job) return;\n if (job.status === 'running') {\n showMotionNote(job.message, '');\n setStatus('saving', 'Making the animation\u2026');\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (job.status === 'failed') {\n showMotionNote('Could not make the animation: ' + job.message, '');\n setStatus('error', 'Animation failed');\n setMotionButtons('ready');\n return;\n }\n showMotionNote(job.message + ' Reloading to show it.', '');\n setStatus('saved', 'Animation added');\n setMotionButtons('ready');\n if (autoReload && !voxelSessionActive && !voxelSessionPending) start();\n else banner.classList.add('show');\n };\n void tick();\n }\n\n async function submitMotion() {\n if (!motionBlob) return;\n var name = motionName.value.trim();\n if (name === '') { motionName.focus(); return; }\n // Whatever the handles show is what gets made; a still-running analysis must not move them.\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n var query = '?name=' + encodeURIComponent(name)\n + '&slot=' + encodeURIComponent(motionSlot.value)\n + '&loop=' + (motionLoop.checked ? '1' : '0');\n if (motionHasTrim()) {\n query += '&trimStart=' + encodeURIComponent(String(Math.round(motionTrimStart * 100) / 100))\n + '&trimEnd=' + encodeURIComponent(String(Math.round(motionTrimEnd * 100) / 100));\n }\n if (motionPendingId) {\n query += '&pendingId=' + encodeURIComponent(motionPendingId);\n }\n motionSubmit.disabled = true;\n showMotionNote('Uploading the video\u2026', '');\n var response, result;\n try {\n response = await fetch('/api/editor/motion-video' + query, {\n method: 'POST',\n headers: { 'Content-Type': motionBlob.type || 'video/webm' },\n body: motionBlob\n });\n result = await response.json();\n } catch (error) {\n showMotionNote('Could not send the video to the sidecar.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (!response.ok || result.ok !== true) {\n showMotionNote(result.error || 'The sidecar refused the video.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (result.running !== true) {\n // The one case the sidecar cannot run itself: nobody is logged in.\n showMotionNote(result.reason || '', result.command || '');\n setStatus('saved', 'Video saved');\n return;\n }\n showMotionNote(result.note || 'Making the animation \u2014 this takes a few minutes. You can keep editing.', '');\n setStatus('saving', 'Making the animation\u2026');\n pollMotionJob();\n }\n\n motionToggle.addEventListener('click', openMotionPanel);\n document.getElementById('motion-close').addEventListener('click', function () { closeMotionPanel(); });\n motionOverlay.addEventListener('click', function (event) {\n if (event.target === motionOverlay) closeMotionPanel();\n });\n motionRecordBtn.addEventListener('click', function () { void startMotionRecording(); });\n motionStopBtn.addEventListener('click', stopMotionRecording);\n motionRetakeBtn.addEventListener('click', resetMotionPanel);\n motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });\n motionFile.addEventListener('change', function () { pickMotionFile(motionFile.files && motionFile.files[0]); });\n motionSubmit.addEventListener('click', function () { void submitMotion(); });\n motionTrimStartHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('start', event); });\n motionTrimEndHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('end', event); });\n motionTrimFullBtn.addEventListener('click', function () {\n motionTrimStart = 0;\n motionTrimEnd = motionTrimDuration;\n renderMotionTrim();\n });\n // \"Preview the kept range\" plays exactly trimStart -> trimEnd once and stops\n // on the last kept frame: what was just watched IS what the animation is\n // made from.\n motionTrimPreviewBtn.addEventListener('click', function () {\n if (motionPreviewingTrim) {\n motionPreview.pause();\n motionPreviewingTrim = false;\n renderMotionTrim();\n return;\n }\n motionPreview.currentTime = motionTrimStart;\n motionPreviewingTrim = true;\n renderMotionTrim();\n var played = motionPreview.play();\n if (played && played.catch) {\n played.catch(function () { motionPreviewingTrim = false; renderMotionTrim(); });\n }\n });\n motionPreview.addEventListener('pause', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('ended', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('timeupdate', function () {\n if (motionTrimDuration > 0) {\n motionTrimPlayhead.style.left = Math.min(Math.max(motionPreview.currentTime / motionTrimDuration, 0), 1) * 100 + '%';\n }\n if (motionPreviewingTrim && motionPreview.currentTime >= motionTrimEnd) {\n motionPreview.pause();\n motionPreview.currentTime = motionTrimEnd;\n motionPreviewingTrim = false;\n renderMotionTrim();\n }\n });\n";
|
|
@@ -32,6 +32,20 @@ export const MOTION_PANEL_CSS = `
|
|
|
32
32
|
#motion-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
33
33
|
text-align: center; padding: 20px; font-size: 13px; color: var(--bm-text-muted); }
|
|
34
34
|
#motion-hint.hidden { display: none; }
|
|
35
|
+
#motion-trim { display: none; margin-top: 10px; }
|
|
36
|
+
#motion-trim.show { display: block; }
|
|
37
|
+
#motion-trimbar { position: relative; height: 14px; border-radius: 7px; background: var(--bm-surface-alt);
|
|
38
|
+
touch-action: none; }
|
|
39
|
+
#motion-trim-kept { position: absolute; top: 0; bottom: 0; border-radius: 7px; background: var(--bm-aqua); }
|
|
40
|
+
#motion-trim-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--bm-pink);
|
|
41
|
+
pointer-events: none; }
|
|
42
|
+
.motion-trim-handle { position: absolute; top: 50%; width: 14px; height: 22px;
|
|
43
|
+
transform: translate(-50%, -50%); border-radius: 5px; background: var(--bm-text);
|
|
44
|
+
cursor: ew-resize; touch-action: none; }
|
|
45
|
+
#motion-trim-row { display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
46
|
+
margin-top: 6px; min-height: 24px; }
|
|
47
|
+
#motion-trim-note { font-size: 11px; color: var(--bm-text-muted); }
|
|
48
|
+
#motion-trim-actions { display: flex; gap: 6px; }
|
|
35
49
|
#motion-sources { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
|
|
36
50
|
#motion-file { display: none; }
|
|
37
51
|
#motion-guidance { margin: 10px 0 0; font-size: 12px; color: var(--bm-text-muted); }
|
|
@@ -59,6 +73,21 @@ export const MOTION_PANEL_HTML = ` <div id="motion-overlay" class="bm-overlay">
|
|
|
59
73
|
<div id="motion-hint">Whole body in frame, one person, plain background, good light.
|
|
60
74
|
2–60 seconds.</div>
|
|
61
75
|
</div>
|
|
76
|
+
<div id="motion-trim">
|
|
77
|
+
<div id="motion-trimbar" title="Drag the handles to choose what the animation is made from">
|
|
78
|
+
<div id="motion-trim-kept"></div>
|
|
79
|
+
<div id="motion-trim-playhead"></div>
|
|
80
|
+
<div id="motion-trim-start" class="motion-trim-handle"></div>
|
|
81
|
+
<div id="motion-trim-end" class="motion-trim-handle"></div>
|
|
82
|
+
</div>
|
|
83
|
+
<div id="motion-trim-row">
|
|
84
|
+
<span id="motion-trim-note"></span>
|
|
85
|
+
<span id="motion-trim-actions">
|
|
86
|
+
<button id="motion-trim-preview" class="bm-pill" type="button">▶ Play</button>
|
|
87
|
+
<button id="motion-trim-full" class="bm-pill bm-pill--ghost" type="button" hidden>Use full clip</button>
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
62
91
|
<div id="motion-sources">
|
|
63
92
|
<button id="motion-record" class="bm-pill bm-pill--primary" type="button">Record with webcam</button>
|
|
64
93
|
<button id="motion-stop" class="bm-pill" type="button" hidden>Stop</button>
|
|
@@ -134,6 +163,184 @@ export const MOTION_PANEL_JS = `
|
|
|
134
163
|
var motionTickTimer = null;
|
|
135
164
|
var motionStartedAt = 0;
|
|
136
165
|
var motionObjectUrl = null;
|
|
166
|
+
// ---- Trim window: seeded by the sidecar's trim analysis, adjusted on the handles ------------
|
|
167
|
+
var motionTrimWrap = document.getElementById('motion-trim');
|
|
168
|
+
var motionTrimBar = document.getElementById('motion-trimbar');
|
|
169
|
+
var motionTrimKept = document.getElementById('motion-trim-kept');
|
|
170
|
+
var motionTrimPlayhead = document.getElementById('motion-trim-playhead');
|
|
171
|
+
var motionTrimStartHandle = document.getElementById('motion-trim-start');
|
|
172
|
+
var motionTrimEndHandle = document.getElementById('motion-trim-end');
|
|
173
|
+
var motionTrimNote = document.getElementById('motion-trim-note');
|
|
174
|
+
var motionTrimFullBtn = document.getElementById('motion-trim-full');
|
|
175
|
+
var motionTrimPreviewBtn = document.getElementById('motion-trim-preview');
|
|
176
|
+
// True while "Preview the kept range" is playing trimStart -> trimEnd.
|
|
177
|
+
var motionPreviewingTrim = false;
|
|
178
|
+
var MOTION_TRIM_MIN_KEPT_S = 2;
|
|
179
|
+
var motionTrimDuration = 0;
|
|
180
|
+
var motionTrimStart = 0;
|
|
181
|
+
var motionTrimEnd = 0;
|
|
182
|
+
var motionPendingId = null;
|
|
183
|
+
var motionAnalysisState = 'idle';
|
|
184
|
+
var motionAnalysisToken = 0;
|
|
185
|
+
var motionAnalyzePollTimer = null;
|
|
186
|
+
|
|
187
|
+
function motionHasTrim() {
|
|
188
|
+
return motionTrimDuration > 0 && motionTrimEnd > motionTrimStart
|
|
189
|
+
&& (motionTrimStart >= 0.1 || motionTrimDuration - motionTrimEnd >= 0.1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderMotionTrim() {
|
|
193
|
+
if (motionTrimDuration <= 0) { motionTrimWrap.classList.remove('show'); return; }
|
|
194
|
+
motionTrimWrap.classList.add('show');
|
|
195
|
+
var pct = function (seconds) {
|
|
196
|
+
return Math.min(Math.max(seconds / motionTrimDuration, 0), 1) * 100 + '%';
|
|
197
|
+
};
|
|
198
|
+
motionTrimKept.style.left = pct(motionTrimStart);
|
|
199
|
+
motionTrimKept.style.width = Math.max(0, (motionTrimEnd - motionTrimStart) / motionTrimDuration) * 100 + '%';
|
|
200
|
+
motionTrimStartHandle.style.left = pct(motionTrimStart);
|
|
201
|
+
motionTrimEndHandle.style.left = pct(motionTrimEnd);
|
|
202
|
+
motionTrimFullBtn.hidden = !motionHasTrim();
|
|
203
|
+
motionTrimPreviewBtn.textContent = motionPreviewingTrim
|
|
204
|
+
? '\u23F9 Stop'
|
|
205
|
+
: (motionHasTrim() ? '\u25B6 Preview the kept range' : '\u25B6 Play');
|
|
206
|
+
var note;
|
|
207
|
+
if (motionAnalysisState === 'running') {
|
|
208
|
+
note = 'Analyzing the clip for a good trim\u2026';
|
|
209
|
+
} else if (!motionHasTrim()) {
|
|
210
|
+
note = 'Using the full clip.';
|
|
211
|
+
} else {
|
|
212
|
+
var parts = [];
|
|
213
|
+
if (motionTrimStart >= 0.1) parts.push(motionTrimStart.toFixed(1) + 's from the start');
|
|
214
|
+
if (motionTrimDuration - motionTrimEnd >= 0.1) parts.push((motionTrimDuration - motionTrimEnd).toFixed(1) + 's from the end');
|
|
215
|
+
note = 'Trimming ' + parts.join(' and ') + '.';
|
|
216
|
+
}
|
|
217
|
+
motionTrimNote.textContent = note;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resetMotionTrim() {
|
|
221
|
+
motionPreviewingTrim = false;
|
|
222
|
+
motionAnalysisToken++;
|
|
223
|
+
if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }
|
|
224
|
+
motionPendingId = null;
|
|
225
|
+
motionAnalysisState = 'idle';
|
|
226
|
+
motionTrimDuration = 0;
|
|
227
|
+
motionTrimStart = 0;
|
|
228
|
+
motionTrimEnd = 0;
|
|
229
|
+
motionTrimWrap.classList.remove('show');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function seedMotionTrim(durationSeconds) {
|
|
233
|
+
if (!(durationSeconds > 0)) return;
|
|
234
|
+
motionTrimDuration = durationSeconds;
|
|
235
|
+
motionTrimStart = 0;
|
|
236
|
+
motionTrimEnd = durationSeconds;
|
|
237
|
+
// Review playback is the trim: the Play/Preview button below drives the
|
|
238
|
+
// video, and a native full-clip scrubber under a trim bar read as "it
|
|
239
|
+
// plays the whole video anyway". Controls stay native only while the
|
|
240
|
+
// duration is unknown and the bar cannot render.
|
|
241
|
+
motionPreview.controls = false;
|
|
242
|
+
renderMotionTrim();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function applyMotionSuggestion(suggestion) {
|
|
246
|
+
if (!suggestion) { renderMotionTrim(); return; }
|
|
247
|
+
if (suggestion.durationSeconds > 0) {
|
|
248
|
+
motionTrimDuration = suggestion.durationSeconds;
|
|
249
|
+
motionTrimStart = 0;
|
|
250
|
+
motionTrimEnd = suggestion.durationSeconds;
|
|
251
|
+
}
|
|
252
|
+
if (suggestion.confidence === 'high'
|
|
253
|
+
&& typeof suggestion.suggestedStartSeconds === 'number'
|
|
254
|
+
&& typeof suggestion.suggestedEndSeconds === 'number'
|
|
255
|
+
&& suggestion.suggestedEndSeconds > suggestion.suggestedStartSeconds) {
|
|
256
|
+
motionTrimStart = suggestion.suggestedStartSeconds;
|
|
257
|
+
motionTrimEnd = Math.min(suggestion.suggestedEndSeconds, motionTrimDuration || suggestion.suggestedEndSeconds);
|
|
258
|
+
}
|
|
259
|
+
renderMotionTrim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function beginMotionTrimDrag(which, event) {
|
|
263
|
+
if (motionTrimDuration <= 0) return;
|
|
264
|
+
var handle = event.currentTarget;
|
|
265
|
+
handle.setPointerCapture(event.pointerId);
|
|
266
|
+
var move = function (e) {
|
|
267
|
+
var rect = motionTrimBar.getBoundingClientRect();
|
|
268
|
+
if (rect.width === 0) return;
|
|
269
|
+
var at = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1) * motionTrimDuration;
|
|
270
|
+
if (which === 'start') motionTrimStart = Math.min(at, motionTrimEnd - MOTION_TRIM_MIN_KEPT_S);
|
|
271
|
+
else motionTrimEnd = Math.max(at, motionTrimStart + MOTION_TRIM_MIN_KEPT_S);
|
|
272
|
+
// Show the exact frame the cut lands on while the handle moves.
|
|
273
|
+
motionPreview.pause();
|
|
274
|
+
motionPreviewingTrim = false;
|
|
275
|
+
motionPreview.currentTime = which === 'start' ? motionTrimStart : motionTrimEnd;
|
|
276
|
+
renderMotionTrim();
|
|
277
|
+
};
|
|
278
|
+
var up = function () {
|
|
279
|
+
handle.removeEventListener('pointermove', move);
|
|
280
|
+
handle.removeEventListener('pointerup', up);
|
|
281
|
+
handle.removeEventListener('pointercancel', up);
|
|
282
|
+
};
|
|
283
|
+
handle.addEventListener('pointermove', move);
|
|
284
|
+
handle.addEventListener('pointerup', up);
|
|
285
|
+
handle.addEventListener('pointercancel', up);
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function pollMotionAnalysis(token) {
|
|
290
|
+
var tick = async function () {
|
|
291
|
+
if (token !== motionAnalysisToken) return;
|
|
292
|
+
var payload;
|
|
293
|
+
try {
|
|
294
|
+
payload = await (await fetch('/api/editor/motion-analyze-status')).json();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
motionAnalyzePollTimer = setTimeout(tick, 2000);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (token !== motionAnalysisToken) return;
|
|
300
|
+
if (payload && payload.status === 'done') {
|
|
301
|
+
motionAnalysisState = 'done';
|
|
302
|
+
applyMotionSuggestion(payload.suggestion);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (payload && payload.status === 'running') {
|
|
306
|
+
motionAnalyzePollTimer = setTimeout(tick, 2000);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// failed / unavailable / none: no suggestion, the full-range handles stand.
|
|
310
|
+
motionAnalysisState = 'failed';
|
|
311
|
+
renderMotionTrim();
|
|
312
|
+
};
|
|
313
|
+
motionAnalyzePollTimer = setTimeout(tick, 2000);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Hand the clip to the sidecar ahead of submission: it uploads once and runs the unbilled trim
|
|
318
|
+
* analysis while the creator reviews. Degraded, never blocking — any failure just leaves the
|
|
319
|
+
* handles at full range, and Submit works throughout.
|
|
320
|
+
*/
|
|
321
|
+
async function startMotionAnalysis(blob) {
|
|
322
|
+
var token = ++motionAnalysisToken;
|
|
323
|
+
motionPendingId = null;
|
|
324
|
+
motionAnalysisState = 'running';
|
|
325
|
+
renderMotionTrim();
|
|
326
|
+
var result = null;
|
|
327
|
+
try {
|
|
328
|
+
var response = await fetch('/api/editor/motion-analyze', {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
headers: { 'Content-Type': blob.type || 'video/webm' },
|
|
331
|
+
body: blob
|
|
332
|
+
});
|
|
333
|
+
result = await response.json();
|
|
334
|
+
} catch (error) { /* the sidecar said nothing; treat as unavailable */ }
|
|
335
|
+
if (token !== motionAnalysisToken) return;
|
|
336
|
+
if (!result || result.ok !== true || result.analysing !== true) {
|
|
337
|
+
motionAnalysisState = 'idle';
|
|
338
|
+
renderMotionTrim();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
motionPendingId = result.pendingId || null;
|
|
342
|
+
pollMotionAnalysis(token);
|
|
343
|
+
}
|
|
137
344
|
|
|
138
345
|
function motionSupported() {
|
|
139
346
|
return !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia
|
|
@@ -201,6 +408,7 @@ export const MOTION_PANEL_JS = `
|
|
|
201
408
|
}
|
|
202
409
|
|
|
203
410
|
function resetMotionPanel() {
|
|
411
|
+
resetMotionTrim();
|
|
204
412
|
stopMotionStream();
|
|
205
413
|
discardMotionRecorder();
|
|
206
414
|
motionRecorder = null;
|
|
@@ -258,6 +466,17 @@ export const MOTION_PANEL_JS = `
|
|
|
258
466
|
: note;
|
|
259
467
|
setMotionButtons('ready');
|
|
260
468
|
motionSubmit.disabled = !ok;
|
|
469
|
+
resetMotionTrim();
|
|
470
|
+
if (ok) {
|
|
471
|
+
seedMotionTrim(seconds);
|
|
472
|
+
// A fresh WebM often reports no duration; the preview's metadata is the fallback scale.
|
|
473
|
+
motionPreview.onloadedmetadata = function () {
|
|
474
|
+
if (motionTrimDuration === 0 && isFinite(motionPreview.duration) && motionPreview.duration > 0) {
|
|
475
|
+
seedMotionTrim(motionPreview.duration);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
void startMotionAnalysis(blob);
|
|
479
|
+
}
|
|
261
480
|
}
|
|
262
481
|
|
|
263
482
|
async function startMotionRecording() {
|
|
@@ -382,10 +601,19 @@ export const MOTION_PANEL_JS = `
|
|
|
382
601
|
if (!motionBlob) return;
|
|
383
602
|
var name = motionName.value.trim();
|
|
384
603
|
if (name === '') { motionName.focus(); return; }
|
|
604
|
+
// Whatever the handles show is what gets made; a still-running analysis must not move them.
|
|
605
|
+
motionAnalysisToken++;
|
|
606
|
+
if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }
|
|
385
607
|
var query = '?name=' + encodeURIComponent(name)
|
|
386
608
|
+ '&slot=' + encodeURIComponent(motionSlot.value)
|
|
387
|
-
+ '&loop=' + (motionLoop.checked ? '1' : '0')
|
|
388
|
-
|
|
609
|
+
+ '&loop=' + (motionLoop.checked ? '1' : '0');
|
|
610
|
+
if (motionHasTrim()) {
|
|
611
|
+
query += '&trimStart=' + encodeURIComponent(String(Math.round(motionTrimStart * 100) / 100))
|
|
612
|
+
+ '&trimEnd=' + encodeURIComponent(String(Math.round(motionTrimEnd * 100) / 100));
|
|
613
|
+
}
|
|
614
|
+
if (motionPendingId) {
|
|
615
|
+
query += '&pendingId=' + encodeURIComponent(motionPendingId);
|
|
616
|
+
}
|
|
389
617
|
motionSubmit.disabled = true;
|
|
390
618
|
showMotionNote('Uploading the video…', '');
|
|
391
619
|
var response, result;
|
|
@@ -428,5 +656,47 @@ export const MOTION_PANEL_JS = `
|
|
|
428
656
|
motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });
|
|
429
657
|
motionFile.addEventListener('change', function () { pickMotionFile(motionFile.files && motionFile.files[0]); });
|
|
430
658
|
motionSubmit.addEventListener('click', function () { void submitMotion(); });
|
|
659
|
+
motionTrimStartHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('start', event); });
|
|
660
|
+
motionTrimEndHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('end', event); });
|
|
661
|
+
motionTrimFullBtn.addEventListener('click', function () {
|
|
662
|
+
motionTrimStart = 0;
|
|
663
|
+
motionTrimEnd = motionTrimDuration;
|
|
664
|
+
renderMotionTrim();
|
|
665
|
+
});
|
|
666
|
+
// "Preview the kept range" plays exactly trimStart -> trimEnd once and stops
|
|
667
|
+
// on the last kept frame: what was just watched IS what the animation is
|
|
668
|
+
// made from.
|
|
669
|
+
motionTrimPreviewBtn.addEventListener('click', function () {
|
|
670
|
+
if (motionPreviewingTrim) {
|
|
671
|
+
motionPreview.pause();
|
|
672
|
+
motionPreviewingTrim = false;
|
|
673
|
+
renderMotionTrim();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
motionPreview.currentTime = motionTrimStart;
|
|
677
|
+
motionPreviewingTrim = true;
|
|
678
|
+
renderMotionTrim();
|
|
679
|
+
var played = motionPreview.play();
|
|
680
|
+
if (played && played.catch) {
|
|
681
|
+
played.catch(function () { motionPreviewingTrim = false; renderMotionTrim(); });
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
motionPreview.addEventListener('pause', function () {
|
|
685
|
+
if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }
|
|
686
|
+
});
|
|
687
|
+
motionPreview.addEventListener('ended', function () {
|
|
688
|
+
if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }
|
|
689
|
+
});
|
|
690
|
+
motionPreview.addEventListener('timeupdate', function () {
|
|
691
|
+
if (motionTrimDuration > 0) {
|
|
692
|
+
motionTrimPlayhead.style.left = Math.min(Math.max(motionPreview.currentTime / motionTrimDuration, 0), 1) * 100 + '%';
|
|
693
|
+
}
|
|
694
|
+
if (motionPreviewingTrim && motionPreview.currentTime >= motionTrimEnd) {
|
|
695
|
+
motionPreview.pause();
|
|
696
|
+
motionPreview.currentTime = motionTrimEnd;
|
|
697
|
+
motionPreviewingTrim = false;
|
|
698
|
+
renderMotionTrim();
|
|
699
|
+
}
|
|
700
|
+
});
|
|
431
701
|
`;
|
|
432
702
|
//# sourceMappingURL=motion-panel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-panel.js","sourceRoot":"","sources":["../../src/editor/motion-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG
|
|
1
|
+
{"version":3,"file":"motion-panel.js","sourceRoot":"","sources":["../../src/editor/motion-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6DhC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2jB9B,CAAC"}
|
|
@@ -17,8 +17,16 @@ export interface MotionVideoRequest {
|
|
|
17
17
|
name: string;
|
|
18
18
|
locomotionState?: LocomotionState;
|
|
19
19
|
loop?: boolean;
|
|
20
|
-
/** Cut walk-to-the-camera frames off both ends
|
|
20
|
+
/** Cut walk-to-the-camera frames off both ends. Legacy clients only; the panel now sends an explicit window. */
|
|
21
21
|
autoTrim?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Explicit trim window (seconds into the source clip), both or neither — the
|
|
24
|
+
* window the creator confirmed on the panel's trim bar. Wins over autoTrim.
|
|
25
|
+
*/
|
|
26
|
+
trimStartSeconds?: number;
|
|
27
|
+
trimEndSeconds?: number;
|
|
28
|
+
/** The sidecar's pending analysis clip; a match reuses its already-uploaded URL. */
|
|
29
|
+
pendingId?: string;
|
|
22
30
|
}
|
|
23
31
|
export interface MotionVideoRefusal {
|
|
24
32
|
status: 400 | 413 | 415;
|
|
@@ -44,4 +52,4 @@ export declare function motionDoneMessage(result: {
|
|
|
44
52
|
trimmedEndSeconds?: number;
|
|
45
53
|
}): string;
|
|
46
54
|
/** The command the panel prints when nobody is logged in — what the agent (or the creator) runs instead. */
|
|
47
|
-
export declare function motionCommand(videoPath: string, request: Pick<MotionVideoRequest, 'name' | 'locomotionState' | 'loop' | 'autoTrim'>): string;
|
|
55
|
+
export declare function motionCommand(videoPath: string, request: Pick<MotionVideoRequest, 'name' | 'locomotionState' | 'loop' | 'autoTrim' | 'trimStartSeconds' | 'trimEndSeconds'>): string;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* because the sidecar has no multipart parser beyond the recorder's frame sink, and a single
|
|
9
9
|
* binary body is the simplest thing both sides get right.
|
|
10
10
|
*/
|
|
11
|
-
import { LOCOMOTION_STATES, describeAutoTrim } from '@bitmagic/asset-core';
|
|
11
|
+
import { LOCOMOTION_STATES, MIN_TRIM_KEPT_SECONDS, describeAutoTrim } from '@bitmagic/asset-core';
|
|
12
12
|
/** Matches the CLI lane's own cap (`generate/animation-from-video.ts`) and asset-forger's. */
|
|
13
13
|
export const MOTION_BODY_LIMIT_BYTES = 200 * 1024 * 1024;
|
|
14
14
|
/** Content types a browser's MediaRecorder or a file picker hands over, and the extension each becomes. */
|
|
@@ -47,6 +47,24 @@ export function parseMotionVideoRequest(input) {
|
|
|
47
47
|
}
|
|
48
48
|
const loop = input.query.get('loop');
|
|
49
49
|
const autoTrim = input.query.get('autoTrim');
|
|
50
|
+
const trimStartRaw = input.query.get('trimStart');
|
|
51
|
+
const trimEndRaw = input.query.get('trimEnd');
|
|
52
|
+
if ((trimStartRaw === null) !== (trimEndRaw === null)) {
|
|
53
|
+
return { status: 400, error: 'trimStart and trimEnd must be given together.' };
|
|
54
|
+
}
|
|
55
|
+
let trimWindow = null;
|
|
56
|
+
if (trimStartRaw !== null && trimEndRaw !== null) {
|
|
57
|
+
const trimStart = Number(trimStartRaw);
|
|
58
|
+
const trimEnd = Number(trimEndRaw);
|
|
59
|
+
if (!Number.isFinite(trimStart) || !Number.isFinite(trimEnd) || trimStart < 0) {
|
|
60
|
+
return { status: 400, error: 'trimStart and trimEnd must be non-negative seconds into the clip.' };
|
|
61
|
+
}
|
|
62
|
+
if (trimEnd - trimStart < MIN_TRIM_KEPT_SECONDS) {
|
|
63
|
+
return { status: 400, error: `trimStart/trimEnd must keep at least ${MIN_TRIM_KEPT_SECONDS} seconds of the clip.` };
|
|
64
|
+
}
|
|
65
|
+
trimWindow = { trimStartSeconds: trimStart, trimEndSeconds: trimEnd };
|
|
66
|
+
}
|
|
67
|
+
const pendingId = input.query.get('pendingId');
|
|
50
68
|
return {
|
|
51
69
|
extension,
|
|
52
70
|
contentType,
|
|
@@ -54,6 +72,8 @@ export function parseMotionVideoRequest(input) {
|
|
|
54
72
|
...(slot !== null && slot !== '' ? { locomotionState: slot } : {}),
|
|
55
73
|
...(loop === '1' || loop === 'true' ? { loop: true } : {}),
|
|
56
74
|
...(autoTrim === '1' || autoTrim === 'true' ? { autoTrim: true } : {}),
|
|
75
|
+
...(trimWindow ?? {}),
|
|
76
|
+
...(pendingId !== null && pendingId !== '' ? { pendingId } : {}),
|
|
57
77
|
};
|
|
58
78
|
}
|
|
59
79
|
/**
|
|
@@ -72,8 +92,13 @@ export function motionCommand(videoPath, request) {
|
|
|
72
92
|
parts.push(`--slot ${request.locomotionState}`);
|
|
73
93
|
if (request.loop)
|
|
74
94
|
parts.push('--loop');
|
|
75
|
-
|
|
95
|
+
// The hand-off must reproduce the creator's confirmed window; --auto-trim only without one.
|
|
96
|
+
if (request.trimStartSeconds !== undefined && request.trimEndSeconds !== undefined) {
|
|
97
|
+
parts.push(`--trim-start ${request.trimStartSeconds}`, `--trim-end ${request.trimEndSeconds}`);
|
|
98
|
+
}
|
|
99
|
+
else if (request.autoTrim) {
|
|
76
100
|
parts.push('--auto-trim');
|
|
101
|
+
}
|
|
77
102
|
return parts.join(' ');
|
|
78
103
|
}
|
|
79
104
|
//# sourceMappingURL=motion-sink.js.map
|