File size: 13,351 Bytes
a5ec907
d103d3a
a5ec907
 
 
 
 
d103d3a
 
 
 
 
a5ec907
 
d103d3a
a5ec907
 
d103d3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5ec907
 
 
 
d103d3a
a5ec907
 
 
 
 
 
 
d103d3a
a5ec907
d103d3a
a5ec907
 
 
d103d3a
 
 
8afa347
 
 
 
 
 
 
 
 
 
 
a5ec907
 
 
 
d103d3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5ec907
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d103d3a
a5ec907
 
 
 
 
 
d103d3a
a5ec907
 
8afa347
a5ec907
 
 
 
 
 
 
d103d3a
8afa347
a5ec907
 
 
 
 
 
 
 
 
 
 
 
 
d103d3a
83e06b1
d103d3a
a5ec907
83e06b1
a5ec907
 
 
 
 
 
 
 
 
 
 
 
 
 
8afa347
 
a5ec907
 
 
 
 
d103d3a
83e06b1
 
 
d103d3a
 
a5ec907
 
 
 
 
 
 
 
 
 
8afa347
a5ec907
83e06b1
a5ec907
 
83e06b1
8afa347
 
 
a5ec907
d103d3a
a5ec907
8afa347
a5ec907
 
d103d3a
a5ec907
d103d3a
8afa347
a5ec907
8afa347
 
 
 
 
a5ec907
 
 
8afa347
 
 
 
 
a5ec907
 
8afa347
 
d103d3a
8afa347
 
 
 
 
 
 
 
d103d3a
8afa347
 
 
 
 
d103d3a
 
 
8afa347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d103d3a
8afa347
 
 
 
 
 
 
 
 
 
d103d3a
8afa347
 
 
d103d3a
a5ec907
 
 
 
 
8afa347
a5ec907
 
83e06b1
d103d3a
 
a5ec907
 
d103d3a
a5ec907
 
 
 
 
 
d103d3a
a5ec907
8afa347
 
d103d3a
8afa347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d103d3a
8afa347
 
 
d103d3a
 
8afa347
d103d3a
8afa347
 
d103d3a
8afa347
a5ec907
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
document.addEventListener("DOMContentLoaded", () => {
    // Récup de tous les éléments du HTML
    const fileInput = document.getElementById('file-input');
    const runBtn = document.getElementById('btn-run');
    const nameInput = document.getElementById('recording-name');
    const preview = document.getElementById('video-preview');
    const placeholder = document.getElementById('video-placeholder');
    const localVideo =  document.getElementById('local-video');
    const youtubeVideo =  document.getElementById('youtube-video');
    const videoLink = document.getElementById('video-link');
    const loadLocal = document.getElementById('load-local');
    const loadYoutube = document.getElementById('load-youtube');
    let selectedFile = null;

    // Charge la bibliothèque des vidéos déjà analysées
    chargerBibliotheque();

    // Affichage selection entre mode local et yt
    localVideo.addEventListener('click', () =>{
        videoLink.style.display = "none";
        loadLocal.style.display = "block";
        loadYoutube.style.display = "none";
    });

    youtubeVideo.addEventListener('click', () =>{
        videoLink.style.display = "block";
        loadLocal.style.display = "none";
        loadYoutube.style.display = "block";
    });

    // Permet de télécharger une vidéo yt
    loadYoutube.addEventListener('click', async () => {
        const link = videoLink.value.trim();
        if (!link) return updateStatus("Entrez le lien d'une vidéo youtube", 'error');

        updateStatus("Téléchargement en cours...", 'active');
        setProgress(20);
        loadYoutube.disabled = true;

        try {
            // Appelle l'api python pour télécharger
            const response = await fetch('/telecharger', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ url: link }) // On envoie l'url au serveur
            });
            const data = await response.json();
            if (data.succes) {
                setProgress(60);
                updateStatus("Vidéo téléchargée — donnez un nom et lancez le pipeline", 'done');
                runBtn.disabled = false; // le bouton lancer le pipeline est débloqué
            } else {
                setProgress(0);
                updateStatus(`Erreur téléchargement : ${data.message}`, 'error');
            }
        } catch (err) {
            setProgress(0);
            updateStatus(`Erreur : ${err.message}`, 'error');
        } finally {
            loadYoutube.disabled = false;
        }
    });

    // Charger une vidéo sur son pc en local
    fileInput.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (!file) return;
        selectedFile = file;
        // Permet de lire la vidéo dans le navigateur
        preview.src = URL.createObjectURL(file);
        preview.style.display = 'block';
        placeholder.style.display = 'none';
        runBtn.disabled = false;
        updateStatus(`Vidéo prête : ${file.name}`, 'active');
    });

    // Lancer le pipeline
    runBtn.addEventListener('click', async () => {
        // Différentes vérif : si la vidéo est chargée, a un nom ou si c'est un double
        const nom = nameInput.value.trim();
        if (!nom) return updateStatus("Donnez un nom à l'enregistrement", 'error');

        const isYoutube = loadYoutube.style.display === "block";
        if (!isYoutube && !selectedFile) return updateStatus("Chargez une vidéo d'abord", 'error');

        try {
            const responsescv = await fetch('/liste');
            if (!responsescv.ok) throw new Error();
            const listescv = await responsescv.json();
            if (listescv.noms.includes(nom)) {
                return updateStatus("Ce nom est déjà dans la bibliothèque", 'error');
            }
        } catch (err) {
            console.error("Erreur de vérification de la bibliothèque:", err);
        }

        runBtn.disabled = true;
        updateStatus("Extraction des landmarks et génération des mouvements...", 'active');
        setProgress(30);

        let response;
        // Si vidéo yt
        if (isYoutube) {
            // lance la vidéo car déjà sur le serv
            response = await fetch('/lancer-youtube', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ nom })
            });
        } else {
            // sinon fichier local donc il faut l'envoyer sur le serveur (upload)
            const formData = new FormData();
            formData.append('video', selectedFile);
            formData.append('nom', nom);
            response = await fetch('/charger', { method: 'POST', body: formData });
        }

        try {
            if (!response.ok) {
                const text = await response.text();
                updateStatus(`Erreur serveur (${response.status}): ${text}`, 'error');
                runBtn.disabled = false;
                return;
            }

            const data = await response.json();

            if (data.succes) {
                setProgress(100);
                updateStatus(`Enregistrement "${nom}" ajouté ! Lancement de la vidéo...`, 'done');

                // Mise à jour du cadre vidéo : le lecteur va lire la vidéo analysée avec les landmarks
                setTimeout(() => {
                    preview.src = `/video/${encodeURIComponent(nom)}?t=${Date.now()}`;
                    preview.style.display = 'block';
                    placeholder.style.display = 'none';

                    const playVideo = () => {
                        preview.play().catch(() => { preview.controls = true; });
                    };

                    if (preview.readyState >= 3) {
                        playVideo();
                    } else {
                        preview.addEventListener('canplay', playVideo, { once: true });
                    }

                    nameInput.value = '';
                    selectedFile = null;
                    chargerBibliotheque(); // Bibliothèque rafraichieavec le nouvel enregistrement
                }, 500);
            } else {
                setProgress(0);
                updateStatus(`Erreur : ${data.message}`, 'error');
            }
        } catch (err) {
            setProgress(0);
            updateStatus(`Erreur: ${err.message}`, 'error');
        } finally {
            runBtn.disabled = false;
        }
    });
});

// Bibliothèque sur le côté droit

// Récupère la liste des vidéos enregistrées sur le serveur pour ajouter des éléments li HTML
async function chargerBibliotheque() {
    try {
        const response = await fetch('/liste');
        if (!response.ok) throw new Error();
        const data = await response.json();

        const liste = document.getElementById('library-list');
        liste.innerHTML = '';

        if (data && data.noms) {
            data.noms.forEach((nom, i) => {
                const item = document.createElement('li');
                item.className = 'library-item';
                item.innerHTML = `
                    <span class="item-index">${String(i + 1).padStart(2, '0')}</span>
                    <span class="item-name">${nom}</span>
                    <button class="btn btn-play button-in-list" onclick="playRecording('${nom}', this)">play</button>
                    <button class="btn btn-delete button-in-list" onclick="deleteRecording('${nom}')">delete</button>
                `;
                liste.appendChild(item);
            });
        }
    } catch (err) {
        console.error("Erreur de chargement de la bibliothèque", err);
    }
}


// Jouer la vidéo qu'on veut sur le robot
async function playRecording(nom, btn) {
    document.querySelectorAll('.library-item').forEach(el => el.classList.remove('playing'));
    document.querySelectorAll('.btn-play').forEach(b => {
        b.classList.remove('active');
        b.textContent = 'play';
    });

    btn.classList.add('active');
    btn.textContent = '...';
    btn.closest('.library-item').classList.add('playing');
    updateStatus(`Chargement pour : ${nom}`, 'active');
    setProgress(50);

    const preview = document.getElementById('video-preview');
    const placeholder = document.getElementById('video-placeholder');

    preview.pause();
    preview.removeAttribute('src');
    preview.load();

    // Le serveur prépare le robot avec le fichier qu'on veut
    try {
        const simResp = await fetch('/play', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ nom })
        });
        const simData = await simResp.json();
        if (!simData.succes) {
            setProgress(0);
            updateStatus(`Erreur simulation : ${simData.message}`, 'error');
            btn.classList.remove('active');
            btn.textContent = 'play';
            btn.closest('.library-item').classList.remove('playing');
            return;
        }
    } catch (err) {
        setProgress(0);
        updateStatus(`Erreur : ${err.message}`, 'error');
        btn.classList.remove('active');
        btn.textContent = 'play';
        btn.closest('.library-item').classList.remove('playing');
        return;
    }

    updateStatus(`En attente du signal robot...`, 'active');

    // Toutes les 250ms on demande au serveur si il y'a un Reachy, ça va permettre d'avoir un rejeu de Reachy synchronisée avec le lancement de la vidéo
    const POLL_INTERVAL = 250;
    const POLL_TIMEOUT  = 60000;
    const pollStart = Date.now();

    try {
        await new Promise((resolve, reject) => {
            const poll = async () => {
                if (Date.now() - pollStart > POLL_TIMEOUT) {
                    return reject(new Error("Timeout : le robot n'a pas répondu dans les 60s"));
                }
                try {
                    const r = await fetch('/is-ready');
                    const d = await r.json();
                    const elapsed = ((Date.now() - pollStart) / 1000).toFixed(1);
                    updateStatus(`En attente du signal robot… ${elapsed}s`, 'active');
                    if (d.ready) resolve(); // le robot est pret
                    else setTimeout(poll, POLL_INTERVAL); // Sinon on lui redemande
                } catch (e) {
                    setTimeout(poll, POLL_INTERVAL);
                }
            };
            poll();
        });
    } catch (err) {
        setProgress(0);
        updateStatus(`Erreur : ${err.message}`, 'error');
        btn.classList.remove('active');
        btn.textContent = 'play';
        btn.closest('.library-item').classList.remove('playing');
        return;
    }

    // Le robot est pret donc on lance la vidéo landmarkée dans le navigateur
    preview.src = `/video/${encodeURIComponent(nom)}`;
    preview.style.display = 'block';
    placeholder.style.display = 'none';

    await new Promise((resolve, reject) => {
        preview.addEventListener('canplay', resolve, { once: true });
        preview.addEventListener('error', () => reject(new Error('Erreur chargement vidéo')), { once: true });
        preview.load();
    });

    preview.play().catch(e => { preview.controls = true; });
    setProgress(100);
    updateStatus(`Lecture en cours...`, 'done');

    // Quand la vidéo est finie, le robot s'arrête
    preview.onended = () => {
        btn.classList.remove('active');
        btn.textContent = 'play';
        btn.closest('.library-item').classList.remove('playing');
        updateStatus(`Séquence "${nom}" terminée`, 'done');
        fetch('/stop', { method: 'POST' }).catch(() => {});
    };
}


// Infos et barre de progression sur l'avancement de l'action qu'on effectue,
function updateStatus(msg, type) {
    const el = document.getElementById('status-line');
    if (el) { el.textContent = msg; el.className = `status-line ${type}`; }
}

function setProgress(pct) {
    const bar = document.getElementById('progress-bar');
    if (bar) {
        bar.style.width = `${pct}%`;
        if (pct === 100) setTimeout(() => { bar.style.width = '0%'; }, 1200);
    }
}

// Supprimer un enregistrement de la bibliothèque
async function deleteRecording(nom) {
    const preview = document.getElementById('video-preview');
    const placeholder = document.getElementById('video-placeholder');

    if (preview.src.includes(encodeURIComponent(nom))) {
        preview.pause();
        preview.src = '';
        preview.load();
        preview.style.display = 'none';
        placeholder.style.display = 'block';
    }

    await new Promise(r => setTimeout(r, 300));

    try {
        const response = await fetch('/supprimer', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ nom })
        });
        const data = await response.json();
        if (data.succes) {
            updateStatus(`"${nom}" supprimé.`, 'done');
            chargerBibliotheque();  // Refresh de la bibliothèque
        } else {
            updateStatus(`Erreur suppression : ${data.message}`, 'error');
        }
    } catch (err) {
        updateStatus(`Erreur : ${err.message}`, 'error');
    }
}