Skip to content

Latest commit

 

History

56 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GraceNote XMLTV Scraper

Generate XMLTV guide data from GraceNote/TMS listings for use with Jellyfin, Plex, Emby, TVHeadend, and other DVR/IPTV software.

Features

  • Scrapes 14 days of GraceNote/TMS program listings and outputs standard XMLTV format
  • Enriches programs with TMDB poster images, ratings, descriptions, and release dates
  • Enriches channel icons via the tv-logo/tv-logos project
  • Runs as a long-lived server with automatic 24-hour refresh, or as a one-shot scrape for cron jobs
  • First-run ZIP/postal-code setup with cable, satellite, and over-the-air lineup selection
  • Guide data cached on disk — fast restarts without re-scraping
  • Automatic XMLTV file rotation with 7-day retention
  • Optional Jellyfin Live TV integration with in-browser streaming
  • Optional channel filter to limit guide output to Jellyfin-available channels
  • Bonus: built-in retro TV guide web UI ("The Grid")

Jellyfin / Plex Setup

  1. Run the scraper in server mode (see below).
  2. Open http://<your-host>:8080/setup and choose your provider lineup.
  3. When the first guide finishes building, click the XMLTV guide URL on the setup page to copy it, or add this equivalent URL to your DVR software:
    http://<your-host>:8080/xmlguide.xmltv
    
  4. Guide data refreshes automatically every 24 hours.

Alternatively, use --guide-only mode with a cron job and point your DVR software at the local xmlguide.xmltv file.

Quick Start (Docker Compose)

  1. Clone the repo:

    git clone https://github.com/daniel-widrick/GraceNoteScraper.git
    cd GraceNoteScraper
  2. Copy the environment file for optional integrations:

    cp .env.example .env
    # Optionally add a TMDB token, Jellyfin settings, or legacy GN_* settings
  3. Start the container:

    docker compose up -d
  4. Open http://<your-host>:8080/setup, enter your ZIP or postal code, and choose a provider lineup.

  5. After the first guide finishes building, click the XMLTV guide URL shown on the setup page to copy it into your DVR software.

Setup, guide data, caches, and images are persisted in a Docker volume. The container restarts automatically and refreshes guide data every 24 hours.

To view logs:

docker compose logs -f

To rebuild after pulling updates:

docker compose up -d --build

Requirements

  • Docker and Docker Compose, or Go 1.27+ for building from source
  • (Optional) A TMDB API read access token for poster images and metadata

Building from Source

If you prefer to run without Docker:

go build -o gracenotescraper .
cp .env.example .env
# Edit .env
./gracenotescraper

Scrape-only mode

Scrapes once, writes xmlguide.xmltv to the working directory, and exits. Useful for cron-based setups where you don't need the server running.

Run server mode once to save a provider through /setup, or provide complete legacy GN_* settings, before using this mode.

./gracenotescraper --guide-only

Environment Variables

Variable Description Default
CONFIG_PATH Saved non-secret setup configuration config.json
GN_HEADEND Legacy/bootstrap GraceNote headend ID; use with GN_LINEUP and GN_ZIPCODE —
GN_LINEUP Legacy/bootstrap full lineup string —
GN_COUNTRY Country code USA
GN_ZIPCODE Legacy/bootstrap ZIP or postal code —
GN_LANGUAGE Language code en-us
GN_DEVICE Device identifier -
TMDB_TOKEN TMDB read access token (optional) —
BASE_URL Server base URL — rewrites XMLTV image URLs to use the built-in proxy cache (e.g. http://192.168.1.50:8080) —
PORT HTTP server port 8080
JELLYFIN_URL Jellyfin server URL (optional — enables live TV integration) —
JELLYFIN_API_KEY Jellyfin API key —
JELLYFIN_CHANNEL_FILTER Set to any non-empty value to filter guide to only Jellyfin-available channels. Requires JELLYFIN_URL and JELLYFIN_API_KEY. —

A saved CONFIG_PATH selection takes precedence over legacy GN_* settings. Delete or move that file if you intentionally want to bootstrap from environment settings again.

HTTP Endpoints

Endpoint Description
GET /setup Choose or change the active provider lineup
GET /api/setup/config Read the current non-secret lineup selection
GET /api/setup/providers?postalCode=... Find Gracenote lineups for an area
POST /api/setup/provider Save the selected provider and queue a fresh guide
GET /xmlguide.xmltv XMLTV guide data (point your DVR here)
GET /api/guide.json Guide data as JSON
GET /api/lineup.json Every channel position in the active provider lineup as JSON (see below)
GET / The Grid — built-in web UI
GET /img?url=... Image proxy with local cache
GET /api/livetv/config Returns {"enabled":true/false} — whether Jellyfin live TV is configured
GET /api/livetv/channels Proxies Jellyfin channel list (requires JELLYFIN_URL and JELLYFIN_API_KEY)
GET /api/livetv/tune?id=<channelId> Starts a live stream for the given channel and returns an HLS URL
POST /api/livetv/stop Forwards a playback-stop notification to Jellyfin to end a live stream

Lineup JSON

/api/lineup.json describes the provider lineup itself rather than the schedule: one entry per channel number, never collapsed, so a station carried at two numbers appears twice. It returns 503 with a Retry-After header until the first guide has been built.

{
  "generated": "2026-09-13T04:10:22Z",
  "source": {
    "providerName": "Local Over the Air Broadcast",
    "providerType": "OTA",
    "location": "",
    "lineupId": "USA-lineupId-DEFAULT",
    "headendId": "lineupId",
    "postalCode": "13490",
    "country": "USA",
    "device": "-",
    "language": "en-us"
  },
  "positions": [
    {
      "number": "2.1",
      "stationId": "53158",
      "placementId": "531580",
      "callSign": "WKTVDT",
      "affiliate": "NATIONAL BROADCASTING COMPANY",
      "affiliateCallSign": "",
      "filters": ["sports", "news"],
      "logoUrl": "https://raw.githubusercontent.com/tv-logo/tv-logos/main/countries/united-states/nbc-us.png"
    }
  ]
}

filters are Gracenote's own station tags with the filter- prefix removed. placementId is Gracenote's row identifier and is not stable across scrapes; use number plus stationId to identify a position. providerName, providerType, and location are filled from the saved setup when it matches the lineup the guide was built from.

The Grid

The server includes a built-in retro-styled TV guide web UI at the root URL. If no provider is configured, / redirects to /setup. Once configured, the guide auto-scrolls through your channel lineup and shows program details, posters, and metadata. Handy for a quick glance at what's on without opening your DVR app.

The Grid

Using as a Library

The grid download and conversion live in the scrape package, so another Go program can fetch a lineup's listings without running the server:

import (
    "context"
    "fmt"

    "github.com/daniel-widrick/GraceNoteScraper/scrape"
    "github.com/daniel-widrick/GraceNoteScraper/web"
)

func main() {
    prefs := web.Preferences{Country: "USA", ZipCode: "13490", Headend: "lineupId", LineupId: "USA-lineupId-DEFAULT", Device: "-", Language: "en-us"}
    g, err := scrape.Fetch(context.Background(), prefs, scrape.Options{Days: 1})
    if err != nil {
        panic(err)
    }
    fmt.Printf("%d stations, %d lineup positions, %d programs\n", len(g.Channels), len(g.Lineup), len(g.Programs))
}

Fetch returns a guide.TVGuide: Channels deduplicated by station, Lineup with every position, Programs, and Source. It pauses five seconds between grid requests by default and skips slots that fail; it returns scrape.ErrNoData only when every slot failed. Logo and TMDB enrichment are not part of the package. Lineup discovery by postal code is available through web.NewProviderClient().FindProviders.

The default grid client is web.NewClient, which retries failed requests and keeps a raw-response cache in a grid_cache/ directory under the working directory. Pass your own Options.Fetcher to change that.

Project Structure

appconfig/       Persisted non-secret provider configuration
main.go          Entry point, HTTP server, enrichment, image proxy
scrape/          Grid download loop and guide assembly (importable)
guide/           GraceNote data types and XMLTV conversion
web/             HTTP client for GraceNote API
tmdb/            TMDB client and cache
tvlogo/          TV logo resolver and cache
util/            Shared helpers
index.html       The Grid web UI (embedded at build time)
setup.html       Provider-selection UI (embedded at build time)
guide.tmpl       XMLTV output template (embedded at build time)

Portions of this project were developed with the assistance of generative AI (Claude).

About

Generate XMLTV guide data from GraceNote/TMS listings for use with Jellyfin, Plex, Emby, TVHeadend, and other DVR/IPTV software.

Resources

Stars

16 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages