VeuReu commited on
Commit
d0bdeb1
·
verified ·
1 Parent(s): e5dde7c

Delete page_modules

Browse files
page_modules/__init__.py DELETED
@@ -1 +0,0 @@
1
- """Modular page renderers for the Veureu Streamlit app."""
 
 
page_modules/analyze_transcriptions.py DELETED
@@ -1,214 +0,0 @@
1
- """UI logic for the "Analitzar video-transcripcions" page."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from typing import Dict
7
-
8
- import streamlit as st
9
-
10
- from utils import save_bytes
11
-
12
-
13
- def render_analyze_transcriptions_page(api, permissions: Dict[str, bool]) -> None:
14
- st.header("Analitzar video-transcripcions")
15
- base_dir = Path("/tmp/data/videos")
16
-
17
- if not base_dir.exists():
18
- st.info("No s'ha trobat la carpeta **videos**. Crea-la i afegeix-hi subcarpetes amb els teus vídeos.")
19
- st.stop()
20
-
21
- carpetes = [p.name for p in sorted(base_dir.iterdir()) if p.is_dir() and p.name != "completed"]
22
- if not carpetes:
23
- st.info("No s'ha trobat la carpeta **videos**. Crea-la i afegeix-hi subcarpetes amb els teus vídeos.")
24
- st.stop()
25
-
26
- if "current_video" not in st.session_state:
27
- st.session_state.current_video = None
28
-
29
- seleccio = st.selectbox("Selecciona un vídeo (carpeta):", carpetes, index=None, placeholder="Tria una carpeta…")
30
-
31
- if seleccio != st.session_state.current_video:
32
- st.session_state.current_video = seleccio
33
- if "version_selector" in st.session_state:
34
- del st.session_state["version_selector"]
35
- st.session_state.add_ad_checkbox = False
36
- st.rerun()
37
-
38
- if not seleccio:
39
- st.stop()
40
-
41
- vid_dir = base_dir / seleccio
42
- mp4s = sorted(vid_dir.glob("*.mp4"))
43
-
44
- col_video, col_txt = st.columns([2, 1], gap="large")
45
-
46
- with col_video:
47
- subcarpetas_ad = [p.name for p in sorted(vid_dir.iterdir()) if p.is_dir()]
48
- default_index_sub = subcarpetas_ad.index("Salamandra") if "Salamandra" in subcarpetas_ad else 0
49
- subcarpeta_seleccio = st.selectbox(
50
- "Selecciona una versió d'audiodescripció:",
51
- subcarpetas_ad,
52
- index=default_index_sub if subcarpetas_ad else None,
53
- placeholder="Tria una versió…" if subcarpetas_ad else "No hi ha versions",
54
- key="version_selector",
55
- )
56
-
57
- video_ad_path = vid_dir / subcarpeta_seleccio / "une_ad.mp4" if subcarpeta_seleccio else None
58
- is_ad_video_available = video_ad_path is not None and video_ad_path.exists()
59
-
60
- add_ad_video = st.checkbox(
61
- "Afegir audiodescripció",
62
- disabled=not is_ad_video_available,
63
- key="add_ad_checkbox",
64
- )
65
-
66
- video_to_show = None
67
- if add_ad_video and is_ad_video_available:
68
- video_to_show = video_ad_path
69
- elif mp4s:
70
- video_to_show = mp4s[0]
71
-
72
- if video_to_show:
73
- st.video(str(video_to_show))
74
- else:
75
- st.warning("No s'ha trobat cap fitxer **.mp4** a la carpeta seleccionada.")
76
-
77
- st.markdown("---")
78
- st.markdown("#### Accions")
79
- c1, c2 = st.columns(2)
80
- with c1:
81
- if st.button("Reconstruir àudio amb narració lliure", use_container_width=True, key="rebuild_free_ad"):
82
- if subcarpeta_seleccio:
83
- free_ad_path = vid_dir / subcarpeta_seleccio / "free_ad.txt"
84
- if free_ad_path.exists():
85
- with st.spinner("Generant àudio de la narració lliure..."):
86
- text_content = free_ad_path.read_text(encoding="utf-8")
87
- voice = "central/grau"
88
- response = api.tts_matxa(text=text_content, voice=voice)
89
- if "mp3_bytes" in response:
90
- output_path = vid_dir / subcarpeta_seleccio / "free_ad.mp3"
91
- save_bytes(output_path, response["mp3_bytes"])
92
- st.success(f"Àudio generat i desat a: {output_path}")
93
- else:
94
- st.error(f"Error en la generació de l'àudio: {response.get('error', 'Desconegut')}")
95
- else:
96
- st.warning("No s'ha trobat el fitxer 'free_ad.txt' en aquesta versió.")
97
-
98
- with c2:
99
- if st.button("Reconstruir vídeo amb audiodescripció", use_container_width=True, key="rebuild_video_ad"):
100
- if subcarpeta_seleccio and mp4s:
101
- une_srt_path = vid_dir / subcarpeta_seleccio / "une_ad.srt"
102
- video_original_path = mp4s[0]
103
- if une_srt_path.exists():
104
- with st.spinner(
105
- "Reconstruint el vídeo amb l'audiodescripció... Aquesta operació pot trigar una estona."
106
- ):
107
- response = api.rebuild_video_with_ad(
108
- video_path=str(video_original_path),
109
- srt_path=str(une_srt_path),
110
- )
111
- if "video_bytes" in response:
112
- output_path = vid_dir / subcarpeta_seleccio / "video_ad_rebuilt.mp4"
113
- save_bytes(output_path, response["video_bytes"])
114
- st.success(f"Vídeo reconstruït i desat a: {output_path}")
115
- st.info(
116
- "Pots visualitzar-lo activant la casella 'Afegir audiodescripció' i seleccionant el nou fitxer si cal."
117
- )
118
- else:
119
- st.error(f"Error en la reconstrucció del vídeo: {response.get('error', 'Desconegut')}")
120
- else:
121
- st.warning("No s'ha trobat el fitxer 'une_ad.srt' en aquesta versió.")
122
-
123
- with col_txt:
124
- tipus_ad_options = ["narració lliure", "UNE-153010"]
125
- tipus_ad_seleccio = st.selectbox("Fitxer d'audiodescripció a editar:", tipus_ad_options)
126
-
127
- ad_filename = "free_ad.txt" if tipus_ad_seleccio == "narració lliure" else "une_ad.srt"
128
-
129
- text_content = ""
130
- ad_path = None
131
- if subcarpeta_seleccio:
132
- ad_path = vid_dir / subcarpeta_seleccio / ad_filename
133
- if ad_path.exists():
134
- try:
135
- text_content = ad_path.read_text(encoding="utf-8")
136
- except Exception:
137
- text_content = ad_path.read_text(errors="ignore")
138
- else:
139
- st.info(f"No s'ha trobat el fitxer **{ad_filename}**.")
140
- else:
141
- st.warning("Selecciona una versió per veure els fitxers.")
142
-
143
- new_text = st.text_area(
144
- f"Contingut de {tipus_ad_seleccio}",
145
- value=text_content,
146
- height=500,
147
- key=f"editor_{seleccio}_{subcarpeta_seleccio}_{ad_filename}",
148
- )
149
-
150
- if st.button(
151
- "▶️ Reproduir narració",
152
- use_container_width=True,
153
- disabled=not new_text.strip(),
154
- key="play_button_editor",
155
- ):
156
- with st.spinner("Generant àudio..."):
157
- pass
158
-
159
- if st.button("Desar canvis", use_container_width=True, type="primary"):
160
- if ad_path:
161
- try:
162
- ad_path.write_text(new_text, encoding="utf-8")
163
- st.success(f"Fitxer **{ad_filename}** desat correctament.")
164
- st.rerun()
165
- except Exception as e:
166
- st.error(f"No s'ha pogut desar el fitxer: {e}")
167
- else:
168
- st.error("No s'ha seleccionat una ruta de fitxer vàlida per desar.")
169
-
170
- st.markdown("---")
171
- st.subheader("Avaluació de la qualitat de l'audiodescripció")
172
-
173
- can_rate = permissions.get("valorar", False)
174
- controls_disabled = not can_rate
175
-
176
- c1, c2, c3 = st.columns(3)
177
- with c1:
178
- transcripcio = st.slider("Transcripció", 1, 10, 7, disabled=controls_disabled)
179
- identificacio = st.slider("Identificació de personatges", 1, 10, 7, disabled=controls_disabled)
180
- with c2:
181
- localitzacions = st.slider("Localitzacions", 1, 10, 7, disabled=controls_disabled)
182
- activitats = st.slider("Activitats", 1, 10, 7, disabled=controls_disabled)
183
- with c3:
184
- narracions = st.slider("Narracions", 1, 10, 7, disabled=controls_disabled)
185
- expressivitat = st.slider("Expressivitat", 1, 10, 7, disabled=controls_disabled)
186
-
187
- comments = st.text_area(
188
- "Comentaris (opcional)",
189
- placeholder="Escriu els teus comentaris lliures…",
190
- height=120,
191
- disabled=controls_disabled,
192
- )
193
-
194
- if not can_rate:
195
- st.info("El teu rol no permet enviar valoracions.")
196
- else:
197
- if st.button("Enviar valoració", type="primary", use_container_width=True):
198
- try:
199
- from database import add_feedback_ad
200
-
201
- add_feedback_ad(
202
- video_name=seleccio,
203
- user_id=st.session_state.user["id"],
204
- transcripcio=transcripcio,
205
- identificacio=identificacio,
206
- localitzacions=localitzacions,
207
- activitats=activitats,
208
- narracions=narracions,
209
- expressivitat=expressivitat,
210
- comments=comments or None,
211
- )
212
- st.success("Gràcies! La teva valoració s'ha desat correctament.")
213
- except Exception as e:
214
- st.error(f"S'ha produït un error en desar la valoració: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
page_modules/process_video.py DELETED
@@ -1,215 +0,0 @@
1
- """UI logic for the "Processar vídeo nou" page."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import shutil
7
- import subprocess
8
- from pathlib import Path
9
-
10
- import streamlit as st
11
-
12
-
13
- def _get_video_duration(path: str) -> float:
14
- """Return video duration in seconds using ffprobe, ffmpeg or OpenCV as fallback."""
15
- cmd = [
16
- "ffprobe",
17
- "-v",
18
- "error",
19
- "-show_entries",
20
- "format=duration",
21
- "-of",
22
- "default=noprint_wrappers=1:nokey=1",
23
- path,
24
- ]
25
- try:
26
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
27
- return float(result.stdout.strip())
28
- except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
29
- pass
30
-
31
- if shutil.which("ffmpeg"):
32
- try:
33
- ffmpeg_cmd = ["ffmpeg", "-i", path]
34
- result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, check=False)
35
- output = result.stderr or result.stdout or ""
36
- match = re.search(r"Duration:\s*(\d+):(\d+):(\d+\.\d+)", output)
37
- if match:
38
- hours, minutes, seconds = match.groups()
39
- total_seconds = (int(hours) * 3600) + (int(minutes) * 60) + float(seconds)
40
- return float(total_seconds)
41
- except FileNotFoundError:
42
- pass
43
-
44
- # Últim recurs: intentar amb OpenCV si està disponible
45
- try:
46
- import cv2
47
-
48
- cap = cv2.VideoCapture(path)
49
- if cap.isOpened():
50
- fps = cap.get(cv2.CAP_PROP_FPS) or 0
51
- frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0
52
- cap.release()
53
-
54
- if fps > 0 and frame_count > 0:
55
- return float(frame_count / fps)
56
- else:
57
- cap.release()
58
- except Exception:
59
- pass
60
-
61
- return 0.0
62
-
63
-
64
- def _transcode_video(input_path: str, output_path: str, max_duration: int | None = None) -> None:
65
- cmd = ["ffmpeg", "-y", "-i", input_path]
66
- if max_duration is not None:
67
- cmd += ["-t", str(max_duration)]
68
- cmd += [
69
- "-c:v",
70
- "libx264",
71
- "-preset",
72
- "veryfast",
73
- "-crf",
74
- "23",
75
- "-c:a",
76
- "aac",
77
- "-movflags",
78
- "+faststart",
79
- output_path,
80
- ]
81
- result = subprocess.run(cmd, capture_output=True, text=True)
82
- if result.returncode != 0:
83
- raise RuntimeError(result.stderr.strip() or "ffmpeg failed")
84
-
85
-
86
- def render_process_video_page() -> None:
87
- st.header("Processar un nou clip de vídeo")
88
-
89
- # Inicializar el estado de la página si no existe
90
- if "video_uploaded" not in st.session_state:
91
- st.session_state.video_uploaded = None
92
- if "characters_detected" not in st.session_state:
93
- st.session_state.characters_detected = None
94
- if "characters_saved" not in st.session_state:
95
- st.session_state.characters_saved = False
96
-
97
- # --- 1. Subida del vídeo ---
98
- MAX_SIZE_MB = 20
99
- MAX_DURATION_S = 240 # 4 minutos
100
-
101
- uploaded_file = st.file_uploader(
102
- "Puja un clip de vídeo (MP4, < 20MB, < 4 minuts)",
103
- type=["mp4"],
104
- key="video_uploader",
105
- )
106
-
107
- if uploaded_file is not None:
108
- # Resetear el estado si se sube un nuevo archivo
109
- if st.session_state.video_uploaded is None or uploaded_file.name != st.session_state.video_uploaded.get(
110
- "original_name"
111
- ):
112
- st.session_state.video_uploaded = {"original_name": uploaded_file.name, "status": "validating"}
113
- st.session_state.characters_detected = None
114
- st.session_state.characters_saved = False
115
-
116
- if st.session_state.video_uploaded["status"] == "validating":
117
- is_valid = True
118
- if uploaded_file.size > MAX_SIZE_MB * 1024 * 1024:
119
- st.error(f"El vídeo supera el límit de {MAX_SIZE_MB}MB.")
120
- is_valid = False
121
-
122
- if is_valid:
123
- with st.spinner("Processant el vídeo..."):
124
- temp_path = Path("temp_video.mp4")
125
- with temp_path.open("wb") as f:
126
- f.write(uploaded_file.getbuffer())
127
-
128
- was_truncated = False
129
- final_video_path = None
130
- try:
131
- duration = _get_video_duration(str(temp_path))
132
- if not duration:
133
- st.error("No s'ha pogut obtenir la durada del vídeo.")
134
- is_valid = False
135
-
136
- if is_valid:
137
- if duration > MAX_DURATION_S:
138
- was_truncated = True
139
-
140
- video_name = Path(uploaded_file.name).stem
141
- video_dir = Path("/tmp/data/videos") / video_name
142
- video_dir.mkdir(parents=True, exist_ok=True)
143
- final_video_path = video_dir / f"{video_name}.mp4"
144
-
145
- try:
146
- _transcode_video(
147
- str(temp_path),
148
- str(final_video_path),
149
- MAX_DURATION_S if was_truncated else None,
150
- )
151
- except RuntimeError as exc:
152
- st.error(f"No s'ha pogut processar el vídeo: {exc}")
153
- is_valid = False
154
-
155
- if is_valid and final_video_path is not None:
156
- st.session_state.video_uploaded.update(
157
- {
158
- "status": "processed",
159
- "path": str(final_video_path),
160
- "was_truncated": was_truncated,
161
- }
162
- )
163
- st.rerun()
164
- finally:
165
- if temp_path.exists():
166
- temp_path.unlink()
167
-
168
- if st.session_state.video_uploaded and st.session_state.video_uploaded["status"] == "processed":
169
- st.success(f"Vídeo '{st.session_state.video_uploaded['original_name']}' pujat i processat correctament.")
170
- if st.session_state.video_uploaded["was_truncated"]:
171
- st.warning("El vídeo s'ha truncat a 4 minuts.")
172
-
173
- st.markdown("---")
174
- col1, col2 = st.columns([1, 3])
175
- with col1:
176
- detect_button_disabled = st.session_state.video_uploaded is None
177
- if st.button("Detectar Personatges", disabled=detect_button_disabled):
178
- with st.spinner("Detectant personatges..."):
179
- st.session_state.characters_detected = [
180
- {
181
- "id": "char1",
182
- "image_path": "init_data/placeholder.png",
183
- "description": "Dona amb cabell ros i ulleres",
184
- },
185
- {
186
- "id": "char2",
187
- "image_path": "init_data/placeholder.png",
188
- "description": "Home amb barba i barret",
189
- },
190
- ]
191
- st.session_state.characters_saved = False
192
-
193
- if st.session_state.characters_detected:
194
- st.subheader("Personatges detectats")
195
- for char in st.session_state.characters_detected:
196
- with st.form(key=f"form_{char['id']}"):
197
- col1, col2 = st.columns(2)
198
- with col1:
199
- st.image(char["image_path"], width=150)
200
- with col2:
201
- st.caption(char["description"])
202
- st.text_input("Nom del personatge", key=f"name_{char['id']}")
203
- st.form_submit_button("Cercar")
204
-
205
- st.markdown("---_**")
206
-
207
- col1, col2, col3 = st.columns([1, 1, 2])
208
- with col1:
209
- if st.button("Desar", type="primary"):
210
- st.session_state.characters_saved = True
211
- st.success("Personatges desats correctament.")
212
-
213
- with col2:
214
- if st.session_state.characters_saved:
215
- st.button("Generar Audiodescripció")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
page_modules/statistics.py DELETED
@@ -1,46 +0,0 @@
1
- """UI logic for the "Estadístiques" page."""
2
-
3
- from __future__ import annotations
4
-
5
- import pandas as pd
6
- import streamlit as st
7
-
8
- from database import get_feedback_ad_stats
9
-
10
-
11
- def render_statistics_page() -> None:
12
- st.header("Estadístiques")
13
-
14
- stats = get_feedback_ad_stats()
15
- if not stats:
16
- st.caption("Encara no hi ha valoracions.")
17
- st.stop()
18
-
19
- df = pd.DataFrame(stats, columns=stats[0].keys())
20
- ordre = st.radio(
21
- "Ordre de rànquing",
22
- ["Descendent (millors primer)", "Ascendent (pitjors primer)"],
23
- horizontal=True,
24
- )
25
- if ordre.startswith("Asc"):
26
- df = df.sort_values("avg_global", ascending=True)
27
- else:
28
- df = df.sort_values("avg_global", ascending=False)
29
-
30
- st.subheader("Rànquing de vídeos")
31
- st.dataframe(
32
- df[
33
- [
34
- "video_name",
35
- "n",
36
- "avg_global",
37
- "avg_transcripcio",
38
- "avg_identificacio",
39
- "avg_localitzacions",
40
- "avg_activitats",
41
- "avg_narracions",
42
- "avg_expressivitat",
43
- ]
44
- ],
45
- use_container_width=True,
46
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
page_modules/validation.py DELETED
@@ -1,221 +0,0 @@
1
- """UI logic for the "Validació" page."""
2
-
3
- from __future__ import annotations
4
-
5
- from datetime import datetime
6
- from pathlib import Path
7
- from typing import Dict
8
-
9
- import streamlit as st
10
-
11
-
12
- def _build_candidates(runtime_videos: Path) -> Path:
13
- candidates = [
14
- runtime_videos,
15
- Path(__file__).resolve().parent.parent / "videos",
16
- Path.cwd() / "videos",
17
- ]
18
- for candidate in candidates:
19
- if candidate.exists():
20
- return candidate
21
- return candidates[0]
22
-
23
-
24
- def render_validation_page(
25
- compliance_client,
26
- runtime_videos: Path,
27
- permissions: Dict[str, bool],
28
- username: str,
29
- ) -> None:
30
- if not permissions.get("validar", False):
31
- st.warning("⚠️ No tens permisos per accedir a aquesta secció de validació.")
32
- st.stop()
33
-
34
- st.header("🔍 Validació de Vídeos")
35
-
36
- tab_videos, tab_ads = st.tabs(["📹 Validar Vídeos", "🎬 Validar Audiodescripcions"])
37
-
38
- base_dir = _build_candidates(runtime_videos)
39
- if not base_dir.exists():
40
- st.info("📝 No s'ha trobat la carpeta **videos**. Crea-la i afegeix-hi subcarpetes amb els teus vídeos.")
41
- st.stop()
42
-
43
- with tab_videos:
44
- st.subheader("📹 Validar Vídeos Pujats")
45
-
46
- video_folders = []
47
- for folder in sorted(base_dir.iterdir()):
48
- if folder.is_dir() and folder.name != "completed":
49
- video_files = list(folder.glob("*.mp4")) + list(folder.glob("*.avi")) + list(folder.glob("*.mov"))
50
- if video_files:
51
- mod_time = folder.stat().st_mtime
52
- fecha = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M")
53
- video_folders.append(
54
- {
55
- "name": folder.name,
56
- "path": str(folder),
57
- "created_at": fecha,
58
- "video_files": video_files,
59
- }
60
- )
61
-
62
- if not video_folders:
63
- st.info("📝 No hi ha vídeos pujats pendents de validació.")
64
- else:
65
- opciones_video = [f"{video['name']} - {video['created_at']}" for video in video_folders]
66
- seleccion = st.selectbox(
67
- "Selecciona un vídeo per validar:",
68
- opciones_video,
69
- index=0 if opciones_video else None,
70
- )
71
-
72
- if seleccion:
73
- indice = opciones_video.index(seleccion)
74
- video_seleccionat = video_folders[indice]
75
-
76
- col1, col2 = st.columns([2, 1])
77
-
78
- with col1:
79
- st.markdown("### 📹 Informació del Vídeo")
80
- st.markdown(f"**Nom:** {video_seleccionat['name']}")
81
- st.markdown(f"**Data:** {video_seleccionat['created_at']}")
82
- st.markdown(f"**Arxius:** {len(video_seleccionat['video_files'])} vídeos trobats")
83
-
84
- if video_seleccionat["video_files"]:
85
- video_path = str(video_seleccionat["video_files"][0])
86
- st.markdown("**Vídeo principal:**")
87
- st.video(video_path)
88
- else:
89
- st.warning("⚠️ No s'han trobat arxius de vídeo.")
90
-
91
- with col2:
92
- st.markdown("### 🔍 Accions de Validació")
93
-
94
- col_btn1, col_btn2 = st.columns(2)
95
-
96
- with col_btn1:
97
- if st.button("✅ Acceptar", type="primary", key=f"accept_video_{video_seleccionat['name']}"):
98
- success = compliance_client.record_validator_decision(
99
- document_id=f"video_{video_seleccionat['name']}",
100
- validator_email=f"{username}@veureu.local",
101
- decision="acceptat",
102
- comments=f"Vídeo validat per {username}",
103
- )
104
- if success:
105
- st.success("✅ Vídeo acceptat i registrat al servei de compliance")
106
- else:
107
- st.error("❌ Error registrant el veredicte")
108
-
109
- with col_btn2:
110
- if st.button("❌ Rebutjar", type="secondary", key=f"reject_video_{video_seleccionat['name']}" ):
111
- success = compliance_client.record_validator_decision(
112
- document_id=f"video_{video_seleccionat['name']}",
113
- validator_email=f"{username}@veureu.local",
114
- decision="rebutjat",
115
- comments=f"Vídeo rebutjat per {username}",
116
- )
117
- if success:
118
- st.success("✅ Vídeo rebutjat i registrat al servei de compliance")
119
- else:
120
- st.error("❌ Error registrant el veredicte")
121
-
122
- with tab_ads:
123
- st.subheader("🎬 Validar Audiodescripcions")
124
-
125
- videos_con_ad = []
126
- if base_dir.exists():
127
- for folder in sorted(base_dir.iterdir()):
128
- if folder.is_dir() and folder.name != "completed":
129
- for subfolder_name in ["MoE", "Salamandra"]:
130
- subfolder = folder / subfolder_name
131
- if subfolder.exists():
132
- ad_files = list(subfolder.glob("*_ad.txt")) + list(subfolder.glob("*_ad.srt"))
133
- if ad_files:
134
- mod_time = folder.stat().st_mtime
135
- fecha = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M")
136
- videos_con_ad.append(
137
- {
138
- "name": folder.name,
139
- "path": str(folder),
140
- "created_at": fecha,
141
- "ad_files": ad_files,
142
- "ad_folder": str(subfolder),
143
- }
144
- )
145
-
146
- if not videos_con_ad:
147
- st.info("📝 No hi ha audiodescripcions pendents de validació.")
148
- else:
149
- videos_ad_ordenats = sorted(videos_con_ad, key=lambda x: x["created_at"], reverse=True)
150
- opciones_ad = [f"{video['name']} - {video['created_at']}" for video in videos_ad_ordenats]
151
-
152
- seleccion_ad = st.selectbox(
153
- "Selecciona una audiodescripció per validar:",
154
- opciones_ad,
155
- index=0 if opciones_ad else None,
156
- )
157
-
158
- if seleccion_ad:
159
- indice = opciones_ad.index(seleccion_ad)
160
- video_seleccionat = videos_ad_ordenats[indice]
161
-
162
- col1, col2 = st.columns([2, 1])
163
-
164
- with col1:
165
- st.markdown("### 🎬 Informació de l'Audiodescripció")
166
- st.markdown(f"**Vídeo:** {video_seleccionat['name']}")
167
- st.markdown(f"**Data:** {video_seleccionat['created_at']}")
168
- st.markdown(f"**Carpeta:** {Path(video_seleccionat['ad_folder']).name}")
169
- st.markdown(f"**Arxius:** {len(video_seleccionat['ad_files'])} audiodescripcions trobades")
170
-
171
- if video_seleccionat["ad_files"]:
172
- ad_path = video_seleccionat["ad_files"][0]
173
- st.markdown(f"#### 📄 Contingut ({ad_path.name}):")
174
- try:
175
- texto = ad_path.read_text(encoding="utf-8")
176
- except Exception:
177
- texto = ad_path.read_text(errors="ignore")
178
- st.text_area("Contingut de l'audiodescripció:", texto, height=300, disabled=True)
179
- else:
180
- st.warning("⚠️ No s'han trobat arxius d'audiodescripció.")
181
-
182
- with col2:
183
- st.markdown("### 🔍 Accions de Validació")
184
-
185
- col_btn1, col_btn2 = st.columns(2)
186
-
187
- with col_btn1:
188
- if st.button("✅ Acceptar", type="primary", key=f"accept_ad_{video_seleccionat['name']}"):
189
- success = compliance_client.record_validator_decision(
190
- document_id=f"ad_{video_seleccionat['name']}",
191
- validator_email=f"{username}@veureu.local",
192
- decision="acceptat",
193
- comments=f"Audiodescripció validada per {username}",
194
- )
195
- if success:
196
- st.success("✅ Audiodescripció acceptada i registrada al servei de compliance")
197
- else:
198
- st.error("❌ Error registrant el veredicte")
199
-
200
- with col_btn2:
201
- if st.button("❌ Rebutjar", type="secondary", key=f"reject_ad_{video_seleccionat['name']}" ):
202
- success = compliance_client.record_validator_decision(
203
- document_id=f"ad_{video_seleccionat['name']}",
204
- validator_email=f"{username}@veureu.local",
205
- decision="rebutjat",
206
- comments=f"Audiodescripció rebutjada per {username}",
207
- )
208
- if success:
209
- st.success("✅ Audiodescripció rebutjada i registrada al servei de compliance")
210
- else:
211
- st.error("❌ Error registrant el veredicte")
212
-
213
- st.markdown("---")
214
- st.markdown("### ℹ️ Informació del Procés de Validació")
215
- st.markdown(
216
- """
217
- - **Tots els veredictes** es registren al servei de compliance per garantir la traçabilitat
218
- - **Cada validació** inclou veredicte, nom del vídeo i validador responsable
219
- - **Els registres** compleixen amb la normativa AI Act i GDPR
220
- """
221
- )