Aggiunge overlay server-side burn-in e stabilizza diretta live.

Tabellone, badge e brand sono ricodificati nel flusso _air via OverlayRelay;
sync punteggio HTTP, volume condiviso rails/sidekiq, qualità 720p migliorata
e badge CONNECTING in ripresa da pausa.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-03 23:55:17 +02:00
parent fd225fbadd
commit ab9cb02083
58 changed files with 2423 additions and 242 deletions

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Esegue un task Rails in produzione (usato da crontab).
set -euo pipefail
ROOT="${MATCHLIVETV_INFRA:-/opt/matchlivetv/infra}"
cd "$ROOT"
exec docker compose -f docker-compose.prod.yml --env-file .env exec -T rails \
bundle exec rails "$@"

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Ridimensiona capacità Garage (layout assign + apply). Uso: garage_set_capacity.sh 85G
set -euo pipefail
CAPACITY="${1:-}"
RESERVE_GB="${GARAGE_RESERVE_GB:-20}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}"
ENV_FILE="${ENV_FILE:-${ROOT}/.env}"
GARAGE_CONTAINER="${GARAGE_CONTAINER:-}"
if [ -z "$CAPACITY" ]; then
avail="$(df -BG "${ROOT}" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || echo 0)"
if [ "$avail" -lt 1 ]; then
avail="$(df -BG / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')"
fi
cap_gb=$((avail - RESERVE_GB))
[ "$cap_gb" -lt 10 ] && cap_gb=10
CAPACITY="${cap_gb}G"
echo "Spazio disponibile ~${avail}G, riservo ${RESERVE_GB}G → capacità Garage ${CAPACITY}"
fi
cd "$ROOT"
if [ -z "$GARAGE_CONTAINER" ]; then
GARAGE_CONTAINER="$(docker ps --format '{{.Names}}' | grep -E 'garage-1$' | head -1)"
fi
[ -n "$GARAGE_CONTAINER" ] || { echo "Container garage non trovato"; exit 1; }
node_id="$(docker exec "$GARAGE_CONTAINER" /garage status 2>/dev/null | awk '/^[0-9a-f]{16,}/{print $1; exit}')"
[ -n "$node_id" ] || { echo "Node id non trovato"; exit 1; }
echo "Node: $node_id → capacità $CAPACITY"
docker exec "$GARAGE_CONTAINER" /garage layout assign -z dc1 -c "$CAPACITY" "$node_id"
ver="$(docker exec "$GARAGE_CONTAINER" /garage layout show 2>/dev/null | awk '/layout version:/{print $NF; exit}')"
next=$((ver + 1))
docker exec "$GARAGE_CONTAINER" /garage layout apply --version "$next"
docker exec "$GARAGE_CONTAINER" /garage layout show

View File

@@ -1,13 +1,33 @@
#!/bin/sh
# Generate offline slate MP4 for MediaMTX alwaysAvailable (pausa / attesa segnale)
# Slate MP4 per MediaMTX alwaysAvailable (pausa / attesa / perdita rete).
# H.264 1280x720 30fps + AAC 48kHz mono (allineato all'app Android).
set -e
OUT_DIR="$(dirname "$0")/../slates"
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
OUT_DIR="$(cd "$(dirname "$0")/../slates" && pwd)"
BRAND_IMAGE="${BRAND_IMAGE:-$ROOT/brand/CopertinaCanale_6.png}"
OUT="$OUT_DIR/offline.mp4"
if [ ! -f "$BRAND_IMAGE" ]; then
echo "Manca immagine brand: $BRAND_IMAGE" >&2
exit 1
fi
mkdir -p "$OUT_DIR"
docker run --rm -v "$OUT_DIR:/out" linuxserver/ffmpeg:latest \
-f lavfi -i color=c=0x1a1a1a:s=1280x720:r=30 \
-vf "drawtext=text='Match Live TV':fontcolor=white:fontsize=48:x=(w-text_w)/2:y=(h-text_h)/2-40,drawtext=text='Trasmissione in pausa':fontcolor=0xaaaaaa:fontsize=28:x=(w-text_w)/2:y=(h-text_h)/2+20" \
-f lavfi -i sine=frequency=440:sample_rate=48000 \
-t 30 -c:v libx264 -pix_fmt yuv420p -g 30 -preset fast \
-c:a aac -b:a 128k -y /out/offline.mp4
echo "Created $OUT"
BRAND_DIR="$(cd "$(dirname "$BRAND_IMAGE")" && pwd)"
BRAND_FILE="$(basename "$BRAND_IMAGE")"
docker run --rm \
-v "$OUT_DIR:/out" \
-v "$BRAND_DIR:/brand:ro" \
linuxserver/ffmpeg:latest \
-loop 1 -framerate 30 -i "/brand/$BRAND_FILE" \
-f lavfi -i anullsrc=r=48000:cl=mono \
-filter:v "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=0x0a0a0e" \
-map 0:v -map 1:a \
-t 30 -c:v libx264 -pix_fmt yuv420p -profile:v baseline -level 3.1 \
-x264-params "keyint=30:min-keyint=30:scenecut=0:bframes=0" \
-g 30 -keyint_min 30 -force_key_frames "expr:gte(t,n_forced*1)" \
-preset fast \
-c:a aac -b:a 128k -ac 1 -shortest -y /out/offline.mp4
echo "Created $OUT from $BRAND_IMAGE"

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Installa crontab replay su server produzione (utente corrente).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
RUNNER="${ROOT}/scripts/cron/run_rails_task.sh"
LOG_DIR="${MATCHLIVETV_LOG_DIR:-/opt/matchlivetv/log}"
LOG_FILE="${LOG_DIR}/cron-replay.log"
chmod +x "$RUNNER"
mkdir -p "$LOG_DIR"
MARKER="# matchlivetv-replay-cron"
CRON_BLOCK="${MARKER}
0 3 * * * ${RUNNER} recordings:purge_expired >> ${LOG_FILE} 2>&1
0 8 * * * ${RUNNER} recordings:expiry_warnings >> ${LOG_FILE} 2>&1
"
existing="$(crontab -l 2>/dev/null || true)"
if echo "$existing" | grep -q "$MARKER"; then
echo "Crontab replay già presente, aggiorno..."
echo "$existing" | awk -v block="$CRON_BLOCK" '
BEGIN { skip=0 }
/# matchlivetv-replay-cron/ { skip=1; next }
skip && /^[^#]/ && $0 !~ /^$/ { skip=0 }
skip { next }
{ print }
END { print block }
' | crontab -
else
(echo "$existing"; echo "$CRON_BLOCK") | crontab -
fi
echo "Crontab installato:"
crontab -l | grep -A3 "$MARKER"

View File

@@ -14,6 +14,13 @@ bash "${ROOT}/scripts/ensure_garage_prod_config.sh"
echo "Avvio Garage..."
docker compose -f docker-compose.prod.yml --env-file .env up -d garage
avail_gb="$(df -BG / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')"
reserve="${GARAGE_RESERVE_GB:-20}"
cap_gb=$((avail_gb - reserve))
[ "$cap_gb" -lt 10 ] && cap_gb=10
export GARAGE_NODE_CAPACITY="${GARAGE_NODE_CAPACITY:-${cap_gb}G}"
echo "Capacità nodo Garage: ${GARAGE_NODE_CAPACITY} (disco ~${avail_gb}G, riserva ${reserve}G)"
bash "${ROOT}/scripts/setup_garage_replays.sh"
echo "Riavvio Rails e Sidekiq..."

View File

@@ -46,7 +46,8 @@ ensure_layout() {
exit 1
fi
echo "Node id: $node_id"
gexec layout assign -z dc1 -c 1G "$node_id" 2>/dev/null || true
local cap="${GARAGE_NODE_CAPACITY:-1G}"
gexec layout assign -z dc1 -c "$cap" "$node_id" 2>/dev/null || true
gexec layout apply --version 1 2>/dev/null || gexec layout apply 2>/dev/null || true
}