Files
projects/Dockers/self-hosted-iptv
Bram bfc3f22c29
Build and Push Docker Images / build-and-push (push) Successful in 21s
logging audio selection
2026-02-11 22:55:14 +01:00
..
2026-02-11 22:37:58 +01:00
2026-02-11 22:55:14 +01:00
2026-02-11 21:06:16 +01:00
2026-02-11 22:37:58 +01:00
2026-02-11 22:40:35 +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.

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.