02-ago: [proyecto] vigilancia - Fase 1: sirena wail estilo Proteccion Civil (400-1000Hz, 8s) + Fase 2: titulo ALERTA rojo parpadeante en el teleprompter

This commit is contained in:
minguezsanzjuanjose
2026-08-02 15:11:11 +02:00
parent 0ad6dac79e
commit fbfc395f3b
4 changed files with 45 additions and 6 deletions

View File

@@ -50,9 +50,13 @@ GOOGLE_TV_VOLUMEN=30
GOOGLE_TV_APAGAR=1
GOOGLE_TV_PITIDO_ACTIVO=1
GOOGLE_TV_ESPERA_SG=15
GOOGLE_TV_PITIDO_SG=6
GOOGLE_TV_PITIDO_SG=8
GOOGLE_TV_PITIDO_ESTILO=wail
GOOGLE_TV_REPETICIONES=3
GOOGLE_TV_TEXTO_FINAL_SG=30
TV_TITULO=ALERTA
TV_TITULO_COLOR=red
TV_TITULO_PARPADEO=1
VOZ_EDGE=es-ES-AlvaroNeural
VOZ_PITCH=-15Hz
VOZ_RATE=-10%

View File

@@ -61,6 +61,9 @@ VOZ_MENSAJE_CONOCIDO = os.getenv(
"VOZ_MENSAJE_CONOCIDO",
"{nombres}, ¿qué estás haciendo? No entres ahí.",
)
TV_TITULO = os.getenv("TV_TITULO", "ALERTA").strip()
TV_TITULO_COLOR = os.getenv("TV_TITULO_COLOR", "red").strip().lower()
TV_TITULO_PARPADEO = os.getenv("TV_TITULO_PARPADEO", "1") == "1"
ALEXA_APAGAR_TV_MONKEY = os.getenv("ALEXA_APAGAR_TV_MONKEY", "").strip()
ADB_PATH = os.getenv(
"ADB_PATH",
@@ -71,6 +74,7 @@ ADB_TV_IP = os.getenv("ADB_TV_IP", "192.168.50.16:5555")
GOOGLE_TV_APAGAR_VIA_ADB = os.getenv("GOOGLE_TV_APAGAR_VIA_ADB", "1") == "1"
GOOGLE_TV_PITIDO_ACTIVO = os.getenv("GOOGLE_TV_PITIDO_ACTIVO", "1") == "1"
GOOGLE_TV_ESPERA_SG = int(os.getenv("GOOGLE_TV_ESPERA_SG", "15"))
GOOGLE_TV_PITIDO_SG = float(os.getenv("GOOGLE_TV_PITIDO_SG", "6"))
GOOGLE_TV_PITIDO_SG = float(os.getenv("GOOGLE_TV_PITIDO_SG", "8"))
GOOGLE_TV_PITIDO_ESTILO = os.getenv("GOOGLE_TV_PITIDO_ESTILO", "wail")
GOOGLE_TV_REPETICIONES = int(os.getenv("GOOGLE_TV_REPETICIONES", "3"))
GOOGLE_TV_TEXTO_FINAL_SG = int(os.getenv("GOOGLE_TV_TEXTO_FINAL_SG", "30"))

View File

@@ -64,6 +64,27 @@ def sintetizar_con_oraciones(texto, nombre="anuncio.mp3"):
return ruta, oraciones
_TITULO_COLORES = {
"red": (255, 30, 30),
"rojo": (255, 30, 30),
"rosa": (255, 105, 180),
"pink": (255, 105, 180),
}
def _dibujar_titulo(d, t, font_title):
"""Titulo parpadeante en mayusculas (rojo/rosa) en la parte superior."""
titulo = config.TV_TITULO.upper()
if not titulo:
return
if config.TV_TITULO_PARPADEO and int(t * 2) % 2 == 1:
return
color = _TITULO_COLORES.get(config.TV_TITULO_COLOR, (255, 30, 30))
ancho = d.textlength(titulo, font=font_title)
x = (W - ancho) // 2
d.text((x, 30), titulo, fill=color, font=font_title)
def _palabras(oraciones):
"""Distribuye las palabras dentro de cada frase por longitud."""
palabras = []
@@ -90,6 +111,7 @@ def generar_video(texto, ruta_audio, oraciones, nombre="anuncio.mp4"):
n_frames = int(total * FPS)
font = ImageFont.truetype(FUENTE, 52) if FUENTE else ImageFont.load_default()
font_title = ImageFont.truetype(FUENTE, 96) if FUENTE else ImageFont.load_default()
gap = 18
# agrupar en lineas
@@ -120,6 +142,7 @@ def generar_video(texto, ruta_audio, oraciones, nombre="anuncio.mp4"):
t = i / FPS
img = Image.new("RGB", (W, H), (8, 8, 8))
d = ImageDraw.Draw(img)
_dibujar_titulo(d, t, font_title)
y = H // 2 - (len(lineas) // 2) * 80 + 30
for linea_words in lineas:
x = 40
@@ -150,6 +173,7 @@ def generar_video_texto_final(texto, segundos, nombre="texto_final.mp4"):
ruta_video = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"voz_media", nombre)
font = ImageFont.truetype(FUENTE, 52) if FUENTE else ImageFont.load_default()
font_title = ImageFont.truetype(FUENTE, 96) if FUENTE else ImageFont.load_default()
gap = 18
lineas = []
@@ -178,6 +202,7 @@ def generar_video_texto_final(texto, segundos, nombre="texto_final.mp4"):
for i in range(n_frames):
img = Image.new("RGB", (W, H), (8, 8, 8))
d = ImageDraw.Draw(img)
_dibujar_titulo(d, i / FPS, font_title)
y = H // 2 - (len(lineas) // 2) * 80 + 30
for linea_words in lineas:
x = 40

View File

@@ -156,7 +156,7 @@ def _esperar_fin(cast, url, timeout=120):
def _generar_pitido():
"""Genera una sirena de emergencia (WAV) si no existe."""
"""Genera una sirena estilo Proteccion Civil (wail) si no existe."""
ruta = os.path.join(MEDIA_DIR, "pitido_emergencia.wav")
if os.path.exists(ruta):
return ruta
@@ -168,10 +168,16 @@ def _generar_pitido():
sr = 44100
dur = config.GOOGLE_TV_PITIDO_SG
t = np.linspace(0, dur, int(sr * dur), endpoint=False)
if config.GOOGLE_TV_PITIDO_ESTILO == "wail":
# wail defensa civil: sube 400->1000 y baja en ciclos de ~4s
periodo = 4.0
fase = (t % periodo) / periodo
freq = 400 + 600 * (1 - abs(2 * fase - 1))
else:
freq = 500 + 400 * np.abs(np.sin(2 * np.pi * 0.5 * t))
fase = 2 * np.pi * np.cumsum(freq) / sr
y = 0.7 * np.sin(fase)
fade = min(0.2, dur / 4)
y = 0.8 * np.sin(fase)
fade = min(0.3, dur / 4)
n_fade = int(fade * sr)
y[:n_fade] *= np.linspace(0, 1, n_fade)
y[-n_fade:] *= np.linspace(1, 0, n_fade)