# 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 keys to include: - `"movies"` — all videos under `/movies` - `"series/Show Name"` — all videos under `/series/Show Name` 2. Run the container with volumes: ```bash 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 ``` When using TMDB metadata, mount `/data` read-write (as above) so the app can store and reuse metadata in `/data/tmdb_cache/`. Use `-v /path/to/data:/data:ro` only if you do not use TMDB. **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](https://www.themoviedb.org/settings/api) (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. 3. In your IPTV client or **Plex DVR**, add: - **Playlist URL:** `http://:8080/playlist.m3u` - **EPG (XMLTV) URL:** `http://: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/`): FFmpeg concatenates the channel’s 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 what’s actually playing. The same order is cached for 24h so the EPG and live stream stay in sync. ## 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 (14 days, UTC) - `GET /live/` — continuous MPEG-TS stream for that channel (for tuning/recording) - `GET /channel//playlist.m3u` — single channel M3U (list of individual video URLs) - `GET /stream?path=movies/...` or `path=series/...` — stream a single video file ## 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.