albertonline.org docs
Apps

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

RouteWhat
GET /api/iptv/channels?country=SE&category=news&q=text&online=true&page=1&per_page=50Filtered, paginated catalog ({items, total})
GET /api/iptv/countriesDistinct country codes + channel counts
GET /api/iptv/categoriesDistinct category ids + channel counts
GET /api/iptv/epg/{ext_id}?from=&to=Programmes overlapping [from, to) (epoch ms)
GET /_ping, GET /_healthLoco built-ins

Stack

  • Rust + loco.rs 0.16 (axum + SeaORM + SQLite)
  • reqwest for HTTP fetching (iptv-org API, XMLTV feeds)
  • quick-xml for streaming XMLTV parsing
  • tokio for 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): pulls channels/streams/blocklist/logos.json from iptv-org, joins them (src/iptv/catalog.rs — drops NSFW, closed, blocklisted and streamless channels, picks the highest-quality stream), replaces iptv_channels, then probes every stream URL (src/iptv/health.rs, bounded concurrency, GET + status code only) and stores online / geo_blocked / timeout / error per channel. Status survives a refresh when the stream URL is unchanged.
  • iptv_epg (daily 04:30): fetches the XMLTV feeds in settings.epg_feeds (gzip supported, epgshare01 ids match iptv-org ids), stream-parses them (src/iptv/xmltv.rs, quick-xml) and replaces iptv_programmes for 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-api

The Dockerfile uses rust:1.95-slim for building and debian:trixie-slim for the runtime image.

See the root README for monorepo-wide commands.

On this page