Skip to content

Commit a62a23f

Browse files
committed
move editor to new "lab" page, hidden as experimental feature
1 parent edbe2c9 commit a62a23f

File tree

10 files changed

+171
-28
lines changed

10 files changed

+171
-28
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<script>
2+
import { Tracker } from '$lib/instructor/bouncy_instructor';
3+
import { getContext, onMount } from 'svelte';
4+
import { landmarksToKeypoints } from '$lib/pose';
5+
import { registerTracker } from '$lib/stores/Beat';
6+
import Svg from '$lib/components/avatar/Svg.svelte';
7+
import SvgAvatar2 from '$lib/components/avatar/SvgAvatar2.svelte';
8+
import { LEFT_RIGHT_COLORING_LIGHT } from '$lib/constants';
9+
import PoseInputForm from '$lib/components/editor/PoseInputForm.svelte';
10+
import PoseEditForm from '$lib/components/editor/PoseEditForm.svelte';
11+
import { fileToUrl, waitForVideoMetaLoaded } from '$lib/promise_util';
12+
import { PoseDetection } from '$lib/pose';
13+
14+
const poseCtx = getContext('pose');
15+
let tracker = new Tracker();
16+
registerTracker(tracker);
17+
18+
/** @type {PoseDetection} */
19+
let dataListener;
20+
/** @type {(skeleton: import("$lib/instructor/bouncy_instructor").SkeletonWrapper)=>void} */
21+
let loadSkeleton;
22+
/** @type {()=>import("$lib/instructor/bouncy_instructor").PoseWrapper} */
23+
let poseFromForm;
24+
/** @type {(skeleton: import("$lib/instructor/bouncy_instructor").PoseWrapper)=>void} */
25+
let loadPose;
26+
27+
/** @type {import("$lib/instructor/bouncy_instructor").SkeletonV2 | undefined} */
28+
let liveSkeleton;
29+
/** @type {import("$lib/instructor/bouncy_instructor").SkeletonWrapper | undefined} */
30+
let poseSkeleton;
31+
32+
/** @type {HTMLInputElement} */
33+
let upload;
34+
/** @type {HTMLVideoElement} */
35+
let video;
36+
let videoSrcWidth = 0;
37+
let videoSrcHeight = 0;
38+
39+
/** @type {undefined | number} */
40+
let recordingStart;
41+
/** @type {undefined | number} */
42+
let recordingEnd;
43+
let prevTime = -1;
44+
let selectedTimestamp = 0;
45+
46+
/** @param { Event } event */
47+
async function loadVideo(event) {
48+
if (event.target && event.target.files && event.target.files[0]) {
49+
video.src = await fileToUrl(event.target.files[0]);
50+
await waitForVideoMetaLoaded(video);
51+
tracker.clear();
52+
video.play();
53+
loop();
54+
}
55+
}
56+
57+
function loop() {
58+
if (dataListener && prevTime !== video.currentTime) {
59+
prevTime = video.currentTime;
60+
dataListener.trackFrame(video, undefined);
61+
}
62+
requestAnimationFrame(loop);
63+
}
64+
65+
onMount(async () => {
66+
dataListener = await poseCtx.newPoseDetection(
67+
(
68+
/** @type {{ landmarks: string | any[]; }} */ result,
69+
/** @type {number} */ timestamp
70+
) => {
71+
if (recordingStart === undefined) {
72+
recordingStart = timestamp;
73+
}
74+
if (result.landmarks && result.landmarks.length >= 1) {
75+
const kp = landmarksToKeypoints(result.landmarks[0]);
76+
tracker.addKeypoints(kp, timestamp);
77+
recordingEnd = Math.max(timestamp, recordingEnd || 0);
78+
selectedTimestamp = timestamp;
79+
liveSkeleton = tracker.renderedKeypointsAt(
80+
timestamp,
81+
videoSrcWidth,
82+
videoSrcHeight
83+
);
84+
}
85+
}
86+
);
87+
});
88+
89+
function copySkeleton() {
90+
poseSkeleton = tracker.skeletonWrapperAt(selectedTimestamp);
91+
if (poseSkeleton) {
92+
loadSkeleton(poseSkeleton);
93+
}
94+
}
95+
96+
function copyPose() {
97+
let pose = poseFromForm();
98+
if (pose) {
99+
loadPose(pose);
100+
}
101+
}
102+
</script>
103+
104+
<p>
105+
<input
106+
bind:this={upload}
107+
type="file"
108+
accept="video/*"
109+
on:change={loadVideo}
110+
/>
111+
</p>
112+
113+
<!-- svelte-ignore a11y-media-has-caption -->
114+
<div class="side-by-side">
115+
<video
116+
bind:this={video}
117+
bind:videoWidth={videoSrcWidth}
118+
bind:videoHeight={videoSrcHeight}
119+
playsinline
120+
controls
121+
></video>
122+
<div>
123+
{#if liveSkeleton}
124+
<Svg width={videoSrcWidth} height={videoSrcHeight} orderByZ showOverflow>
125+
<SvgAvatar2
126+
skeleton={liveSkeleton}
127+
lineWidth={3}
128+
style={LEFT_RIGHT_COLORING_LIGHT}
129+
/>
130+
</Svg>
131+
{/if}
132+
</div>
133+
</div>
134+
135+
<button class="light full-width short" on:click={copySkeleton}> ↓ </button>
136+
137+
<PoseInputForm bind:loadSkeleton bind:readPose={poseFromForm}></PoseInputForm>
138+
139+
<button class="light full-width short" on:click={copyPose}> ↓ </button>
140+
141+
<PoseEditForm bind:loadPose></PoseEditForm>

bouncy_frontend/src/lib/i18n/de-CH.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
}
7474
},
7575
"editor": {
76+
"title": "Labor",
77+
"nav": "Labor",
7678
"edit-dance-context-menu": "Bearbeiten",
7779
"dance-copy-postfix": "Kopie",
7880
"delete-dance-button": "Tanz löschen",

bouncy_frontend/src/lib/i18n/en-GB.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
}
7474
},
7575
"editor": {
76+
"title": "Laboratory",
77+
"nav": "Laboratory",
7678
"edit-dance-context-menu": "Edit",
7779
"dance-copy-postfix": "copy",
7880
"delete-dance-button": "Delete dance",

bouncy_frontend/src/lib/pose.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export class PoseDetection {
9191
/**
9292
*
9393
* @param {import('@mediapipe/tasks-vision').ImageSource} videoElement
94-
* @param {number} timestamp
94+
* @param {number | undefined} timestamp
9595
*/
9696
trackFrame(videoElement, timestamp) {
9797
if (timestamp === undefined || timestamp === null) {

bouncy_frontend/src/lib/stores/FeatureSelection.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { derived, readable, writable } from "svelte/store";
44
import { dev as envDev, browser } from '$app/environment';
55

6+
let experimental = writable(false);
67
let privDev = envDev;
78
export const dev = readable(privDev, (set) => {
89
if (browser) {
@@ -23,7 +24,7 @@ function versionNumberToString(v) {
2324
}
2425

2526
/** @type {import("svelte/motion").Readable<Features>} */
26-
export const features = derived([version, dev], ([$v, $dev]) => {
27+
export const features = derived([version, dev, experimental], ([$v, $dev, $experimental]) => {
2728
return {
2829
/* Fully enabled features for now but might be disabled again*/
2930
enableDanceCollection: $v >= 0.003,
@@ -32,6 +33,7 @@ export const features = derived([version, dev], ([$v, $dev]) => {
3233

3334
/* Partially enabled features */
3435
enableStepRecording: (stepName) => STABLE_TRACKING_STEPS.includes(stepName),
36+
enableEditorPage: $experimental,
3537

3638
/* Features that are not ready to be released */
3739
enableAvatarRotation: $v >= 0.999,
@@ -43,6 +45,11 @@ export const features = derived([version, dev], ([$v, $dev]) => {
4345
}
4446
);
4547

48+
/** @param {boolean} yes */
49+
export function showExperimentalFeatures(yes) {
50+
experimental.set(yes);
51+
}
52+
4653
/** Steps that should be possible to track, with passing tests. */
4754
export const STABLE_TRACKING_STEPS = [
4855
"Running Man",

bouncy_frontend/src/lib/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @property {(step: string)=>boolean} enableStepRecording
77
* @property {boolean} enableDanceCollection
88
* @property {boolean} enableDanceCreator
9+
* @property {boolean} enableEditorPage
910
* @property {boolean} enableCourses
1011
* @property {boolean} enableDevView
1112
*

bouncy_frontend/src/routes/TabNavigation.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
icon: 'book_5',
3232
route: `${base}/collection`,
3333
});
34+
if (features.enableEditorPage) {
35+
tabs.push({
36+
label: $t('editor.nav'),
37+
icon: 'experiment',
38+
route: `${base}/editor`,
39+
});
40+
}
3441
tabs.push({
3542
label: $t('profile.nav'),
3643
icon: 'account_circle',

bouncy_frontend/src/routes/UserContext.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { generateRandomUsername } from '$lib/username';
88
import { setContext } from 'svelte';
99
import { writable } from 'svelte/store';
10+
import { showExperimentalFeatures } from '$lib/stores/FeatureSelection.js';
1011
1112
function fromLocalStorage() {
1213
try {
@@ -49,6 +50,9 @@
4950
);
5051
if (browser) {
5152
user.subscribe((value) => (localStorage.user = JSON.stringify(value)));
53+
user.subscribe((value) => {
54+
showExperimentalFeatures(value.experimentalFeatures);
55+
});
5256
}
5357
5458
/**

bouncy_frontend/src/routes/dev/+page.svelte

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import Svg from '$lib/components/avatar/Svg.svelte';
1010
import SvgAvatar2 from '$lib/components/avatar/SvgAvatar2.svelte';
1111
import { LEFT_RIGHT_COLORING_LIGHT } from '$lib/constants';
12-
import PoseInputForm from '$lib/components/editor/PoseInputForm.svelte';
13-
import PoseEditForm from '$lib/components/editor/PoseEditForm.svelte';
1412
1513
/** @type {HTMLInputElement} */
1614
let upload;
@@ -22,8 +20,6 @@
2220
let videoSrcHeight = 0;
2321
/** @type {import("$lib/instructor/bouncy_instructor").SkeletonV2 | undefined} */
2422
let liveSkeleton;
25-
/** @type {import("$lib/instructor/bouncy_instructor").SkeletonWrapper | undefined} */
26-
let poseSkeleton;
2723
2824
let dataListener;
2925
/** @type {(skeleton: import("$lib/instructor/bouncy_instructor").SkeletonWrapper)=>void} */
@@ -130,20 +126,6 @@
130126
console.log(step.name, step.start, step.end);
131127
});
132128
}
133-
134-
function copySkeleton() {
135-
poseSkeleton = tracker.skeletonWrapperAt(selectedTimestamp);
136-
if (poseSkeleton) {
137-
loadSkeleton(poseSkeleton);
138-
}
139-
}
140-
141-
function copyPose() {
142-
let pose = poseFromForm();
143-
if (pose) {
144-
loadPose(pose);
145-
}
146-
}
147129
</script>
148130

149131
<h1>Dev</h1>
@@ -179,14 +161,6 @@
179161
</div>
180162
</div>
181163

182-
<button class="light full-width short" on:click={copySkeleton}> ↓ </button>
183-
184-
<PoseInputForm bind:loadSkeleton bind:readPose={poseFromForm}></PoseInputForm>
185-
186-
<button class="light full-width short" on:click={copyPose}> ↓ </button>
187-
188-
<PoseEditForm bind:loadPose></PoseEditForm>
189-
190164
<button on:click={downloadFrame}> Download Keypoints of Frame </button>
191165
<button on:click={downloadKeypoints}> Download Keypoints of Video </button>
192166
<h2>Dance Evaluation</h2>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import VideoToStep from '$lib/components/editor/VideoToStep.svelte';
3+
</script>
4+
5+
<VideoToStep></VideoToStep>

0 commit comments

Comments
 (0)