展示HN:我将免费体育直播导入Jellyfin – 无广告,仅HLS
Show HN: I pipe free sports streams into Jellyfin – no ads, just HLS

原始链接: https://github.com/pcruz1905/hls-restream-proxy

## HLS 重流代理:摘要 此工具包解决了在 Jellyfin、Emby 和 Plex 等媒体服务器中访问免费 HLS/IPTV 流的问题。许多流需要特定的 HTTP 标头(User-Agent、Referer),而这些服务器不会自动提供,导致播放错误。 `hls-proxy.py` 充当反向代理,注入这些必要的标头并将 m3u8 播放列表重写,以将所有片段请求路由到它。它包括 `refresh-m3u.sh` 等工具,用于抓取和维护稳定的 M3U 播放列表,以及 `detect-headers.sh`,用于自动识别给定流所需的标头。 配置通过 `channels.conf` 进行,定义具有其源 URL 的频道。代理动态更新 m3u8 URL(缓存一小时)并处理令牌刷新,确保持续访问。它使用 Python 3.8+(仅标准库)和 Bash 编写,并支持 systemd 用于用户级服务。Docker 用户需要使用 Docker 网关 IP 配置代理 URL。包含详细的标头检测指南,利用 `curl` 和浏览器开发者工具来确定所需的标头。

一位 Hacker News 用户“pruz”开发了一个系统,可以将免费体育内容直接流式传输到他们自托管的 Jellyfin 媒体服务器,从而消除广告和不可靠网站的麻烦。 挑战在于访问底层的 HLS 流,这些流被隐藏在混淆的 JavaScript、即将过期的令牌和严格的标头要求之后。 为了克服这个问题,pruz 创建了三个脚本:`detect-headers.sh` 用于识别必要的请求标头,`hls-proxy.py` – 一个 Python 反向代理,用于注入这些标头并重写流,以及 `refresh-m3u.sh` 用于在令牌过期之前自动更新流 URL。 该项目总共约 300 行代码,成功地为 Jellyfin 提供了干净、无广告的流媒体体验,处理了相对 URL 和 CORS 问题等复杂问题。 代码可在 GitHub 上找到 ([github.com/pcruz1905](https://github.com/pcruz1905))。
相关文章

原文

Lightweight HLS restream toolkit for self-hosted media servers (Jellyfin, Emby, Plex).

Many free IPTV/HLS 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.

File Purpose
hls-proxy.py HTTP reverse proxy that adds headers to upstream HLS requests
refresh-m3u.sh Scrapes source pages, extracts m3u8 URLs, writes M3U playlist
detect-headers.sh Auto-detects which HTTP headers a stream requires
channels.conf Your channel list (slug, name, logo, group, source URL)
┌──────────┐     ┌───────────┐     ┌──────────────┐     ┌──────────┐
│ Jellyfin │────▶│ hls-proxy │────▶│ upstream HLS │────▶│ segments │
│          │     │ :8089     │     │ server       │     │ (.ts)    │
└──────────┘     └───────────┘     └──────────────┘     └──────────┘
                  adds headers:
                  • User-Agent
                  • Referer
  1. refresh-m3u.sh generates an M3U file with stable /channel/<slug> URLs pointing to the proxy
  2. When Jellyfin requests /channel/sporttv1, the proxy scrapes a fresh m3u8 URL on the fly (cached for 1 hour)
  3. The proxy injects the required headers and rewrites the playlist so .ts segments also go through it
  4. The proxy auto-learns the correct Referer for each upstream host — no manual configuration needed

No more expired tokens — the M3U URLs never change, the proxy handles token refresh transparently.

  • Python 3.8+ (stdlib only, no pip packages)
  • bash, curl, grep (with PCRE / -P)
# 1. Clone
git clone https://github.com/pcruz1905/hls-restream-proxy.git
cd hls-restream-proxy

# 2. Configure channels
cp channels.conf.example channels.conf
# Edit channels.conf with your channels

# 3. Start the proxy
export HLS_PROXY_PORT=8089
export HLS_PROXY_REFERER="https://your-upstream-embed-domain.com/"
python3 hls-proxy.py &

# 4. Generate the M3U
export M3U_OUTPUT=/path/to/jellyfin/config/iptv.m3u
export HLS_PROXY_URL="http://YOUR_HOST_IP:8089"
bash refresh-m3u.sh

# 5. Add the M3U file as an M3U Tuner in your media server

channels.conf — one channel per line, pipe-delimited:

slug|Display Name|chno|logo_url|Group|source_page_url|mode|referer
  • mode: iframe (default) — page has an iframe whose embed contains the m3u8. direct — page itself contains the m3u8 URL.
  • referer: optional override for the Referer header when fetching the embed page.

See channels.conf.example for details.

User-level services are provided in systemd/:

# Copy units
cp systemd/*.service systemd/*.timer ~/.config/systemd/user/

# Edit paths in the service files, then:
systemctl --user daemon-reload
systemctl --user enable --now hls-proxy.service
systemctl --user enable --now refresh-m3u.timer

# Enable linger so services run without an active login session
sudo loginctl enable-linger $USER

The timer refreshes URLs every 4 hours by default (edit OnUnitActiveSec in the timer).

Docker / container media servers

If 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:

docker inspect <container> | grep Gateway
# Typically 172.x.0.1

Then set HLS_PROXY_URL=http://172.x.0.1:8089.

Finding the required headers (User-Agent & Referer)

Every streaming site is different. Here's how to figure out which headers your upstream needs:

Automatic detection (recommended)

Run 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:

./detect-headers.sh "https://streaming-site.com/channel.php"

Output:

=== HLS Header Detector ===

[1/4] Extracting iframe from page...
  iframe: https://embed-domain.com/embed/abc123
  embed host: https://embed-domain.com

[1/4] Extracting m3u8 from embed page...
  m3u8: https://cdn.example.com/hls/abc123.m3u8?s=token&e=123

[2/4] Testing header combinations on m3u8 playlist...
  403  no-UA
  200  UA
  200  UA+Ref(https://embed-domain.com/)

[3/4] Testing header combinations on .ts segments...
  403  no-UA
  403  UA
  200  UA+Ref(https://embed-domain.com/)

[4/4] Recommended configuration:
  User-Agent + Referer

  export HLS_PROXY_REFERER="https://embed-domain.com/"

You can also pass a direct m3u8 URL:

./detect-headers.sh "https://cdn.example.com/hls/stream.m3u8?token=xxx" --direct

If the auto-detector can't find the m3u8 (some sites use heavy JS), use browser DevTools:

Step 1: Open DevTools Network tab

  1. Open the streaming site in Chrome/Firefox
  2. Press F12Network tab
  3. Filter by m3u8 or media
  4. Play the stream — you'll see .m3u8 and .ts requests appear

Step 2: Find the m3u8 request

Click on the .m3u8 request and look at the Request Headers. Note down:

  • User-Agent — usually a standard browser UA
  • Referer — this is the key one, usually the embed/iframe domain (not the main site)
  • Origin — sometimes needed instead of Referer
# Get a fresh m3u8 URL from the Network tab, then test:

# Without headers (probably 403)
curl -o /dev/null -w "%{http_code}" "https://example.com/hls/stream.m3u8?token=xxx"

# With User-Agent only
curl -o /dev/null -w "%{http_code}" -A "Mozilla/5.0" "https://example.com/hls/stream.m3u8?token=xxx"

# With User-Agent + Referer
curl -o /dev/null -w "%{http_code}" -A "Mozilla/5.0" \
  -e "https://embed-domain.com/" \
  "https://example.com/hls/stream.m3u8?token=xxx"

Try each combination until you get 200. That tells you which headers are required.

Step 4: Test .ts segments too

The playlist (.m3u8) and segments (.ts) may need different headers. Grab a .ts URL from the playlist and repeat the curl test:

# Get a segment URL from the m3u8 content
curl -s -A "Mozilla/5.0" -e "https://embed-domain.com/" \
  "https://example.com/hls/stream.m3u8?token=xxx" | grep ".ts" | head -1

# Test that segment
curl -o /dev/null -w "%{http_code}" -A "Mozilla/5.0" \
  -e "https://embed-domain.com/" \
  "https://example.com/hls/segment-12345.ts"

Step 5: Configure the proxy

Once you know the required headers:

export HLS_PROXY_UA="Mozilla/5.0 ..."        # usually the default is fine
export HLS_PROXY_REFERER="https://embed-domain.com/"  # the iframe/embed host

Quick reference: common patterns

Site pattern Usually needs
Page → iframe → m3u8 Referer = iframe embed domain
Direct m3u8 with token User-Agent only
Cloudflare-protected User-Agent + Referer + sometimes Origin

Tip: find the iframe chain automatically

Most sites follow this pattern: page → iframe → embed page → m3u8

# Extract the iframe src
curl -sL -A "Mozilla/5.0" "https://streaming-site.com/channel.php" \
  | grep -oP 'iframe\s+src="\K[^"]+'

# That gives you the embed domain for the Referer
Variable Default Description
HLS_PROXY_PORT 8089 Proxy listen port
HLS_PROXY_BIND 127.0.0.1 Bind address (localhost only by default)
HLS_PROXY_UA Chrome UA string User-Agent sent to upstream
HLS_PROXY_REFERER (empty) Fallback Referer header (auto-learned from /channel/)
HLS_ALLOWED_IPS (empty) Comma-separated client IP allowlist (empty = all)
CHANNELS_CONF ./channels.conf Path to channel config file
HLS_CACHE_TTL 3600 Seconds to cache scraped m3u8 URLs (per channel)
M3U_OUTPUT /tmp/iptv.m3u Output M3U file path
HLS_PROXY_URL http://127.0.0.1:8089 Proxy URL written into M3U

MIT

联系我们 contact @ memedata.com