Skip to content

EPG

Add and refresh guide sources, configure caching, size limits, and time zones, and serve a merged XMLTV document to players.

Updated View as Markdown

Kiln downloads the XMLTV sources you enable, matches their programs to your channels, and merges the results into one guide. Players only need /v1/epg.xml.gz, regardless of how many sources are behind it.

How it works

  • Sources are managed on the EPG page of the admin console and stored in SQLite. No config file edit is required.
  • Every built-in preset source ships disabled. Enable at least one, or add a custom source, before you expect any data.
  • [[epg.sources]] in the config file seeds the database once, and only while no source rows exist. After that the database wins.
  • There is no EPG master switch. Whether a guide is served depends solely on whether any source is enabled. The lite build has no EPG at all, and refuses to start when the config carries an enabled EPG source.
kiln.tomltoml
[epg]
cache = true
cache_dir = "./data/epg"
refresh_interval_min = 360
max_refresh_concurrency = 0
max_source_bytes = 67108864
default_timezone = "UTC"
serve_timezone = "keep"

Enable a source

Turn on a preset, or add a custom source pointing at a reachable XMLTV URL. Both .xml and .xml.gz are supported.

Run the first refresh

Select Refresh now. Kiln fetches every enabled source immediately and reports channel counts, program counts, and failures for each source.

Confirm the matches

On the channel pages, pick the guide channel for anything listed as suggested or unmatched. Only matched channels reach the published guide, see Channels.

Cache modes

cache and cache_dir together select one of three behaviors for the raw XMLTV payloads.

cache = true with cache_dir pointing at a directory, {data_dir}/epg by default.

Each source is stored as a JSON header plus the raw body, with a length and SHA-256 checksum, written to a temporary file and atomically renamed into place. The cache survives restarts, feeds If-None-Match and If-Modified-Since on the next fetch so an unchanged source costs a 304, and provides the fallback body when a fetch or parse fails.

cache = true with cache_dir = "memory" (:memory: is equivalent).

Conditional requests and failure fallback behave exactly as on disk, but nothing is written to the filesystem and every source is downloaded in full again after a restart. Use it on read-only root filesystems, or wherever you do not want large files accumulating in the data directory.

cache = false.

Raw payloads are not retained, so there are no conditional requests and no fallback. On top of that, every request to /v1/epg.xml or /v1/epg.xml.gz synchronously refreshes all sources before rendering the response.

Refreshing

  • refresh_interval_min defaults to 360. Kiln refreshes once at startup, then on that interval.
  • max_refresh_concurrency = 0 refreshes every source in parallel. A positive value caps how many sources are in flight at once.
  • Under server.resource_mode = "auto" (the default), both 0 and oversized values are clamped from the detected CPU and memory limits, and any constrained profile pins it to 1. constrained also pins it to 1; performance applies no adaptation at all. See the configuration reference.

For a manual run, call POST /v1/admin/epg/refresh with the refresh scope. It returns 409 when no source is enabled. Sources refresh independently, so one failing source does not block the others, and the response carries the latest per-source status.

Size limit

max_source_bytes defaults to 64 MiB (67108864) and applies to the decompressed XMLTV body.

  • An oversized download fails the round. Existing data stays available and is flagged as stale rather than being replaced by a truncated body.
  • A cached entry that already exceeds the limit is discarded and never used as a fallback.
  • Under auto the limit is lowered with memory: below 256 MiB of memory it drops to 4 MiB, and other constrained profiles land somewhere between 4 MiB and 64 MiB depending on available memory.

Time zones

  • Every source carries its own IANA time zone, used when parsing timestamps that lack an explicit offset. Custom sources created in the console default to UTC.
  • default_timezone is the fallback for sources that declare no zone. It must be a valid IANA name; an invalid value fails config validation at startup.
  • serve_timezone currently accepts only keep: the published guide preserves each program’s original UTC offset instead of converting everything to one serving zone. Any other value fails validation.

Per-source egress

Each source has its own egress setting that plugs into the global outbound routing.

Value Meaning
direct Default. Connect directly, ignoring outbound routing rules
auto Follow the global outbound routing and let the rules decide
A proxy id Always use that proxy, ignoring the rules

What gets published

Path Auth Notes
GET /v1/epg.xml None The merged XMLTV document
GET /v1/epg.xml.gz None The same document, gzipped
GET /v1/logo/{id} None Channel logo, fetched upstream when the built-in logo table matches
  • Only matched channels are published. Kiln rewrites their channel IDs and the associated programs to its own channel IDs, so tvg-id in the M3U matches the guide automatically.
  • /v1/playlist.m3u and the playlist behind an access token carry x-tvg-url on the #EXTM3U line, pointing at /v1/epg.xml.gz.
  • With no source enabled, both endpoints return a valid but empty XMLTV document rather than a 404.
  • The gzipped output is cached in memory and invalidated whenever source data or the channel list changes. Payloads larger than a quarter of max_source_bytes (capped at 8 MiB) are returned without being cached.
  • The logo endpoint normalizes the channel’s epg_name (or its title when empty), looks it up in the built-in logo table, and tries each candidate URL in order. Images are limited to 2 MiB and served with a one-hour public cache and an X-Kiln-Logo-Source header. The endpoint returns 404 when there is no candidate and 502 when candidates exist but every download fails. Logo requests follow the channel’s outbound route.

Admin endpoints

Method and path Scope
GET /v1/admin/epg/presets, /v1/admin/epg/sources, /v1/admin/epg/matches read
POST /v1/admin/epg/sources, PUT /v1/admin/epg/sources/{id} write
DELETE /v1/admin/epg/sources/{id} delete
POST /v1/admin/epg/refresh refresh

Updates and deletes require an If-Match revision and return 409 on conflict. Deleting a preset source hides it rather than removing it permanently. Full details are in the API reference.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close