Servidor EPG (XMLTV)

Um servidor HTTP integrado separado do Perfect Streamer entrega o XMLTV completo para um dado conjunto de canais. O endpoint é destinado a middleware e players que mantêm o guia localmente e o atualizam com pouca frequência, como arquivo inteiro — normalmente uma ou duas vezes por dia.

O servidor e seus clientes são configurados em Configuration/EPG/EPG Server.

URL e autenticação

O serviço é atendido por um servidor HTTP separado epg-server (não o de /data/*). Por padrão, ele escuta nas portas 10444 (HTTP) e 10445 (HTTPS); portas e SSL são configurados em /config/epg-server.

Rotas:

URL

Content-Type

Comportamento

GET /xmltv

text/xml

Com Accept-Encoding: gzip a resposta é comprimida em tempo real (Content-Encoding: gzip); caso contrário, é entregue como XML sem compressão.

GET /xmltv.gz

application/octet-stream

Sempre retorna um fluxo gzip com Content-Disposition: inline; filename="xmltv.xml.gz" — prático para salvar como arquivo.

São suportados três métodos de autenticação:

  • HTTP Digest (preferencial) — conta em /config/epg-server/login.

  • Parâmetros na URL — ?l=<login>&p=<password> (sinônimos: login=…, password=…).

  • Loopback — uma solicitação de 127.0.0.1 é tratada anonimamente. Útil para scripts implantados na mesma máquina.

Aviso

Um login e senha na URL acabam nos access-logs do reverse-proxy e no histórico do navegador. Para distribuição pública do XMLTV, use HTTP Digest ou aceite solicitações apenas de endereços privados.

Acesso por channel-set

Cada conta epg-server/login está vinculada a um único channel-set em /config/epg-channel-set. O EPG retornado contém apenas os canais desse grupo. Isso permite que uma mesma instalação do PSS forneça XMLTV distintos para diferentes operadoras/middleware.

Configuração básica na IU:

  1. Em Configuration/EPG/EPG Channel Sets, crie um grupo de canais e atribua os canais desejados nas fontes EPG.

  2. Em Configuration/EPG/EPG Server Clients, crie uma conta e vincule o grupo criado a ela. Sem vínculo de channel-set, o cliente recebe um XMLTV vazio.

Restrições adicionais para um login:

  • ip-addr — se definido e não for curinga, uma solicitação de outro IP recebe 403 Forbidden.

  • limit-day — Unix epoch em s, após o qual a conta deixa de ser atendida (403 Forbidden). Útil para um modelo de assinatura.

  • pause — desativar temporariamente um login sem excluí-lo.

Formato da resposta

O corpo da resposta é um documento XMLTV com raiz <tv>. A estrutura segue o 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 os campos:

  • Os atributos source-info-name e source-info-url da raiz <tv> são preenchidos a partir dos campos EPG Source Name e EPG Source URL em Configuration/EPG/EPG Server.

  • Os atributos start e stop seguem o formato YYYYMMDDhhmmss ±zone (o fuso horário é obtido do campo time_zone do canal).

  • Um <programme> pode conter vários <title>/<desc> para idiomas diferentes. O atributo lang fica vazio quando o identificador de idioma dos dados EPG de origem não pôde ser mapeado no dicionário (a entrada ainda aparece na saída).

  • Canais com channel_id em conflito (quando o mesmo id chega de várias fontes) aparecem uma vez; as fontes restantes são ignoradas com um aviso no log do servidor.

  • Somente eventos com stop_time >= now entram na saída.

Cabeçalhos HTTP

O servidor sempre envia:

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

Para /xmltv, adicionalmente — Content-Encoding: gzip quando Accept-Encoding: gzip está presente na solicitação. Para /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".

A proibição do cache do lado do cliente é intencional: o XMLTV muda a cada importação EIT ou refresh de fontes XMLTV externas, e o player não deve manter dados desatualizados indefinidamente. Um cache edge (nginx), porém, é perfeitamente aceitável — veja a seção sobre desempenho abaixo.

Cache do servidor e sua limpeza

O XMLTV pronto é armazenado em cache na memória do processo PSS:

  • Uma entrada por channel-set; ambas as variantes do corpo (raw e gzip) são guardadas — solicitações repetidas com Accept-Encoding: gzip ou /xmltv.gz não recomprimem os dados.

  • Cada entrada é marcada com um contador update-time. Qualquer atualização de EPG (importação EIT, refresh de fonte XMLTV) incrementa o contador, e o cache é reconstruído na próxima solicitação.

Limpeza forçada do cache:

POST /xmltv/reset-cache

A rota é atendida pelo servidor admin (porta 43971/43981), não pelo epg-server. Corpo vazio; a resposta é 200 OK com um envelope JSON.

Códigos de resposta HTTP

Código

Condição

200 OK

Solicitação processada. O corpo é um documento XMLTV (possivelmente um <tv></tv> vazio em caso de falha transitória da BD).

401 Unauthorized

Nem Digest nem os parâmetros l/p passaram na verificação (para solicitações não loopback).

403 Forbidden

O login existe, mas a solicitação não vem de um IP permitido, ou limit-day expirou.

404 Not Found

Qualquer URL além de /xmltv e /xmltv.gz.

405 Method Not Allowed

Método diferente de GET.

O corpo do erro é um envelope JSON de formato fixo:

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

Desempenho e escalabilidade

Cache do servidor

O cache do servidor atende solicitações repetidas a um mesmo channel-set sem acessar o SQLite — copiando o corpo já pronto.

Construir o XMLTV «do zero» (cache miss) é mais caro: para cada canal é feito um SELECT separado em channel_name e, para cada evento, em event_text e event_rating. Tempos de construção aproximados:

Tamanho da saída

Cache hit

Cache miss (build)

100 canais / dia

dezenas de ms

~0,5–1 s

500 canais / dia

~50 ms

2–5 s

1000+ canais / semana

~100–300 ms

5–15 s

Para a maioria dos middleware é aceitável buscar o XMLTV a cada algumas horas ou uma vez por dia.

Quando é preciso um reverse-proxy externo (nginx)

Ao contrário de /data/epg/channel (respostas JSON curtas), o XMLTV é um único documento grande por channel-set, ideal para um cache edge:

  • Dezenas a centenas de clientes por channel-set — o cache interno do PSS costuma ser suficiente se eles consultam o XMLTV de hora em hora ou uma vez por dia.

  • Milhares de clientes simultâneos — recomenda-se um reverse-proxy com cache. Servir um arquivo XMLTV a centenas/milhares de solicitações é, antes de tudo, uma carga de rede (centenas de KB a alguns MB por resposta) que é melhor retirar do PSS.

  • Entrega geograficamente distribuída — um CDN/cache edge é indispensável independentemente do número de clientes.

O PSS devolve Cache-Control: no-cache, portanto é preciso dizer explicitamente ao nginx para ignorar o cabeçalho do upstream e manter o próprio TTL.

Exemplo de configuração 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;
    }
}

Explicações e recomendações

  • TTL ``proxy_cache_valid 200 30m`` — o XMLTV raramente muda com frequência maior que a cada meia hora. Se a sincronização com as fontes ocorre uma vez por hora ou menos, dá para elevar para 1 hora ou mais; se a frescura após POST /xmltv/reset-cache importa, reduza.

  • ``proxy_cache_lock_timeout 60s`` — aumentado em relação a /data/epg/channel (onde 5 segundos é o usual), porque construir o XMLTV de um channel-set grande leva mais tempo.

  • Uma ``keys_zone`` dedicada — mesmo em uma instalação grande, as chaves XMLTV únicas são poucas (número de contas × channel-set); 8 MB são suficientes. max_size é dimensionado pelo volume de XMLTV, não pela quantidade de chaves.

  • gzip do lado do nginx é desnecessário: para /xmltv.gz a resposta já vem comprimida, e para /xmltv o próprio PSS responde com gzip-encoded quando Accept-Encoding: gzip está presente.

  • Terminação HTTPS no nginx oferece melhor desempenho com muitos handshakes TLS simultâneos.