Skip to content

Plex Live TV / Threadfin EPG

Plex DVR in the tv namespace pulls IPTV streams from two Threadfin instances (HDHomeRun emulators):

  • threadfin — WebTV M3U source
  • threadfin-bis — BlueTV M3U source

Each Threadfin instance ingests a large source XMLTV feed (~13 700 channels, ~1 M programs) and emits a filtered threadfin.xml that Plex's XMLTV grabber consumes. Plex stores the resulting airings in tv.plex.providers.epg.xmltv-<uuid>.db alongside its main com.plexapp.plugins.library.db.

Threadfin channel mapping (xepg.json)

Each entry in /home/threadfin/conf/xepg.json represents a lineup channel. The critical field is x-mapping: it must be a real channel ID from the source XMLTV (e.g. SRF1.ch, NDR.de, BBCTwo.ch). Threadfin only emits <programme> entries for channels whose x-mapping resolves to a <channel id="X"> in the source XML.

Auto-generated values like x-mapping: "threadfin-<hex>" are placeholders that do not match any source channel — the channel renders in the lineup but its XMLTV output contains zero programs, so Plex shows "Unknown Airing" for every slot.

Symptoms of broken mappings:

Symptom Likely cause
All channels show "Unknown Airing" in Plex Guide Plex EPG DB empty, or every x-mapping is threadfin-<hex>
Many channels show the same program Multiple x-mapping values point to a single source ID
One channel shows no EPG x-mapping: "-" or refers to a stale source channel

The match between tvg-name / name in xepg.json and the source's <display-name> requires normalization:

  • German umlauts: ZüriZueriZuri (source uses ü; M3U providers may use either digraph)
  • German number-words: eins/zwei/drei1/2/3 (e.g. ORF1ORFeins.ch)
  • Country tags on M3U names: TMC CH → source TMC.fr or TMCMonteCarlo.ch
  • Resolution markers: strip HD, UHD, 720p, 1080p before comparison

Threadfin UI channel-number display

The web UI multiplies x-channelID by 10 for display. Stored 1149 shows as 11490 in the Mapping page. Inactive channels typically appear at these high numbers — they are the deactivated entries in xepg.json (with x-active: false), not pending source channels.

Forcing a Plex EPG re-ingest

Plex's XMLTV grabber refreshes on its own ~every 12 h. A pod restart alone does not force a fresh ingest if Plex thinks the current EPG is recent. To force a full re-ingest, reset the timestamps in media_provider_resources (the EPG provider row, identifier='tv.plex.providers.epg.xmltv') and restart Plex:

-- Inside Plex SQLite, against com.plexapp.plugins.library.db
UPDATE media_provider_resources
SET extra_data = json_set(
        json_set(
            json_set(extra_data,
                '$."pv:timeOfLastRefresh"', '0'),
            '$."pv:lastChunkEndedAt"', '0'),
        '$."url"',
        REPLACE(REPLACE(json_extract(extra_data,'$."url"'),
            'pv%3AlastChunkEndedAt=<old_value>', 'pv%3AlastChunkEndedAt=0'),
            'pv%3AtimeOfLastRefresh=<old_value>', 'pv%3AtimeOfLastRefresh=0')
    ),
    updated_at = strftime('%s','now')
WHERE identifier='tv.plex.providers.epg.xmltv';

The url field inside extra_data is a URL-encoded mirror of the same JSON values — both layers must be reset for the grabber to treat the EPG as stale.

Plex re-ingest of the full ~28k-program XMLTV from both Threadfins takes 5–7 minutes (~400 s database time). Watch for EPG[xmltv]: Step 1/1 and Total time to load EPG in Plex Media Server.log.

Operating on xepg.json

The file is JSON — but Threadfin reads it on startup and only writes back when changed via its own UI. Editing the file on disk + restarting the pod is safe:

POD=$(kubectl -n tv get pods -l app=threadfin -o jsonpath='{.items[0].metadata.name}')
kubectl -n tv cp "$POD":/home/threadfin/conf/xepg.json /tmp/xepg.json
# edit /tmp/xepg.json (keep ownership/perms intact)
kubectl -n tv exec "$POD" -- cp /home/threadfin/conf/xepg.json /home/threadfin/conf/xepg.json.bak
kubectl -n tv cp /tmp/xepg.json "$POD":/home/threadfin/conf/xepg.json
kubectl -n tv exec "$POD" -- chown root:root /home/threadfin/conf/xepg.json
kubectl -n tv rollout restart deployment/threadfin

Threadfin regenerates threadfin.xml on startup (~1–2 min for 13 700 source channels) — wait for XEPG: Ready to use in the pod logs before triggering a Plex re-ingest.

Plex Live TV API endpoints

Most /livetv/dvrs/... and /tv.plex.providers.epg.xmltv:NN/refresh endpoints return 404 in current Plex versions (1.43.x+). Use the SQL timestamp-reset above instead of looking for a refresh API.