Commit inicial: G Radio Player

Sistema de automatización de radio en Rust (GTK4 + GStreamer): reproductor
principal con crossfade y ducking, scheduler de comerciales, refill de
playlist, y herramientas auxiliares (pautaje, parrilla, botonera, visor,
buscador, playlist, grabador, reportes). Incluye sistema de skins y modos
de Players (paneles de reproducción configurables).
This commit is contained in:
2026-08-23 00:57:40 -05:00
commit 03084e2d5a
231 changed files with 31055 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
# Estructura de G-Radio-Player
Sistema de automatización de radio profesional en **Rust + GTK4**. Tres procesos coordinados que se comunican mediante archivos compartidos en `~/.gradio/data/tmp/`.
## Binarios instalados en `/usr/local/bin/`
| Binario | Descripción |
|---|---|
| `radio-player` | UI principal GTK4 — reproduce música, maneja crossfade, Duck |
| `comercial-scheduler` | Daemon que se activa en el minuto :58 y carga las tandas del minuto siguiente |
| `playlist-refill` | Daemon que revisa cada 5s y rellena `playlist4` con ~14 temas de la parrilla |
| `gradio.sh` | Lanzador principal (mata procesos previos, inicia los 3 procesos + watchdog) |
| `gr-botonera` | Ventana de botonera de efectos y jingles rápidos |
| `gr-parrilla` | Editor de parrilla semanal (qué suena cada hora) |
| `gr-pautaje` | Editor de pautaje comercial (qué comerciales suenan cada minuto) |
| `gr-playlist` | Editor de playlist |
| `gr-buscador` | Buscador de archivos de audio con preescucha |
| `gr-visor` | Visor de reportes y programación |
| `gr-reportes` | Generador de reportes de reproducción |
| `gr-record` | Grabación de audio |
## Árbol de datos `~/.gradio/data/`
```
~/.gradio/data/
├── tmp/ ← Estado en vivo (leer/escribir aquí)
│ ├── gradio.config ← Configuración principal (18 líneas)
│ ├── playlist4 ← Cola musical: "ruta\tduracion" por línea
│ ├── comercialeslist4 ← Cola activa de comerciales (solo rutas)
│ ├── eventoslist ← Cola activa de eventos (solo rutas)
│ ├── eventos-esperalist ← Cola activa de eventos en espera
│ ├── estado.json ← Estado completo del player (se escribe cada 2s)
│ ├── upvol ← Volumen principal 0-100
│ ├── downvol ← Volumen duck 0-100
│ ├── cmd_play ← Señal: reproducir (crear el archivo para activar)
│ ├── cmd_pause ← Señal: pausar
│ ├── cmd_stop ← Señal: detener deck A
│ ├── cmd_stop_after ← Señal: toggle "detener al terminar el tema"
│ ├── cmd_siguiente ← Señal: saltar al siguiente tema
│ ├── cmd_stop_general ← Señal: detener TODO (música + comerciales + eventos)
│ ├── cmd_play_tanda ← Señal: reproducir tanda de comerciales inmediata
│ ├── botonera_now ← Señal: ruta de audio a reproducir en botonera
│ ├── play_now ← Señal: "ruta\tduracion" para reproducir ahora
│ └── played_breaks ← Tandas ya reproducidas hoy (YYYYMMDD HH:MM por línea)
├── parrilla/ ← Programación semanal de música
│ ├── 1/ ← Lunes (1=Lun, 2=Mar, ..., 7=Dom)
│ │ ├── 0-1.mus ← Bloque 00:00-01:00 del lunes
│ │ ├── 8-9.mus
│ │ └── ...
│ ├── 2/ ... 7/
├── comerciales/ ← Pautaje de comerciales
│ ├── 0/ ← Hora 0 (00:xx)
│ │ ├── 0.com ← Comerciales a las 00:00
│ │ ├── 30.com ← Comerciales a las 00:30
│ │ └── ...
│ └── 1/ ... 23/
├── eventos/ ← Eventos programados (mismo formato .com)
│ └── <hora>/<minuto>.com
├── eventos-espera/ ← Eventos en espera del operador
│ └── <hora>/<minuto>.com
├── botonera/config.json ← Botones configurados en gr-botonera
├── panel/Time/ ← 109 audios de hora (HRS00..HRS23, MIN01..MIN59, etc.)
└── reporte/ ← Reportes CSV de reproducción
```
## Archivo `gradio.config` (18 líneas)
```
línea 1: tarjeta audio principal (vacío = auto)
línea 2: tarjeta audio CUE (vacío = auto)
línea 3: nombre del medio
línea 4: tiempo de fundido crossfade (segundos)
línea 5: pisador habilitado (1/0)
línea 6: carpeta de pisadores
línea 7: cada cuántos temas tocar pisador
línea 8: carpetas excluidas de pisador (formato: "/ruta/a";"/ruta/b")
línea 9: silencio máximo en segundos (0=desactivado)
línea 10: carpetas de música nacional (para reportes)
línea 11: carpetas de interculturalidad (para reportes)
línea 12: puerto servidor gr-client (default 7777)
línea 13: token de autenticación gr-client
línea 14: relay habilitado (1/0)
línea 15: ID de relay (8 dígitos)
línea 16: días de no-repetición (default 3)
línea 17: locale ("es"/"en"/"pt" o vacío=auto)
línea 18: IA habilitada (1/0)
```
## Archivo `estado.json` (se actualiza cada 2 segundos)
```json
{
"reproduciendo": true,
"pausado": false,
"track_actual": "/home/usuario/Música/tema.mp3",
"titulo_actual": "Nombre del tema",
"posicion_secs": 45.3,
"duracion_secs": 210.0,
"upvol": 90,
"downvol": 20,
"comerciales_activos": false,
"eventos_activos": false,
"num_comerciales": 3,
"num_eventos": 0,
"num_eventos_espera": 1,
"indice_actual": 7
}
```
@@ -0,0 +1,90 @@
# Programación Musical (Parrilla)
La **parrilla** define qué música suena en cada bloque horario de cada día de la semana.
## Archivos `.mus`
Ruta: `~/.gradio/data/parrilla/<día>/<hora_inicio>-<hora_fin>.mus`
- **Día**: 1=Lunes, 2=Martes, 3=Miércoles, 4=Jueves, 5=Viernes, 6=Sábado, 7=Domingo
- **Hora**: bloque de una hora (0-1, 1-2, ..., 23-24)
- Ejemplo: `~/.gradio/data/parrilla/1/10-11.mus` = Lunes de 10:00 a 11:00
## Formato de un archivo `.mus`
Cada línea puede ser:
```
/home/usuario/Música/tema_exacto.mp3 ← archivo específico
/home/usuario/Música/Salsa/* ← archivo ALEATORIO de esa carpeta (comodín *)
Hora ← insertar anuncio de hora del sistema
```
Ejemplo real:
```
/home/grecord/Musica/Inicio/intro.mp3
/home/grecord/Musica/Pop/*
/home/grecord/Musica/Pop/*
Hora
/home/grecord/Musica/Rock/*
/home/grecord/Musica/Baladas/*
```
## Cola activa `playlist4`
`~/.gradio/data/tmp/playlist4` contiene la playlist en ejecución. Formato TAB-separado:
```
/ruta/absoluta/al/archivo.mp3\t00:03:45.000
/ruta/absoluta/al/archivo2.mp3\t00:04:12.000
```
El daemon `playlist-refill` mantiene esta cola con ~14 temas. Cada tema consumido se elimina de la primera línea.
## Cómo ver la programación actual
```bash
# Qué tema está sonando ahora
cat ~/.gradio/data/tmp/estado.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['titulo_actual'], '-', d['track_actual'])"
# Cola de música pendiente
cat ~/.gradio/data/tmp/playlist4
# Parrilla del día de hoy (ej. lunes = día 1)
DIA=$(date +%u) # 1=Lun...7=Dom
HORA=$(date +%-H)
cat ~/.gradio/data/parrilla/$DIA/$HORA-$((HORA+1)).mus
```
## Cómo modificar la programación
```bash
# Agregar un tema al frente de la cola actual (próximo a reproducir)
TEMA="/ruta/al/archivo.mp3"
DUR="00:03:45.000"
(echo -e "$TEMA\t$DUR"; cat ~/.gradio/data/tmp/playlist4) > /tmp/pl_tmp && mv /tmp/pl_tmp ~/.gradio/data/tmp/playlist4
# Agregar un tema al final de la cola
echo -e "/ruta/al/archivo.mp3\t00:04:00.000" >> ~/.gradio/data/tmp/playlist4
# Editar la parrilla de mañana (agregar tema a las 9:00-10:00 del martes)
echo "/ruta/nuevo_tema.mp3" >> ~/.gradio/data/parrilla/2/9-10.mus
# Ver toda la programación de la semana para una hora específica
for dia in 1 2 3 4 5 6 7; do
echo "=== Día $dia ==="
cat ~/.gradio/data/parrilla/$dia/10-11.mus 2>/dev/null || echo "(vacío)"
done
```
## Días con nombres
| Número | Día |
|--------|-----|
| 1 | Lunes |
| 2 | Martes |
| 3 | Miércoles |
| 4 | Jueves |
| 5 | Viernes |
| 6 | Sábado |
| 7 | Domingo |
+103
View File
@@ -0,0 +1,103 @@
# Comerciales (Pautaje)
Los **comerciales** son audios programados para sonar a horas y minutos específicos, con control de días activos y fechas de vigencia.
## Archivos `.com`
Ruta: `~/.gradio/data/comerciales/<hora>/<minuto>.com`
- Hora: 0 a 23 (sin cero a la izquierda)
- Minuto: 0 a 59 (sin cero a la izquierda)
- Ejemplo: `~/.gradio/data/comerciales/15/30.com` = tanda de las 15:30
## Formato de cada línea en un `.com`
```
/ruta/absoluta/al/audio.mp3|1234567|20250101|20251231
```
| Campo | Significado |
|-------|-------------|
| `/ruta/...` | Ruta absoluta al archivo de audio |
| `1234567` | Máscara de días activos: cada dígito = un día (1=Lun, 2=Mar, 3=Mié, 4=Jue, 5=Vie, 6=Sáb, 7=Dom). `1234567` = todos los días |
| `20250101` | Fecha de inicio de vigencia (AAAAMMDD) |
| `20251231` | Fecha de fin de vigencia (AAAAMMDD) |
### Ejemplos de máscaras de días
```
1234567 → todos los días
12345 → lunes a viernes
67 → sábado y domingo
135 → lunes, miércoles, viernes
1 → solo lunes
```
### Ejemplos de entradas `.com`
```
/home/grecord/Comerciales/spot_banco.mp3|12345|20250601|20250630
/home/grecord/Comerciales/promo_fin_semana.mp3|67|20250101|20251231
/home/grecord/Comerciales/jingle_general.mp3|1234567|20250101|20261231
```
## Cola activa `comercialeslist4`
`~/.gradio/data/tmp/comercialeslist4` contiene la cola que el player está procesando **ahora**. Solo tiene rutas (ya sin máscara ni fechas, esas ya fueron evaluadas por el scheduler):
```
/home/grecord/Comerciales/spot_banco.mp3
/home/grecord/Comerciales/jingle_general.mp3
```
## Archivo `played_breaks`
`~/.gradio/data/tmp/played_breaks` registra las tandas que ya fueron reproducidas manualmente hoy (para que el scheduler no las vuelva a disparar):
```
20250627 15:30
20250627 16:00
```
## Comandos útiles
```bash
# Ver la tanda de las 15:30
cat ~/.gradio/data/comerciales/15/30.com
# Ver todos los comerciales programados para hoy (lunes = día 1)
HOY=$(date +%u)
for h in $(seq 0 23); do
for f in ~/.gradio/data/comerciales/$h/*.com; do
[ -f "$f" ] || continue
grep "|.*$HOY" "$f" && echo "$f"
done
done
# Ver qué está en cola activa
cat ~/.gradio/data/tmp/comercialeslist4
# Agregar un comercial a las 16:00 de hoy (todos los días, vigente todo el año)
echo "/ruta/spot.mp3|1234567|20250101|20261231" >> ~/.gradio/data/comerciales/16/0.com
# Crear directorio si no existe
mkdir -p ~/.gradio/data/comerciales/16
echo "/ruta/spot.mp3|1234567|20250101|20261231" >> ~/.gradio/data/comerciales/16/0.com
# Reproducir tanda inmediatamente (sin esperar al scheduler)
echo "/ruta/spot.mp3" > ~/.gradio/data/tmp/comercialeslist4
touch ~/.gradio/data/tmp/cmd_play_tanda
# Ver tandas ya reproducidas hoy
cat ~/.gradio/data/tmp/played_breaks
```
## Cómo funciona el scheduler
El daemon `comercial-scheduler` (Rust) se activa en el minuto **:58** de cada hora y:
1. Lee los archivos `.com` del minuto y hora siguientes
2. Filtra por día activo (máscara) y fechas de vigencia
3. Carga las rutas válidas en `comercialeslist4`
4. El player las procesa cuando llega el momento
Si una tanda aparece en `played_breaks` con fecha/hora de hoy, el scheduler la salta (ya se reprodujo manualmente).
+113
View File
@@ -0,0 +1,113 @@
# Eventos
Los **eventos** son acciones programadas que suenan de forma emergente: interrumpen la música brevemente, reproducen el audio, y la música se reanuda. Comparten el mismo formato `.com` que los comerciales.
## Archivos `.com` de eventos
Ruta: `~/.gradio/data/eventos/<hora>/<minuto>.com`
- Mismo formato que comerciales: `/ruta/audio.mp3|máscara_días|fecha_inicio|fecha_fin`
- Ejemplo: `~/.gradio/data/eventos/12/0.com` = evento del mediodía
## Cola activa `eventoslist`
`~/.gradio/data/tmp/eventoslist` — lista de rutas que el player procesará como evento emergente.
El player detecta cuando esta cola tiene contenido y la procesa de inmediato (interrumpe brevemente la música).
## Diferencia entre Comerciales, Eventos y Eventos-en-Espera
| Aspecto | Comerciales | Eventos | Eventos-en-Espera |
|---------|-------------|---------|-------------------|
| Directorio | `data/comerciales/` | `data/eventos/` | `data/eventos-espera/` |
| Cola activa | `tmp/comercialeslist4` | `tmp/eventoslist` | `tmp/eventos-esperalist` |
| Típico uso | Publicidad programada | Cuñas institucionales, separadores | Acciones espontáneas del operador |
| Scheduler | `comercial-scheduler` | `comercial-scheduler` | `comercial-scheduler` |
## Comandos útiles
```bash
# Ver eventos programados para hoy a las 12:00
cat ~/.gradio/data/eventos/12/0.com
# Ver cola activa de eventos
cat ~/.gradio/data/tmp/eventoslist
# Disparar un evento inmediatamente (reproducir ahora)
echo "/ruta/cuña.mp3" > ~/.gradio/data/tmp/eventoslist
# Agregar evento al mediodía todos los días
mkdir -p ~/.gradio/data/eventos/12
echo "/ruta/cuña_mediodia.mp3|1234567|20250101|20261231" >> ~/.gradio/data/eventos/12/0.com
# Vaciar la cola activa de eventos
> ~/.gradio/data/tmp/eventoslist
# Ver estado general del sistema (incluye num_eventos)
cat ~/.gradio/data/tmp/estado.json | python3 -c "import sys,json; d=json.load(sys.stdin); print('Eventos en cola:', d['num_eventos'])"
```
## Estado en `estado.json`
El campo `eventos_activos` indica si hay un evento reproduciéndose ahora mismo. `num_eventos` muestra cuántos hay en cola.
# Eventos en Espera
Los **eventos en espera** son acciones programadas que el operador prepara con antelación para disparar cuando lo decida, o que el scheduler carga automáticamente. Son el equivalente de una "lista de espera" de efectos.
## Archivos `.com`
Ruta: `~/.gradio/data/eventos-espera/<hora>/<minuto>.com`
- Mismo formato que comerciales y eventos: `/ruta/audio.mp3|máscara_días|fecha_inicio|fecha_fin`
- Ejemplo: `~/.gradio/data/eventos-espera/9/30.com` = evento en espera a las 9:30
## Cola activa `eventos-esperalist`
`~/.gradio/data/tmp/eventos-esperalist` — lista de rutas pendientes en espera.
El player la monitorea: cuando tiene contenido, lo reproduce en modo emergente (igual que `eventoslist`).
## Uso típico
Los eventos en espera se usan para:
- Anuncios de noticias que salen al aire cuando el operador decide
- Efectos especiales cargados con antelación
- Separadores y cortinas que se reproducen bajo demanda
## Comandos útiles
```bash
# Ver cola de eventos en espera
cat ~/.gradio/data/tmp/eventos-esperalist
# Poner un audio en la cola de espera (listo para reproducir)
echo "/ruta/noticia_urgente.mp3" > ~/.gradio/data/tmp/eventos-esperalist
# Agregar sin borrar los anteriores
echo "/ruta/efecto.mp3" >> ~/.gradio/data/tmp/eventos-esperalist
# Vaciar la cola de espera
> ~/.gradio/data/tmp/eventos-esperalist
# Programar evento en espera para las 10:30 todos los lunes-viernes
mkdir -p ~/.gradio/data/eventos-espera/10
echo "/ruta/noticia.mp3|12345|20250601|20251231" >> ~/.gradio/data/eventos-espera/10/30.com
# Estado: cuántos hay en la cola de espera
cat ~/.gradio/data/tmp/estado.json | python3 -c "import sys,json; d=json.load(sys.stdin); print('En espera:', d['num_eventos_espera'])"
```
## Resumen de las tres colas
```bash
# Ver el estado de todas las colas a la vez
echo "=== MÚSICA (playlist4) ==="
wc -l ~/.gradio/data/tmp/playlist4
echo "=== COMERCIALES activos ==="
cat ~/.gradio/data/tmp/comercialeslist4
echo "=== EVENTOS activos ==="
cat ~/.gradio/data/tmp/eventoslist
echo "=== EVENTOS EN ESPERA ==="
cat ~/.gradio/data/tmp/eventos-esperalist
```
+180
View File
@@ -0,0 +1,180 @@
# Comandos y Control del Sistema
## Control de reproducción (señales IPC)
El player monitorea la carpeta `~/.gradio/data/tmp/` cada 200ms. Para enviar un comando, **crea el archivo** — el player lo lee, ejecuta la acción y borra el archivo.
```bash
TMP=~/.gradio/data/tmp
# Reproducir (reanudar desde pausa)
touch "$TMP/cmd_play"
# Pausar / reanudar (toggle)
touch "$TMP/cmd_pause"
# Detener deck A (deja los comerciales intactos)
touch "$TMP/cmd_stop"
# Toggle "detener al terminar el tema actual"
touch "$TMP/cmd_stop_after"
# Saltar al siguiente tema
touch "$TMP/cmd_siguiente"
# STOP GENERAL: detiene música + comerciales + eventos todo
touch "$TMP/cmd_stop_general"
# Reproducir tanda de comerciales AHORA (sin esperar al scheduler)
# Primero cargar la lista, luego la señal
echo "/ruta/spot1.mp3" > "$TMP/comercialeslist4"
echo "/ruta/spot2.mp3" >> "$TMP/comercialeslist4"
touch "$TMP/cmd_play_tanda"
# Reproducir audio en botonera (sin interrumpir música ni comerciales)
echo "/ruta/efecto.mp3" > "$TMP/botonera_now"
# Reproducir tema inmediatamente (crossfade hacia él)
echo -e "/ruta/tema.mp3\t00:03:45.000" > "$TMP/play_now"
```
## Estado del sistema
```bash
# Estado completo (JSON actualizado cada 2s)
cat ~/.gradio/data/tmp/estado.json
# Estado formateado
cat ~/.gradio/data/tmp/estado.json | python3 -m json.tool
# Valores individuales
python3 -c "
import json
d = json.load(open('$HOME/.gradio/data/tmp/estado.json'))
print(f'Reproduciendo: {d[\"reproduciendo\"]}')
print(f'Tema actual: {d[\"titulo_actual\"]}')
print(f'Posición: {d[\"posicion_secs\"]:.0f}s / {d[\"duracion_secs\"]:.0f}s')
print(f'Volumen: up={d[\"upvol\"]}% duck={d[\"downvol\"]}%')
print(f'Comerciales: {d[\"num_comerciales\"]} en cola, activos={d[\"comerciales_activos\"]}')
print(f'Eventos: {d[\"num_eventos\"]}')
print(f'En espera: {d[\"num_eventos_espera\"]}')
"
# Playlist pendiente (cuántos temas quedan)
wc -l ~/.gradio/data/tmp/playlist4
# Cola de comerciales activa
cat ~/.gradio/data/tmp/comercialeslist4
```
## Volumen
```bash
# Ver volumen actual
echo "Principal: $(cat ~/.gradio/data/tmp/upvol)%"
echo "Duck: $(cat ~/.gradio/data/tmp/downvol)%"
# Cambiar volumen principal a 85%
echo "85" > ~/.gradio/data/tmp/upvol
# Cambiar volumen duck a 20%
echo "20" > ~/.gradio/data/tmp/downvol
```
## Parrilla (programación)
```bash
# Ver qué suena ahora
HORA=$(date +%-H)
DIA=$(date +%u) # 1=Lun...7=Dom
echo "=== Parrilla actual: día $DIA, hora $HORA ==="
cat ~/.gradio/data/parrilla/$DIA/$HORA-$((HORA+1)).mus
# Agregar tema a la hora actual
echo "/ruta/nuevo_tema.mp3" >> ~/.gradio/data/parrilla/$DIA/$HORA-$((HORA+1)).mus
# Crear parrilla para todas las horas de un día (comodín)
for h in $(seq 0 23); do
mkdir -p ~/.gradio/data/parrilla/1
echo "/home/grecord/Musica/Variado/*" >> ~/.gradio/data/parrilla/1/$h-$((h+1)).mus
done
```
## Comerciales
```bash
# Agregar comercial a las 16:00 (todos los días, todo el año)
mkdir -p ~/.gradio/data/comerciales/16
echo "/ruta/spot.mp3|1234567|20250101|20261231" >> ~/.gradio/data/comerciales/16/0.com
# Ver tandas de la hora actual
HORA=$(date +%-H)
MIN=$(date +%-M)
ls ~/.gradio/data/comerciales/$HORA/
# Ver tandas ya reproducidas hoy
cat ~/.gradio/data/tmp/played_breaks
```
## Locución con IA
```bash
# Generar locución con voz clonada de Pato (requiere GPU NVIDIA)
locuta "Texto a locutar"
# Especificar nombre de archivo
cd ~/Dropbox/LocutorIA
./locucion_pato.sh "Texto a locutar" nombre_salida
# La locución se genera en:
# ~/Dropbox/LocutorIA/salida_mp3/locucion_AAAAMMDD_HHMMSS.mp3
# (o salida_mp3/nombre_salida.mp3 si se usó nombre personalizado)
# Copiar a la carpeta Time de G Radio Player para usar como locución de hora:
cp ~/Dropbox/LocutorIA/salida_mp3/locucion_*.mp3 ~/.gradio/data/panel/Time/
# O copiar a la carpeta de comerciales/eventos para pautar:
cp ~/Dropbox/LocutorIA/salida_mp3/mi_locucion.mp3 ~/.gradio/data/comerciales/15/
```
## Verificar daemons
```bash
# Ver si los daemons están corriendo
ps aux | grep -E 'comercial-scheduler|playlist-refill' | grep -v grep
# Verificar que el player está activo
ps aux | grep radio-player | grep -v grep
# Leer configuración actual
cat ~/.gradio/data/tmp/gradio.config
```
## Diagnóstico rápido
```bash
# Script de diagnóstico completo
echo "=== G-Radio Player — Estado ==="
echo ""
echo "Procesos:"
ps aux | grep -E 'radio-player|comercial-scheduler|playlist-refill' | grep -v grep
echo ""
echo "Reproducción:"
python3 -c "
import json, sys
try:
d = json.load(open('$HOME/.gradio/data/tmp/estado.json'))
print(f' Reproduciendo: {d[\"reproduciendo\"]}')
print(f' Tema: {d[\"titulo_actual\"]}')
print(f' Posición: {d[\"posicion_secs\"]:.0f}s / {d[\"duracion_secs\"]:.0f}s')
except: print(' (estado.json no disponible)')
"
echo ""
echo "Colas:"
echo " Música pendiente: $(wc -l < ~/.gradio/data/tmp/playlist4 2>/dev/null || echo 0) temas"
echo " Comerciales: $(wc -l < ~/.gradio/data/tmp/comercialeslist4 2>/dev/null | tr -d ' ') items"
echo " Eventos: $(wc -l < ~/.gradio/data/tmp/eventoslist 2>/dev/null | tr -d ' ') items"
echo " En espera: $(wc -l < ~/.gradio/data/tmp/eventos-esperalist 2>/dev/null | tr -d ' ') items"
```