masqueradarr
epg & guide data · 06 the guide subsystem

Guide data — what's on, and when

An EPG source registers one guide provider; a sync writes its channels and programs. Channel Mapping links them to your channels, and a compose weaves the guide into your playlist's .m3u — as its XMLTV sibling.

01 What an EPG source is

One provider, two collections

An EPG source is a row in epgsources registering one guide provider. A sync writes epgchannels (one row per guide channel) and programs (the airings), both keyed by a composite <epg>:<tvg_id> id so multiple sources never collide.

● one shared sync path

syncEpgSource.ts

Every kind — whether a manual Sync now or a scheduler tick — goes through the same path, which maintains each source's syncSuccessCount / syncFailCount and status.

● a sync is a per-source replace

Swap, don't merge

A source's old channels/programs are swapped for the fresh pull. The streaming-XMLTV path replaces up-front, so a mid-stream failure marks the source error and the next good sync heals it cleanly.

02 Provider kinds

Four you add, one that's automatic

The source discriminator (stored lowercase) sorts guide providers. National guides are streamed so multi-GB XMLTV parses with bounded memory.

KindAdded viaNotes
gracenoteAdd EPG Source → GracenoteProvider/lineup grid; provenance fields (headend / lineup / postal / country) let the grid URL be rebuilt + re-synced.
epg-pwAdd EPG Source → EPG-PWepg.pw per-channel XML.
jesmannAdd EPG Source → Jesmann (guided picker)Large national XMLTV guides, streamed so multi-GB files parse with bounded memory.
xml fileAdd EPG Source → Custom (upload)One-shot uploaded XMLTV document.
remote urlAdd EPG Source → Custom (URL)Re-fetchable remote XMLTV URL (streamed, gzip-aware).
playlist-bound(automatic) — the playlist's afterSyncPlaylist-bound self-EPG (playlistBinding:true); not user-added.
03 Standalone vs. playlist-bound

Who drives the refresh cadence

● standalone

You own the schedule

Gracenote / EPG-PW / Jesmann / Custom XMLTV sync on demand or on a cronjobs schedule, independent of any playlist. Guides go stale fast, so a schedule is recommended.

● playlist-bound

The playlist owns the schedule

Built-in self-EPG sources have no manual sync of their own. They're written by the playlist's afterSync hook and re-asserted on every playlist sync. To refresh a bound guide you sync its playlist — the EPG Sources screen hides their sync/schedule controls.

either way, the binding is the same

The link between guide data and a playlist's channels is always the channel-level (tvg_id, epg) pair — set via Channel Mapping for user-added sources, self-linked for channel-adapter built-ins.

04 Channel Mapping

The two-sided linking workbench

Channel Mapping connects each playlist channel to the matching EPG channel so your channels display the right schedule. Your channels on one side, guide channels on the other.

● source filters

Apples to apples

Narrow each column to a single source so you're comparing like with like. Per-column search and A–Z jump bars help you find a channel fast.

● match scoring

Best candidates first

An intelligent composite score of names and identifiers surfaces the likely matches — suggestions, not automatic links; you confirm them.

● 2-factor link

Guide id + its source

Linking saves a persisted two-factor pair immediately. The channel then shows program data in the published guide and its Unmatched count on the Dashboard drops.

● many-to-one

Shared networks

Several playlist channels can point at the same EPG channel — useful when the same network appears in multiple playlists.

EPG Sources list
EPG Sources · guide providers
EPG Source detail
Detail · channels & programs
failover children don't get linked here

A channel that is a child of a failover group inherits its parent's guide link. Mapping badges it, won't let you select it, and auto-match skips it; a direct write is refused with 409 failover_child_epg_locked. Link the parent instead — the change cascades to every child in the same request.

05 The compose pipeline

How a guide reaches a downstream client

Guide data only reaches a client at compose time, and composition is playlist-scoped: a guide is written as a sibling of the M3U by composeGuide(), which runs off the same Active channel set composeM3u() just wrote — so a guide can never drift from its M3U.

composeM3u → composeGuide → <tv> per composed surface
1
composeM3u(surface)
Write the Active channel set
The Global union, or one Custom playlist — whichever surface is being composed.
2
composeGuide(sameChannels, path)
Run off the exact same channels
The guide is built from the identical set the M3U just used — the invariant that keeps them in lock-step.
3
Select
Keep Active + linked
Keep only Active channels that carry a 2-factor (tvg_id, epg) link; index by the composite key <epg>:<tvg_id>.
4
Channels & programmes
Emit <channel> and <programme>
Resolve each key's epgchannels row → one <channel>, deduped by the bare tvg_id (first-wins). Pull programs<programme>s, re-tagged to the bare tvg_id so each airing matches its channel id.
5
Merge & advertise
One <tv> beside the .m3u
Programme data from every source the channels link to merges into one <tv>, advertised via x-tvg-url. token-free · NOT per-user
Credit
Update run-stats
Every contributing source gets lastXmlAt + xmlGeneratedCount++ (or xmlFailCount++ on failure).
the join that binds guide to lineup

Downstream, the player pairs the guide to the M3U by matching each <channel id> to a channel's tvg-id. That's why the compose re-tags everything to the bare tvg_id — a channel linked to an epgchannels row that isn't synced yet is skipped, never orphaned.

README · EPG Sources · compose pipeline xmltv skill · the <tv>/<channel>/<programme> wire format src/docs · epg-sources · channel-mapping next → Video proxy engine
masqueradarr · epg & guide data next → video proxy engine