[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1443":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":15,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":15,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":38,"readmeContent":39,"aiSummary":40,"trendingCount":16,"starSnapshotCount":16,"syncStatus":41,"lastSyncTime":42,"discoverSource":43},1443,"hls-restream-proxy","pcruz1905\u002Fhls-restream-proxy","pcruz1905","Lightweight HLS restream proxy for Jellyfin\u002FEmby\u002FPlex — injects headers, rewrites playlists, auto-refreshes tokens","",null,"Python",229,14,217,3,0,1,11,48.13,"MIT License",false,"main",true,[25,26,27,28,29,30,31,32,33,34,35,36,37],"emby","hls","homelab","iptv","jellyfin","live-tv","m3u","m3u8","plex","proxy","restream","selfhosted","streaming","2026-06-12 04:00:09","# hls-restream-proxy\n\nLightweight HLS restream toolkit for self-hosted media servers.\n\nMany free IPTV\u002FHLS sources require specific HTTP headers (User-Agent, Referer) that media servers don't send. This proxy sits between your media server and the upstream, injecting the required headers and rewriting m3u8 playlists so all segment requests also go through the proxy.\n\n## Components\n\n| File | Purpose |\n|------|---------|\n| `hls-proxy.py` | HTTP reverse proxy that adds headers to upstream HLS requests |\n| `refresh-m3u.sh` | Scrapes source pages, extracts m3u8 URLs, writes M3U playlist |\n| `detect-headers.sh` | Auto-detects which HTTP headers a stream requires |\n| `channels.conf` | Your channel list (slug, name, logo, group, source URL) |\n\n## How it works\n\n```\n┌──────────┐     ┌───────────┐     ┌──────────────┐     ┌──────────┐\n│ Jellyfin │────▶│ hls-proxy │────▶│ upstream HLS │────▶│ segments │\n│          │     │ :8089     │     │ server       │     │ (.ts)    │\n└──────────┘     └───────────┘     └──────────────┘     └──────────┘\n                  adds headers:\n                  • User-Agent\n                  • Referer\n```\n\n1. `refresh-m3u.sh` generates an M3U file with stable `\u002Fchannel\u002F\u003Cslug>` URLs pointing to the proxy\n2. When Jellyfin requests `\u002Fchannel\u002Fsporttv1`, the proxy scrapes a fresh m3u8 URL on the fly (cached for 1 hour)\n3. The proxy injects the required headers and rewrites the playlist so `.ts` segments also go through it\n4. The proxy auto-learns the correct Referer for each upstream host — no manual configuration needed\n\n**No more expired tokens** — the M3U URLs never change, the proxy handles token refresh transparently.\n\n## Requirements\n\n- Python 3.8+ (stdlib only, no pip packages)\n- bash, curl, grep (with PCRE \u002F `-P`)\n\n## Quick start\n\n### Docker (recommended)\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fpcruz1905\u002Fhls-restream-proxy.git\ncd hls-restream-proxy\n\ncp channels.conf.example channels.conf\n# Edit channels.conf with your channels\n\ndocker compose -f docker-compose.example.yml up -d\n```\n\n### Docker one-liner (no clone)\n\n```bash\ndocker run -d --name hls-proxy \\\n  -p 8089:8089 \\\n  -v .\u002Fchannels.conf:\u002Fapp\u002Fchannels.conf:ro \\\n  ghcr.io\u002Fpcruz1905\u002Fhls-restream-proxy:latest\n```\n\n### Manual (no Docker)\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fpcruz1905\u002Fhls-restream-proxy.git\ncd hls-restream-proxy\n\ncp channels.conf.example channels.conf\n# Edit channels.conf with your channels\n\npython3 hls-proxy.py &\n\n# Generate the M3U\nexport M3U_OUTPUT=\u002Fpath\u002Fto\u002Fjellyfin\u002Fconfig\u002Fiptv.m3u\nexport HLS_PROXY_URL=\"http:\u002F\u002FYOUR_HOST_IP:8089\"\nbash refresh-m3u.sh\n\n# Add the M3U file as an M3U Tuner in your media server\n```\n\n## Channel config format\n\n`channels.conf` — one channel per line, pipe-delimited:\n\n```\nslug|Display Name|chno|logo_url|Group|source_page_url|mode|referer\n```\n\n- **mode**: `iframe` (default) — page has an iframe whose embed contains the m3u8. `direct` — page itself contains the m3u8 URL.\n- **referer**: optional override for the Referer header when fetching the embed page.\n\nSee `channels.conf.example` for details.\n\n## Systemd setup\n\nUser-level services are provided in `systemd\u002F`:\n\n```bash\n# Copy units\ncp systemd\u002F*.service systemd\u002F*.timer ~\u002F.config\u002Fsystemd\u002Fuser\u002F\n\n# Edit paths in the service files, then:\nsystemctl --user daemon-reload\nsystemctl --user enable --now hls-proxy.service\nsystemctl --user enable --now refresh-m3u.timer\n\n# Enable linger so services run without an active login session\nsudo loginctl enable-linger $USER\n```\n\nThe timer refreshes URLs every 4 hours by default (edit `OnUnitActiveSec` in the timer).\n\n## Docker \u002F container media servers\n\nIf your media server runs in Docker on a bridge network, the proxy URL must use the Docker gateway IP (not `127.0.0.1`). Find it with:\n\n```bash\ndocker inspect \u003Ccontainer> | grep Gateway\n# Typically 172.x.0.1\n```\n\nThen set `HLS_PROXY_URL=http:\u002F\u002F172.x.0.1:8089`.\n\n## Finding the required headers (User-Agent & Referer)\n\nEvery streaming site is different. Here's how to figure out which headers your upstream needs:\n\n### Automatic detection (recommended)\n\nRun the detector tool — it follows the iframe chain, tests every header combination on both the m3u8 playlist and .ts segments, and tells you exactly what you need:\n\n```bash\n.\u002Fdetect-headers.sh \"https:\u002F\u002Fstreaming-site.com\u002Fchannel.php\"\n```\n\nOutput:\n```\n=== HLS Header Detector ===\n\n[1\u002F4] Extracting iframe from page...\n  iframe: https:\u002F\u002Fembed-domain.com\u002Fembed\u002Fabc123\n  embed host: https:\u002F\u002Fembed-domain.com\n\n[1\u002F4] Extracting m3u8 from embed page...\n  m3u8: https:\u002F\u002Fcdn.example.com\u002Fhls\u002Fabc123.m3u8?s=token&e=123\n\n[2\u002F4] Testing header combinations on m3u8 playlist...\n  403  no-UA\n  200  UA\n  200  UA+Ref(https:\u002F\u002Fembed-domain.com\u002F)\n\n[3\u002F4] Testing header combinations on .ts segments...\n  403  no-UA\n  403  UA\n  200  UA+Ref(https:\u002F\u002Fembed-domain.com\u002F)\n\n[4\u002F4] Recommended configuration:\n  User-Agent + Referer\n\n  export HLS_PROXY_REFERER=\"https:\u002F\u002Fembed-domain.com\u002F\"\n```\n\nYou can also pass a direct m3u8 URL:\n```bash\n.\u002Fdetect-headers.sh \"https:\u002F\u002Fcdn.example.com\u002Fhls\u002Fstream.m3u8?token=xxx\" --direct\n```\n\n### Manual detection\n\nIf the auto-detector can't find the m3u8 (some sites use heavy JS), use browser DevTools:\n\n### Step 1: Open DevTools Network tab\n\n1. Open the streaming site in Chrome\u002FFirefox\n2. Press `F12` → **Network** tab\n3. Filter by `m3u8` or `media`\n4. Play the stream — you'll see `.m3u8` and `.ts` requests appear\n\n### Step 2: Find the m3u8 request\n\nClick on the `.m3u8` request and look at the **Request Headers**. Note down:\n- **User-Agent** — usually a standard browser UA\n- **Referer** — this is the key one, usually the embed\u002Fiframe domain (not the main site)\n- **Origin** — sometimes needed instead of Referer\n\n### Step 3: Test with curl\n\n```bash\n# Get a fresh m3u8 URL from the Network tab, then test:\n\n# Without headers (probably 403)\ncurl -o \u002Fdev\u002Fnull -w \"%{http_code}\" \"https:\u002F\u002Fexample.com\u002Fhls\u002Fstream.m3u8?token=xxx\"\n\n# With User-Agent only\ncurl -o \u002Fdev\u002Fnull -w \"%{http_code}\" -A \"Mozilla\u002F5.0\" \"https:\u002F\u002Fexample.com\u002Fhls\u002Fstream.m3u8?token=xxx\"\n\n# With User-Agent + Referer\ncurl -o \u002Fdev\u002Fnull -w \"%{http_code}\" -A \"Mozilla\u002F5.0\" \\\n  -e \"https:\u002F\u002Fembed-domain.com\u002F\" \\\n  \"https:\u002F\u002Fexample.com\u002Fhls\u002Fstream.m3u8?token=xxx\"\n```\n\nTry each combination until you get `200`. That tells you which headers are required.\n\n### Step 4: Test .ts segments too\n\nThe playlist (`.m3u8`) and segments (`.ts`) may need different headers. Grab a `.ts` URL from the playlist and repeat the curl test:\n\n```bash\n# Get a segment URL from the m3u8 content\ncurl -s -A \"Mozilla\u002F5.0\" -e \"https:\u002F\u002Fembed-domain.com\u002F\" \\\n  \"https:\u002F\u002Fexample.com\u002Fhls\u002Fstream.m3u8?token=xxx\" | grep \".ts\" | head -1\n\n# Test that segment\ncurl -o \u002Fdev\u002Fnull -w \"%{http_code}\" -A \"Mozilla\u002F5.0\" \\\n  -e \"https:\u002F\u002Fembed-domain.com\u002F\" \\\n  \"https:\u002F\u002Fexample.com\u002Fhls\u002Fsegment-12345.ts\"\n```\n\n### Step 5: Configure the proxy\n\nOnce you know the required headers:\n\n```bash\nexport HLS_PROXY_UA=\"Mozilla\u002F5.0 ...\"        # usually the default is fine\nexport HLS_PROXY_REFERER=\"https:\u002F\u002Fembed-domain.com\u002F\"  # the iframe\u002Fembed host\n```\n\n### Quick reference: common patterns\n\n| Site pattern | Usually needs |\n|-------------|---------------|\n| Page → iframe → m3u8 | Referer = iframe embed domain |\n| Direct m3u8 with token | User-Agent only |\n| Cloudflare-protected | User-Agent + Referer + sometimes Origin |\n\n### Tip: find the iframe chain automatically\n\nMost sites follow this pattern: **page → iframe → embed page → m3u8**\n\n```bash\n# Extract the iframe src\ncurl -sL -A \"Mozilla\u002F5.0\" \"https:\u002F\u002Fstreaming-site.com\u002Fchannel.php\" \\\n  | grep -oP 'iframe\\s+src=\"\\K[^\"]+'\n\n# That gives you the embed domain for the Referer\n```\n\n## Environment variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `HLS_PROXY_PORT` | `8089` | Proxy listen port |\n| `HLS_PROXY_BIND` | `127.0.0.1` | Bind address (localhost only by default) |\n| `HLS_PROXY_UA` | Chrome UA string | User-Agent sent to upstream |\n| `HLS_PROXY_REFERER` | _(empty)_ | Fallback Referer header (auto-learned from \u002Fchannel\u002F) |\n| `HLS_ALLOWED_IPS` | _(empty)_ | Comma-separated client IP allowlist (empty = all) |\n| `CHANNELS_CONF` | `.\u002Fchannels.conf` | Path to channel config file |\n| `HLS_CACHE_TTL` | `3600` | Seconds to cache scraped m3u8 URLs (per channel) |\n| `HLS_DEFAULT_BANDWIDTH` | _(empty)_ | Fallback BANDWIDTH (bits\u002Fsec) advertised in the master playlist when a channel has no value set in `channels.conf`. Prevents Jellyfin's ~20 Mbps default guess. |\n| `M3U_OUTPUT` | `\u002Ftmp\u002Fiptv.m3u` | Output M3U file path |\n| `HLS_PROXY_URL` | `http:\u002F\u002F127.0.0.1:8089` | Proxy URL written into M3U |\n\n## Media server compatibility\n\n| Media server | M3U support | How to use |\n|-------------|------------|-----------|\n| **Jellyfin** | Native | Add M3U file as a tuner in Live TV settings |\n| **Channels DVR** | Native | Add as custom M3U source |\n| **Plex** | Via proxy | Use [Threadfin](https:\u002F\u002Fgithub.com\u002FThreadfin\u002FThreadfin) or [xTeVe](https:\u002F\u002Fgithub.com\u002Fxteve-project\u002FxTeVe) to expose M3U as a virtual tuner |\n| **Emby** | Via proxy | Same as Plex — use Threadfin or xTeVe |\n| **VLC** | Direct | `vlc http:\u002F\u002FYOUR_HOST:8089\u002Fchannel\u002Fsporttv1` |\n| **mpv** | Direct | `mpv http:\u002F\u002FYOUR_HOST:8089\u002Fchannel\u002Fsporttv1` |\n| **Any HLS player** | Direct | Point at `http:\u002F\u002FYOUR_HOST:8089\u002Fchannel\u002F\u003Cslug>` |\n\n## FAQ\n\n**Why not a Jellyfin plugin?**\nStandalone scripts work with any media server and any player. No .NET dependency, no breakage when Jellyfin updates, and it also works with VLC, mpv, or any HLS-capable player.\n\n**Does this add latency?**\nNo. The proxy is passthrough only — it forwards the exact same bytes from the upstream, no transcoding. The only added latency is the network hop through the proxy (typically \u003C1ms on localhost).\n\n**Do I still need to transcode in Jellyfin?**\nThe proxy itself never transcodes — it only fixes headers so Jellyfin can fetch the stream. Whether Jellyfin transcodes afterwards depends on the stream and the client:\n\n- Most free HLS streams are H.264 + AAC, which direct-play on virtually every client. No transcode.\n- If Jellyfin still transcodes, check the \"Playback info\" overlay in the web client. Common causes:\n  1. **Bitrate cap too low** — Jellyfin defaults to ~20 Mbps when the playlist has no `BANDWIDTH` hint, so any bandwidth limit below that triggers a transcode. Fix it by declaring the real bitrate — see the bitrate FAQ below.\n  2. **Unsupported codec** (e.g. HEVC on older Chromecasts) — no way around this, the client genuinely can't play it.\n  3. **Forced transcoding in user settings** — Jellyfin Dashboard → Playback → raise the streaming bitrate limits.\n\n**Jellyfin thinks my stream is 20 Mbps when it's really ~6 Mbps (or similar)?**\nThis happens when the upstream returns a single-variant media playlist with no `#EXT-X-STREAM-INF:BANDWIDTH` tag. Jellyfin can't see the real bitrate and defaults to a worst-case ~20 Mbps, which forces transcoding on any client with a stricter bandwidth cap. Same issue that hits ErsatzTV and Dispatcharr outputs.\n\nFix: declare the real bitrate as the 9th pipe-delimited field in `channels.conf`:\n\n```\nsporttv1|Sport TV 1|1|logo.png|Sports|https:\u002F\u002F...|iframe||6000000\n```\n\nThe proxy then wraps the stream in a thin master playlist with that `BANDWIDTH`, and Jellyfin reads the correct value. The underlying media playlist stays reachable at `\u002Fchannel\u002F\u003Cslug>\u002Fmedia.m3u8`.\n\nOr set a global default for all channels:\n```bash\nexport HLS_DEFAULT_BANDWIDTH=6000000\n```\n\n**Does this work with Plex or Emby?**\nNot directly — Plex and Emby don't read M3U files. You need [Threadfin](https:\u002F\u002Fgithub.com\u002FThreadfin\u002FThreadfin) or [xTeVe](https:\u002F\u002Fgithub.com\u002Fxteve-project\u002FxTeVe) between this proxy and Plex\u002FEmby. These tools make M3U sources appear as a local TV tuner (HDHomeRun) that Plex\u002FEmby can use.\n\n**Can I use this with VLC or mpv?**\nYes. The `\u002Fchannel\u002F\u003Cslug>` endpoint returns a standard HLS playlist. Any player that supports HLS can play it directly:\n```bash\nvlc http:\u002F\u002Flocalhost:8089\u002Fchannel\u002Fsporttv1\nmpv http:\u002F\u002Flocalhost:8089\u002Fchannel\u002Fsporttv1\n```\n\n## See also\n\n- [Threadfin](https:\u002F\u002Fgithub.com\u002FThreadfin\u002FThreadfin) — M3U proxy for Plex\u002FJellyfin\u002FEmby, makes M3U look like a local tuner\n- [xTeVe](https:\u002F\u002Fgithub.com\u002Fxteve-project\u002FxTeVe) — M3U proxy for Plex DVR and Emby Live TV\n- [Dispatcharr](https:\u002F\u002Fgithub.com\u002FDispatcharr\u002FDispatcharr) — IPTV stream management and distribution\n- [Restreamer](https:\u002F\u002Fgithub.com\u002Fdatarhei\u002Frestreamer) — Full-featured self-hosted streaming server with web UI\n\n## License\n\nMIT\n","hls-restream-proxy 是一个轻量级的HLS重流代理工具，专为Jellyfin\u002FEmby\u002FPlex等自托管媒体服务器设计。它通过在媒体服务器和上游HLS源之间注入必要的HTTP头（如User-Agent、Referer）并重写m3u8播放列表，确保所有分段请求也通过代理进行，从而解决许多免费IPTV\u002FHLS源需要特定HTTP头的问题。此外，该工具还支持自动刷新令牌和生成稳定的M3U URL，无需手动配置即可自动学习正确的引用页。适用于希望增强其家庭实验室或自托管媒体服务器功能的用户，特别是那些使用IPTV或直播电视服务的人。项目基于Python编写，仅依赖标准库，并提供了Docker快速启动选项。",2,"2026-06-11 02:43:50","CREATED_QUERY"]