import asyncio import os import socket import subprocess import sys import time import config MEDIA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "voz_media") _server = None def _python_firewall_ok(): """Python del sistema (con regla de firewall Allow) para servir el mp3.""" if hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix: ruta = os.path.join(sys.base_prefix, "python.exe") if os.path.exists(ruta): return ruta return sys.executable 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="anuncio.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, rate=config.VOZ_RATE, pitch=config.VOZ_PITCH, ) await comunicador.save(ruta) asyncio.run(_gen()) return ruta except Exception as e: print(f"[Voz] Error sintetizando: {e}") return None def _arrancar_servidor(): global _server if _server is not None and _server.poll() is None: return try: py = _python_firewall_ok() _server = subprocess.Popen( [py, "-m", "http.server", str(config.GOOGLE_TV_PUERTO), "--directory", MEDIA_DIR], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) time.sleep(1) except Exception as e: print(f"[Voz] No se pudo arrancar el servidor HTTP: {e}") def _descubrir(): import pychromecast chromecasts, browser = pychromecast.get_chromecasts(timeout=8) try: if config.GOOGLE_TV_NOMBRES: return [c for c in chromecasts if c.cast_info.friendly_name in config.GOOGLE_TV_NOMBRES] return chromecasts finally: pychromecast.discovery.stop_discovery(browser) def _url_video(texto): """Genera el video teleprompter (texto que se compone mientras habla).""" try: from teleprompter import generar_video, sintetizar_con_oraciones ruta_audio, oraciones = sintetizar_con_oraciones(texto) if ruta_audio is None or not oraciones: return None, None ruta_video = generar_video(texto, ruta_audio, oraciones) _arrancar_servidor() if _server is None: return None, None return f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta_video)}", "video/mp4" except Exception as e: print(f"[Voz] No se pudo generar el video: {e}") return None, None def _url_texto_final(texto): """Video estatico con el texto completo (al final del bucle).""" if config.GOOGLE_TV_TEXTO_FINAL_SG <= 0: return None try: from teleprompter import generar_video_texto_final ruta = generar_video_texto_final(texto, config.GOOGLE_TV_TEXTO_FINAL_SG) _arrancar_servidor() if _server is None: return None return f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta)}" except Exception as e: print(f"[Voz] No se pudo generar el texto final: {e}") return None def _url_audio(texto, nombre="anuncio.mp3"): ruta = sintetizar(texto, nombre=nombre) if ruta is None: return None _arrancar_servidor() if _server is None: return None return f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta)}" def _esperar_reproduccion(cast, url, timeout=20): """Espera a que NUESTRA media empiece (PLAYING/BUFFERING) por content_id.""" mc = cast.media_controller t0 = time.time() while time.time() - t0 < timeout: try: st = mc.status if st.content_id == url and st.player_state in ("PLAYING", "BUFFERING"): return True except Exception: pass time.sleep(1) return False def _esperar_fin(cast, url, timeout=120): """Espera a que NUESTRA media termine (IDLE + FINISHED) por content_id.""" mc = cast.media_controller t0 = time.time() while time.time() - t0 < timeout: try: st = mc.status if st.content_id == url and st.player_state == "IDLE" and st.idle_reason == "FINISHED": return True except Exception: pass time.sleep(1) return False def _generar_pitido(): """Genera sirena estilo Proteccion Civil (wail). Usa solo math (sin numpy).""" import math, struct ruta = os.path.join(MEDIA_DIR, "pitido_emergencia.wav") if os.path.exists(ruta): return ruta try: os.makedirs(MEDIA_DIR, exist_ok=True) sr = 44100 dur = config.GOOGLE_TV_PITIDO_SG n = int(sr * dur) periodo = 4.0 nfade = min(int(0.3 * sr), n // 4) phase = 0.0 samples = [] for i in range(n): t = i / sr if config.GOOGLE_TV_PITIDO_ESTILO == "wail": f = (t % periodo) / periodo freq = 400 + 600 * (1 - abs(2 * f - 1)) else: freq = 500 + 400 * abs(math.sin(2 * math.pi * 0.5 * t)) phase += 2 * math.pi * freq / sr y = 0.8 * math.sin(phase) if i < nfade: y *= i / nfade elif i > n - nfade: y *= (n - i) / nfade samples.append(struct.pack(" 0: print(f"[Voz] Esperando {config.GOOGLE_TV_ESPERA_SG}s...") time.sleep(config.GOOGLE_TV_ESPERA_SG) if url_pitido: _reproducir_y_esperar( c, url_pitido, "audio/wav", "pitido", f"Pitido de emergencia en {c.cast_info.friendly_name}") rep = max(1, config.GOOGLE_TV_REPETICIONES) for n in range(rep): _reproducir_y_esperar( c, url_mensaje, tipo_mensaje, texto[:80], f"Anuncio {n + 1}/{rep} en {c.cast_info.friendly_name} " f"(vol {config.GOOGLE_TV_VOLUMEN})") if url_final: _reproducir_y_esperar( c, url_final, "video/mp4", "texto final", f"Texto final {config.GOOGLE_TV_TEXTO_FINAL_SG}s en " f"{c.cast_info.friendly_name}") if config.GOOGLE_TV_APAGAR_VIA_ADB: from tv_adb import apagar_tv apagar_tv() elif config.ALEXA_APAGAR_TV_MONKEY: print("[Voz] Apagando la tele via Alexa...") from notificador import disparar_monkey_alexa disparar_monkey_alexa(config.ALEXA_APAGAR_TV_MONKEY) if config.GOOGLE_TV_APAGAR: c.quit_app() print(f"[Voz] {c.cast_info.friendly_name} apagada (standby)") 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}") def reproducir_en_teles(texto): anuncio_en_teles(texto) def anuncio_simple(texto): """Mensaje rapido: enciende, reproduce el mensaje una vez y apaga.""" if not config.GOOGLE_TV_ACTIVO: return url_mensaje = _url_audio(texto) if url_mensaje is None: return tipo = "audio/mpeg" 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 for c in objetivos: try: c.wait(timeout=10) c.set_volume(config.GOOGLE_TV_VOLUMEN / 100.0) _reproducir_y_esperar( c, url_mensaje, tipo, texto[:80], f"Mensaje simple en {c.cast_info.friendly_name}") if config.GOOGLE_TV_APAGAR_VIA_ADB: from tv_adb import apagar_tv apagar_tv() 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}")