Files
Bram f2e442218a
Build and Push Docker Images / build-and-push (push) Successful in 22s
freaking languages
2026-02-11 23:43:38 +01:00
..
2026-02-11 22:37:58 +01:00
2026-02-11 23:43:38 +01:00
2026-02-11 21:06:16 +01:00
2026-02-11 23:06:58 +01:00
2026-02-11 23:06:58 +01:00
2026-02-10 23:45:33 +01:00

Self-hosted IPTV

Serves M3U playlists that randomly schedule video files from /movies and /series, with the same show never appearing twice in a row.

Setup

  1. Create /data/channels.json (see data/channels.json.example).

channels.json format:

  • channels: array of channel objects
  • Each channel:
    • id: unique id (used in URLs)
    • name: display name
    • paths: list of path entries. Each entry can be:
      • String: "movies" or "series/Show Name" — path to videos; EPG title and TMDB search use the folder name.
      • Object: {"path": "series/W817", "display_name": "Phineas en Ferb", "tmdb_search": "Phineas and Ferb"}path is required; display_name is shown in the EPG; tmdb_search is used when querying TMDB. You can use tmdb_id instead of (or to override) tmdb_search for a direct lookup, e.g. 97596 or "97596-kika-bob" (the numeric ID is extracted).
  1. Run the container with volumes:
docker build -t self-hosted-iptv .
docker run -d --name iptv -p 8080:8080 \
  -v /path/to/movies:/movies:ro \
  -v /path/to/series:/series:ro \
  -v /path/to/data:/data \
  self-hosted-iptv

Mount /data read-write (omit :ro) so the app can persist the schedule cache in /data/cache/ — otherwise the schedule must be rebuilt on every restart. TMDB metadata also requires read-write access for /data/tmdb_cache/.

EPG timespan: Set EPG_TIMESPAN to control how far ahead the guide is generated (default 14d). Examples: 2d, 7d, 48h. The randomized schedule is refreshed after (timespan 3 hours) so a new block of content is ready before the current one ends. Example: -e EPG_TIMESPAN=2d gives 2 days of EPG and a new schedule every 45 hours.

Optional EPG metadata (Dutch) from TMDB:
To show series titles, episode names, and descriptions in the TV guide in Dutch, set a TMDB API key (free after sign-up):

  • TMDB_API_KEY your TMDB API key (v3).

Series are matched by folder name (e.g. series/W817 → search "W817"); episode info is matched from filenames like S01E01. All metadata is requested in Dutch (language=nl).

  • Persistent cache: Fetched metadata is stored under /data/tmdb_cache/ (series search, series details, episode, episode credits). When generating the EPG, the app checks this cache first and only calls TMDB for missing entries. Mount /data read-write (omit :ro) so the cache can be written.
  • Rate limit: TMDB allows 40 requests per 10 seconds. The app enforces this limit when calling the API; if the cache is warm, few or no requests are made during EPG generation.

Logging: Set LOG_LEVEL=DEBUG (e.g. in your docker run with -e LOG_LEVEL=DEBUG) to see where time is spent: schedule building (including ffprobe per video), EPG per-channel programme count and duration, TMDB cache hits (memory/disk) vs API requests and rate-limit waits, and EPG cache hit/miss reasons.

Cache locations (under /data):

  • EPG: /data/cache/epg.xml — XMLTV guide; valid only while channels.json is unchanged.
  • Schedule: /data/cache/schedule_<channel>.json — per-channel video schedules.

On startup, schedules are preloaded in the background so the first stream starts quickly. If the hash file (channels_hash.txt) is missing but cache files exist and are recent, the hash is restored so existing cache can be used.

Stream crashes when switching episodes? If the live stream freezes or crashes when transitioning between videos (e.g. different episodes with different resolutions), enable normalization to re-encode everything to a uniform format:

  • NORMALIZE_LIVE_STREAM=1 — enables re-encoding (uses more CPU, produces a stable continuous stream)
  • NORMALIZE_TARGET_RES=1920x1080 — target resolution (default). Use 1280x720 for lower CPU usage.
  1. In your IPTV client or Plex DVR, add:
    • Playlist URL: http://<host>:8080/playlist.m3u
    • EPG (XMLTV) URL: http://<host>:8080/epg.xml
      Plex will show programme titles and times for each channel.

Each channel in the M3U points to a continuous live stream (/live/<id>): FFmpeg concatenates the channels randomized videos into one MPEG-TS stream and loops it. The schedule (and EPG) is built from video durations (via ffprobe) so start/stop times match whats actually playing. The schedule is cached for (timespan 3 h) so the EPG and live stream stay in sync and a new span is ready in time.

Endpoints

  • GET / — simple web index with playlist and EPG links
  • GET /playlist.m3u — master M3U for Plex DVR (one live stream URL per channel)
  • GET /epg.xml or GET /xmltv.xml — XMLTV EPG (duration set by EPG_TIMESPAN, UTC)
  • GET /live/<channel_id> — continuous MPEG-TS stream for that channel (for tuning/recording)
  • GET /channel/<id>/playlist.m3u — single channel M3U (list of individual video URLs)
  • GET /stream?path=movies/... or path=series/... — stream a single video file (audio track is auto-selected: Dutch → Flemish → default)

Shuffle behaviour

Videos are grouped by path (e.g. one group per show or the whole movies folder). Each group is shuffled, then items are interleaved round-robin so the same show never plays back-to-back.