VeuReu commited on
Commit
c389f60
·
1 Parent(s): fc920ec

Upload 2 files

Browse files
Files changed (2) hide show
  1. api_client.py +99 -1
  2. app.py +109 -0
api_client.py CHANGED
@@ -4,7 +4,8 @@ import requests
4
  import base64
5
  import zipfile
6
  import io
7
- from typing import Iterable, Dict, Any
 
8
 
9
  class APIClient:
10
  """
@@ -413,3 +414,100 @@ class APIClient:
413
  return {"error": str(e)}
414
 
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import base64
5
  import zipfile
6
  import io
7
+ from typing import Iterable, Dict, Any, Tuple
8
+ from PIL import Image
9
 
10
  class APIClient:
11
  """
 
414
  return {"error": str(e)}
415
 
416
 
417
+ # ===========================
418
+ # Cliente para SVision Space
419
+ # ===========================
420
+
421
+ def describe_image_with_svision(image_path: str, is_face: bool = True) -> Tuple[str, str]:
422
+ """
423
+ Llama al space svision para describir una imagen (cara o escena).
424
+
425
+ Args:
426
+ image_path: Ruta absoluta a la imagen
427
+ is_face: True si es una cara, False si es una escena
428
+
429
+ Returns:
430
+ tuple (descripción_completa, nombre_abreviado)
431
+ """
432
+ try:
433
+ from gradio_client import Client
434
+
435
+ # Conectar al space svision con timeout generoso para cold start de ZeroGPU
436
+ svision_url = os.getenv("SVISION_URL", "https://veureu-svision.hf.space")
437
+ print(f"[svision] Connectant a {svision_url}...")
438
+ client = Client(svision_url)
439
+
440
+ # Cargar imagen
441
+ img = Image.open(image_path)
442
+
443
+ # Preparar prompt según el tipo
444
+ if is_face:
445
+ prompt = "Descriu aquesta persona. Inclou: edat aproximada (jove/adult), gènere, característiques físiques notables (ulleres, barba, bigoti, etc.), expressió i vestimenta."
446
+ else:
447
+ prompt = "Descriu aquesta escena. Inclou: tipus de localització (interior/exterior), elements principals, ambient, il·luminació."
448
+
449
+ print(f"[svision] Enviant petició (pot trigar si ZeroGPU està en cold start)...")
450
+
451
+ # Llamar al endpoint /describe con timeout aumentado para ZeroGPU cold start
452
+ # El primer request puede tardar 30-60 segundos en ZeroGPU
453
+ import time
454
+ start_time = time.time()
455
+
456
+ result = client.predict(
457
+ img, # imagen PIL
458
+ prompt, # texto
459
+ 256, # max_new_tokens
460
+ 0.7, # temperature
461
+ api_name="/describe"
462
+ )
463
+
464
+ elapsed = time.time() - start_time
465
+ print(f"[svision] Resposta rebuda en {elapsed:.1f}s")
466
+
467
+ full_description = result.strip() if result else ""
468
+
469
+ if not full_description:
470
+ return ("", "")
471
+
472
+ # Generar nombre abreviado para caras
473
+ if is_face:
474
+ # Extraer características clave para el nombre
475
+ desc_lower = full_description.lower()
476
+
477
+ # Determinar edad y género
478
+ is_young = any(word in desc_lower for word in ["jove", "nen", "nena", "adolescent", "noi", "noia"])
479
+ is_female = any(word in desc_lower for word in ["dona", "noia", "nena", "femení"])
480
+
481
+ if is_young:
482
+ base_name = "Noia" if is_female else "Noi"
483
+ else:
484
+ base_name = "Dona" if is_female else "Home"
485
+
486
+ # Añadir características distintivas
487
+ features = []
488
+ if "ullere" in desc_lower or "gafa" in desc_lower:
489
+ features.append("ulleres")
490
+ if "barba" in desc_lower:
491
+ features.append("barba")
492
+ if "bigoti" in desc_lower:
493
+ features.append("bigoti")
494
+
495
+ if features:
496
+ short_name = f"{base_name} amb {', '.join(features)}"
497
+ else:
498
+ short_name = base_name
499
+ else:
500
+ # Para escenas, extraer primeras palabras clave
501
+ words = full_description.split()[:4]
502
+ short_name = " ".join(words).capitalize()
503
+
504
+ print(f"[svision] Descripció generada: {full_description[:100]}...")
505
+ print(f"[svision] Nom: {short_name}")
506
+
507
+ return (full_description, short_name)
508
+
509
+ except Exception as e:
510
+ print(f"[svision] Error al descriure imatge: {e}")
511
+ import traceback
512
+ traceback.print_exc()
513
+ return ("", "")
app.py CHANGED
@@ -454,6 +454,61 @@ if page == "Processar vídeo nou":
454
  st.session_state.video_name_from_engine = vname
455
  st.session_state.engine_base_dir = base_dir
456
  st.session_state.diarization_info = diar_info or {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  # Escenes (opcional, quan l'engine ho proporcioni)
458
  try:
459
  st.session_state.scene_clusters = res.get("scene_clusters", []) or []
@@ -471,6 +526,60 @@ if page == "Processar vídeo nou":
471
  scs = scene_out.get("scene_clusters") if isinstance(scene_out, dict) else None
472
  if isinstance(scs, list):
473
  st.session_state.scene_clusters = scs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  else:
475
  st.session_state.scene_clusters = []
476
  except Exception as _esc:
 
454
  st.session_state.video_name_from_engine = vname
455
  st.session_state.engine_base_dir = base_dir
456
  st.session_state.diarization_info = diar_info or {}
457
+
458
+ # Llamar a svision para generar descripciones de caras
459
+ if chars:
460
+ from api_client import describe_image_with_svision
461
+ import os as _os2
462
+ import tempfile
463
+ import requests as _req
464
+
465
+ # Mostrar progreso para el usuario
466
+ total_chars = len(chars)
467
+ progress_text = st.empty()
468
+ progress_bar = st.progress(0.0)
469
+ progress_text.info("🎨 Generant descripcions amb Salamandra Vision (pot trigar uns segons en la primera càrrega)...")
470
+
471
+ for idx, ch in enumerate(chars):
472
+ # Actualizar progreso
473
+ progress_bar.progress((idx + 1) / total_chars)
474
+ progress_text.info(f"🎨 Descrivint cara {idx + 1}/{total_chars} amb Salamandra Vision...")
475
+
476
+ # Obtener URL de imagen representativa
477
+ img_url = ch.get("image_url")
478
+ if img_url:
479
+ # Construir URL completa
480
+ if not img_url.startswith("http"):
481
+ img_url = f"{BACKEND_BASE_URL}{img_url}"
482
+
483
+ try:
484
+ # Descargar imagen temporalmente
485
+ resp = _req.get(img_url, timeout=10)
486
+ if resp.status_code == 200:
487
+ # Guardar en archivo temporal
488
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
489
+ tmp.write(resp.content)
490
+ tmp_path = tmp.name
491
+
492
+ # Llamar a svision (con timeout generoso para cold start)
493
+ desc, name = describe_image_with_svision(tmp_path, is_face=True)
494
+ if desc:
495
+ ch["description"] = desc
496
+ if name:
497
+ ch["name"] = name
498
+ log(f"Descripció svision per {ch.get('id', 'unknown')}: {name}")
499
+
500
+ # Limpiar archivo temporal
501
+ _os2.unlink(tmp_path)
502
+ except Exception as e:
503
+ log(f"Error cridant svision per {ch.get('id', 'unknown')}: {e}")
504
+ # Continuar con valores por defecto si falla
505
+ if "name" not in ch or not ch["name"]:
506
+ ch["name"] = f"Personatge {idx + 1}"
507
+
508
+ # Limpiar indicadores de progreso
509
+ progress_bar.empty()
510
+ progress_text.success(f"✅ {total_chars} cares descrites amb Salamandra Vision")
511
+
512
  # Escenes (opcional, quan l'engine ho proporcioni)
513
  try:
514
  st.session_state.scene_clusters = res.get("scene_clusters", []) or []
 
526
  scs = scene_out.get("scene_clusters") if isinstance(scene_out, dict) else None
527
  if isinstance(scs, list):
528
  st.session_state.scene_clusters = scs
529
+
530
+ # Llamar a svision para generar descripciones de escenas
531
+ if scs:
532
+ from api_client import describe_image_with_svision
533
+ import os as _os3
534
+ import tempfile
535
+ import requests as _req2
536
+
537
+ # Mostrar progreso para el usuario
538
+ total_scenes = len(scs)
539
+ scene_progress_text = st.empty()
540
+ scene_progress_bar = st.progress(0.0)
541
+ scene_progress_text.info("🎬 Generant descripcions d'escenes amb Salamandra Vision...")
542
+
543
+ for sidx, sc in enumerate(scs):
544
+ # Actualizar progreso
545
+ scene_progress_bar.progress((sidx + 1) / total_scenes)
546
+ scene_progress_text.info(f"🎬 Descrivint escena {sidx + 1}/{total_scenes} amb Salamandra Vision...")
547
+
548
+ # Obtener URL de imagen representativa
549
+ img_url = sc.get("image_url")
550
+ if img_url:
551
+ # Construir URL completa
552
+ if not img_url.startswith("http"):
553
+ img_url = f"{BACKEND_BASE_URL}{img_url}"
554
+
555
+ try:
556
+ # Descargar imagen temporalmente
557
+ resp = _req2.get(img_url, timeout=10)
558
+ if resp.status_code == 200:
559
+ # Guardar en archivo temporal
560
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
561
+ tmp.write(resp.content)
562
+ tmp_path = tmp.name
563
+
564
+ # Llamar a svision
565
+ desc, name = describe_image_with_svision(tmp_path, is_face=False)
566
+ if desc:
567
+ sc["description"] = desc
568
+ if name:
569
+ sc["name"] = name
570
+ log(f"Descripció svision per escena {sc.get('id', 'unknown')}: {name}")
571
+
572
+ # Limpiar archivo temporal
573
+ _os3.unlink(tmp_path)
574
+ except Exception as e:
575
+ log(f"Error cridant svision per escena {sc.get('id', 'unknown')}: {e}")
576
+ # Continuar con valores por defecto si falla
577
+ if "name" not in sc or not sc["name"]:
578
+ sc["name"] = f"Escena {sidx + 1}"
579
+
580
+ # Limpiar indicadores de progreso
581
+ scene_progress_bar.empty()
582
+ scene_progress_text.success(f"✅ {total_scenes} escenes descrites amb Salamandra Vision")
583
  else:
584
  st.session_state.scene_clusters = []
585
  except Exception as _esc: