demo / app.py
VeuReu's picture
Upload 10 files
e2c60fb verified
raw
history blame
8.14 kB
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 API_SHARED_TOKEN
API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("API_SHARED_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(
"""
<style>
.stButton > button[kind="primary"] {
background-color: #f97316;
border-color: #ea580c;
color: white;
}
.stButton > button[kind="primary"]:hover {
background-color: #ea580c;
border-color: #c2410c;
}
</style>
""",
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.")