diff --git a/.gitignore b/.gitignore index 497c146..4f9d284 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ vigilancia/*.pt vigilancia/.venv/ vigilancia/modelos/ vigilancia/conocidos/ +vigilancia/voz_media/ diff --git a/vigilancia/.env.example b/vigilancia/.env.example index 1ea1e80..61ac07d 100644 --- a/vigilancia/.env.example +++ b/vigilancia/.env.example @@ -38,3 +38,11 @@ TELEGRAM_RECIPIENTES= # Discord: bot + usuarios a los que se envia el aviso personalizado (TTS). DISCORD_TOKEN= DISCORD_IDS= + +# Teles Google (Chromecast/Google TV) que hablan el aviso. +# GOOGLE_TV_ACTIVO=1/0 (siempre por defecto activo). +# GOOGLE_TV_NOMBRES: nombres de las teles separados por coma (vacio = todas las detectadas). +GOOGLE_TV_ACTIVO=1 +GOOGLE_TV_NOMBRES= +VOZ_EDGE=es-ES-ElviraNeural +GOOGLE_TV_PUERTO=8500 diff --git a/vigilancia/config.py b/vigilancia/config.py index abebc44..14f3496 100644 --- a/vigilancia/config.py +++ b/vigilancia/config.py @@ -42,3 +42,9 @@ DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "").strip() DISCORD_IDS = [ x.strip() for x in os.getenv("DISCORD_IDS", "").split(",") if x.strip() ] +GOOGLE_TV_ACTIVO = os.getenv("GOOGLE_TV_ACTIVO", "1") == "1" +GOOGLE_TV_NOMBRES = { + n.strip() for n in os.getenv("GOOGLE_TV_NOMBRES", "").split(",") if n.strip() +} +VOZ_EDGE = os.getenv("VOZ_EDGE", "es-ES-ElviraNeural") +GOOGLE_TV_PUERTO = int(os.getenv("GOOGLE_TV_PUERTO", "8500")) diff --git a/vigilancia/main.py b/vigilancia/main.py index 4a5017f..23dea7a 100644 --- a/vigilancia/main.py +++ b/vigilancia/main.py @@ -36,6 +36,21 @@ def notificar_discord(resultados, cam_nombre): print(f"[Discord] Error: {e}") +def texto_voz(resultados, cam_nombre): + conocidas = {r["nombre"] for r in resultados if "nombre" in r} + if conocidas: + return f"{', '.join(sorted(conocidas))}, ¿qué estás haciendo? No entres ahí." + return f"Presencia detectada en {cam_nombre}." + + +def hablar_en_teles(mensaje): + try: + from voz import reproducir_en_teles + reproducir_en_teles(mensaje) + except Exception as e: + print(f"[TV] Error: {e}") + + def cargar_identificador(): global identificador if identificador is None: @@ -184,7 +199,10 @@ def main(): notificar_discord(resultados, cam.nombre) except Exception as e: print(f"[{cam.nombre}] Error notificando personales: {e}") - disparar_webhook_alexa(texto) + + mensaje_voz = texto_voz(resultados, cam.nombre) + hablar_en_teles(mensaje_voz) + disparar_webhook_alexa(mensaje_voz) ultima_alerta[i] = ahora print(texto) diff --git a/vigilancia/voz.py b/vigilancia/voz.py new file mode 100644 index 0000000..b0f8211 --- /dev/null +++ b/vigilancia/voz.py @@ -0,0 +1,95 @@ +import asyncio +import os +import socket +import threading +from http.server import HTTPServer, SimpleHTTPRequestHandler + +import config + +MEDIA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "voz_media") +_server = None + + +def _ip_lan(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s.connect(("8.8.8.8", 80)) + return s.getsockname()[0] + finally: + s.close() + + +def sintetizar(texto, nombre="alerta.mp3"): + os.makedirs(MEDIA_DIR, exist_ok=True) + ruta = os.path.join(MEDIA_DIR, nombre) + try: + import edge_tts + + async def _gen(): + comunicador = edge_tts.Communicate(texto, config.VOZ_EDGE) + await comunicador.save(ruta) + + asyncio.run(_gen()) + return ruta + except Exception as e: + print(f"[Voz] Error sintetizando: {e}") + return None + + +class _Handler(SimpleHTTPRequestHandler): + def __init__(self, *a, **kw): + super().__init__(*a, directory=MEDIA_DIR, **kw) + + def log_message(self, *a): + pass + + +def _arrancar_servidor(): + global _server + if _server is not None: + return + try: + _server = HTTPServer(("0.0.0.0", config.GOOGLE_TV_PUERTO), _Handler) + threading.Thread(target=_server.serve_forever, daemon=True).start() + except Exception as e: + print(f"[Voz] No se pudo arrancar el servidor HTTP: {e}") + + +def reproducir_en_teles(texto): + if not config.GOOGLE_TV_ACTIVO: + return + ruta = sintetizar(texto) + if ruta is None: + return + _arrancar_servidor() + if _server is None: + return + + url = f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta)}" + try: + import pychromecast + + chromecasts, browser = pychromecast.get_chromecasts(timeout=8) + try: + if config.GOOGLE_TV_NOMBRES: + objetivos = [c for c in chromecasts + if c.cast_info.friendly_name in config.GOOGLE_TV_NOMBRES] + else: + objetivos = chromecasts + + if not objetivos: + print("[Voz] No hay teles Google detectadas") + return + + for c in objetivos: + try: + c.wait(timeout=10) + c.media_controller.play_media(url, "audio/mpeg", title=texto[:80]) + c.media_controller.block_until_active(timeout=10) + print(f"[Voz] Reproduciendo en {c.cast_info.friendly_name}") + except Exception as e: + print(f"[Voz] Error en {c.cast_info.friendly_name}: {e}") + finally: + pychromecast.discovery.stop_discovery(browser) + except Exception as e: + print(f"[Voz] Error de casting: {e}")