Plex Live TV / Threadfin EPG¶
Plex DVR in the tv namespace pulls IPTV streams from two Threadfin instances (HDHomeRun emulators):
threadfin— WebTV M3U sourcethreadfin-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üri↔Zueri↔Zuri(source usesü; M3U providers may use either digraph) - German number-words:
eins/zwei/drei↔1/2/3(e.g.ORF1↔ORFeins.ch) - Country tags on M3U names:
TMC CH→ sourceTMC.frorTMCMonteCarlo.ch - Resolution markers: strip
HD,UHD,720p,1080pbefore 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.