iptv-api
Public IPTV catalog service (Rust/loco.rs) — aggregates iptv-org into a health-checked channel catalog with EPG, served as read-only JSON.
Overview
iptv-api is a public IPTV catalog service built on
loco.rs 0.16 (axum + SeaORM + SQLite). It aggregates
the iptv-org hosted API into a
playable, health-checked channel catalog with EPG, served as public
read-only JSON with open CORS — the TV client (tv-web) calls it
directly from the browser.
Deployed at https://iptv-api.albertonline.org (Docker on the NixOS
VPS, behind Caddy).
Endpoints
| Route | What |
|---|---|
GET /api/iptv/channels?country=SE&category=news&q=text&online=true&page=1&per_page=50 | Filtered, paginated catalog ({items, total}) |
GET /api/iptv/countries | Distinct country codes + channel counts |
GET /api/iptv/categories | Distinct category ids + channel counts |
GET /api/iptv/epg/{ext_id}?from=&to= | Programmes overlapping [from, to) (epoch ms) |
GET /_ping, GET /_health | Loco built-ins |
Stack
- Rust + loco.rs 0.16 (axum + SeaORM + SQLite)
reqwestfor HTTP fetching (iptv-org API, XMLTV feeds)quick-xmlfor streaming XMLTV parsingtokiofor async runtime (50-way bounded concurrency for health checks)- SQLite via SeaORM (batched writes)
Data pipeline
Two loco tasks, run by the in-process scheduler (see
config/production.yaml):
iptv_refresh(daily 04:00): pullschannels/streams/blocklist/logos.jsonfrom iptv-org, joins them (src/iptv/catalog.rs— drops NSFW, closed, blocklisted and streamless channels, picks the highest-quality stream), replacesiptv_channels, then probes every stream URL (src/iptv/health.rs, bounded concurrency, GET + status code only) and storesonline / geo_blocked / timeout / errorper channel. Status survives a refresh when the stream URL is unchanged.iptv_epg(daily 04:30): fetches the XMLTV feeds insettings.epg_feeds(gzip supported, epgshare01 ids match iptv-org ids), stream-parses them (src/iptv/xmltv.rs, quick-xml) and replacesiptv_programmesfor channels present in the catalog.
Run manually: cargo loco task iptv_refresh / cargo loco task iptv_epg.
Develop
cargo loco start # server on :5150
cargo test # all suites (unit + request tests on sqlite)The monorepo package.json shim maps pnpm -F iptv-api test|build|lint
onto cargo. lint needs clippy (rustup toolchain); the NixOS host's
plain cargo runs tests only.
Architecture
The pure catalog logic (src/iptv/catalog.rs) is TDD'd independently of
the I/O layer — the join/filter/sort logic operates on in-memory vectors
and is unit-tested without a database. The health-check sweep
(src/iptv/health.rs) uses tokio::join with a bounded semaphore for
50-way concurrency over ~10.5k manifests. The EPG parser
(src/iptv/xmltv.rs) streams XMLTV with quick-xml (no full DOM
materialization) and remaps epgshare01 ids onto iptv-org ids via
normalize_xmltv_id.
Batched writes are essential: row-at-a-time inserts starved the 1-connection SQLite pool at 10k rows; the pipeline now batches via SeaORM's bulk insert.
Deploy
Docker on the NixOS VPS behind Caddy (iptv-api.albertonline.org):
docker build -t iptv-api apps/iptv-api
docker run -d --name iptv-api --restart unless-stopped \
-p 127.0.0.1:5150:5150 -v iptv-api-data:/data iptv-apiThe Dockerfile uses rust:1.95-slim for building and
debian:trixie-slim for the runtime image.
See the root README for monorepo-wide commands.