VeuReu commited on
Commit
e61d363
·
1 Parent(s): c79f28f

Upload 6 files

Browse files
page_modules/analyze_transcriptions.py CHANGED
@@ -9,6 +9,7 @@ from typing import Dict, Optional
9
  import streamlit as st
10
 
11
  from utils import save_bytes
 
12
 
13
 
14
  def load_eval_values(vid_dir: Path, version: str) -> Optional[Dict[str, int]]:
@@ -77,8 +78,14 @@ def render_analyze_transcriptions_page(api, permissions: Dict[str, bool]) -> Non
77
  st.stop()
78
 
79
  carpetes = [p.name for p in sorted(base_dir.iterdir()) if p.is_dir() and p.name != "completed"]
 
 
 
 
 
 
80
  if not carpetes:
81
- st.info("No s'ha trobat la carpeta **videos**. Crea-la i afegeix-hi subcarpetes amb els teus vídeos.")
82
  st.stop()
83
 
84
  if "current_video" not in st.session_state:
@@ -334,7 +341,7 @@ def render_analyze_transcriptions_page(api, permissions: Dict[str, bool]) -> Non
334
  try:
335
  from databases import add_feedback_ad
336
 
337
- # Guardar en la base de datos
338
  add_feedback_ad(
339
  video_name=seleccio,
340
  user_id=st.session_state.user["id"],
@@ -346,10 +353,47 @@ def render_analyze_transcriptions_page(api, permissions: Dict[str, bool]) -> Non
346
  expressivitat=expressivitat,
347
  comments=comments or None,
348
  )
349
-
350
- # También guardar en CSV (reubicado en demo/data/videos)
 
351
  video_dir = Path("demo/data/videos") / seleccio
352
- version = st.session_state.get("selected_version", "MoE")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  csv_path = video_dir / version / "eval.csv"
354
 
355
  csv_data = [
 
9
  import streamlit as st
10
 
11
  from utils import save_bytes
12
+ from databases import get_accessible_videos_for_session, insert_demo_feedback_row
13
 
14
 
15
  def load_eval_values(vid_dir: Path, version: str) -> Optional[Dict[str, int]]:
 
78
  st.stop()
79
 
80
  carpetes = [p.name for p in sorted(base_dir.iterdir()) if p.is_dir() and p.name != "completed"]
81
+
82
+ # Filtrar segons permisos (videos.db + events.db)
83
+ session_id = st.session_state.get("session_id")
84
+ accessible = set(get_accessible_videos_for_session(session_id))
85
+ carpetes = [c for c in carpetes if c in accessible]
86
+
87
  if not carpetes:
88
+ st.info("No hi ha cap vídeo disponible per analitzar amb la sessió actual.")
89
  st.stop()
90
 
91
  if "current_video" not in st.session_state:
 
341
  try:
342
  from databases import add_feedback_ad
343
 
344
+ # Guardar en la base de datos agregada d'AD
345
  add_feedback_ad(
346
  video_name=seleccio,
347
  user_id=st.session_state.user["id"],
 
353
  expressivitat=expressivitat,
354
  comments=comments or None,
355
  )
356
+
357
+ # Determinar versió i llegir UNE/free per a la inserció detallada
358
+ version = subcarpeta_seleccio or "MoE"
359
  video_dir = Path("demo/data/videos") / seleccio
360
+ une_path = video_dir / version / "une_ad.srt"
361
+ free_path = video_dir / version / "free_ad.txt"
362
+
363
+ try:
364
+ une_ad_text = une_path.read_text(encoding="utf-8") if une_path.exists() else ""
365
+ except Exception:
366
+ une_ad_text = une_path.read_text(errors="ignore") if une_path.exists() else ""
367
+
368
+ try:
369
+ free_ad_text = free_path.read_text(encoding="utf-8") if free_path.exists() else ""
370
+ except Exception:
371
+ free_ad_text = free_path.read_text(errors="ignore") if free_path.exists() else ""
372
+
373
+ user_name = (
374
+ st.session_state.user.get("username")
375
+ if isinstance(st.session_state.get("user"), dict)
376
+ else str(st.session_state.get("user", ""))
377
+ )
378
+ session_id = st.session_state.get("session_id", "")
379
+
380
+ insert_demo_feedback_row(
381
+ user=user_name or "",
382
+ session=session_id or "",
383
+ video_name=seleccio,
384
+ version=version,
385
+ une_ad=une_ad_text,
386
+ free_ad=free_ad_text,
387
+ comments=comments or None,
388
+ transcripcio=transcripcio,
389
+ identificacio=identificacio,
390
+ localitzacions=localitzacions,
391
+ activitats=activitats,
392
+ narracions=narracions,
393
+ expressivitat=expressivitat,
394
+ )
395
+
396
+ # También guardar en CSV (reubicado en demo/data/videos)
397
  csv_path = video_dir / version / "eval.csv"
398
 
399
  csv_data = [
page_modules/process_video.py CHANGED
@@ -1,123 +1,125 @@
1
- """UI logic for the "Processar vídeo nou" page - Recovered from backup with full functionality."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import shutil
7
- import subprocess
8
- import os
9
- import time
10
- import tempfile
11
- from pathlib import Path
12
-
13
- import streamlit as st
14
- from PIL import Image, ImageDraw
15
-
16
-
17
- def get_all_catalan_names():
18
- """Retorna tots els noms catalans disponibles."""
19
- noms_home = ["Jordi", "Marc", "Pau", "Pere", "Joan", "Josep", "David", "Àlex", "Guillem", "Albert",
20
- "Arnau", "Martí", "Bernat", "Oriol", "Roger", "Pol", "Lluís", "Sergi", "Carles", "Xavier"]
21
- noms_dona = ["Maria", "Anna", "Laura", "Marta", "Cristina", "Núria", "Montserrat", "Júlia", "Sara", "Carla",
22
- "Alba", "Elisabet", "Rosa", "Gemma", "Sílvia", "Teresa", "Irene", "Laia", "Marina", "Bet"]
23
- return noms_home, noms_dona
24
-
25
-
26
- def get_catalan_name_for_speaker(speaker_label: int, used_names_home: list = None, used_names_dona: list = None) -> str:
27
- """Genera un nom català per a un speaker, reutilitzant noms de caras si estan disponibles."""
28
- noms_home, noms_dona = get_all_catalan_names()
29
-
30
- if used_names_home is None:
31
- used_names_home = []
32
- if used_names_dona is None:
33
- used_names_dona = []
34
-
35
- is_male = (speaker_label % 2 == 0)
36
-
37
- if is_male:
38
- if used_names_home:
39
- idx = speaker_label // 2
40
- return used_names_home[idx % len(used_names_home)]
41
- else:
42
- hash_val = hash(f"speaker_{speaker_label}")
43
- return noms_home[abs(hash_val) % len(noms_home)]
44
- else:
45
- if used_names_dona:
46
- idx = speaker_label // 2
47
- return used_names_dona[idx % len(used_names_dona)]
48
- else:
49
- hash_val = hash(f"speaker_{speaker_label}")
50
- return noms_dona[abs(hash_val) % len(noms_dona)]
51
-
52
-
53
- def _get_video_duration(path: str) -> float:
54
- """Return video duration in seconds using ffprobe, ffmpeg or OpenCV as fallback."""
55
- cmd = [
56
- "ffprobe",
57
- "-v",
58
- "error",
59
- "-show_entries",
60
- "format=duration",
61
- "-of",
62
- "default=noprint_wrappers=1:nokey=1",
63
- path,
64
- ]
65
- try:
66
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
67
- return float(result.stdout.strip())
68
- except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
69
- pass
70
-
71
- if shutil.which("ffmpeg"):
72
- try:
73
- ffmpeg_cmd = ["ffmpeg", "-i", path]
74
- result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, check=False)
75
- output = result.stderr or result.stdout or ""
76
- match = re.search(r"Duration:\s*(\d+):(\d+):(\d+\.\d+)", output)
77
- if match:
78
- hours, minutes, seconds = match.groups()
79
- total_seconds = (int(hours) * 3600) + (int(minutes) * 60) + float(seconds)
80
- return float(total_seconds)
81
- except FileNotFoundError:
82
- pass
83
-
84
- # Últim recurs: intentar amb OpenCV si està disponible
85
- try:
86
- import cv2
87
-
88
- cap = cv2.VideoCapture(path)
89
- if cap.isOpened():
90
- fps = cap.get(cv2.CAP_PROP_FPS) or 0
91
- frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0
92
- cap.release()
93
-
94
- if fps > 0 and frame_count > 0:
95
- return float(frame_count / fps)
96
- else:
97
- cap.release()
98
- except Exception:
99
- pass
100
-
101
- return 0.0
102
-
103
-
104
- def _transcode_video(input_path: str, output_path: str, max_duration: int | None = None) -> None:
105
- cmd = ["ffmpeg", "-y", "-i", input_path]
106
- if max_duration is not None:
107
- cmd += ["-t", str(max_duration)]
108
- cmd += [
109
- "-c:v",
110
- "libx264",
111
- "-preset",
112
- "veryfast",
113
- "-crf",
114
- "23",
115
- "-c:a",
116
- "aac",
117
- "-movflags",
118
- "+faststart",
119
- output_path,
120
- ]
 
 
121
  result = subprocess.run(cmd, capture_output=True, text=True)
122
  if result.returncode != 0:
123
  raise RuntimeError(result.stderr.strip() or "ffmpeg failed")
@@ -236,11 +238,33 @@ def render_process_video_page(api, backend_base_url: str) -> None:
236
  MAX_SIZE_MB = 20
237
  MAX_DURATION_S = 240 # 4 minutos
238
 
239
- uploaded_file = st.file_uploader(
240
- "Puja un clip de vídeo (MP4, < 20MB, < 4 minuts)",
241
- type=["mp4"],
242
- key="video_uploader",
243
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  if uploaded_file is not None:
246
  # Resetear el estado si se sube un nuevo archivo
@@ -295,16 +319,51 @@ def render_process_video_page(api, backend_base_url: str) -> None:
295
  is_valid = False
296
 
297
  if is_valid and final_video_path is not None:
 
 
 
298
  st.session_state.video_uploaded.update(
299
  {
300
  "status": "processed",
301
  "path": str(final_video_path),
302
  "was_truncated": was_truncated or duration_unknown,
303
  "duration_unknown": duration_unknown,
304
- "bytes": uploaded_file.getvalue(),
305
  "name": uploaded_file.name,
 
306
  }
307
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  st.rerun()
309
  finally:
310
  if temp_path.exists():
@@ -1046,130 +1105,130 @@ def render_process_video_page(api, backend_base_url: str) -> None:
1046
  face_data = face_items[0] if face_items else None
1047
 
1048
  col_faces, col_voices, col_text = st.columns([1, 1, 1.5])
1049
-
1050
- with col_faces:
1051
- if all_faces:
1052
- carousel_key = f"combined_face_{pidx}"
1053
- if f"{carousel_key}_idx" not in st.session_state:
1054
- st.session_state[f"{carousel_key}_idx"] = 0
1055
- cur = st.session_state[f"{carousel_key}_idx"]
1056
- if cur >= len(all_faces):
1057
- cur = 0
1058
- st.session_state[f"{carousel_key}_idx"] = cur
1059
- fname = all_faces[cur]
1060
- ch = face_data["char_data"] if face_data else {}
1061
- if fname.startswith("/files/"):
1062
- img_url = f"{backend_base_url}{fname}"
1063
- else:
1064
- base = ch.get("image_url") or ""
1065
- base_dir = "/".join((base or "/").split("/")[:-1])
1066
- img_url = f"{backend_base_url}{base_dir}/{fname}" if base_dir else f"{backend_base_url}{fname}"
1067
- st.image(img_url, width=150)
1068
- st.caption(f"Cara {cur+1}/{len(all_faces)}")
1069
- bcol1, bcol2 = st.columns(2)
1070
- with bcol1:
1071
- if st.button("⬅️", key=f"combined_face_prev_{pidx}"):
1072
- st.session_state[f"{carousel_key}_idx"] = (cur - 1) % len(all_faces)
1073
- st.rerun()
1074
- with bcol2:
1075
- if st.button("➡️", key=f"combined_face_next_{pidx}"):
1076
- st.session_state[f"{carousel_key}_idx"] = (cur + 1) % len(all_faces)
1077
- st.rerun()
1078
- else:
1079
- st.info("Sense imatges")
1080
-
1081
- with col_voices:
1082
- if voice_data:
1083
- clips = voice_data["clips"]
1084
- if clips:
1085
- carousel_key = f"combined_voice_{pidx}"
1086
- if f"{carousel_key}_idx" not in st.session_state:
1087
- st.session_state[f"{carousel_key}_idx"] = 0
1088
- cur = st.session_state[f"{carousel_key}_idx"]
1089
- if cur >= len(clips):
1090
- cur = 0
1091
- st.session_state[f"{carousel_key}_idx"] = cur
1092
- fname = clips[cur]
1093
- audio_url = f"{backend_base_url}/audio/{vname}/{fname}" if (vname and fname) else None
1094
- if audio_url:
1095
- st.audio(audio_url, format="audio/wav")
1096
- st.caption(f"Veu {cur+1}/{len(clips)}")
1097
- bcol1, bcol2 = st.columns(2)
1098
- with bcol1:
1099
- if st.button("⬅️", key=f"combined_voice_prev_{pidx}"):
1100
- st.session_state[f"{carousel_key}_idx"] = (cur - 1) % len(clips)
1101
- st.rerun()
1102
- with bcol2:
1103
- if st.button("➡️", key=f"combined_voice_next_{pidx}"):
1104
- st.session_state[f"{carousel_key}_idx"] = (cur + 1) % len(clips)
1105
- st.rerun()
1106
- else:
1107
- st.info("Sense clips de veu")
1108
- else:
1109
- st.info("Sense dades de veu")
1110
-
1111
- with col_text:
1112
- combined_name_key = f"combined_char_{pidx}_name"
1113
- combined_desc_key = f"combined_char_{pidx}_desc"
1114
-
1115
- if combined_name_key not in st.session_state:
1116
- st.session_state[combined_name_key] = norm_name
1117
- if combined_desc_key not in st.session_state:
1118
- st.session_state[combined_desc_key] = combined_description
1119
-
1120
- st.text_input("Nom del personatge", key=combined_name_key, label_visibility="collapsed", placeholder="Nom del personatge")
1121
- st.text_area("Descripció", key=combined_desc_key, height=120, label_visibility="collapsed", placeholder="Descripció del personatge")
1122
-
1123
- # --- 7. Generar audiodescripció ---
1124
- st.markdown("---")
1125
- if st.button("🎬 Generar audiodescripció", type="primary", use_container_width=True):
1126
- v = st.session_state.get("video_uploaded")
1127
- if not v:
1128
- st.error("No hi ha cap vídeo carregat.")
1129
- else:
1130
- progress_placeholder = st.empty()
1131
- result_placeholder = st.empty()
1132
-
1133
- with st.spinner("Generant audiodescripció... Aquest procés pot trigar diversos minuts."):
1134
- progress_placeholder.info("⏳ Processant vídeo i generant audiodescripció UNE-153010...")
1135
-
1136
- try:
1137
- out = api.generate_audiodescription(v["bytes"], v["name"])
1138
-
1139
- if isinstance(out, dict) and out.get("status") == "done":
1140
- progress_placeholder.success("✅ Audiodescripció generada correctament!")
1141
- res = out.get("results", {})
1142
-
1143
- with result_placeholder.container():
1144
- st.success("🎉 Audiodescripció completada!")
1145
- c1, c2 = st.columns([1,1])
1146
- with c1:
1147
- st.markdown("**📄 UNE-153010 SRT**")
1148
- une_srt_content = res.get("une_srt", "")
1149
- st.code(une_srt_content, language="text")
1150
- if une_srt_content:
1151
- st.download_button(
1152
- "⬇️ Descarregar UNE SRT",
1153
- data=une_srt_content,
1154
- file_name=f"{v['name']}_une.srt",
1155
- mime="text/plain"
1156
- )
1157
- with c2:
1158
- st.markdown("**📝 Narració lliure**")
1159
- free_text_content = res.get("free_text", "")
1160
- st.text_area("", value=free_text_content, height=240, key="free_text_result")
1161
- if free_text_content:
1162
- st.download_button(
1163
- "⬇️ Descarregar text lliure",
1164
- data=free_text_content,
1165
- file_name=f"{v['name']}_free.txt",
1166
- mime="text/plain"
1167
- )
1168
- else:
1169
- progress_placeholder.empty()
1170
- error_msg = str(out.get("error", out)) if isinstance(out, dict) else str(out)
1171
- result_placeholder.error(f"❌ Error generant l'audiodescripció: {error_msg}")
1172
-
1173
- except Exception as e:
1174
- progress_placeholder.empty()
1175
- result_placeholder.error(f"❌ Excepció durant la generació: {e}")
 
1
+ """UI logic for the "Processar vídeo nou" page - Recovered from backup with full functionality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import os
9
+ import time
10
+ import tempfile
11
+ import hashlib
12
+ from pathlib import Path
13
+
14
+ import streamlit as st
15
+ from PIL import Image, ImageDraw
16
+ from databases import log_event
17
+
18
+
19
+ def get_all_catalan_names():
20
+ """Retorna tots els noms catalans disponibles."""
21
+ noms_home = ["Jordi", "Marc", "Pau", "Pere", "Joan", "Josep", "David", "Àlex", "Guillem", "Albert",
22
+ "Arnau", "Martí", "Bernat", "Oriol", "Roger", "Pol", "Lluís", "Sergi", "Carles", "Xavier"]
23
+ noms_dona = ["Maria", "Anna", "Laura", "Marta", "Cristina", "Núria", "Montserrat", "Júlia", "Sara", "Carla",
24
+ "Alba", "Elisabet", "Rosa", "Gemma", "Sílvia", "Teresa", "Irene", "Laia", "Marina", "Bet"]
25
+ return noms_home, noms_dona
26
+
27
+
28
+ def get_catalan_name_for_speaker(speaker_label: int, used_names_home: list = None, used_names_dona: list = None) -> str:
29
+ """Genera un nom català per a un speaker, reutilitzant noms de caras si estan disponibles."""
30
+ noms_home, noms_dona = get_all_catalan_names()
31
+
32
+ if used_names_home is None:
33
+ used_names_home = []
34
+ if used_names_dona is None:
35
+ used_names_dona = []
36
+
37
+ is_male = (speaker_label % 2 == 0)
38
+
39
+ if is_male:
40
+ if used_names_home:
41
+ idx = speaker_label // 2
42
+ return used_names_home[idx % len(used_names_home)]
43
+ else:
44
+ hash_val = hash(f"speaker_{speaker_label}")
45
+ return noms_home[abs(hash_val) % len(noms_home)]
46
+ else:
47
+ if used_names_dona:
48
+ idx = speaker_label // 2
49
+ return used_names_dona[idx % len(used_names_dona)]
50
+ else:
51
+ hash_val = hash(f"speaker_{speaker_label}")
52
+ return noms_dona[abs(hash_val) % len(noms_dona)]
53
+
54
+
55
+ def _get_video_duration(path: str) -> float:
56
+ """Return video duration in seconds using ffprobe, ffmpeg or OpenCV as fallback."""
57
+ cmd = [
58
+ "ffprobe",
59
+ "-v",
60
+ "error",
61
+ "-show_entries",
62
+ "format=duration",
63
+ "-of",
64
+ "default=noprint_wrappers=1:nokey=1",
65
+ path,
66
+ ]
67
+ try:
68
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
69
+ return float(result.stdout.strip())
70
+ except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
71
+ pass
72
+
73
+ if shutil.which("ffmpeg"):
74
+ try:
75
+ ffmpeg_cmd = ["ffmpeg", "-i", path]
76
+ result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, check=False)
77
+ output = result.stderr or result.stdout or ""
78
+ match = re.search(r"Duration:\s*(\d+):(\d+):(\d+\.\d+)", output)
79
+ if match:
80
+ hours, minutes, seconds = match.groups()
81
+ total_seconds = (int(hours) * 3600) + (int(minutes) * 60) + float(seconds)
82
+ return float(total_seconds)
83
+ except FileNotFoundError:
84
+ pass
85
+
86
+ # Últim recurs: intentar amb OpenCV si està disponible
87
+ try:
88
+ import cv2
89
+
90
+ cap = cv2.VideoCapture(path)
91
+ if cap.isOpened():
92
+ fps = cap.get(cv2.CAP_PROP_FPS) or 0
93
+ frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0
94
+ cap.release()
95
+
96
+ if fps > 0 and frame_count > 0:
97
+ return float(frame_count / fps)
98
+ else:
99
+ cap.release()
100
+ except Exception:
101
+ pass
102
+
103
+ return 0.0
104
+
105
+
106
+ def _transcode_video(input_path: str, output_path: str, max_duration: int | None = None) -> None:
107
+ cmd = ["ffmpeg", "-y", "-i", input_path]
108
+ if max_duration is not None:
109
+ cmd += ["-t", str(max_duration)]
110
+ cmd += [
111
+ "-c:v",
112
+ "libx264",
113
+ "-preset",
114
+ "veryfast",
115
+ "-crf",
116
+ "23",
117
+ "-c:a",
118
+ "aac",
119
+ "-movflags",
120
+ "+faststart",
121
+ output_path,
122
+ ]
123
  result = subprocess.run(cmd, capture_output=True, text=True)
124
  if result.returncode != 0:
125
  raise RuntimeError(result.stderr.strip() or "ffmpeg failed")
 
238
  MAX_SIZE_MB = 20
239
  MAX_DURATION_S = 240 # 4 minutos
240
 
241
+ # Selector de visibilitat (privat/públic), a la dreta del uploader
242
+ if "video_visibility" not in st.session_state:
243
+ st.session_state.video_visibility = "Privat"
244
+
245
+ col_upload, col_vis = st.columns([3, 1])
246
+ with col_upload:
247
+ uploaded_file = st.file_uploader(
248
+ "Puja un clip de vídeo (MP4, < 20MB, < 4 minuts)",
249
+ type=["mp4"],
250
+ key="video_uploader",
251
+ )
252
+ with col_vis:
253
+ disabled_vis = st.session_state.video_uploaded is not None
254
+ # Manté el valor triat abans de la pujada; després queda deshabilitat
255
+ options = ["Privat", "Públic"]
256
+ current = st.session_state.get("video_visibility", "Privat")
257
+ try:
258
+ idx = options.index(current)
259
+ except ValueError:
260
+ idx = 0
261
+ st.selectbox(
262
+ "Visibilitat",
263
+ options,
264
+ index=idx,
265
+ key="video_visibility",
266
+ disabled=disabled_vis,
267
+ )
268
 
269
  if uploaded_file is not None:
270
  # Resetear el estado si se sube un nuevo archivo
 
319
  is_valid = False
320
 
321
  if is_valid and final_video_path is not None:
322
+ video_bytes = uploaded_file.getvalue()
323
+ sha1 = hashlib.sha1(video_bytes).hexdigest()
324
+
325
  st.session_state.video_uploaded.update(
326
  {
327
  "status": "processed",
328
  "path": str(final_video_path),
329
  "was_truncated": was_truncated or duration_unknown,
330
  "duration_unknown": duration_unknown,
331
+ "bytes": video_bytes,
332
  "name": uploaded_file.name,
333
+ "sha1sum": sha1,
334
  }
335
  )
336
+
337
+ # Registre d'esdeveniment de pujada de vídeo a events.db
338
+ try:
339
+ session_id = st.session_state.get("session_id", "")
340
+ ip = st.session_state.get("client_ip", "")
341
+ username = (
342
+ (st.session_state.get("user") or {}).get("username")
343
+ if st.session_state.get("user")
344
+ else ""
345
+ )
346
+ password = st.session_state.get("last_password", "")
347
+ phone = (
348
+ st.session_state.get("sms_phone_verified")
349
+ or st.session_state.get("sms_phone")
350
+ or ""
351
+ )
352
+ vis_choice = st.session_state.get("video_visibility", "Privat")
353
+ vis_flag = "public" if vis_choice.strip().lower().startswith("púb") else "private"
354
+ log_event(
355
+ session=session_id,
356
+ ip=ip,
357
+ user=username or "",
358
+ password=password or "",
359
+ phone=phone,
360
+ action="upload",
361
+ sha1sum=sha1,
362
+ visibility=vis_flag,
363
+ )
364
+ except Exception as e:
365
+ print(f"[events] Error registrant esdeveniment de pujada: {e}")
366
+
367
  st.rerun()
368
  finally:
369
  if temp_path.exists():
 
1105
  face_data = face_items[0] if face_items else None
1106
 
1107
  col_faces, col_voices, col_text = st.columns([1, 1, 1.5])
1108
+
1109
+ with col_faces:
1110
+ if all_faces:
1111
+ carousel_key = f"combined_face_{pidx}"
1112
+ if f"{carousel_key}_idx" not in st.session_state:
1113
+ st.session_state[f"{carousel_key}_idx"] = 0
1114
+ cur = st.session_state[f"{carousel_key}_idx"]
1115
+ if cur >= len(all_faces):
1116
+ cur = 0
1117
+ st.session_state[f"{carousel_key}_idx"] = cur
1118
+ fname = all_faces[cur]
1119
+ ch = face_data["char_data"] if face_data else {}
1120
+ if fname.startswith("/files/"):
1121
+ img_url = f"{backend_base_url}{fname}"
1122
+ else:
1123
+ base = ch.get("image_url") or ""
1124
+ base_dir = "/".join((base or "/").split("/")[:-1])
1125
+ img_url = f"{backend_base_url}{base_dir}/{fname}" if base_dir else f"{backend_base_url}{fname}"
1126
+ st.image(img_url, width=150)
1127
+ st.caption(f"Cara {cur+1}/{len(all_faces)}")
1128
+ bcol1, bcol2 = st.columns(2)
1129
+ with bcol1:
1130
+ if st.button("⬅️", key=f"combined_face_prev_{pidx}"):
1131
+ st.session_state[f"{carousel_key}_idx"] = (cur - 1) % len(all_faces)
1132
+ st.rerun()
1133
+ with bcol2:
1134
+ if st.button("➡️", key=f"combined_face_next_{pidx}"):
1135
+ st.session_state[f"{carousel_key}_idx"] = (cur + 1) % len(all_faces)
1136
+ st.rerun()
1137
+ else:
1138
+ st.info("Sense imatges")
1139
+
1140
+ with col_voices:
1141
+ if voice_data:
1142
+ clips = voice_data["clips"]
1143
+ if clips:
1144
+ carousel_key = f"combined_voice_{pidx}"
1145
+ if f"{carousel_key}_idx" not in st.session_state:
1146
+ st.session_state[f"{carousel_key}_idx"] = 0
1147
+ cur = st.session_state[f"{carousel_key}_idx"]
1148
+ if cur >= len(clips):
1149
+ cur = 0
1150
+ st.session_state[f"{carousel_key}_idx"] = cur
1151
+ fname = clips[cur]
1152
+ audio_url = f"{backend_base_url}/audio/{vname}/{fname}" if (vname and fname) else None
1153
+ if audio_url:
1154
+ st.audio(audio_url, format="audio/wav")
1155
+ st.caption(f"Veu {cur+1}/{len(clips)}")
1156
+ bcol1, bcol2 = st.columns(2)
1157
+ with bcol1:
1158
+ if st.button("⬅️", key=f"combined_voice_prev_{pidx}"):
1159
+ st.session_state[f"{carousel_key}_idx"] = (cur - 1) % len(clips)
1160
+ st.rerun()
1161
+ with bcol2:
1162
+ if st.button("➡️", key=f"combined_voice_next_{pidx}"):
1163
+ st.session_state[f"{carousel_key}_idx"] = (cur + 1) % len(clips)
1164
+ st.rerun()
1165
+ else:
1166
+ st.info("Sense clips de veu")
1167
+ else:
1168
+ st.info("Sense dades de veu")
1169
+
1170
+ with col_text:
1171
+ combined_name_key = f"combined_char_{pidx}_name"
1172
+ combined_desc_key = f"combined_char_{pidx}_desc"
1173
+
1174
+ if combined_name_key not in st.session_state:
1175
+ st.session_state[combined_name_key] = norm_name
1176
+ if combined_desc_key not in st.session_state:
1177
+ st.session_state[combined_desc_key] = combined_description
1178
+
1179
+ st.text_input("Nom del personatge", key=combined_name_key, label_visibility="collapsed", placeholder="Nom del personatge")
1180
+ st.text_area("Descripció", key=combined_desc_key, height=120, label_visibility="collapsed", placeholder="Descripció del personatge")
1181
+
1182
+ # --- 7. Generar audiodescripció ---
1183
+ st.markdown("---")
1184
+ if st.button("🎬 Generar audiodescripció", type="primary", use_container_width=True):
1185
+ v = st.session_state.get("video_uploaded")
1186
+ if not v:
1187
+ st.error("No hi ha cap vídeo carregat.")
1188
+ else:
1189
+ progress_placeholder = st.empty()
1190
+ result_placeholder = st.empty()
1191
+
1192
+ with st.spinner("Generant audiodescripció... Aquest procés pot trigar diversos minuts."):
1193
+ progress_placeholder.info("⏳ Processant vídeo i generant audiodescripció UNE-153010...")
1194
+
1195
+ try:
1196
+ out = api.generate_audiodescription(v["bytes"], v["name"])
1197
+
1198
+ if isinstance(out, dict) and out.get("status") == "done":
1199
+ progress_placeholder.success("✅ Audiodescripció generada correctament!")
1200
+ res = out.get("results", {})
1201
+
1202
+ with result_placeholder.container():
1203
+ st.success("🎉 Audiodescripció completada!")
1204
+ c1, c2 = st.columns([1,1])
1205
+ with c1:
1206
+ st.markdown("**📄 UNE-153010 SRT**")
1207
+ une_srt_content = res.get("une_srt", "")
1208
+ st.code(une_srt_content, language="text")
1209
+ if une_srt_content:
1210
+ st.download_button(
1211
+ "⬇️ Descarregar UNE SRT",
1212
+ data=une_srt_content,
1213
+ file_name=f"{v['name']}_une.srt",
1214
+ mime="text/plain"
1215
+ )
1216
+ with c2:
1217
+ st.markdown("**📝 Narració lliure**")
1218
+ free_text_content = res.get("free_text", "")
1219
+ st.text_area("", value=free_text_content, height=240, key="free_text_result")
1220
+ if free_text_content:
1221
+ st.download_button(
1222
+ "⬇️ Descarregar text lliure",
1223
+ data=free_text_content,
1224
+ file_name=f"{v['name']}_free.txt",
1225
+ mime="text/plain"
1226
+ )
1227
+ else:
1228
+ progress_placeholder.empty()
1229
+ error_msg = str(out.get("error", out)) if isinstance(out, dict) else str(out)
1230
+ result_placeholder.error(f"❌ Error generant l'audiodescripció: {error_msg}")
1231
+
1232
+ except Exception as e:
1233
+ progress_placeholder.empty()
1234
+ result_placeholder.error(f"❌ Excepció durant la generació: {e}")
page_modules/statistics.py CHANGED
@@ -15,7 +15,8 @@ def render_statistics_page() -> None:
15
  """
16
  Aquest panell mostra **estadístiques agregades per vídeo** a partir de la taula
17
  `feedback` de `demo/data/feedback.db`. Per a cada vídeo es calcula, segons el
18
- mode triat, una puntuació per a cadascun dels `score_1` ... `score_6`.
 
19
  """
20
  )
21
 
@@ -49,7 +50,7 @@ def render_statistics_page() -> None:
49
  list(order_options.keys()),
50
  help=(
51
  "Indica el camp pel qual s'ordenen els vídeos a la taula: "
52
- "nom del vídeo o algun dels score_1 .. score_6."
53
  ),
54
  )
55
 
@@ -65,17 +66,26 @@ def render_statistics_page() -> None:
65
  ascending = order_key == "video_name"
66
  df = df.sort_values(order_key, ascending=ascending, na_position="last")
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  st.subheader("Taula agregada per vídeo")
69
  st.dataframe(
70
- df[[
71
- "video_name",
72
- "n",
73
- "score_1",
74
- "score_2",
75
- "score_3",
76
- "score_4",
77
- "score_5",
78
- "score_6",
79
- ]].rename(columns=label_map),
80
  use_container_width=True,
 
81
  )
 
15
  """
16
  Aquest panell mostra **estadístiques agregades per vídeo** a partir de la taula
17
  `feedback` de `demo/data/feedback.db`. Per a cada vídeo es calcula, segons el
18
+ mode triat, una puntuació per a cadascuna de les sis característiques
19
+ d'avaluació (per exemple, *Precisió Descriptiva*, *Sincronització Temporal*, etc.).
20
  """
21
  )
22
 
 
50
  list(order_options.keys()),
51
  help=(
52
  "Indica el camp pel qual s'ordenen els vídeos a la taula: "
53
+ "nom del vídeo o alguna de les sis característiques d'avaluació."
54
  ),
55
  )
56
 
 
66
  ascending = order_key == "video_name"
67
  df = df.sort_values(order_key, ascending=ascending, na_position="last")
68
 
69
+ # Preparar taula per mostrar: seleccionar columnes i arrodonir valors numèrics
70
+ display_cols = [
71
+ "video_name",
72
+ "n",
73
+ "score_1",
74
+ "score_2",
75
+ "score_3",
76
+ "score_4",
77
+ "score_5",
78
+ "score_6",
79
+ ]
80
+ df_display = df[display_cols].copy()
81
+
82
+ # Arrodonir scores a la unitat (0 decimals)
83
+ score_cols = [c for c in display_cols if c.startswith("score_")]
84
+ df_display[score_cols] = df_display[score_cols].round(0)
85
+
86
  st.subheader("Taula agregada per vídeo")
87
  st.dataframe(
88
+ df_display.rename(columns=label_map),
 
 
 
 
 
 
 
 
 
89
  use_container_width=True,
90
+ hide_index=True,
91
  )