VeuReu commited on
Commit
048c619
·
1 Parent(s): 153f06f

Upload 5 files

Browse files
Files changed (3) hide show
  1. app.py +183 -22
  2. scripts/client_example.py +33 -0
  3. sources/UNE-153020.txt +73 -0
app.py CHANGED
@@ -507,33 +507,194 @@ if page == "Processar vídeo nou":
507
  # ]
508
  # st.session_state.characters_saved = False
509
 
510
- # --- 3. Formularios de personajes ---
511
  if st.session_state.characters_detected:
512
- st.subheader("Personatges detectats")
513
- for char in st.session_state.characters_detected:
514
- with st.form(key=f"form_{char['id']}"):
515
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  with col1:
517
- st.image(char['image_path'], width=150)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
 
519
  with col2:
520
- st.caption(char['description'])
521
- st.text_input("Nom del personatge", key=f"name_{char['id']}")
522
- st.form_submit_button("Cercar")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
 
524
- st.markdown("---_**")
525
-
526
- # --- 4. Guardar y Generar ---
527
- col1, col2, col3 = st.columns([1,1,2])
528
- with col1:
529
- if st.button("Desar", type="primary"):
530
- # Aquí iría la lógica para guardar los nombres de los personajes
531
- st.session_state.characters_saved = True
532
- st.success("Personatges desats correctament.")
533
-
534
- with col2:
535
- if st.session_state.characters_saved:
536
- st.button("Generar Audiodescripció")
537
 
538
  elif page == "Analitzar audio-descripcions":
539
  require_login()
 
507
  # ]
508
  # st.session_state.characters_saved = False
509
 
510
+ # --- 3. Formularios de personajes (apilados) ---
511
  if st.session_state.characters_detected:
512
+ st.markdown("---")
513
+ st.subheader(f"📋 Personatges detectats: {len(st.session_state.characters_detected)}")
514
+ st.info("Edita cada personatge i confirma el fine-tuning manual al final. Els personatges amb el mateix nom es fusionaran.")
515
+
516
+ # Inicializar datos de personajes si no existe
517
+ if 'character_data' not in st.session_state:
518
+ st.session_state.character_data = {}
519
+ # Inicializar con datos por defecto
520
+ for char in st.session_state.characters_detected:
521
+ char_id = char['id']
522
+ st.session_state.character_data[char_id] = {
523
+ 'name': char.get('name', ''),
524
+ 'description': '',
525
+ 'selected_faces': list(range(char.get('num_faces', 0))), # Todas seleccionadas por defecto
526
+ 'selected_voices': [], # Por ahora vacío
527
+ 'current_face_idx': 0,
528
+ 'current_voice_idx': 0
529
+ }
530
+
531
+ # Mostrar formulario para cada personaje
532
+ for idx, char in enumerate(st.session_state.characters_detected):
533
+ char_id = char['id']
534
+ char_data = st.session_state.character_data[char_id]
535
+
536
+ # Contenedor con borde para cada personaje
537
+ with st.container():
538
+ st.markdown(f"### Personatge {idx + 1}: {char_data['name'] or char_id}")
539
+
540
+ col1, col2 = st.columns([1, 1])
541
+
542
+ # --- Columna 1: Visualizadores ---
543
  with col1:
544
+ # Visualizador de caras
545
+ st.markdown("**🖼️ Mostres de cara:**")
546
+
547
+ num_faces = char.get('num_faces', 0)
548
+ if num_faces > 0 and char_data['selected_faces']:
549
+ current_face_idx = char_data['current_face_idx']
550
+ selected_faces = char_data['selected_faces']
551
+
552
+ # Navegación de caras
553
+ col_nav1, col_nav2, col_nav3, col_nav4 = st.columns([1, 2, 1, 1])
554
+
555
+ with col_nav1:
556
+ if st.button("◀", key=f"face_prev_{char_id}", disabled=(current_face_idx == 0)):
557
+ st.session_state.character_data[char_id]['current_face_idx'] = max(0, current_face_idx - 1)
558
+ st.rerun()
559
+
560
+ with col_nav2:
561
+ st.caption(f"Cara {current_face_idx + 1} de {len(selected_faces)}")
562
+
563
+ with col_nav3:
564
+ if st.button("▶", key=f"face_next_{char_id}", disabled=(current_face_idx >= len(selected_faces) - 1)):
565
+ st.session_state.character_data[char_id]['current_face_idx'] = min(len(selected_faces) - 1, current_face_idx + 1)
566
+ st.rerun()
567
+
568
+ with col_nav4:
569
+ if st.button("❌", key=f"face_delete_{char_id}", disabled=(len(selected_faces) <= 1)):
570
+ # Eliminar cara actual
571
+ face_to_remove = selected_faces[current_face_idx]
572
+ st.session_state.character_data[char_id]['selected_faces'].remove(face_to_remove)
573
+ st.session_state.character_data[char_id]['current_face_idx'] = min(current_face_idx, len(selected_faces) - 2)
574
+ st.rerun()
575
+
576
+ # Mostrar imagen de la cara actual
577
+ if 'folder' in char:
578
+ try:
579
+ # Construir URL de la cara
580
+ face_filename = f"face_{selected_faces[current_face_idx]:03d}.jpg"
581
+ face_url = f"{BACKEND_BASE_URL}/files/{st.session_state.video_name}/{char_id}/{face_filename}"
582
+ st.image(face_url, width=250)
583
+ except Exception as e:
584
+ st.info(f"Imatge no disponible: {e}")
585
+ else:
586
+ st.info("No hi ha mostres de cara")
587
+
588
+ st.markdown("---")
589
+
590
+ # Visualizador de voces
591
+ st.markdown("**🎤 Mostres de veu:**")
592
+ st.info("🚧 Funcionalitat de veu en desenvolupament")
593
+
594
+ # TODO: Implementar visualizador de voces similar al de caras
595
 
596
+ # --- Columna 2: Datos del personaje ---
597
  with col2:
598
+ st.markdown("**📝 Informació del personatge:**")
599
+
600
+ # Nombre del personaje
601
+ char_name = st.text_input(
602
+ "Nom del personatge:",
603
+ value=char_data['name'],
604
+ key=f"name_input_{char_id}",
605
+ placeholder="Ex: Maria, Joan, etc.",
606
+ help="Personatges amb el mateix nom es fusionaran"
607
+ )
608
+
609
+ # Actualizar nombre en tiempo real
610
+ if char_name != char_data['name']:
611
+ st.session_state.character_data[char_id]['name'] = char_name
612
+
613
+ # Descripción
614
+ char_description = st.text_area(
615
+ "Descripció (text lliure):",
616
+ value=char_data['description'],
617
+ key=f"desc_input_{char_id}",
618
+ placeholder="Ex: Dona d'uns 30 anys, cabell ros, ulleres...",
619
+ height=150
620
+ )
621
+
622
+ # Actualizar descripción en tiempo real
623
+ if char_description != char_data['description']:
624
+ st.session_state.character_data[char_id]['description'] = char_description
625
+
626
+ # Información adicional
627
+ st.caption(f"**ID original:** {char_id}")
628
+ st.caption(f"**Caras seleccionades:** {len(char_data['selected_faces'])} de {num_faces}")
629
+
630
+ st.markdown("---")
631
+
632
+ # --- 4. Botón de confirmación de fine-tuning ---
633
+ st.markdown("### 🎯 Confirmació del fine-tuning manual")
634
+
635
+ if st.button("✅ Confirmar fine-tuning i fusionar personatges", type="primary", use_container_width=True):
636
+ # Agrupar personajes por nombre
637
+ merged_characters = {}
638
+
639
+ for char in st.session_state.characters_detected:
640
+ char_id = char['id']
641
+ char_data = st.session_state.character_data[char_id]
642
+ char_name = char_data['name'].strip()
643
+
644
+ if not char_name:
645
+ char_name = f"Personatge sense nom {char_id}"
646
+
647
+ if char_name not in merged_characters:
648
+ merged_characters[char_name] = {
649
+ 'id': f"merged_{len(merged_characters) + 1}",
650
+ 'name': char_name,
651
+ 'description': char_data['description'],
652
+ 'selected_faces': [],
653
+ 'selected_voices': [],
654
+ 'original_ids': []
655
+ }
656
+
657
+ # Fusionar datos
658
+ merged_characters[char_name]['selected_faces'].extend(char_data['selected_faces'])
659
+ merged_characters[char_name]['selected_voices'].extend(char_data['selected_voices'])
660
+ merged_characters[char_name]['original_ids'].append(char_id)
661
+
662
+ # Fusionar descripciones (concatenar si hay múltiples)
663
+ if char_data['description'] and char_data['description'] not in merged_characters[char_name]['description']:
664
+ if merged_characters[char_name]['description']:
665
+ merged_characters[char_name]['description'] += " | " + char_data['description']
666
+ else:
667
+ merged_characters[char_name]['description'] = char_data['description']
668
+
669
+ # Actualizar personajes con los fusionados
670
+ st.session_state.characters_detected = list(merged_characters.values())
671
+
672
+ # Reinicializar character_data con los nuevos personajes
673
+ st.session_state.character_data = {}
674
+ for char in st.session_state.characters_detected:
675
+ char_id = char['id']
676
+ st.session_state.character_data[char_id] = {
677
+ 'name': char['name'],
678
+ 'description': char['description'],
679
+ 'selected_faces': char['selected_faces'],
680
+ 'selected_voices': char['selected_voices'],
681
+ 'current_face_idx': 0,
682
+ 'current_voice_idx': 0
683
+ }
684
+
685
+ # Marcar como guardados
686
+ st.session_state.characters_saved = True
687
+ st.success(f"✅ Fine-tuning confirmat! {len(merged_characters)} personatges finals.")
688
+ st.balloons()
689
+ st.rerun()
690
 
691
+ # --- 5. Botón para generar audiodescripción (solo si están guardados) ---
692
+ if st.session_state.characters_saved:
693
+ st.markdown("---")
694
+ st.markdown("### 🎬 Generar audiodescripció")
695
+ if st.button("🎬 Generar Audiodescripció", type="primary", use_container_width=True):
696
+ st.info("🚧 Funcionalitat en desenvolupament...")
697
+ # Aquí iría la lógica para generar la audiodescripción
 
 
 
 
 
 
698
 
699
  elif page == "Analitzar audio-descripcions":
700
  require_login()
scripts/client_example.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =====================================
2
+ # File: client_example.py (opcional)
3
+ # =====================================
4
+ import requests
5
+
6
+ class VeureuEngineClient:
7
+ def __init__(self, base_url: str):
8
+ self.base = base_url.rstrip("/")
9
+
10
+ def process_video(self, video_path: str, **kwargs):
11
+ with open(video_path, "rb") as f:
12
+ files = {"video_file": (Path(video_path).name, f, "video/mp4")}
13
+ data = {"config_path": kwargs.get("config_path", "config_veureu.yaml"),
14
+ "out_root": kwargs.get("out_root", "results"),
15
+ "db_dir": kwargs.get("db_dir", "chroma_db")}
16
+ r = requests.post(f"{self.base}/process_video", files=files, data=data, timeout=3600)
17
+ r.raise_for_status()
18
+ return r.json()
19
+
20
+ def load_casting(self, faces_dir: str, voices_dir: str, db_dir: str = "chroma_db", drop_collections: bool = False):
21
+ data = {"faces_dir": faces_dir, "voices_dir": voices_dir, "db_dir": db_dir, "drop_collections": str(drop_collections)}
22
+ r = requests.post(f"{self.base}/load_casting", data=data, timeout=600)
23
+ r.raise_for_status(); return r.json()
24
+
25
+ def refine_narration(self, dialogues_srt: str, frame_descriptions: list, model_url: str, une_guidelines_path: str):
26
+ data = {
27
+ "dialogues_srt": dialogues_srt,
28
+ "frame_descriptions_json": json.dumps(frame_descriptions, ensure_ascii=False),
29
+ "model_url": model_url,
30
+ "une_guidelines_path": une_guidelines_path,
31
+ }
32
+ r = requests.post(f"{self.base}/refine_narration", data=data, timeout=600)
33
+ r.raise_for_status(); return r.json()
sources/UNE-153020.txt ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. DEFINICIÓN Y OBJETIVO
2
+
3
+ La audiodescripción (AD) es un servicio de apoyo a la comunicación para las personas ciegas o con baja visión, regulado por la norma UNE 153020 y que consiste en compensar la falta de información visual relevante para la comprensión global y el disfrute del producto audiovisual con la descripción objetiva, clara y sucinta de las imágenes, de modo que las personas con discapacidad visual lo perciban de la forma más parecida posible a como lo hacen las personas videntes.
4
+
5
+ 2. PAUTAS
6
+ 2.1. ¿QUÉ INFORMACIÓN SE AUDIODESCRIBE?
7
+
8
+ Se debe tratar de informar de:
9
+ ● CUÁNDO Y DÓNDE (Describir lugares y dimensiones, hora del día, si está oscuro…)
10
+ “En un callejón oscuro…”
11
+ “Es de día, una señora anda por la calle”.
12
+ ● QUIÉN (Describir e identificar, ropa, atributos físicos, etnia si es relevante…)
13
+ ● QUÉ (Respuestas no verbales, relacionadas con el diálogo)
14
+ ● CÓMO
15
+
16
+ Solo se audiodescribe la información que aporta algo.
17
+ 2.2. ¿CUÁNDO SE AUDIODESCRIBE?
18
+
19
+ Se audiodescribe en los huecos o silencios. Si las personas en escena están hablando, no podemos audiodescribir por encima. En los programas de televisión, sobre todo en los más dinámicos, suele haber poca cabida para audiodescribir.
20
+
21
+ En ocasiones, hay silencios relevantes para la trama y hay que respetarlos (Para generar sensación de suspense, por motivos cómicos…). Esas veces hay que dejar que el programa audiovisual “respire” por sí mismo, evitando audiodescribir lo que es obvio o ya viene transmitido en la pista sonora (diálogos o ruidos)
22
+
23
+
24
+ 2.2. LENGUAJE EN LA AUDIODESCRIPCIÓN
25
+
26
+ ● Siempre desde la perspectiva del espectador. Describimos lo que nosotros vemos como espectadores, sin información privilegiada.
27
+ ● Se describe en PRESENTE
28
+ “Broncano se agacha”.
29
+ ● VALOR NARRATIVO: Se describe el lenguaje corporal, las expresiones faciales, los movimientos…
30
+ “María lo mira asombrada”.
31
+ “Kevin guiña un ojo”.
32
+ ● El vocabulario debe ser EXPRESIVO: lleno de significado
33
+ ● Objetividad y claridad
34
+ ● Se premia la riqueza léxica. Adjetivos específicos (“astuto” mejor que “malo”).
35
+ ● Lenguaje conciso
36
+ ● Adaptarse al tono y estilo del contenido (sin lenguaje ofensivo, a menos que aparezca como texto en pantalla)
37
+ ● Los logotipos y títulos se describen
38
+ ● Evitar la voz pasiva.
39
+ ● Si hay cortes o cambios de tiempo, se indica. (Característica de películas y series)
40
+ “3 años después…”.
41
+ “Valencia, 1995”.
42
+ ● También se audiodescriben las cabeceras, aunque siempre sea la misma.
43
+ ● También se audiodescribe el texto en pantalla
44
+
45
+ 2.3. ERRORES A EVITAR
46
+
47
+ - Evitar: metáforas, coloquialismos, regionalismos, léxico cinematográfico, los verbos “aparecer, ver…”.
48
+ Las siguientes descripciones serían incorrectas:
49
+ “Ramón aparece en escena…”
50
+ “Vemos al niño saltar…”
51
+ “En primer plano, Lucía…”
52
+ - No interpretar, debemos ser neutros y objetivos
53
+ - No explicar más de lo que se ve ni hacer suposiciones
54
+ - No ser demasiado literario ni didáctico.
55
+
56
+
57
+ 3. LOCUCIÓN
58
+
59
+ En cuanto a la locución, se recomienda el uso de voz profesional, neutra y con buena dicción. No obstante, en el ámbito educativo, así como en otros contextos como los museos o el teatro, se ha utilizado motores de síntesis de voz para generar la locución de audiodescripción. Esta práctica no está recogida en la norma.
60
+
61
+ “Las locuciones deben ser neutras y la dicción correcta (entonación, ritmo y vocalización adecuados), debiendo evitarse la entonación afectiva". Para obras infantiles, "se recomienda que el locutor o locutora utilice una entonación adecuada para niños, pudiendo ser algo más expresiva" (AENOR, 2005, p. 9).
62
+
63
+
64
+ 4. FORMATO
65
+
66
+ Formato ESEF (*.esf) El formato ESEF es un estándar de la industria audiovisual para la producción y reproducción la audiodescripción en entornos de emisión de televisión.
67
+
68
+ Este formato maneja archivos de audio en formato Broadcast WAV (BWF).
69
+
70
+ Una de las ventajas de este formato es que almacena toda la información de la audiodescripción de manera independiente del contenido audiodescrito. Este formato contiene por separado la información del guión (texto temporizado), los contenidos de la locución en ficheros independientes y la configuración para garantizar una mezcla de la banda sonora en el destino.
71
+
72
+ Este formato debe manipularse solamente con herramientas profesionales específicas de audiodescripción.
73
+