Denisijcu commited on
Commit
ff46e56
·
verified ·
1 Parent(s): 1e79c63

update index

Browse files
Files changed (1) hide show
  1. index.html +610 -0
index.html ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Analizador Facial de Videos de YouTube</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Arial', sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 20px;
27
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
28
+ padding: 40px;
29
+ max-width: 800px;
30
+ width: 100%;
31
+ }
32
+
33
+ .header {
34
+ text-align: center;
35
+ margin-bottom: 40px;
36
+ }
37
+
38
+ .header h1 {
39
+ color: #333;
40
+ margin-bottom: 10px;
41
+ font-size: 2.5em;
42
+ }
43
+
44
+ .header p {
45
+ color: #666;
46
+ font-size: 1.1em;
47
+ }
48
+
49
+ .form-group {
50
+ margin-bottom: 30px;
51
+ }
52
+
53
+ .form-group label {
54
+ display: block;
55
+ margin-bottom: 10px;
56
+ color: #333;
57
+ font-weight: bold;
58
+ font-size: 1.1em;
59
+ }
60
+
61
+ .form-group input {
62
+ width: 100%;
63
+ padding: 15px;
64
+ border: 2px solid #ddd;
65
+ border-radius: 10px;
66
+ font-size: 1em;
67
+ transition: border-color 0.3s ease;
68
+ }
69
+
70
+ .form-group input:focus {
71
+ outline: none;
72
+ border-color: #667eea;
73
+ }
74
+
75
+ .btn {
76
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
77
+ color: white;
78
+ border: none;
79
+ padding: 15px 30px;
80
+ border-radius: 10px;
81
+ font-size: 1.1em;
82
+ cursor: pointer;
83
+ width: 100%;
84
+ transition: transform 0.3s ease;
85
+ }
86
+
87
+ .btn:hover {
88
+ transform: translateY(-2px);
89
+ }
90
+
91
+ .btn:disabled {
92
+ opacity: 0.6;
93
+ cursor: not-allowed;
94
+ transform: none;
95
+ }
96
+
97
+ .loading {
98
+ display: none;
99
+ text-align: center;
100
+ margin: 30px 0;
101
+ }
102
+
103
+ .loading .spinner {
104
+ border: 4px solid #f3f3f3;
105
+ border-top: 4px solid #667eea;
106
+ border-radius: 50%;
107
+ width: 50px;
108
+ height: 50px;
109
+ animation: spin 1s linear infinite;
110
+ margin: 0 auto 20px;
111
+ }
112
+
113
+ @keyframes spin {
114
+ 0% { transform: rotate(0deg); }
115
+ 100% { transform: rotate(360deg); }
116
+ }
117
+
118
+ .alert {
119
+ padding: 15px;
120
+ margin: 20px 0;
121
+ border-radius: 10px;
122
+ display: none;
123
+ }
124
+
125
+ .alert.error {
126
+ background-color: #f8d7da;
127
+ color: #721c24;
128
+ border: 1px solid #f5c6cb;
129
+ }
130
+
131
+ .alert.success {
132
+ background-color: #d4edda;
133
+ color: #155724;
134
+ border: 1px solid #c3e6cb;
135
+ }
136
+
137
+ .results {
138
+ display: none;
139
+ margin-top: 30px;
140
+ }
141
+
142
+ .results h2 {
143
+ color: #333;
144
+ margin-bottom: 20px;
145
+ text-align: center;
146
+ }
147
+
148
+ .metric-grid {
149
+ display: grid;
150
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
151
+ gap: 20px;
152
+ margin-bottom: 30px;
153
+ }
154
+
155
+ .metric-card {
156
+ background: #f8f9fa;
157
+ padding: 20px;
158
+ border-radius: 15px;
159
+ text-align: center;
160
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
161
+ }
162
+
163
+ .metric-card h3 {
164
+ color: #667eea;
165
+ margin-bottom: 15px;
166
+ font-size: 1.2em;
167
+ }
168
+
169
+ .metric-item {
170
+ display: flex;
171
+ justify-content: space-between;
172
+ margin-bottom: 10px;
173
+ padding: 8px 0;
174
+ border-bottom: 1px solid #eee;
175
+ }
176
+
177
+ .metric-item:last-child {
178
+ border-bottom: none;
179
+ margin-bottom: 0;
180
+ }
181
+
182
+ .metric-label {
183
+ color: #666;
184
+ font-weight: 500;
185
+ }
186
+
187
+ .metric-value {
188
+ color: #333;
189
+ font-weight: bold;
190
+ }
191
+
192
+ .video-info {
193
+ background: #e3f2fd;
194
+ padding: 20px;
195
+ border-radius: 15px;
196
+ margin-bottom: 20px;
197
+ }
198
+
199
+ .video-info h3 {
200
+ color: #1976d2;
201
+ margin-bottom: 10px;
202
+ }
203
+
204
+ .video-title {
205
+ font-weight: bold;
206
+ color: #333;
207
+ margin-bottom: 5px;
208
+ }
209
+
210
+ .video-player {
211
+ display: none;
212
+ margin: 30px 0;
213
+ background: #f8f9fa;
214
+ padding: 20px;
215
+ border-radius: 15px;
216
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
217
+ }
218
+
219
+ .video-player h3 {
220
+ color: #667eea;
221
+ margin-bottom: 15px;
222
+ font-size: 1.2em;
223
+ text-align: center;
224
+ }
225
+
226
+ .video-player iframe {
227
+ width: 100%;
228
+ height: 360px;
229
+ border-radius: 10px;
230
+ }
231
+
232
+ @media (max-width: 600px) {
233
+ .container {
234
+ padding: 20px;
235
+ }
236
+
237
+ .header h1 {
238
+ font-size: 2em;
239
+ }
240
+
241
+ .metric-grid {
242
+ grid-template-columns: 1fr;
243
+ }
244
+
245
+ .video-player iframe {
246
+ height: 200px;
247
+ }
248
+ }
249
+ </style>
250
+ <!-- Añadimos Chart.js desde un CDN -->
251
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
252
+ </head>
253
+ <body>
254
+ <div class="container">
255
+ <div class="header">
256
+ <h1>🎥 Analizador Facial</h1>
257
+ <p>Analiza videos de YouTube para obtener métricas de comportamiento facial</p>
258
+ </div>
259
+
260
+ <form id="analyzeForm">
261
+ <div class="form-group">
262
+ <label for="youtube_url">URL del Video de YouTube:</label>
263
+ <input
264
+ type="url"
265
+ id="youtube_url"
266
+ name="youtube_url"
267
+ placeholder="https://www.youtube.com/watch?v=..."
268
+ required
269
+ >
270
+ </div>
271
+ <button type="submit" class="btn" id="analyzeBtn">
272
+ Analizar Video
273
+ </button>
274
+ <button type="button" class="btn" id="testBtn" style="margin-top: 10px; background: #28a745;">
275
+ 🔧 Probar Conexión
276
+ </button>
277
+ </form>
278
+
279
+ <div class="loading" id="loading">
280
+ <div class="spinner"></div>
281
+ <p>Analizando video... Esto puede tomar varios minutos.</p>
282
+ </div>
283
+
284
+ <div class="alert" id="alert"></div>
285
+
286
+ <!-- Reproductor de video -->
287
+ <div class="video-player" id="videoPlayer">
288
+ <h3>Video Analizado</h3>
289
+ <iframe id="youtubeIframe" src="" frameborder="0" allowfullscreen></iframe>
290
+ </div>
291
+
292
+ <!-- Resultados del análisis -->
293
+ <div class="results" id="results">
294
+ <h2>📊 Resultados del Análisis</h2>
295
+
296
+ <div class="video-info" id="videoInfo">
297
+ <h3>Información del Video</h3>
298
+ <div class="video-title" id="videoTitle"></div>
299
+ <div id="videoDetails"></div>
300
+ </div>
301
+
302
+ <div class="metric-grid">
303
+ <div class="metric-card">
304
+ <h3>😊 Análisis de Sonrisas</h3>
305
+ <div id="smileMetrics"></div>
306
+ </div>
307
+
308
+ <div class="metric-card">
309
+ <h3>👁️ Análisis de Parpadeos</h3>
310
+ <div id="blinkMetrics"></div>
311
+ </div>
312
+
313
+ <div class="metric-card">
314
+ <h3>🎯 Movimiento de Cabeza</h3>
315
+ <div id="headMetrics"></div>
316
+ </div>
317
+ <!-- Nueva tarjeta para emociones negativas -->
318
+ <div class="metric-card">
319
+ <h3>😡 Emociones Negativas</h3>
320
+ <div id="negativeMetrics"></div>
321
+ </div>
322
+ <!-- Tarjeta para el gráfico -->
323
+ <div class="metric-card">
324
+ <h3>📈 Resumen de Métricas</h3>
325
+ <canvas id="analysisChart"></canvas>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+
331
+ <script>
332
+ // Botón de prueba de conexión
333
+ document.getElementById('testBtn').addEventListener('click', async function() {
334
+ const testBtn = this;
335
+ const originalText = testBtn.textContent;
336
+
337
+ testBtn.disabled = true;
338
+ testBtn.textContent = 'Probando...';
339
+
340
+ try {
341
+ const response = await fetch('/test', {
342
+ method: 'POST',
343
+ headers: {
344
+ 'Content-Type': 'application/x-www-form-urlencoded',
345
+ },
346
+ body: new URLSearchParams({
347
+ 'test': 'connection'
348
+ })
349
+ });
350
+
351
+ const data = await response.json();
352
+
353
+ if (data.status === 'success') {
354
+ showAlert('✅ Conexión exitosa - El servidor está funcionando', 'success');
355
+ } else {
356
+ showAlert('❌ Error en la respuesta del servidor', 'error');
357
+ }
358
+
359
+ } catch (error) {
360
+ showAlert('❌ Error de conexión: ' + error.message, 'error');
361
+ } finally {
362
+ testBtn.disabled = false;
363
+ testBtn.textContent = originalText;
364
+ }
365
+ });
366
+
367
+ document.getElementById('analyzeForm').addEventListener('submit', async function(e) {
368
+ e.preventDefault();
369
+
370
+ const youtubeUrl = document.getElementById('youtube_url').value.trim();
371
+ const analyzeBtn = document.getElementById('analyzeBtn');
372
+ const loading = document.getElementById('loading');
373
+ const alert = document.getElementById('alert');
374
+ const results = document.getElementById('results');
375
+ const videoPlayer = document.getElementById('videoPlayer');
376
+ const youtubeIframe = document.getElementById('youtubeIframe');
377
+
378
+ // Validación básica
379
+ if (!youtubeUrl) {
380
+ showAlert('Por favor ingresa una URL válida', 'error');
381
+ return;
382
+ }
383
+
384
+ // Reset UI
385
+ alert.style.display = 'none';
386
+ results.style.display = 'none';
387
+ videoPlayer.style.display = 'none';
388
+ loading.style.display = 'block';
389
+ analyzeBtn.disabled = true;
390
+ analyzeBtn.textContent = 'Analizando...';
391
+
392
+ try {
393
+ console.log('Enviando URL:', youtubeUrl);
394
+
395
+ const response = await fetch('/analyze', {
396
+ method: 'POST',
397
+ headers: {
398
+ 'Content-Type': 'application/x-www-form-urlencoded',
399
+ },
400
+ body: new URLSearchParams({
401
+ 'youtube_url': youtubeUrl
402
+ })
403
+ });
404
+
405
+ console.log('Response status:', response.status);
406
+ console.log('Response headers:', [...response.headers.entries()]);
407
+
408
+ if (!response.ok) {
409
+ throw new Error(`HTTP error! status: ${response.status}`);
410
+ }
411
+
412
+ const data = await response.json();
413
+ console.log('Response data:', data);
414
+
415
+ loading.style.display = 'none';
416
+
417
+ if (data.status === 'success') {
418
+ // Mostrar reproductor de video
419
+ const videoId = youtubeUrl.match(/[?&]v=([^&]+)/)?.[1] || youtubeUrl.match(/youtu\.be\/([^&?]+)/)?.[1];
420
+ console.log('Video ID extraído:', videoId);
421
+ if (videoId) {
422
+ youtubeIframe.src = `https://www.youtube.com/embed/${videoId}?rel=0`; // ?rel=0 evita videos relacionados
423
+ youtubeIframe.src += ''; // Fuerza recarga
424
+ videoPlayer.style.display = 'block';
425
+ } else {
426
+ console.warn('No se pudo extraer el ID del video');
427
+ }
428
+
429
+ // Mostrar resultados
430
+ displayResults(data.data);
431
+ showAlert('¡Análisis completado exitosamente!', 'success');
432
+ } else {
433
+ showAlert(data.message || 'Error en el análisis', 'error');
434
+ }
435
+
436
+ } catch (error) {
437
+ console.error('Error completo:', error);
438
+ loading.style.display = 'none';
439
+ showAlert('Error de conexión: ' + error.message, 'error');
440
+ } finally {
441
+ analyzeBtn.disabled = false;
442
+ analyzeBtn.textContent = 'Analizar Video';
443
+ }
444
+ });
445
+
446
+ function showAlert(message, type) {
447
+ const alert = document.getElementById('alert');
448
+ alert.textContent = message;
449
+ alert.className = `alert ${type}`;
450
+ alert.style.display = 'block';
451
+ }
452
+
453
+ function displayResults(data) {
454
+ const results = document.getElementById('results');
455
+
456
+ // Video info
457
+ if (data.video_title) {
458
+ document.getElementById('videoTitle').textContent = data.video_title;
459
+ }
460
+
461
+ if (data.video_info) {
462
+ const videoDetails = document.getElementById('videoDetails');
463
+ videoDetails.innerHTML = `
464
+ <div class="metric-item">
465
+ <span class="metric-label">Duración:</span>
466
+ <span class="metric-value">${data.video_info.duracion_total.toFixed(2)}s</span>
467
+ </div>
468
+ <div class="metric-item">
469
+ <span class="metric-label">FPS:</span>
470
+ <span class="metric-value">${data.video_info.fps.toFixed(1)}</span>
471
+ </div>
472
+ <div class="metric-item">
473
+ <span class="metric-label">Detección facial:</span>
474
+ <span class="metric-value">${data.video_info.tasa_deteccion_facial.toFixed(2)}%</span>
475
+ </div>
476
+ `;
477
+ }
478
+
479
+ // Smile metrics
480
+ if (data.sonrisas) {
481
+ const smileMetrics = document.getElementById('smileMetrics');
482
+ smileMetrics.innerHTML = `
483
+ <div class="metric-item">
484
+ <span class="metric-label">Total sonrisas:</span>
485
+ <span class="metric-value">${data.sonrisas.total_sonrisas}</span>
486
+ </div>
487
+ <div class="metric-item">
488
+ <span class="metric-label">Duración promedio:</span>
489
+ <span class="metric-value">${data.sonrisas.duracion_promedio_sonrisas.toFixed(2)}s</span>
490
+ </div>
491
+ <div class="metric-item">
492
+ <span class="metric-label">% tiempo sonriendo:</span>
493
+ <span class="metric-value">${data.sonrisas.porcentaje_tiempo_sonriendo.toFixed(2)}%</span>
494
+ </div>
495
+ `;
496
+ }
497
+
498
+ // Blink metrics
499
+ if (data.parpadeos) {
500
+ const blinkMetrics = document.getElementById('blinkMetrics');
501
+ blinkMetrics.innerHTML = `
502
+ <div class="metric-item">
503
+ <span class="metric-label">Total parpadeos:</span>
504
+ <span class="metric-value">${data.parpadeos.total_parpadeos}</span>
505
+ </div>
506
+ <div class="metric-item">
507
+ <span class="metric-label">Tasa por minuto:</span>
508
+ <span class="metric-value">${data.parpadeos.tasa_parpadeo_por_minuto.toFixed(2)}</span>
509
+ </div>
510
+ `;
511
+ }
512
+
513
+ // Head movement metrics
514
+ if (data.movimiento_cabeza) {
515
+ const headMetrics = document.getElementById('headMetrics');
516
+ headMetrics.innerHTML = `
517
+ <div class="metric-item">
518
+ <span class="metric-label">Estabilidad vertical:</span>
519
+ <span class="metric-value">${data.movimiento_cabeza.estabilidad_vertical.toFixed(4)}</span>
520
+ </div>
521
+ <div class="metric-item">
522
+ <span class="metric-label">Estabilidad horizontal:</span>
523
+ <span class="metric-value">${data.movimiento_cabeza.estabilidad_horizontal.toFixed(4)}</span>
524
+ </div>
525
+ <div class="metric-item">
526
+ <span class="metric-label">Movimiento total:</span>
527
+ <span class="metric-value">${data.movimiento_cabeza.movimiento_total.toFixed(4)}</span>
528
+ </div>
529
+ `;
530
+ }
531
+
532
+ // Negative emotions metrics
533
+ if (data.emociones_negativas) {
534
+ const negativeMetrics = document.getElementById('negativeMetrics');
535
+ negativeMetrics.innerHTML = `
536
+ <div class="metric-item">
537
+ <span class="metric-label">Total enojado:</span>
538
+ <span class="metric-value">${data.emociones_negativas.total_enojado || 0}</span>
539
+ </div>
540
+ <div class="metric-item">
541
+ <span class="metric-label">Duración promedio enojado:</span>
542
+ <span class="metric-value">${(data.emociones_negativas.duracion_promedio_enojado || 0).toFixed(2)}s</span>
543
+ </div>
544
+ <div class="metric-item">
545
+ <span class="metric-label">% tiempo enojado:</span>
546
+ <span class="metric-value">${(data.emociones_negativas.porcentaje_tiempo_enojado || 0).toFixed(2)}%</span>
547
+ </div>
548
+ <div class="metric-item">
549
+ <span class="metric-label">Total gruñón:</span>
550
+ <span class="metric-value">${data.emociones_negativas.total_grunon || 0}</span>
551
+ </div>
552
+ <div class="metric-item">
553
+ <span class="metric-label">Duración promedio gruñón:</span>
554
+ <span class="metric-value">${(data.emociones_negativas.duracion_promedio_grunon || 0).toFixed(2)}s</span>
555
+ </div>
556
+ <div class="metric-item">
557
+ <span class="metric-label">% tiempo gruñón:</span>
558
+ <span class="metric-value">${(data.emociones_negativas.porcentaje_tiempo_grunon || 0).toFixed(2)}%</span>
559
+ </div>
560
+ <div class="metric-item">
561
+ <span class="metric-label">Total furioso:</span>
562
+ <span class="metric-value">${data.emociones_negativas.total_furioso || 0}</span>
563
+ </div>
564
+ <div class="metric-item">
565
+ <span class="metric-label">Duración promedio furioso:</span>
566
+ <span class="metric-value">${(data.emociones_negativas.duracion_promedio_furioso || 0).toFixed(2)}s</span>
567
+ </div>
568
+ <div class="metric-item">
569
+ <span class="metric-label">% tiempo furioso:</span>
570
+ <span class="metric-value">${(data.emociones_negativas.porcentaje_tiempo_furioso || 0).toFixed(2)}%</span>
571
+ </div>
572
+ `;
573
+ }
574
+
575
+ // Crear gráfico
576
+ const ctx = document.getElementById('analysisChart').getContext('2d');
577
+ new Chart(ctx, {
578
+ type: 'bar',
579
+ data: {
580
+ labels: ['Sonrisas', 'Parpadeos', 'Enojado', 'Gruñón', 'Furioso'],
581
+ datasets: [{
582
+ label: 'Métricas de Comportamiento',
583
+ data: [
584
+ data.sonrisas?.total_sonrisas || 0,
585
+ data.parpadeos?.total_parpadeos || 0,
586
+ data.emociones_negativas?.total_enojado || 0,
587
+ data.emociones_negativas?.total_grunon || 0,
588
+ data.emociones_negativas?.total_furioso || 0
589
+ ],
590
+ backgroundColor: ['#667eea', '#28a745', '#ff6384', '#ffcd56', '#ff4500'],
591
+ borderColor: ['#5a67d8', '#219653', '#e31a4c', '#e6b800', '#cc3700'],
592
+ borderWidth: 1
593
+ }]
594
+ },
595
+ options: {
596
+ scales: {
597
+ y: { beginAtZero: true }
598
+ },
599
+ plugins: {
600
+ legend: { display: false },
601
+ title: { display: true, text: 'Resumen de Análisis' }
602
+ }
603
+ }
604
+ });
605
+
606
+ results.style.display = 'block';
607
+ }
608
+ </script>
609
+ </body>
610
+ </html>