From 2706a2181878bab05de9c75b06772ae2761fc90c Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 11 Feb 2026 22:37:58 +0100 Subject: [PATCH] channel ids --- Dockers/self-hosted-iptv/README.md | 2 +- Dockers/self-hosted-iptv/data/channels.json.example | 2 ++ Dockers/self-hosted-iptv/iptv/cache.py | 5 ++++- Dockers/self-hosted-iptv/iptv/server.py | 7 ++++++- Dockers/self-hosted-iptv/main.py | 8 +++++++- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Dockers/self-hosted-iptv/README.md b/Dockers/self-hosted-iptv/README.md index 518d2b6..7c8d936 100644 --- a/Dockers/self-hosted-iptv/README.md +++ b/Dockers/self-hosted-iptv/README.md @@ -26,7 +26,7 @@ docker run -d --name iptv -p 8080:8080 \ -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. +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. diff --git a/Dockers/self-hosted-iptv/data/channels.json.example b/Dockers/self-hosted-iptv/data/channels.json.example index 9218110..4752533 100644 --- a/Dockers/self-hosted-iptv/data/channels.json.example +++ b/Dockers/self-hosted-iptv/data/channels.json.example @@ -13,6 +13,8 @@ "tmdb_id": "97596-kika-bob" }, "series/Kim Possible", + "series/As Told By Ginger", + "series/De grote boze wolf show", { "path": "series/Beugelbekkie", "display_name": "Beugelbekkie", diff --git a/Dockers/self-hosted-iptv/iptv/cache.py b/Dockers/self-hosted-iptv/iptv/cache.py index 6846f53..481aa3b 100644 --- a/Dockers/self-hosted-iptv/iptv/cache.py +++ b/Dockers/self-hosted-iptv/iptv/cache.py @@ -75,6 +75,9 @@ class CacheManager: """ If channels.json has changed, clear all caches and return True. Otherwise return False. + Only clear when we have a stored hash that explicitly differs; when + stored is None (hash file missing), do not delete cache files — they + may be valid and we don't want to destroy them on every restart. """ current = get_channels_hash(self._channels_file) stored = self._get_stored_hash() @@ -82,7 +85,7 @@ class CacheManager: if current is None: return False - if stored != current: + if stored is not None and stored != current: logger.info("Channels changed (hash %s -> %s), invalidating cache", stored, current) self._clear_cache() if self._schedule_builder and hasattr(self._schedule_builder, "invalidate_cache"): diff --git a/Dockers/self-hosted-iptv/iptv/server.py b/Dockers/self-hosted-iptv/iptv/server.py index 7d1928a..9cc6f19 100644 --- a/Dockers/self-hosted-iptv/iptv/server.py +++ b/Dockers/self-hosted-iptv/iptv/server.py @@ -74,6 +74,11 @@ class IPTVHandler(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(body.encode("utf-8")) + def _extinf_channel(self, cid: str, name: str) -> str: + """Build EXTINF line with tvg-id for EPG matching and persistence.""" + safe = lambda s: (s or "").replace("\\", "\\\\").replace('"', '\\"') + return f'#EXTINF:-1 tvg-id="{safe(cid)}" tvg-name="{safe(name)}",{name}' + def send_channels_m3u(self): """Master M3U for Plex DVR: one live stream URL per channel.""" base = get_base_url(self) @@ -82,7 +87,7 @@ class IPTVHandler(BaseHTTPRequestHandler): for ch in channels: cid = ch.get("id", ch.get("name", "")) name = ch.get("name", cid) - lines.append(f"#EXTINF:-1,{name}") + lines.append(self._extinf_channel(cid, name)) lines.append(f"{base}/live/{cid}") body = "\n".join(lines) + "\n" self.send_response(200) diff --git a/Dockers/self-hosted-iptv/main.py b/Dockers/self-hosted-iptv/main.py index 8bb3555..b1c26c0 100644 --- a/Dockers/self-hosted-iptv/main.py +++ b/Dockers/self-hosted-iptv/main.py @@ -50,9 +50,15 @@ def main(): channel_repo = ChannelRepository() schedule_builder = ScheduleBuilder() schedule_builder.set_channel_repo(channel_repo) - tmdb_client = TMDBClient() cache_manager = CacheManager(schedule_builder=schedule_builder) schedule_builder.set_cache_manager(cache_manager) + if not cache_manager._ensure_cache_dir(): + logger.warning( + "Cannot create cache dir %s — schedule will rebuild on every restart. " + "Ensure /data is mounted read-write (not :ro).", + cache_manager._cache_dir, + ) + tmdb_client = TMDBClient() prefetcher = MetadataPrefetcher(schedule_builder, tmdb_client, channel_repo) prefetcher.start() epg_builder = EPGBuilder(