Skip to main content
Dispatcharr provides comprehensive EPG (Electronic Program Guide) management with support for multiple sources, automatic matching, and custom guide generation.

Overview

The EPG system handles TV program schedules and metadata:
  • Multiple Sources: XMLTV URLs, Schedules Direct API, custom dummy EPG
  • Auto-Matching: Intelligent matching of EPG data to channels
  • Priority System: Handle conflicts when multiple sources have the same channel
  • Caching & Compression: Efficient storage of large XMLTV files
  • Real-Time Updates: WebSocket notifications during refresh operations

EPG Source Types

XMLTV

Standard XMLTV format from URLsSupports .xml, .gz, .zip compression

Schedules Direct

Premium EPG data via APIAccurate schedules for US/Canada

Dummy EPG

Auto-generated schedulesPerfect for channels without guide data

EPG Data Model

EPG Sources

class EPGSource:
    name = CharField(unique=True)
    source_type = CharField()  # xmltv, schedules_direct, dummy
    url = URLField()  # For XMLTV sources
    api_key = CharField()  # For Schedules Direct
    is_active = BooleanField()
    refresh_interval = IntegerField()  # Hours between refreshes
    priority = PositiveIntegerField()  # Higher = higher priority
    status = CharField()  # idle, fetching, parsing, success, error
    custom_properties = JSONField()  # Dummy EPG config

EPG Data

class EPGData:
    tvg_id = CharField(db_index=True)
    name = CharField()
    icon_url = URLField()
    epg_source = ForeignKey(EPGSource)
    
    # Unique per source + tvg_id
    class Meta:
        unique_together = ('tvg_id', 'epg_source')

Program Data

class ProgramData:
    epg = ForeignKey(EPGData)
    start_time = DateTimeField()
    end_time = DateTimeField()
    title = CharField()
    sub_title = TextField()
    description = TextField()
    custom_properties = JSONField()  # Categories, ratings, etc.

Setting Up EPG Sources

1

Add EPG Source

Navigate to Settings → EPG Sources and click “Add Source”
2

Choose Source Type

Select from XMLTV URL, Schedules Direct, or Dummy EPG
3

Configure Source

Provide the required credentials or URL:
  • XMLTV: Enter the URL to the XMLTV file
  • Schedules Direct: Enter API key and configure lineups
  • Dummy EPG: Configure regex patterns and schedules
4

Set Refresh Schedule

Configure automatic refresh interval (in hours)
5

Set Priority

Higher priority sources override lower priority ones when matching

XMLTV Sources

Supported Formats

Dispatcharr automatically handles compression:
  • Uncompressed XML: .xml files
  • Gzip Compressed: .gz files (recommended for bandwidth)
  • Zip Compressed: .zip archives
The system automatically detects compression format by inspecting file headers, so extensions aren’t strictly required.

Fetching & Parsing

The EPG refresh process:
# From epg/tasks.py
def refresh_epg_data(source_id):
    # Download with progress tracking
    response = requests.get(url, stream=True)
    total_size = int(response.headers.get('content-length', 0))
    
    # Send WebSocket updates during download
    for chunk in response.iter_content(chunk_size=8192):
        downloaded += len(chunk)
        progress = int((downloaded / total_size) * 100)
        send_epg_update(source_id, 'downloading', progress)
Real-time progress updates show download status in the UI.
Automatically detects and extracts:
  • Gzip files using Python’s gzip module
  • Zip archives using zipfile module
  • Stores extracted XML for parsing
Large XMLTV files can consume significant memory during decompression. The system uses chunked processing to minimize memory usage.
# Efficient streaming XML parser using lxml
for event, elem in etree.iterparse(xml_path):
    if elem.tag == 'channel':
        # Extract channel metadata
        tvg_id = elem.get('id')
        name = elem.find('display-name').text
        icon_url = elem.find('icon').get('src')
        
    elif elem.tag == 'programme':
        # Extract program data
        channel_id = elem.get('channel')
        start = parse_datetime(elem.get('start'))
        end = parse_datetime(elem.get('stop'))
        title = elem.find('title').text
        
    # Clear element to free memory
    elem.clear()
Streaming parser handles files with 100k+ programs efficiently.

Schedules Direct Integration

Setup

  1. Get API Credentials: Sign up at schedulesdirect.org
  2. Add Source: Create new EPG source with type “Schedules Direct”
  3. Configure Lineups: Select which lineups (cable/satellite providers) to import
  4. Refresh: Initial sync downloads guide data for configured lineups

Features

Rich Metadata

Episode numbers, air dates, ratings, categories

High Accuracy

Official guide data for US/Canada channels

Extended Data

14+ days of guide data available

Auto-Updates

Daily refresh keeps data current

Dummy EPG Generation

For channels without guide data, generate placeholder schedules:

Configuration

{
  "custom_properties": {
    "regex_patterns": [
      "^(Sports|ESPN)",  // Match channel names
      "Soccer.*Live"
    ],
    "default_duration": 3600,  // 1 hour blocks
    "timezone": "America/New_York",
    "program_template": {
      "title": "Live Programming",
      "description": "Scheduled programming"
    }
  }
}

How It Works

  1. Channel Matching: Regex patterns match channel names
  2. Schedule Generation: Creates repeating time blocks
  3. Custom Templates: Define program titles and descriptions
  4. Dynamic Updates: Regenerates on channel changes
Dummy EPG is perfect for 24/7 live channels where exact program information isn’t available or needed.

Auto-Matching Channels

Matching Strategies

Dispatcharr uses multiple strategies to match channels to EPG data:
1

TVG ID Match

Exact match on channel.tvg_id == epg_data.tvg_idMost reliable method when IDs are consistent
2

Name Match

Fuzzy matching on channel namesHandles variations like “CNN HD” vs “CNN”
3

Custom Mappings

Manual overrides for problem channelsStore in custom_properties for persistence

Running Auto-Match

# From channels/tasks.py
def match_epg_channels():
    """Match all unmatched channels to EPG data"""
    channels_without_epg = Channel.objects.filter(epg_data__isnull=True)
    
    for channel in channels_without_epg:
        # Try TVG ID match first
        if channel.tvg_id:
            epg_matches = EPGData.objects.filter(tvg_id=channel.tvg_id)
            
            # Use priority if multiple matches
            if epg_matches.exists():
                epg = epg_matches.order_by('-epg_source__priority').first()
                channel.epg_data = epg
                channel.save()
Run auto-matching from the Channels page → Actions → Match EPG, or configure automatic matching after EPG refresh.

Priority System

Handling Conflicts

When multiple EPG sources have data for the same channel:
class EPGSource:
    priority = PositiveIntegerField(default=0)
Example:
Channel: HBO
├── EPG Source A (Priority: 100) ← Selected
├── EPG Source B (Priority: 50)
└── EPG Source C (Priority: 10)
Higher priority sources override lower priority ones. Set priorities carefully to ensure the most accurate data is used.

Refresh Management

Automatic Refresh

class EPGSource:
    refresh_interval = IntegerField(default=0)  # Hours
    refresh_task = ForeignKey(PeriodicTask)  # Celery scheduled task
When refresh_interval > 0, a periodic task is created:
  • Celery Beat: Schedules refresh based on interval
  • Task Management: Automatically created/updated/deleted with source
  • Lock Prevention: Prevents concurrent refreshes of same source

Manual Refresh

Trigger refresh from UI or API:
POST /api/epg/sources/{id}/refresh/

Status Tracking

class EPGSource:
    STATUS_IDLE = 'idle'
    STATUS_FETCHING = 'fetching'
    STATUS_PARSING = 'parsing'
    STATUS_SUCCESS = 'success'
    STATUS_ERROR = 'error'
    
    status = CharField(choices=STATUS_CHOICES)
    last_message = TextField()  # Error details or success stats

Performance Optimization

Memory Management

Uses lxml’s iterparse for memory-efficient parsing:
for event, elem in etree.iterparse(xml_file):
    process_element(elem)
    elem.clear()  # Free memory immediately
    
    # Periodic garbage collection
    if progress % 1000 == 0:
        gc.collect()
Programs are inserted in batches:
programs_to_create = []
for program_data in parsed_programs:
    programs_to_create.append(ProgramData(**program_data))
    
    if len(programs_to_create) >= 1000:
        ProgramData.objects.bulk_create(programs_to_create)
        programs_to_create = []
Reduces database round-trips significantly.
Downloaded EPG files are cached:
cache_dir = MEDIA_ROOT/cached_epg/
cache_file = f"{source_id}.{extension}"
Subsequent parses use cached file if available.

Real-Time Progress

def send_epg_update(source_id, action, progress, **kwargs):
    send_websocket_update('updates', 'update', {
        'type': 'epg_refresh',
        'source': source_id,
        'action': action,  # downloading, parsing, parsing_programs
        'progress': progress,  # 0-100
        **kwargs
    })
WebSocket updates keep UI in sync during long refresh operations.

Export & Integration

XMLTV Export

Generate XMLTV file from Dispatcharr’s EPG data:
<?xml version="1.0" encoding="UTF-8"?>
<tv generator-info-name="Dispatcharr">
  <channel id="cnn">
    <display-name>CNN</display-name>
    <icon src="https://example.com/cnn.png" />
  </channel>
  <programme start="20260303120000 +0000" stop="20260303130000 +0000" channel="cnn">
    <title>Breaking News</title>
    <desc>Latest news coverage</desc>
  </programme>
</tv>

Xtream Codes EPG

Serve EPG data via Xtream Codes API:
{
  "epg_listings": [
    {
      "id": "12345",
      "epg_id": "cnn",
      "title": "Breaking News",
      "start": "2026-03-03 12:00:00",
      "end": "2026-03-03 13:00:00",
      "description": "Latest news coverage"
    }
  ]
}

Real-World Use Cases

Multi-Source Aggregation

EPG Setup:
├── XMLTV from Provider (Priority: 100)
│   └── Covers 80% of channels
├── Schedules Direct (Priority: 200)
│   └── Covers US channels with high accuracy
└── Dummy EPG (Priority: 10)
    └── Fallback for remaining channels

Result: Best available guide data for each channel

Custom TV Guide

Scenario: Build custom guide for curated channels

1. Import multiple XMLTV sources
2. Set priorities based on accuracy
3. Auto-match channels to EPG data
4. Export unified XMLTV for Plex/Jellyfin

Bandwidth Optimization

Large XMLTV: 50MB uncompressed
├── Download: 5MB gzipped (10x reduction)
├── Cache: Store compressed version
└── Parse: Stream from compressed file

Result: Minimal bandwidth and storage usage

Build docs developers (and LLMs) love