02-ago: [proyecto] vigilancia - voz por edge-tts + casting a teles Google (pychromecast, enciende la tele en standby) + mensaje hablado limpio para Alexa/TVs
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ vigilancia/*.pt
|
|||||||
vigilancia/.venv/
|
vigilancia/.venv/
|
||||||
vigilancia/modelos/
|
vigilancia/modelos/
|
||||||
vigilancia/conocidos/
|
vigilancia/conocidos/
|
||||||
|
vigilancia/voz_media/
|
||||||
|
|||||||
@@ -38,3 +38,11 @@ TELEGRAM_RECIPIENTES=
|
|||||||
# Discord: bot + usuarios a los que se envia el aviso personalizado (TTS).
|
# Discord: bot + usuarios a los que se envia el aviso personalizado (TTS).
|
||||||
DISCORD_TOKEN=
|
DISCORD_TOKEN=
|
||||||
DISCORD_IDS=
|
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
|
||||||
|
|||||||
@@ -42,3 +42,9 @@ DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "").strip()
|
|||||||
DISCORD_IDS = [
|
DISCORD_IDS = [
|
||||||
x.strip() for x in os.getenv("DISCORD_IDS", "").split(",") if x.strip()
|
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"))
|
||||||
|
|||||||
@@ -36,6 +36,21 @@ def notificar_discord(resultados, cam_nombre):
|
|||||||
print(f"[Discord] Error: {e}")
|
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():
|
def cargar_identificador():
|
||||||
global identificador
|
global identificador
|
||||||
if identificador is None:
|
if identificador is None:
|
||||||
@@ -184,7 +199,10 @@ def main():
|
|||||||
notificar_discord(resultados, cam.nombre)
|
notificar_discord(resultados, cam.nombre)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{cam.nombre}] Error notificando personales: {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
|
ultima_alerta[i] = ahora
|
||||||
print(texto)
|
print(texto)
|
||||||
|
|
||||||
|
|||||||
95
vigilancia/voz.py
Normal file
95
vigilancia/voz.py
Normal file
@@ -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}")
|
||||||
Reference in New Issue
Block a user