Skip to content

hdhr-guide

A lightweight Docker container that queries an HDHomeRun tuner for its DeviceAuth, downloads the XMLTV guide from the Silicondust API, and serves it over HTTP for Jellyfin. It's the guide provider for the Live TV setup on the Jellyfin page.

Zero config beyond the HDHomeRun URL. Auto-refreshes every 12 hours (configurable) with the random jitter the API requires, re-queries DeviceAuth before each pull, and runs on a ~50 MB Alpine image.

Compose

hdhr-guide:
  image: ghcr.io/wildium/hdhr-guide:latest
  container_name: hdhr-guide
  restart: unless-stopped
  environment:
    - HDHR_URL=${HDHR_URL}
    - PORT=${HDHR_GUIDE_PORT:-9000}
    - REFRESH_HOURS=${HDHR_GUIDE_REFRESH_HOURS:-12}

.env:

HDHR_URL=http://192.168.4.34/discover.json

Find your tuner URL by opening http://hdhomerun.local/discover.json — the response contains DeviceID and DeviceAuth.

The guide is served at http://hdhr-guide:9000/xmlguide.xmltv, reachable to any container on the same Docker network.

Jellyfin side

  1. Dashboard → Live TV
  2. Guide Data Providers → add a provider of type XmlTV
  3. XMLTV URL: http://hdhr-guide:9000/xmlguide.xmltv
  4. Refresh Guide Data

Guide data window

2 days of guide data for free, 14 days with an HDHomeRun DVR subscription. DeviceAuth is valid 16–24 hours, so keep REFRESH_HOURS between 6 and 20.

When you'd pick the zap2it scrape instead

The Silicondust API gives 2 days of guide data free, 14 days with a DVR subscription, and DeviceAuth is only valid 16–24 hours. If you want a longer window, or you do not have a DVR subscription, a zap2it scraper is the alternative — it does not depend on DeviceAuth at all. I went with this container because the subscription is already paid and the footprint is tiny.

Publishing to GHCR

The image is ghcr.io/wildium/hdhr-guide. Worth knowing if you build and push your own: gh auth login produces an OAuth token starting with gho_, and GHCR rejects that for registry operations with denied / invalid token. Being a member of the owning org is not sufficient — the token type is the blocker. You need a classic PAT with read:packages (and write:packages to push), or a fine-grained PAT with Packages read-and-write on the repo.

See also