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:
@@ -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%
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user