tvtimes

The TV guide that runs on your hardware.

Point it at your M3U playlists, Xtream panels, Stalker portals and HDHomeRun tuners. Get one colourful, set-top-box-style guide — logos, release years, a cinematic TMDB panel — served from a single container on your home lab. No account on someone else's server.

it is · the guide already knows what's on
The tvtimes guide grid: BBC One, BBC Two, STV, Channel 4, Channel 5 and ITV2 with channel logos, colour-coded programme cells, release years, and a bright magenta now-line running down the current time.

01 the guide

A grid you'd actually leave open.

Virtualised both ways, so thousands of channels scroll at 60fps. Cells are coloured by genre, the now-line glows down the current minute, and a long film keeps its title pinned as you scroll past it. On a phone it folds into a single-channel agenda.

  • Live now-line — with a per-channel offset you nudge from a programme, to line up an East/West feed pair.
  • Full keyboard nav — arrows, PgUp/Dn, Home jumps to now.
  • Installable PWA — offline shell, and an in-tab prompt when a new version lands.
  • Drag to reorder sources — the guide lists channels in that order.
  • Search the whole line-up — every channel, two weeks out, in guide order.
  • Watchlist reminders — flag one airing or a whole title; an email lands ~15 min before it starts.
  • Push notifications — reminders, source-health alerts and opt-in activity pings to Gotify, ntfy, Discord, Telegram, Pushover… via Apprise, alongside email.
A guide view filtered to Turner Classic Movies showing TCM US East and TCM US West as separate rows with the same films offset in time, plus a hover tooltip reading The Godfather.

02 sources

Everything you already watch, in one lineup.

The same source types the tvdinner CLI supports — brought in behind an SSRF guard, refreshed on a schedule by the background worker, credentials encrypted at rest.

M3UPlaylists & M3U8x-tvg-url EPG
XCXtream Codescreds never persisted in stream refs
STKStalker portalsMAC encrypted · links resolved lazily
HDHRHDHomeRunnative discovery · SiliconDust guide
Connector agentoutbound-only, for tuners on another network

And back out again: turn on export feeds and tvtimes serves the merged line-up as one M3U playlist + XMLTV guide — times already zone-corrected per channel — behind a rotatable token. Drop the two URLs into Jellyfin, Plex, Emby, TiviMate or Threadfin.

Or just press Play on a programme and that one channel opens straight in a player — tvdinner on desktop, the system app chooser on Android, or Download .m3u for anything else — no export feed needed. On a Fire TV Stick it just works in the built-in Silk browser: Play hands the channel over to MX Player Pro, nothing sideloaded.

03 enrichment

Click a film. Get the whole story.

Add your own TMDB key and a movie-category programme opens into a cinematic panel: textless backdrop, title logo, ★ rating, genres, director, top cast and synopsis — matched carefully so a wrong result never shows the wrong art.

  • Channel logos — from the playlist, else the iptv-org database, else the SiliconDust guide for HDHomeRun.
  • Global cache — enrichment is public data, cached 30 days, negatives cached but failures never.
  • Release year & runtime — on the cell and in the panel.
The tvtimes programme panel for Foreign Correspondent: a black-and-white backdrop, the film's title logo, a 7.0 TMDB rating, Thriller / Mystery / Action genre chips, 'Directed by Alfred Hitchcock', the cast list, and the full synopsis.

04 yours to run

One image. One compose up.

A single multi-arch image carries the API, the worker and the web app; it serves the SPA from the same origin, so there's no CORS and the session cookie is first-party. Postgres, Redis and the generated secrets live in named volumes.

~/tvtimes
# grab the compose file and a starter .env, then:
docker compose up -d
# open the URL you set as TVTIMES_PUBLIC_ORIGIN — default :8888

Passkeys first

WebAuthn primary, Argon2id + HIBP password fallback, TOTP, 60-day rotating sessions with replay detection.

Secrets on first run

The signing key and the at-rest encryption key are generated into a volume and never regenerated.

Migrations on start

alembic upgrade head runs before the API serves; docker compose pull is the whole upgrade.

amd64 & arm64

Runs on a NUC or a Pi. Published to Docker Hub and mirrored to GHCR on every v* tag.

05 under the hood

Boring where it counts.

Backend
FastAPI · SQLAlchemy 2.0 async · Alembic · arq worker · structlog with credential redaction
Data
Postgres 16 · Redis 7 · streaming XMLTV parse · conditional GET · rolling −1d…+14d window
Frontend
React 18 · Vite · TypeScript · TanStack Query · react-virtual · typed client from the OpenAPI schema
Security
SSRF guard on every user URL · Fernet at rest · EdDSA access JWT · double-submit CSRF · strict CSP
Tests
ruff + mypy --strict + eslint + tsc in CI, plus a full image build on every PR