Servidor EPG (XMLTV)

Un servidor HTTP integrado e independiente de Perfect Streamer devuelve el XMLTV completo para un conjunto dado de canales. El endpoint está pensado para middleware y reproductores que guardan la guía localmente y la actualizan con poca frecuencia, como archivo completo — normalmente una o dos veces al día.

El servidor y sus clientes se configuran en Configuration/EPG/EPG Server.

URL y autenticación

El servicio lo ofrece un servidor HTTP independiente epg-server (no el que atiende /data/*). Por defecto escucha en los puertos 10444 (HTTP) y 10445 (HTTPS); los puertos y SSL se configuran en /config/epg-server.

Rutas:

URL

Content-Type

Comportamiento

GET /xmltv

text/xml

Con Accept-Encoding: gzip la respuesta se comprime al vuelo (Content-Encoding: gzip); en caso contrario, se entrega como XML sin comprimir.

GET /xmltv.gz

application/octet-stream

Siempre devuelve un flujo gzip con Content-Disposition: inline; filename="xmltv.xml.gz" — práctico para guardarlo como archivo.

Se admiten tres métodos de autenticación:

  • HTTP Digest (preferido) — cuenta de /config/epg-server/login.

  • Parámetros en la URL — ?l=<login>&p=<password> (sinónimos: login=…, password=…).

  • Loopback — una solicitud desde 127.0.0.1 se trata como anónima. Útil para scripts implantados en la misma máquina.

Advertencia

Un login y una contraseña en la URL acaban en los access-logs del reverse-proxy y en el historial del navegador. Para distribución pública de XMLTV utilice HTTP Digest o acepte solicitudes únicamente desde direcciones privadas.

Acceso por channel-set

Cada cuenta epg-server/login está vinculada a un único channel-set de /config/epg-channel-set. El EPG devuelto contiene solo los canales del grupo indicado. Esto permite que una misma instalación de PSS sirva XMLTV distintos a distintos operadores/middleware.

Configuración básica en la UI:

  1. En Configuration/EPG/EPG Channel Sets cree un grupo de canales y asigne los canales deseados en las fuentes EPG.

  2. En Configuration/EPG/EPG Server Clients cree una cuenta y vincúlela al grupo creado. Sin vincular un channel-set, el cliente recibe un XMLTV vacío.

Restricciones adicionales para un login:

  • ip-addr — si está definido y no es un comodín, una solicitud desde otra IP recibe 403 Forbidden.

  • limit-day — Unix epoch en s, después del cual la cuenta deja de ser atendida (403 Forbidden). Útil para un modelo de suscripción.

  • pause — desactivar temporalmente un login sin eliminarlo.

Formato de la respuesta

El cuerpo de la respuesta es un documento XMLTV con raíz <tv>. La estructura sigue el esquema XMLTV DTD habitual:

<?xml version="1.0" encoding="utf-8"?>
<tv source-info-name="..." source-info-url="...">
  <channel id="channel.one">
    <display-name lang="en">Channel One</display-name>
    <icon src="https://.../channel-one.png"/>
  </channel>
  ...
  <programme start="20260504060000 +0300"
             stop ="20260504070000 +0300"
             channel="channel.one">
    <title lang="en">Morning News</title>
    <desc  lang="en">Overview of the day's events</desc>
    <rating system="MPAA"><value>PG</value></rating>
  </programme>
  ...
</tv>

Notas sobre los campos:

  • Los atributos source-info-name y source-info-url de la raíz <tv> se completan con los campos EPG Source Name y EPG Source URL de Configuration/EPG/EPG Server.

  • Los atributos start y stop siguen el formato YYYYMMDDhhmmss ±zone (la zona horaria se toma del campo time_zone del canal).

  • Un <programme> puede contener varios <title>/<desc> en distintos idiomas. El atributo lang queda vacío cuando el identificador de idioma del EPG original no se pudo asignar al diccionario (la entrada igualmente aparece en la salida).

  • Los canales con un channel_id en conflicto (si el mismo id procede de varias fuentes) se muestran una sola vez; las fuentes restantes se omiten con una advertencia en el log del servidor.

  • Solo se incluyen en la salida los eventos con stop_time >= now.

Encabezados HTTP

El servidor siempre envía:

Cache-Control: no-cache, no-store, must-revalidate
Pragma:        no-cache
Expires:       0
Connection:    close

Para /xmltv adicionalmente — Content-Encoding: gzip cuando Accept-Encoding: gzip está presente en la solicitud. Para /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".

El veto al caché del cliente es intencional: el XMLTV cambia con cada importación EIT o refresco de fuentes XMLTV externas, y el reproductor no debe conservar datos obsoletos de forma indefinida. Una caché edge (nginx), sin embargo, es perfectamente aceptable — véase la sección sobre rendimiento más abajo.

Caché del servidor y su vaciado

El XMLTV listo se almacena en la memoria del proceso PSS:

  • Una entrada por cada channel-set; se guardan ambas variantes del cuerpo (cruda y gzip) — las solicitudes repetidas con Accept-Encoding: gzip o /xmltv.gz no recomprimen los datos.

  • Cada entrada lleva un contador update-time. Cualquier actualización de EPG (importación EIT, refresco de fuente XMLTV) incrementa el contador y la caché se reconstruye en la siguiente solicitud.

Vaciado forzado de la caché:

POST /xmltv/reset-cache

La ruta la atiende el servidor admin (puerto 43971/43981), no el epg-server. Cuerpo vacío; la respuesta es 200 OK con un envoltorio JSON.

Códigos de respuesta HTTP

Código

Condición

200 OK

Solicitud procesada. El cuerpo es un documento XMLTV (posiblemente un <tv></tv> vacío ante un fallo transitorio de la BD).

401 Unauthorized

Ni Digest ni los parámetros l/p superaron la validación (para solicitudes no loopback).

403 Forbidden

El login existe pero la solicitud no proviene de una IP permitida, o ha expirado limit-day.

404 Not Found

Cualquier URL distinta de /xmltv y /xmltv.gz.

405 Method Not Allowed

Método distinto de GET.

El cuerpo del error es un envoltorio JSON de formato fijo:

{"status": 401, "message": "Unauthorized"}

Rendimiento y escalado

Caché del servidor

La caché del servidor atiende las solicitudes repetidas sobre un mismo channel-set sin acceder a SQLite — copiando el cuerpo ya preparado.

Construir XMLTV «desde cero» (cache miss) es más costoso: por cada canal se hace un SELECT aparte por channel_name, y por cada evento por event_text y event_rating. Tiempos de construcción aproximados:

Tamaño de salida

Cache hit

Cache miss (build)

100 canales / día

decenas de ms

~0,5–1 s

500 canales / día

~50 ms

2–5 s

1000+ canales / semana

~100–300 ms

5–15 s

Para la mayoría de los middleware es aceptable solicitar XMLTV cada pocas horas o una vez al día.

Cuándo se necesita un reverse-proxy externo (nginx)

A diferencia de /data/epg/channel (respuestas JSON cortas), XMLTV es un único documento grande por channel-set, ideal para una caché edge:

  • Decenas o cientos de clientes por channel-set — la caché interna de PSS suele bastar si consultan el XMLTV cada hora o cada día.

  • Miles de clientes simultáneos — se recomienda un reverse-proxy con caché. Servir un archivo XMLTV a cientos/miles de solicitudes es, ante todo, una carga de red (cientos de KB a unos pocos MB por respuesta) que conviene quitar de PSS.

  • Distribución geográficamente repartida — un CDN/caché edge es ineludible sin importar el número de clientes.

PSS devuelve Cache-Control: no-cache, por lo que hay que indicarle explícitamente a nginx que ignore la cabecera del upstream y mantenga su propio TTL.

Ejemplo de configuración nginx

# /etc/nginx/conf.d/pss-xmltv.conf

proxy_cache_path /var/cache/nginx/pss-xmltv
                 levels=1:2
                 keys_zone=pss_xmltv:8m
                 max_size=4g
                 inactive=2h
                 use_temp_path=off;

upstream pss_epg {
    server 127.0.0.1:10444;
    keepalive 16;
}

server {
    listen 80;
    # listen 443 ssl http2;     # SSL termination makes sense here
    server_name epg-files.example.com;

    # Do not enable gzip on: /xmltv.gz is already compressed, and /xmltv
    # arrives gzip-encoded from PSS — re-compressing is pointless.

    location ~ ^/xmltv(\.gz)?$ {
        proxy_pass http://pss_epg;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # PSS returns no-cache; force-cache it at the edge.
        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        proxy_hide_header   Cache-Control;
        proxy_hide_header   Pragma;
        proxy_hide_header   Expires;

        # Cache key = full URL including query (login/password in /xmltv?l=...&p=...)
        # produce distinct keys for different accounts with different channel-sets.
        proxy_cache_key     "$scheme$host$request_uri";

        proxy_cache         pss_xmltv;
        proxy_cache_valid   200      30m;       # XMLTV changes infrequently
        proxy_cache_valid   401 403  1m;
        proxy_cache_lock           on;          # coalesce cache misses
        proxy_cache_lock_timeout   60s;         # XMLTV build may take seconds
        proxy_cache_use_stale      error timeout updating
                                   http_500 http_502 http_503;

        # Large buffer: XMLTV for a big channel-set can reach
        # several megabytes.
        proxy_buffering      on;
        proxy_buffers        16 256k;
        proxy_buffer_size    256k;
        proxy_busy_buffers_size 1m;

        # Let the client cache for a short period.
        add_header Cache-Control "public, max-age=600";
        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Explicaciones y recomendaciones

  • TTL ``proxy_cache_valid 200 30m`` — el XMLTV rara vez cambia con mayor frecuencia que cada media hora. Si la sincronización con las fuentes es horaria o menos frecuente, puede subirse a 1 hora o más; si importa la frescura tras POST /xmltv/reset-cache, redúzcalo.

  • ``proxy_cache_lock_timeout 60s`` — incrementado respecto a /data/epg/channel (donde lo habitual son 5 segundos), porque construir el XMLTV de un channel-set grande lleva más tiempo.

  • Una ``keys_zone`` dedicada — incluso en una instalación grande, las claves XMLTV únicas son pocas (número de cuentas × channel-set); 8 MB sobran. max_size se dimensiona por el volumen de XMLTV, no por el número de claves.

  • gzip en el lado de nginx no hace falta: para /xmltv.gz la respuesta ya está comprimida y para /xmltv PSS responde directamente con gzip-encoded si está presente Accept-Encoding: gzip.

  • Terminación HTTPS en nginx ofrece mejor rendimiento ante muchos handshakes TLS simultáneos.