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
Executable
+405
View File
@@ -0,0 +1,405 @@
#!/bin/bash
# package-deb.sh — Empaqueta G Radio Player como .deb instalable
# Uso: ./package-deb.sh <versión> [--target-dir <dir>] [--suffix <sufijo>]
# --target-dir directorio de binarios (default: target/release)
# --suffix sufijo al nombre del paquete (ej: _bookworm)
set -e
VERSION="${1:-0.2.8}"
TARGET_DIR="target/release"
SUFFIX=""
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--target-dir) TARGET_DIR="$2"; shift 2 ;;
--suffix) SUFFIX="$2"; shift 2 ;;
*) shift ;;
esac
done
ARCH="$(dpkg --print-architecture)"
PKG_NAME="gradio-player"
PKG_DIR="${PKG_NAME}_${VERSION}_${ARCH}${SUFFIX}"
ROOT="$(cd "$(dirname "$0")" && pwd)"
# ── Verificar que los binarios existen ────────────────────────────────────────
for BIN in radio-player comercial-scheduler playlist-refill \
gr-buscador gr-playlist gr-pautaje gr-parrilla \
gr-botonera gr-visor gr-reportes gr-record; do
if [ ! -f "$ROOT/$TARGET_DIR/$BIN" ]; then
echo "[!] Binario faltante: $TARGET_DIR/$BIN"
echo " Ejecuta primero: cargo build --release"
exit 1
fi
done
echo "========================================"
echo " Empaquetando $PKG_NAME $VERSION ($ARCH)"
echo "========================================"
# ── Limpiar directorio anterior ───────────────────────────────────────────────
rm -rf "$PKG_DIR"
# ── Verificar que existe la carpeta Time ──────────────────────────────────────
if [ -d "$ROOT/assets/Time" ]; then
TIME_SRC="$ROOT/assets/Time"
elif [ -d "$ROOT/target/data/panel/Time" ]; then
TIME_SRC="$ROOT/target/data/panel/Time"
else
echo "[!] Carpeta Time no encontrada (assets/Time ni target/data/panel/Time)"
exit 1
fi
# ── Estructura de directorios ─────────────────────────────────────────────────
mkdir -p "$PKG_DIR/DEBIAN"
mkdir -p "$PKG_DIR/usr/local/bin"
mkdir -p "$PKG_DIR/usr/local/share/radio-player/Time"
mkdir -p "$PKG_DIR/usr/local/share/radio-player/opencode/agents"
mkdir -p "$PKG_DIR/usr/local/share/radio-player/opencode/context"
mkdir -p "$PKG_DIR/usr/local/share/radio-player/skins"
mkdir -p "$PKG_DIR/usr/share/applications"
mkdir -p "$PKG_DIR/usr/share/icons/hicolor/48x48/apps"
# ── Copiar archivos de hora (Time) ───────────────────────────────────────────
echo "[*] Copiando carpeta Time..."
cp "$TIME_SRC/"* "$PKG_DIR/usr/local/share/radio-player/Time/"
# ── Copiar contexto de IA (opencode) ─────────────────────────────────────────
if [ -d "$ROOT/assets/opencode" ]; then
echo "[*] Copiando contexto IA (opencode)..."
cp "$ROOT/assets/opencode/AGENTS.md" "$PKG_DIR/usr/local/share/radio-player/opencode/"
cp "$ROOT/assets/opencode/opencode.jsonc" "$PKG_DIR/usr/local/share/radio-player/opencode/" 2>/dev/null || true
cp "$ROOT/assets/opencode/agents/"* "$PKG_DIR/usr/local/share/radio-player/opencode/agents/"
cp "$ROOT/assets/opencode/context/"* "$PKG_DIR/usr/local/share/radio-player/opencode/context/"
fi
# ── Copiar skins de ejemplo ───────────────────────────────────────────────────
if [ -d "$ROOT/assets/skins" ]; then
echo "[*] Copiando skins de ejemplo..."
cp -r "$ROOT/assets/skins/"* "$PKG_DIR/usr/local/share/radio-player/skins/" 2>/dev/null || true
fi
# ── Copiar binarios ───────────────────────────────────────────────────────────
echo "[*] Copiando binarios..."
for BIN in radio-player comercial-scheduler playlist-refill \
gr-buscador gr-playlist gr-pautaje gr-parrilla \
gr-botonera gr-visor gr-reportes gr-record; do
cp "$ROOT/$TARGET_DIR/$BIN" "$PKG_DIR/usr/local/bin/$BIN"
chmod 755 "$PKG_DIR/usr/local/bin/$BIN"
done
# ── Lanzador principal ────────────────────────────────────────────────────────
echo "[*] Creando lanzador gradio.sh..."
cat > "$PKG_DIR/usr/local/bin/gradio.sh" << 'LAUNCHER'
#!/bin/bash
# Lanzador de G Radio Player — equivalente al run.sh del proyecto fuente
BIN="/usr/local/bin"
HOME_GRADIO="$HOME/.gradio/data"
PIDDIR="$HOME_GRADIO/tmp/pids"
mkdir -p "$PIDDIR"
# URL del relay internet (usada por relay.rs si "relay habilitado" está activo
# en Configuración). Sin esto, relay.rs cae al placeholder wss://relay.example.com/ws
# y jamás llega a registrarse — la conexión remota vía Internet queda rota en silencio.
export GRADIO_RELAY_URL="${GRADIO_RELAY_URL:-wss://relay.gradio.net/ws}"
# ── Matar proceso por PID guardado + búsqueda por nombre ─────────────────────
kill_aux() {
local name="$1"
local pidfile="$PIDDIR/$name.pid"
if [ -f "$pidfile" ]; then
local pid
pid=$(cat "$pidfile" 2>/dev/null)
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
echo " Deteniendo $name (PID $pid)..."
kill "$pid" 2>/dev/null
for i in $(seq 1 6); do
sleep 0.5
kill -0 "$pid" 2>/dev/null || break
done
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null
fi
rm -f "$pidfile"
fi
pkill -x "$name" 2>/dev/null
sleep 0.3
}
# ── Matar instancias previas ──────────────────────────────────────────────────
echo "==> Deteniendo instancias previas (si las hay)..."
kill_aux "comercial-scheduler"
kill_aux "playlist-refill"
sleep 0.5
# ── Crear estructura de directorios ──────────────────────────────────────────
mkdir -p "$HOME_GRADIO/tmp"
mkdir -p "$HOME_GRADIO/comerciales"
mkdir -p "$HOME_GRADIO/eventos-espera"
mkdir -p "$HOME_GRADIO/eventos"
mkdir -p "$HOME_GRADIO/parrilla"
mkdir -p "$HOME_GRADIO/reporte"
mkdir -p "$HOME_GRADIO/panel"
mkdir -p "$HOME/G Radio/inicio-espacio-pub"
mkdir -p "$HOME/G Radio/fin-espacio-pub"
mkdir -p "$HOME/G Radio/comerciales"
# ── Instalar carpeta Time (audios de hora) si aún no existe ──────────────────
if [ ! -d "$HOME_GRADIO/panel/Time" ]; then
echo "==> Instalando carpeta Time en $HOME_GRADIO/panel/..."
cp -r /usr/local/share/radio-player/Time "$HOME_GRADIO/panel/Time"
fi
# ── Instalar contexto IA (opencode) si no existe o está desactualizado ───────
OPENCODE_SRC="/usr/local/share/radio-player/opencode"
OPENCODE_DST="$HOME_GRADIO/opencode"
if [ -d "$OPENCODE_SRC" ] && [ ! -f "$OPENCODE_DST/AGENTS.md" ]; then
echo "==> Instalando contexto IA en $OPENCODE_DST/..."
mkdir -p "$OPENCODE_DST/agents" "$OPENCODE_DST/context"
cp -r "$OPENCODE_SRC/"* "$OPENCODE_DST/"
fi
# ── Instalar skins de ejemplo (solo los que aún no existan) ──────────────────
SKINS_SRC="/usr/local/share/radio-player/skins"
SKINS_DST="$HOME_GRADIO/skins"
if [ -d "$SKINS_SRC" ]; then
mkdir -p "$SKINS_DST"
for skin_dir in "$SKINS_SRC"/*/; do
[ -d "$skin_dir" ] || continue
skin_name=$(basename "$skin_dir")
if [ ! -d "$SKINS_DST/$skin_name" ]; then
echo "==> Instalando skin '$skin_name' en $SKINS_DST/..."
cp -r "$skin_dir" "$SKINS_DST/$skin_name"
fi
done
fi
# ── Migración device → gradio.config (compatibilidad con versiones anteriores)
DEVICE_FILE="$HOME_GRADIO/tmp/device"
CONFIG_FILE="$HOME_GRADIO/tmp/gradio.config"
if [ -f "$DEVICE_FILE" ] && [ ! -f "$CONFIG_FILE" ]; then
echo "==> Migrando device → gradio.config..."
MIX=$(cat "$HOME_GRADIO/mix" 2>/dev/null || echo "3.0")
paste -d"\n" "$DEVICE_FILE" /dev/null > "$CONFIG_FILE"
printf "G Radio\n%s\n" "$MIX" >> "$CONFIG_FILE"
rm -f "$DEVICE_FILE"
fi
# ── Archivos de configuración por defecto ─────────────────────────────────────
[ -f "$HOME_GRADIO/tmp/upvol" ] || echo "90" > "$HOME_GRADIO/tmp/upvol"
[ -f "$HOME_GRADIO/tmp/downvol" ] || echo "20" > "$HOME_GRADIO/tmp/downvol"
[ -f "$HOME_GRADIO/mix" ] || echo "3" > "$HOME_GRADIO/mix"
# ── Limpiar listas temporales (toma la programación de la hora actual) ────────
> "$HOME_GRADIO/tmp/comercialeslist4"
> "$HOME_GRADIO/tmp/eventos-esperalist"
> "$HOME_GRADIO/tmp/eventoslist"
> "$HOME_GRADIO/tmp/playlist4"
echo "==> Listas temporales limpiadas."
# ── Iniciar daemons auxiliares ────────────────────────────────────────────────
echo "==> Iniciando comercial-scheduler..."
"$BIN/comercial-scheduler" &
SCHED_PID=$!
echo "$SCHED_PID" > "$PIDDIR/comercial-scheduler.pid"
echo " PID: $SCHED_PID"
echo "==> Iniciando playlist-refill..."
"$BIN/playlist-refill" &
REFILL_PID=$!
echo "$REFILL_PID" > "$PIDDIR/playlist-refill.pid"
echo " PID: $REFILL_PID"
# ── Watchdog: verifica cada 30s que los daemons siguen vivos ──────────────────
watchdog() {
while true; do
sleep 30
kill -0 "$RADIO_PID" 2>/dev/null || break
if ! kill -0 "$SCHED_PID" 2>/dev/null; then
echo "==> WATCHDOG: comercial-scheduler caído, reiniciando..."
"$BIN/comercial-scheduler" &
SCHED_PID=$!
echo "$SCHED_PID" > "$PIDDIR/comercial-scheduler.pid"
fi
if ! kill -0 "$REFILL_PID" 2>/dev/null; then
echo "==> WATCHDOG: playlist-refill caído, reiniciando..."
"$BIN/playlist-refill" &
REFILL_PID=$!
echo "$REFILL_PID" > "$PIDDIR/playlist-refill.pid"
fi
done
}
# ── Inhibir suspensión / DPMS (ARM: previene congelamiento del main loop GTK4) ──
if command -v systemd-inhibit &>/dev/null; then
echo "==> Inhibiendo idle con systemd-inhibit..."
RUST_LOG=info systemd-inhibit \
--what=idle \
--who="G Radio Player" \
--why="Reproducción de audio en curso" \
--mode=block \
"$BIN/radio-player" "$@" &
RADIO_PID=$!
else
xset s off 2>/dev/null || true
xset -dpms 2>/dev/null || true
xset s noblank 2>/dev/null || true
echo "==> Iniciando radio-player..."
RUST_LOG=info "$BIN/radio-player" "$@" &
RADIO_PID=$!
fi
watchdog &
WATCHDOG_PID=$!
wait "$RADIO_PID"
# ── Limpieza al salir ─────────────────────────────────────────────────────────
echo "==> Cerrando procesos auxiliares..."
kill "$WATCHDOG_PID" 2>/dev/null
kill_aux "comercial-scheduler"
kill_aux "playlist-refill"
rm -f "$PIDDIR/comercial-scheduler.pid" "$PIDDIR/playlist-refill.pid"
echo "==> Terminado."
LAUNCHER
chmod 755 "$PKG_DIR/usr/local/bin/gradio.sh"
# ── Icono ─────────────────────────────────────────────────────────────────────
if [ -f "$ROOT/assets/gradio.png" ]; then
cp "$ROOT/assets/gradio.png" "$PKG_DIR/usr/share/icons/hicolor/48x48/apps/gradio-player.png"
chmod 644 "$PKG_DIR/usr/share/icons/hicolor/48x48/apps/gradio-player.png"
fi
# ── Entrada de escritorio ─────────────────────────────────────────────────────
cat > "$PKG_DIR/usr/share/applications/com.gradio.radio-player.desktop" << DESKTOP
[Desktop Entry]
Type=Application
Name=G Radio Player
GenericName=Radio Automation
Comment=Sistema de automatización de radio profesional
Exec=/usr/local/bin/gradio.sh
Icon=gradio-player
Categories=AudioVideo;Audio;Player;
Terminal=false
StartupNotify=true
Keywords=radio;audio;player;automation;
DESKTOP
# ── DEBIAN/control ────────────────────────────────────────────────────────────
echo "[*] Generando DEBIAN/control..."
INSTALLED_SIZE=$(du -sk "$PKG_DIR/usr" | cut -f1)
cat > "$PKG_DIR/DEBIAN/control" << CONTROL
Package: gradio-player
Version: ${VERSION}
Architecture: ${ARCH}
Maintainer: G Radio <gradio@example.com>
Installed-Size: ${INSTALLED_SIZE}
Depends: libgtk-4-1 (>= 4.6),
libgstreamer1.0-0 (>= 1.20),
libgstreamer-plugins-base1.0-0 (>= 1.20),
gstreamer1.0-plugins-base (>= 1.20),
gstreamer1.0-plugins-good (>= 1.20),
gstreamer1.0-plugins-bad (>= 1.20),
gstreamer1.0-plugins-ugly (>= 1.20),
gstreamer1.0-libav (>= 1.20),
gstreamer1.0-alsa | gstreamer1.0-pulseaudio,
ffmpeg,
libc6 (>= 2.17)
Section: sound
Priority: optional
Description: G Radio Player — sistema de automatización de radio
Reproductor profesional para emisoras de radio con programación automática
de música, comerciales y eventos. Incluye interfaz GTK4, crossfading,
duck de volumen y gestión de parrilla horaria.
.
Componentes: radio-player, comercial-scheduler, playlist-refill,
gr-pautaje, gr-parrilla, gr-botonera, gr-visor, gr-buscador, gr-playlist,
gr-record.
CONTROL
# ── DEBIAN/postinst ───────────────────────────────────────────────────────────
cat > "$PKG_DIR/DEBIAN/postinst" << 'POSTINST'
#!/bin/bash
set -e
# Limpiar entrada de escritorio de versiones antiguas (renombrada a
# com.gradio.radio-player.desktop para que el app_id coincida con el nombre
# del .desktop — requerido para que Wayland resuelva el ícono correctamente).
rm -f /usr/share/applications/gradio-player.desktop
# Actualizar caché de iconos y menú de escritorio
if command -v gtk-update-icon-cache &>/dev/null; then
gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true
fi
if command -v update-desktop-database &>/dev/null; then
update-desktop-database /usr/share/applications 2>/dev/null || true
fi
echo ""
echo "G Radio Player instalado correctamente."
echo "Ejecutar: gradio.sh o buscar 'G Radio Player' en el menú."
POSTINST
chmod 755 "$PKG_DIR/DEBIAN/postinst"
# ── DEBIAN/prerm ──────────────────────────────────────────────────────────────
# No matamos procesos aquí: Linux mantiene el ejecutable anterior en memoria
# hasta que cierre (safe inode replacement). Tener pkill en prerm rompe dpkg
# al actualizar porque el prerm de la versión INSTALADA corre primero — y si
# esa versión tenía pkill -f (bug histórico), mata al propio dpkg.
cat > "$PKG_DIR/DEBIAN/prerm" << 'PRERM'
#!/bin/bash
exit 0
PRERM
chmod 755 "$PKG_DIR/DEBIAN/prerm"
# ── DEBIAN/postrm ─────────────────────────────────────────────────────────────
cat > "$PKG_DIR/DEBIAN/postrm" << 'POSTRM'
#!/bin/bash
# Solo borrar archivos en remove/purge — nunca en upgrade/failed-upgrade.
# Durante un upgrade dpkg instala el nuevo paquete ANTES de correr este
# postrm; si borramos incondicionalmente, eliminamos los binarios recién
# instalados por la versión nueva.
case "$1" in
remove|purge)
for BIN in radio-player comercial-scheduler playlist-refill \
gradio.sh gr-buscador gr-playlist gr-pautaje gr-parrilla \
gr-botonera gr-visor gr-reportes gr-record; do
rm -f "/usr/local/bin/$BIN"
done
rm -rf /usr/local/share/radio-player
rm -f /usr/share/applications/com.gradio.radio-player.desktop
rm -f /usr/share/applications/gradio-player.desktop # limpieza de versiones antiguas
rm -f /usr/share/icons/hicolor/48x48/apps/gradio-player.png
command -v gtk-update-icon-cache &>/dev/null && \
gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true
command -v update-desktop-database &>/dev/null && \
update-desktop-database /usr/share/applications 2>/dev/null || true
;;
esac
POSTRM
chmod 755 "$PKG_DIR/DEBIAN/postrm"
# ── Construir el .deb ─────────────────────────────────────────────────────────
echo "[*] Construyendo paquete .deb..."
dpkg-deb --build --root-owner-group "$PKG_DIR"
DEB_FILE="${PKG_DIR}.deb"
SIZE=$(du -sh "$DEB_FILE" | cut -f1)
echo ""
echo "========================================"
echo " Paquete listo: $DEB_FILE ($SIZE)"
echo "========================================"
echo ""
echo " Instalar: sudo dpkg -i $DEB_FILE"
echo " O con apt: sudo apt install ./$DEB_FILE"
echo ""
echo " Desinstalar: sudo apt remove gradio-player"
echo ""