|
|
||
|---|---|---|
| .gitignore | ||
| docker-compose.yml | ||
| LICENSE | ||
| merge.py | ||
| README.md | ||
etv-epg-merge
Give your ErsatzTV live (FAST) channels a real TV guide. ErsatzTV only produces a proper on-screen guide for its scheduled channels — its live "remote-stream" channels just show one repeating placeholder block. This tool fetches the actual now/next listings from the FAST provider each channel mirrors (Pluto TV / Samsung TV Plus, plus broadcast PBS), grafts them onto your ErsatzTV channels, and serves the merged guide for your player (e.g. OTT Navigator).
It is read-only and safe — it never changes ErsatzTV or your media; it only fetches public guides and writes a single output file. It re-runs on a timer (every 4 hours by default) and keeps the last good guide if a refresh ever fails. It has no dependencies — just Python.
New to all this? No problem. The Quick start below is a few copy-paste steps, and every step has a click-to-expand explainer telling you what the command does and why.
Quick start
The intended way to run it is the included Docker Compose file: it builds nothing, runs the script on a timer, and serves the guide at a URL you paste into your player.
First time using a terminal? Start here
A "terminal" is a window where you type commands instead of clicking buttons.
- Windows: install Docker Desktop, then open PowerShell (search for it in the Start menu).
- macOS: install Docker Desktop, then open Terminal (in Applications → Utilities).
- Linux: you likely already have a terminal and may already have Docker.
To run a command, copy it, paste it into the terminal window, and press Enter. That's it. "Docker" is just a tool that runs this program in a tidy box so you don't have to install Python or anything else yourself.
1. Get the code
git clone https://github.com/you/etv-epg-merge.git
cd etv-epg-merge
What did that do?
git clone downloads a copy of this project to your computer. cd etv-epg-merge ("change
directory") moves you into the folder it created, so the next commands run in the right place.
Replace the URL with wherever you got this from (e.g. your own Forgejo/GitHub address).
2. Point it at your ErsatzTV (and check the channel map)
Open docker-compose.yml and merge.py in a text editor and adjust two things — your ErsatzTV
address and the channel mapping. See Personalize it to your setup
for exactly what to change and how to find your values.
3. Start it
docker compose up -d
docker compose logs -f etv-epg
What do these two commands do?
docker compose up -d— starts the program in the background (-d= detached, so it keeps running after you close the terminal). It does not build anything; it just runs the officialpythonimage with this folder mounted inside.docker compose logs -f etv-epg— shows its live output so you can watch what it's doing. You'll see lines likech41: grafted 137 programmes from pluto/...and finallyDONE: grafted real EPG onto N live channels. PressCtrl+Cto stop watching; the program keeps running.
If a channel is skipped, the log says why (e.g. skip ch20: not in ErsatzTV xmltv, or a provider
returned 0 programmes). That's the place to confirm your map is correct.
4. Point your player's guide at it
In your player (e.g. OTT Navigator), keep using ErsatzTV's existing M3U for the channel list and streams, and set the EPG / XMLTV URL to:
http://<your-server>:8186/guide.xml
What's the difference between the M3U and the XMLTV URL?
- The M3U (from ErsatzTV) is the channel list — what to play. Don't change it.
- The XMLTV / EPG is the program guide — what's on now and next. That's what this tool
produces. Because the merged guide reuses ErsatzTV's own channel ids (
tvg-ids), the player lines the guide up with the right channels automatically — no extra mapping in the player.
Replace <your-server> with your server's LAN IP or hostname (e.g. 192.168.1.209). The tool
serves the file on port 8186.
Personalize it to your setup
There are two things to set: where your ErsatzTV is, and which channels map to which provider feed.
Your ErsatzTV address — set the ETV_XMLTV_URL environment variable to your ErsatzTV's XMLTV
endpoint. The default is http://localhost:8409/iptv/xmltv.xml. In docker-compose.yml, add it
under the service's environment: (or set it in your stack's env). The provider feeds (Pluto /
Samsung / epgshare01) are public and need no config.
How do I find my ErsatzTV XMLTV URL?
It's your ErsatzTV server address plus /iptv/xmltv.xml. ErsatzTV usually listens on port 8409,
so on the same machine it's http://localhost:8409/iptv/xmltv.xml, and from elsewhere on your
network it's http://<ErsatzTV-IP>:8409/iptv/xmltv.xml (e.g. http://192.168.1.209:8409/iptv/xmltv.xml).
Open that URL in a browser to confirm it returns XML before pasting it in.
The mount path — docker-compose.yml mounts a folder into the container with
${DOCKER_PATH}etv-epg:/app. That folder must contain merge.py, and the tool writes its output to
its out/ subfolder.
What is ${DOCKER_PATH} and what do I put there?
${DOCKER_PATH} is a placeholder for wherever you keep your Docker app folders. Either define
DOCKER_PATH in your environment / stack so it ends in a slash (e.g.
/srv/docker/), or just replace ${DOCKER_PATH}etv-epg in docker-compose.yml with a real path
like /srv/docker/etv-epg. Put this repo's merge.py in that folder; the container creates out/
itself and writes guide.xml there.
The channel map — the per-channel mapping lives in the MAP dictionary near the top of
merge.py. Each line maps your ErsatzTV channel number to a (provider, provider channel id).
Edit it to add, remove, or repoint channels. If you add a brand-new source, also add it to the
PROV dictionary above MAP.
How do I find a provider's channel id to put in MAP?
Each MAP entry looks like "41": ("pluto", "5d14fd1a252d35decbc4080c") — that's ErsatzTV channel
41 mirrors the Pluto channel with that id.
- The left key (
"41") is the channel number as it appears in ErsatzTV. - The provider is one of the keys in
PROV(pluto,samsung,epgshare). - The provider channel id comes from that provider's own XMLTV. Open the provider URL listed in
PROV(the.gzones download a gzipped XML), and look at its<channel>elements: each has a human-readable<display-name>and anid. Find the channel by name, copy itsid, and use that as the value.
If a mapped channel logs 0 programmes or not in ErsatzTV xmltv, the id or the channel number is
off — fix it and the next refresh will pick it up.
Settings
| Variable | Default | What it does |
|---|---|---|
ETV_XMLTV_URL |
http://localhost:8409/iptv/xmltv.xml |
Your ErsatzTV XMLTV endpoint — the only required setting. |
Everything else is edited directly in merge.py:
In merge.py |
What it does |
|---|---|
MAP |
Maps each ErsatzTV channel number to a (provider, provider channel id). This is the main thing you'll edit. |
PROV |
The provider guide sources (Pluto, Samsung, epgshare01). Add an entry here to introduce a new source. |
And in docker-compose.yml:
| Setting | Default | What it does |
|---|---|---|
ports |
8186:8186 |
The port the guide is served on. Change the left number to serve it elsewhere. |
sleep (in command) |
14400 |
Seconds between refreshes. 14400 = every 4 hours. |
volumes |
${DOCKER_PATH}etv-epg:/app |
The folder containing merge.py; output lands in its out/. |
How it works
- Fetch ErsatzTV's
/iptv/xmltv.xmland build a lookup from each channel number to its ErsatzTVtvg-id. - Fetch the provider XMLTV guides listed in
PROV. - For each entry in
MAP: drop ErsatzTV's placeholder programmes for that channel, then graft the provider's programmes in their place, retagged to the ErsatzTVtvg-idso the player maps them automatically. - Write the merged XMLTV to standard output. The container saves that to
out/guide.xmland serves it over HTTP on port8186.
The refresh runs on a loop (every 4 hours). If a run fails, the previous guide.xml is kept so your
player never sees an empty guide.
I'd rather just run the script directly, without Docker
It's plain Python (3.x), standard library only — no pip install needed. It prints the merged guide
to standard output, so redirect it to a file:
ETV_XMLTV_URL="http://192.168.1.209:8409/iptv/xmltv.xml" python3 merge.py > guide.xml
Progress and any skipped channels are logged to standard error, so they show in your terminal while
the guide goes to the file. Serve guide.xml however you like, or just re-run on your own schedule.
Good to know
- Coverage: the shipped
MAPcovers a set of live remote-stream channels using FAST sources (Pluto / Samsung via i.mjh.nz) plus epgshare01 broadcast guides for real PBS national and Create TV feeds. Add or repoint channels by editingMAP(andPROVfor a new source). - Provider feeds change. A provider can rename or drop a channel id; when that happens the log
shows
0 programmesfor it. Skimdocker compose logs etv-epgafter the first run and re-check any channel that didn't graft. - Guide data is courtesy of the i.mjh.nz grabbers and epgshare01 community feeds. They're public and read-only — be a good citizen and don't refresh more often than you need to (the default 4-hour interval is plenty).
- The tool uses an unverified SSL context for these public, read-only feeds.
License
MIT — see LICENSE. Do what you like; no warranty.