If you self-host live TV, you've met the gap tvarr fills. You have a couple of IPTV sources in different formats, each with its own playlist, its own EPG, its own quirks, and a client that wants exactly one tidy M3U and one tidy guide. Stitching those together by hand is miserable, and the tools I tried either did too much or assumed a workflow that wasn't mine.
So tvarr is a self-hosted relay that sits between your sources and your clients. It aggregates M3U and Xtream inputs, merges the EPG data, and generates a single unified playlist. Point your player at tvarr instead of at five different providers, and it sees one coherent channel list. The aggregation is the easy half, though. The interesting half is what happens to the actual streams.
One origin, many players
Most IPTV origins allow a single connection per channel. But you might have a phone on HLS, a TV on MPEG-TS and a browser on DASH all wanting that same channel at once. Open one upstream connection per client and you trip the limit, and waste the bandwidth, immediately.
So tvarr holds one origin connection per channel and fans it out through a shared buffer. Ingest probes the source (HLS, DASH, RTSP or raw MPEG-TS) and demuxes it down to codec-agnostic elementary streams, the raw H.264 or H.265 video and AAC or AC3 audio with their timing intact, written into a per-channel SharedESBuffer. Output processors then mux those same elementary streams into whatever each client actually needs: HLS-TS, HLS-fMP4, DASH or raw TS. One fetch upstream, many formats downstream, and the whole thing tears itself down when the last client disconnects.
If a client needs a codec the source doesn't carry, transcoding happens on demand and once. A single FFmpeg transcode produces, say, a VP9/Opus variant that lands back in the same buffer, and every client that wants that variant is served from it. No per-client transcode, no pre-transcoding a library you might never watch.
Distributed transcoding over gRPC
Transcoding is the expensive part, so tvarr can push it off the box doing the relaying entirely. A separate worker daemon, ffmpegd, connects to tvarr (the coordinator) over gRPC, detects its own FFmpeg capabilities (which encoders, which GPUs) and registers them. Run one embedded locally, or a pool across machines: a couple of GPU boxes for NVENC and VAAPI, a spare CPU server for overflow.
When a variant is needed, the coordinator builds a job and picks a worker on what actually matters: does it have the required encoder, does it have a free job slot, and is there a GPU going spare (preferred over CPU). The job is dispatched down a gRPC stream, the worker spawns FFmpeg, and the transcoded elementary streams come back into the shared buffer. If a worker drops off mid-stream its jobs are reassigned to another. A low-power machine can serve the playlist and the UI while the heavy lifting happens somewhere with a graphics card.
Filters and data mapping
The other half of taming five providers' worth of inconsistent metadata is rules, and tvarr uses an expression language rather than a pile of checkboxes. There are two kinds, applied in order. Data mapping runs first and rewrites messy fields; filters then include or exclude channels on the now-clean values, so you tidy before you decide.
# data mapping (first): normalise the mess
SET group_title = "UK Sports" where group_title matches ".*UK.*Sport.*"
# filters (second): include/exclude on the clean values
INCLUDE group_title contains "Sports"
EXCLUDE channel_name matches ".*(Test|Backup|XXX).*"
Map the mess into shape, then filter on the result, and a tidy, predictable playlist falls out of a heap of inconsistent sources. Filters apply to EPG entries too, not just channels.
It runs as a single container with a web UI for managing sources, rules and output playlists:
docker run -d -p 8080:8080 -v tvarr-data:/data \
-e TVARR_SERVER_BASE_URL=http://your-host:8080 \
ghcr.io/jmylchreest/tvarr:release
If you've used Threadfin or StreamMaster it lives in the same neighbourhood; this is just my take on the shape of the problem. It's early and moving quickly. Repo and docs are on GitHub; if it's useful, brilliant, and if it isn't quite right, open an issue.
