| """UI logic for the "Validació" page.""" | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Dict | |
| import streamlit as st | |
| def _build_candidates(runtime_videos: Path) -> Path: | |
| candidates = [ | |
| runtime_videos, | |
| Path(__file__).resolve().parent.parent / "videos", | |
| Path.cwd() / "videos", | |
| ] | |
| for candidate in candidates: | |
| if candidate.exists(): | |
| return candidate | |
| return candidates[0] | |
| def render_validation_page( | |
| compliance_client, | |
| runtime_videos: Path, | |
| permissions: Dict[str, bool], | |
| username: str, | |
| ) -> None: | |
| if not permissions.get("validar", False): | |
| st.warning("⚠️ No tens permisos per accedir a aquesta secció de validació.") | |
| st.stop() | |
| st.header("🔍 Validació de Vídeos") | |
| tab_videos, tab_ads = st.tabs(["📹 Validar Vídeos", "🎬 Validar Audiodescripcions"]) | |
| base_dir = _build_candidates(runtime_videos) | |
| if not base_dir.exists(): | |
| st.info("📝 No s'ha trobat la carpeta **videos**. Crea-la i afegeix-hi subcarpetes amb els teus vídeos.") | |
| st.stop() | |
| with tab_videos: | |
| st.subheader("📹 Validar Vídeos Pujats") | |
| video_folders = [] | |
| for folder in sorted(base_dir.iterdir()): | |
| if folder.is_dir() and folder.name != "completed": | |
| video_files = list(folder.glob("*.mp4")) + list(folder.glob("*.avi")) + list(folder.glob("*.mov")) | |
| if video_files: | |
| mod_time = folder.stat().st_mtime | |
| fecha = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M") | |
| video_folders.append( | |
| { | |
| "name": folder.name, | |
| "path": str(folder), | |
| "created_at": fecha, | |
| "video_files": video_files, | |
| } | |
| ) | |
| if not video_folders: | |
| st.info("📝 No hi ha vídeos pujats pendents de validació.") | |
| else: | |
| opciones_video = [f"{video['name']} - {video['created_at']}" for video in video_folders] | |
| seleccion = st.selectbox( | |
| "Selecciona un vídeo per validar:", | |
| opciones_video, | |
| index=0 if opciones_video else None, | |
| ) | |
| if seleccion: | |
| indice = opciones_video.index(seleccion) | |
| video_seleccionat = video_folders[indice] | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.markdown("### 📹 Informació del Vídeo") | |
| st.markdown(f"**Nom:** {video_seleccionat['name']}") | |
| st.markdown(f"**Data:** {video_seleccionat['created_at']}") | |
| st.markdown(f"**Arxius:** {len(video_seleccionat['video_files'])} vídeos trobats") | |
| if video_seleccionat["video_files"]: | |
| video_path = str(video_seleccionat["video_files"][0]) | |
| st.markdown("**Vídeo principal:**") | |
| st.video(video_path) | |
| else: | |
| st.warning("⚠️ No s'han trobat arxius de vídeo.") | |
| with col2: | |
| st.markdown("### 🔍 Accions de Validació") | |
| col_btn1, col_btn2 = st.columns(2) | |
| with col_btn1: | |
| if st.button("✅ Acceptar", type="primary", key=f"accept_video_{video_seleccionat['name']}"): | |
| success = compliance_client.record_validator_decision( | |
| document_id=f"video_{video_seleccionat['name']}", | |
| validator_email=f"{username}@veureu.local", | |
| decision="acceptat", | |
| comments=f"Vídeo validat per {username}", | |
| ) | |
| if success: | |
| st.success("✅ Vídeo acceptat i registrat al servei de compliance") | |
| else: | |
| st.error("❌ Error registrant el veredicte") | |
| with col_btn2: | |
| if st.button("❌ Rebutjar", type="secondary", key=f"reject_video_{video_seleccionat['name']}" ): | |
| success = compliance_client.record_validator_decision( | |
| document_id=f"video_{video_seleccionat['name']}", | |
| validator_email=f"{username}@veureu.local", | |
| decision="rebutjat", | |
| comments=f"Vídeo rebutjat per {username}", | |
| ) | |
| if success: | |
| st.success("✅ Vídeo rebutjat i registrat al servei de compliance") | |
| else: | |
| st.error("❌ Error registrant el veredicte") | |
| with tab_ads: | |
| st.subheader("🎬 Validar Audiodescripcions") | |
| videos_con_ad = [] | |
| if base_dir.exists(): | |
| for folder in sorted(base_dir.iterdir()): | |
| if folder.is_dir() and folder.name != "completed": | |
| for subfolder_name in ["MoE", "Salamandra"]: | |
| subfolder = folder / subfolder_name | |
| if subfolder.exists(): | |
| ad_files = list(subfolder.glob("*_ad.txt")) + list(subfolder.glob("*_ad.srt")) | |
| if ad_files: | |
| mod_time = folder.stat().st_mtime | |
| fecha = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M") | |
| videos_con_ad.append( | |
| { | |
| "name": folder.name, | |
| "path": str(folder), | |
| "created_at": fecha, | |
| "ad_files": ad_files, | |
| "ad_folder": str(subfolder), | |
| } | |
| ) | |
| if not videos_con_ad: | |
| st.info("📝 No hi ha audiodescripcions pendents de validació.") | |
| else: | |
| videos_ad_ordenats = sorted(videos_con_ad, key=lambda x: x["created_at"], reverse=True) | |
| opciones_ad = [f"{video['name']} - {video['created_at']}" for video in videos_ad_ordenats] | |
| seleccion_ad = st.selectbox( | |
| "Selecciona una audiodescripció per validar:", | |
| opciones_ad, | |
| index=0 if opciones_ad else None, | |
| ) | |
| if seleccion_ad: | |
| indice = opciones_ad.index(seleccion_ad) | |
| video_seleccionat = videos_ad_ordenats[indice] | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.markdown("### 🎬 Informació de l'Audiodescripció") | |
| st.markdown(f"**Vídeo:** {video_seleccionat['name']}") | |
| st.markdown(f"**Data:** {video_seleccionat['created_at']}") | |
| st.markdown(f"**Carpeta:** {Path(video_seleccionat['ad_folder']).name}") | |
| st.markdown(f"**Arxius:** {len(video_seleccionat['ad_files'])} audiodescripcions trobades") | |
| if video_seleccionat["ad_files"]: | |
| ad_path = video_seleccionat["ad_files"][0] | |
| st.markdown(f"#### 📄 Contingut ({ad_path.name}):") | |
| try: | |
| texto = ad_path.read_text(encoding="utf-8") | |
| except Exception: | |
| texto = ad_path.read_text(errors="ignore") | |
| st.text_area("Contingut de l'audiodescripció:", texto, height=300, disabled=True) | |
| else: | |
| st.warning("⚠️ No s'han trobat arxius d'audiodescripció.") | |
| with col2: | |
| st.markdown("### 🔍 Accions de Validació") | |
| col_btn1, col_btn2 = st.columns(2) | |
| with col_btn1: | |
| if st.button("✅ Acceptar", type="primary", key=f"accept_ad_{video_seleccionat['name']}"): | |
| success = compliance_client.record_validator_decision( | |
| document_id=f"ad_{video_seleccionat['name']}", | |
| validator_email=f"{username}@veureu.local", | |
| decision="acceptat", | |
| comments=f"Audiodescripció validada per {username}", | |
| ) | |
| if success: | |
| st.success("✅ Audiodescripció acceptada i registrada al servei de compliance") | |
| else: | |
| st.error("❌ Error registrant el veredicte") | |
| with col_btn2: | |
| if st.button("❌ Rebutjar", type="secondary", key=f"reject_ad_{video_seleccionat['name']}" ): | |
| success = compliance_client.record_validator_decision( | |
| document_id=f"ad_{video_seleccionat['name']}", | |
| validator_email=f"{username}@veureu.local", | |
| decision="rebutjat", | |
| comments=f"Audiodescripció rebutjada per {username}", | |
| ) | |
| if success: | |
| st.success("✅ Audiodescripció rebutjada i registrada al servei de compliance") | |
| else: | |
| st.error("❌ Error registrant el veredicte") | |
| st.markdown("---") | |
| st.markdown("### ℹ️ Informació del Procés de Validació") | |
| st.markdown( | |
| """ | |
| - **Tots els veredictes** es registren al servei de compliance per garantir la traçabilitat | |
| - **Cada validació** inclou veredicte, nom del vídeo i validador responsable | |
| - **Els registres** compleixen amb la normativa AI Act i GDPR | |
| """ | |
| ) | |