import os import yaml import shutil from pathlib import Path import uuid from datetime import datetime try: import tomllib except ModuleNotFoundError: # Py<3.11 import tomli as tomllib import streamlit as st from databases import set_db_path, init_schema, set_blockchain_enabled from api_client import APIClient from utils import ensure_dirs from auth import initialize_auth_system, render_login_form, render_sidebar, require_login from persistent_data_gate import ensure_temp_databases, maybe_publish_monthly_actions_digest from mobile_verification import render_mobile_verification_screen, get_user_permissions from compliance_client import compliance_client from page_modules.new_video_processing import render_process_video_page from page_modules.analyze_audiodescriptions import render_analyze_audiodescriptions_page from page_modules.statistics import render_statistics_page from page_modules.validation import render_validation_page from page_modules.machine_room import render_machine_room_page # -- Move DB / vídeos en entorns de desplegament -- os.environ["STREAMLIT_DATA_DIRECTORY"] = "/tmp/.streamlit" Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True) # Vídeos estàtics: prioritzar demo/data/media, caure a demo/videos si cal. # Es copien a /tmp/data/videos només per a entorns de desplegament; la BD # principal d'usuaris es fa servir directament des de DATA_DIR/users.db. runtime_videos = Path("/tmp/data/videos") if not runtime_videos.exists(): Path("/tmp/data").mkdir(parents=True, exist_ok=True) base_dir = Path(__file__).parent candidates = [ base_dir / "data" / "media", # nova ubicació base_dir / "videos", # compatibilitat enrere ] static_videos = None for cand in candidates: if cand.exists(): static_videos = cand break if static_videos is not None: shutil.copytree(static_videos, runtime_videos, dirs_exist_ok=True) # --- Config --- def _load_yaml(path="config.yaml") -> dict: with open(path, "r", encoding="utf-8") as f: cfg = yaml.safe_load(f) or {} # interpolación sencilla de ${VARS} si las usas en el YAML def _subst(s: str) -> str: return os.path.expandvars(s) if isinstance(s, str) else s # aplica sustitución en los campos que te interesan if "api" in cfg: cfg["api"]["base_url"] = _subst(cfg["api"].get("base_url", "")) cfg["api"]["token"] = _subst(cfg["api"].get("token", "")) cfg["api"]["tts_url"] = _subst(cfg["api"].get("tts_url", "")) if "storage" in cfg and "root_dir" in cfg["storage"]: cfg["storage"]["root_dir"] = _subst(cfg["storage"]["root_dir"]) if "sqlite" in cfg and "path" in cfg["sqlite"]: cfg["sqlite"]["path"] = _subst(cfg["sqlite"]["path"]) return cfg CFG = _load_yaml("config.yaml") # Ajuste de variables según tu esquema YAML # Para la interfaz demo, usaremos sempre la carpeta "temp" local DATA_DIR = "temp" # Backend del engine: primero config.yaml, luego variable de entorno, luego fallback local BACKEND_BASE_URL = ( CFG.get("api", {}).get("base_url") or os.getenv("API_BASE_URL") or "http://localhost:8000" ) TTS_URL = "https://veureu-tts.hf.space" USE_MOCK = bool(CFG.get("app", {}).get("use_mock", False)) # si no la tienes en el yaml, queda False COMPLIANCE_CFG = CFG.get("compliance", {}) or {} PRIVATE_BLOCKCHAIN_ENABLE = bool( COMPLIANCE_CFG.get("private_blockchain_enabled", COMPLIANCE_CFG.get("private_blockchain_enable", False)) ) PUBLIC_BLOCKCHAIN_ENABLE = bool( COMPLIANCE_CFG.get("public_blockchain_enabled", COMPLIANCE_CFG.get("public_blockchain_enable", False)) ) MONTHLY_DIGEST_ENABLED = bool(COMPLIANCE_CFG.get("monthly_digest_enabled", False)) # Token compartido: primero config.yaml (api.token), luego variable de entorno VEUREU_TOKEN API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("VEUREU_TOKEN") # Cliente del backend (engine) api = APIClient(BACKEND_BASE_URL, use_mock=USE_MOCK, data_dir=DATA_DIR, token=API_TOKEN, tts_url=TTS_URL) os.makedirs(DATA_DIR, exist_ok=True) ensure_dirs(DATA_DIR) base_dir = Path(__file__).parent ensure_temp_databases(base_dir, api) DB_PATH = os.path.join(DATA_DIR, "db", "users.db") set_db_path(DB_PATH) # Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB set_blockchain_enabled(PRIVATE_BLOCKCHAIN_ENABLE) # Si està habilitat el registre públic i el digest mensual, publicar si cal # el digest mensual sobre actions.db (es calcula al voltant del mes anterior). maybe_publish_monthly_actions_digest( base_dir, compliance_client, public_blockchain_enabled=PUBLIC_BLOCKCHAIN_ENABLE, monthly_digest_enabled=MONTHLY_DIGEST_ENABLED, ) init_schema() # Initialize authentication system and sync default users initialize_auth_system(DB_PATH) # Identificador de sessió per a traça d'esdeveniments if "session_id" not in st.session_state: st.session_state.session_id = str(uuid.uuid4()) # Exposar client d'API al session_state per a altres mòduls (p.ex. auth/persistent_data_gate) st.session_state.api_client = api st.set_page_config(page_title="Veureu — Audiodescripció", page_icon="🎬", layout="wide") # Estil global: botons primaris en taronja (en lloc de blau per defecte) st.markdown( """ """, unsafe_allow_html=True, ) # Initialize session state for user if "user" not in st.session_state: st.session_state.user = None # Render sidebar and get navigation page, role = render_sidebar() # Pre-login screen if not st.session_state.user: st.title("Veureu — Audiodescripció") render_login_form() st.stop() # Post-login: Verificación por móvil si es necesaria if st.session_state.user and 'sms_verified' not in st.session_state: st.session_state.sms_verified = None permissions = None if st.session_state.user: username = st.session_state.user['username'] role = st.session_state.user['role'] # Obtener permisos para ver si requiere SMS permissions = get_user_permissions(role, st.session_state.get('sms_verified')) # Si requiere SMS y aún no está verificado/omitido, mostrar pantalla de verificación if permissions["requires_sms"] and st.session_state.sms_verified is None: result = render_mobile_verification_screen(username, role) if result is None: # Aún en proceso de verificación st.stop() # Si result es True o False, ya se ha completado/omitido y continúa # --- Pages --- if page == "Processar vídeo nou": require_login(render_login_form) permissions = get_user_permissions(role, st.session_state.get('sms_verified')) if not permissions["procesar_videos"]: st.error("No tens permisos per processar nous vídeos. Verifica el teu mòbil per obtenir accés complet.") st.stop() render_process_video_page(api, BACKEND_BASE_URL) elif page == "Analitzar audiodescripcions": require_login(render_login_form) permissions = get_user_permissions(role, st.session_state.get('sms_verified')) render_analyze_audiodescriptions_page(api, permissions) elif page == "Estadístiques": require_login(render_login_form) render_statistics_page() elif page == "Validació": require_login(render_login_form) permissions = get_user_permissions(role, st.session_state.get('sms_verified')) render_validation_page(compliance_client, runtime_videos, permissions, username) elif page == "Sala de màquines": require_login(render_login_form) # Només l'usuari 'verd' pot accedir a aquesta pantalla if st.session_state.user and st.session_state.user.get("username") == "verd": render_machine_room_page() else: st.error("No tens permisos per accedir a aquesta pantalla.")