Serveur EPG (XMLTV)

Un serveur HTTP intégré distinct de Perfect Streamer délivre le XMLTV complet pour un ensemble de chaînes donné. L’endpoint est destiné aux middleware et lecteurs qui stockent le guide localement et ne le rafraîchissent que rarement, en bloc — généralement une à deux fois par jour.

Le serveur et ses clients se configurent dans la section Configuration/EPG/EPG Server.

URL et authentification

Le service est assuré par un serveur HTTP distinct epg-server (pas celui de /data/*). Par défaut, il écoute sur les ports 10444 (HTTP) et 10445 (HTTPS) ; les ports et SSL se configurent sous /config/epg-server.

Routes :

URL

Content-Type

Comportement

GET /xmltv

text/xml

Avec Accept-Encoding: gzip la réponse est compressée à la volée (Content-Encoding: gzip), sinon elle est renvoyée en XML simple.

GET /xmltv.gz

application/octet-stream

Renvoie toujours un flux gzip avec Content-Disposition: inline; filename="xmltv.xml.gz" — pratique pour enregistrer comme fichier.

Trois méthodes d’authentification sont prises en charge :

  • HTTP Digest (recommandé) — compte issu de /config/epg-server/login.

  • Paramètres dans l’URL — ?l=<login>&p=<password> (synonymes : login=…, password=…).

  • Loopback — une requête depuis 127.0.0.1 est traitée anonymement. Pratique pour les scripts déployés sur la même machine.

Avertissement

Un login et un mot de passe dans l’URL finissent dans les logs d’accès du reverse-proxy et dans l’historique du navigateur. Pour la distribution publique du XMLTV, utilisez HTTP Digest ou n’acceptez les requêtes que depuis des adresses privées.

Accès par channel-set

Chaque compte epg-server/login est lié à un seul channel-set issu de /config/epg-channel-set. L’EPG renvoyé ne contient que les chaînes appartenant à ce groupe. Cela permet à une même installation PSS de fournir des XMLTV différents à différents opérateurs/middleware.

Configuration de base dans l’IU :

  1. Dans Configuration/EPG/EPG Channel Sets, créez un groupe de chaînes et affectez-y les chaînes voulues au niveau des sources EPG.

  2. Dans Configuration/EPG/EPG Server Clients, créez un compte et liez-y le groupe de chaînes créé. Sans liaison channel-set, le client reçoit un XMLTV vide.

Restrictions supplémentaires pour un login :

  • ip-addr — si défini et non joker, une requête depuis une autre IP reçoit 403 Forbidden.

  • limit-day — Unix epoch en s, après lequel le compte cesse d’être servi (403 Forbidden). Pratique pour un modèle d’abonnement.

  • pause — désactiver temporairement un login sans le supprimer.

Format de la réponse

Le corps de la réponse est un document XMLTV avec la racine <tv>. La structure suit le schéma XMLTV DTD communément utilisé :

<?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>

Remarques sur les champs :

  • Les attributs source-info-name et source-info-url de la racine <tv> sont alimentés depuis les champs EPG Source Name et EPG Source URL dans Configuration/EPG/EPG Server.

  • Les attributs start et stop sont au format YYYYMMDDhhmmss ±zone (le fuseau horaire est tiré du champ time_zone de la chaîne).

  • Un <programme> peut contenir plusieurs <title>/<desc> pour différentes langues. L’attribut lang est vide quand l’identifiant de langue dans les données EPG sources n’a pas pu être mappé au dictionnaire (l’entrée apparaît tout de même dans la sortie).

  • Les chaînes avec un channel_id en conflit (lorsque le même id provient de plusieurs sources) sont listées une seule fois, les sources restantes sont ignorées avec un avertissement dans le journal du serveur.

  • Seuls les événements avec stop_time >= now sont inclus dans la sortie.

En-têtes HTTP

Le serveur envoie toujours :

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

Pour /xmltv en plus — Content-Encoding: gzip lorsque Accept-Encoding: gzip est présent dans la requête. Pour /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".

L’interdiction du cache côté client est intentionnelle : le XMLTV change à chaque import EIT ou rafraîchissement de source XMLTV externe, et le lecteur ne doit pas conserver des données obsolètes indéfiniment. Un cache edge (nginx) reste tout à fait admissible — voir la section performance ci-dessous.

Cache serveur et sa purge

Le XMLTV prêt est mis en cache dans la mémoire du processus PSS :

  • Une entrée par channel-set ; les deux variantes du corps (brute et gzip) sont stockées — les requêtes répétées avec Accept-Encoding: gzip ou /xmltv.gz ne recompressent pas les données.

  • Chaque entrée est étiquetée avec un compteur update-time. Toute mise à jour EPG (import EIT, rafraîchissement de source XMLTV) incrémente le compteur, et le cache est reconstruit à la requête suivante.

Purge forcée du cache :

POST /xmltv/reset-cache

La route est servie par le serveur admin (port 43971/43981), pas par epg-server. Corps vide ; la réponse est 200 OK avec une enveloppe JSON.

Codes de réponse HTTP

Code

Condition

200 OK

Requête traitée. Le corps est un document XMLTV (éventuellement un <tv></tv> vide en cas de panne transitoire de la BD).

401 Unauthorized

Ni Digest ni les paramètres l/p n’ont passé le contrôle (pour les requêtes non loopback).

403 Forbidden

Le login existe mais la requête ne vient pas d’une IP autorisée, ou limit-day a expiré.

404 Not Found

Toute URL autre que /xmltv et /xmltv.gz.

405 Method Not Allowed

Méthode autre que GET.

Le corps de l’erreur est une enveloppe JSON de format fixe :

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

Performance et mise à l’échelle

Cache serveur

Le cache serveur sert les requêtes répétées sur un même channel-set sans accéder à SQLite — en copiant le corps déjà prêt.

Construire le XMLTV « depuis zéro » (cache miss) est plus coûteux : un SELECT distinct sur channel_name par chaîne, et sur event_text et event_rating par événement. Temps de construction indicatifs :

Taille de la sortie

Cache hit

Cache miss (build)

100 chaînes / jour

dizaines de ms

~0,5–1 s

500 chaînes / jour

~50 ms

2–5 s

1000+ chaînes / semaine

~100–300 ms

5–15 s

Pour la plupart des middleware, il est acceptable de récupérer le XMLTV toutes les quelques heures ou une fois par jour.

Quand un reverse-proxy externe (nginx) est nécessaire

Contrairement à /data/epg/channel (réponses JSON courtes), le XMLTV est un seul gros document par channel-set, idéal pour un cache edge :

  • Des dizaines à des centaines de clients par channel-set — le cache interne de PSS suffit généralement s’ils interrogent le XMLTV toutes les heures à toutes les 24 h.

  • Milliers de clients simultanés — un reverse-proxy avec cache est recommandé. Servir un fichier XMLTV à des centaines/milliers de requêtes est avant tout une charge réseau (centaines de Ko à quelques Mo par réponse) qu’il est préférable de retirer à PSS.

  • Distribution géographiquement répartie — un CDN/cache edge est indispensable quel que soit le nombre de clients.

PSS renvoie Cache-Control: no-cache ; il faut donc indiquer explicitement à nginx d’ignorer l’en-tête upstream et de tenir son propre TTL.

Exemple de configuration 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;
    }
}

Explications et recommandations

  • TTL ``proxy_cache_valid 200 30m`` — le XMLTV change rarement plus souvent que toutes les 30 minutes. Si la synchronisation avec les sources est horaire ou moins fréquente, on peut monter à 1 heure et plus ; si la fraîcheur après POST /xmltv/reset-cache importe, baissez-le.

  • ``proxy_cache_lock_timeout 60s`` — augmenté par rapport à /data/epg/channel (où 5 s est habituel), car la construction du XMLTV pour un grand channel-set est plus longue.

  • Une ``keys_zone`` dédiée — même sur une grande installation, les clés XMLTV uniques se comptent sur les doigts (nombre de comptes × channel-set) ; 8 Mo suffisent largement. max_size se dimensionne selon le volume XMLTV, pas selon le nombre de clés.

  • inutile d’activer gzip côté nginx : pour /xmltv.gz la réponse est déjà compressée et pour /xmltv PSS répond lui-même en gzip si Accept-Encoding: gzip est présent.

  • Terminaison HTTPS sur nginx offre de meilleures performances en cas de nombreux handshakes TLS simultanés.