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_APAGAR=1
|
||||||
GOOGLE_TV_PITIDO_ACTIVO=1
|
GOOGLE_TV_PITIDO_ACTIVO=1
|
||||||
GOOGLE_TV_ESPERA_SG=15
|
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_REPETICIONES=3
|
||||||
GOOGLE_TV_TEXTO_FINAL_SG=30
|
GOOGLE_TV_TEXTO_FINAL_SG=30
|
||||||
|
TV_TITULO=ALERTA
|
||||||
|
TV_TITULO_COLOR=red
|
||||||
|
TV_TITULO_PARPADEO=1
|
||||||
VOZ_EDGE=es-ES-AlvaroNeural
|
VOZ_EDGE=es-ES-AlvaroNeural
|
||||||
VOZ_PITCH=-15Hz
|
VOZ_PITCH=-15Hz
|
||||||
VOZ_RATE=-10%
|
VOZ_RATE=-10%
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ VOZ_MENSAJE_CONOCIDO = os.getenv(
|
|||||||
"VOZ_MENSAJE_CONOCIDO",
|
"VOZ_MENSAJE_CONOCIDO",
|
||||||
"{nombres}, ¿qué estás haciendo? No entres ahí.",
|
"{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()
|
ALEXA_APAGAR_TV_MONKEY = os.getenv("ALEXA_APAGAR_TV_MONKEY", "").strip()
|
||||||
ADB_PATH = os.getenv(
|
ADB_PATH = os.getenv(
|
||||||
"ADB_PATH",
|
"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_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_PITIDO_ACTIVO = os.getenv("GOOGLE_TV_PITIDO_ACTIVO", "1") == "1"
|
||||||
GOOGLE_TV_ESPERA_SG = int(os.getenv("GOOGLE_TV_ESPERA_SG", "15"))
|
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_REPETICIONES = int(os.getenv("GOOGLE_TV_REPETICIONES", "3"))
|
||||||
GOOGLE_TV_TEXTO_FINAL_SG = int(os.getenv("GOOGLE_TV_TEXTO_FINAL_SG", "30"))
|
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
|
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):
|
def _palabras(oraciones):
|
||||||
"""Distribuye las palabras dentro de cada frase por longitud."""
|
"""Distribuye las palabras dentro de cada frase por longitud."""
|
||||||
palabras = []
|
palabras = []
|
||||||
@@ -90,6 +111,7 @@ def generar_video(texto, ruta_audio, oraciones, nombre="anuncio.mp4"):
|
|||||||
n_frames = int(total * FPS)
|
n_frames = int(total * FPS)
|
||||||
|
|
||||||
font = ImageFont.truetype(FUENTE, 52) if FUENTE else ImageFont.load_default()
|
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
|
gap = 18
|
||||||
|
|
||||||
# agrupar en lineas
|
# agrupar en lineas
|
||||||
@@ -120,6 +142,7 @@ def generar_video(texto, ruta_audio, oraciones, nombre="anuncio.mp4"):
|
|||||||
t = i / FPS
|
t = i / FPS
|
||||||
img = Image.new("RGB", (W, H), (8, 8, 8))
|
img = Image.new("RGB", (W, H), (8, 8, 8))
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
|
_dibujar_titulo(d, t, font_title)
|
||||||
y = H // 2 - (len(lineas) // 2) * 80 + 30
|
y = H // 2 - (len(lineas) // 2) * 80 + 30
|
||||||
for linea_words in lineas:
|
for linea_words in lineas:
|
||||||
x = 40
|
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__)),
|
ruta_video = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
"voz_media", nombre)
|
"voz_media", nombre)
|
||||||
font = ImageFont.truetype(FUENTE, 52) if FUENTE else ImageFont.load_default()
|
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
|
gap = 18
|
||||||
|
|
||||||
lineas = []
|
lineas = []
|
||||||
@@ -178,6 +202,7 @@ def generar_video_texto_final(texto, segundos, nombre="texto_final.mp4"):
|
|||||||
for i in range(n_frames):
|
for i in range(n_frames):
|
||||||
img = Image.new("RGB", (W, H), (8, 8, 8))
|
img = Image.new("RGB", (W, H), (8, 8, 8))
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
|
_dibujar_titulo(d, i / FPS, font_title)
|
||||||
y = H // 2 - (len(lineas) // 2) * 80 + 30
|
y = H // 2 - (len(lineas) // 2) * 80 + 30
|
||||||
for linea_words in lineas:
|
for linea_words in lineas:
|
||||||
x = 40
|
x = 40
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ def _esperar_fin(cast, url, timeout=120):
|
|||||||
|
|
||||||
|
|
||||||
def _generar_pitido():
|
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")
|
ruta = os.path.join(MEDIA_DIR, "pitido_emergencia.wav")
|
||||||
if os.path.exists(ruta):
|
if os.path.exists(ruta):
|
||||||
return ruta
|
return ruta
|
||||||
@@ -168,10 +168,16 @@ def _generar_pitido():
|
|||||||
sr = 44100
|
sr = 44100
|
||||||
dur = config.GOOGLE_TV_PITIDO_SG
|
dur = config.GOOGLE_TV_PITIDO_SG
|
||||||
t = np.linspace(0, dur, int(sr * dur), endpoint=False)
|
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))
|
freq = 500 + 400 * np.abs(np.sin(2 * np.pi * 0.5 * t))
|
||||||
fase = 2 * np.pi * np.cumsum(freq) / sr
|
fase = 2 * np.pi * np.cumsum(freq) / sr
|
||||||
y = 0.7 * np.sin(fase)
|
y = 0.8 * np.sin(fase)
|
||||||
fade = min(0.2, dur / 4)
|
fade = min(0.3, dur / 4)
|
||||||
n_fade = int(fade * sr)
|
n_fade = int(fade * sr)
|
||||||
y[:n_fade] *= np.linspace(0, 1, n_fade)
|
y[:n_fade] *= np.linspace(0, 1, n_fade)
|
||||||
y[-n_fade:] *= np.linspace(1, 0, n_fade)
|
y[-n_fade:] *= np.linspace(1, 0, n_fade)
|
||||||
|
|||||||
Reference in New Issue
Block a user