Skip to main content
Dispatcharr provides comprehensive VOD management for movies and TV series, with support for metadata enrichment and multi-provider content aggregation.

Overview

The VOD system manages on-demand content from Xtream Codes providers:
  • Movies: Full-length films with metadata
  • Series: TV shows with seasons and episodes
  • Categories: Organize content by genre, type, or custom categories
  • Metadata Integration: TMDB and IMDB ID support for deduplication
  • Multi-Provider: Aggregate content from multiple M3U accounts
  • Stream Management: Provider-specific streaming information

Content Structure

Movies

Individual films with metadataDirect streaming URLs

Series

TV shows with hierarchical structureSeasons → Episodes

Episodes

Individual episodes of seriesSeason/episode numbering

Categories

Genre and type organizationMovie vs Series separation

Data Model

Movies

class Movie:
    uuid = UUIDField(unique=True)
    name = CharField(max_length=255)
    description = TextField()
    year = IntegerField()
    rating = CharField(max_length=10)  # PG-13, R, etc.
    genre = CharField()  # Action, Drama, etc.
    duration_secs = IntegerField()
    logo = ForeignKey(VODLogo)
    
    # Metadata for deduplication
    tmdb_id = CharField(unique=True)
    imdb_id = CharField(unique=True)
    custom_properties = JSONField()  # Additional metadata

Series

class Series:
    uuid = UUIDField(unique=True)
    name = CharField(max_length=255)
    description = TextField()
    year = IntegerField()
    rating = CharField()
    genre = CharField()
    logo = ForeignKey(VODLogo)
    
    # Metadata IDs
    tmdb_id = CharField(unique=True)
    imdb_id = CharField(unique=True)
    custom_properties = JSONField()

Episodes

class Episode:
    uuid = UUIDField(unique=True)
    name = CharField(max_length=255)
    description = TextField()
    air_date = DateField()
    rating = CharField()
    duration_secs = IntegerField()
    
    # Hierarchy
    series = ForeignKey(Series)
    season_number = IntegerField()
    episode_number = IntegerField()
    
    # Metadata
    tmdb_id = CharField(db_index=True)
    imdb_id = CharField(db_index=True)
    custom_properties = JSONField()
    
    class Meta:
        unique_together = [('series', 'season_number', 'episode_number')]

Provider Relations

VOD content is linked to M3U providers through relation models:

Why Relations?

The relation model separates global content from provider-specific data:
Same movie from multiple providers = one Movie record:
Movie: "The Matrix" (1999)
├── M3UMovieRelation: Provider A
│   ├── stream_id: "12345"
│   └── container_extension: "mp4"
├── M3UMovieRelation: Provider B
│   ├── stream_id: "67890"
│   └── container_extension: "mkv"
└── Unified metadata from TMDB
Benefits:
  • Single source of metadata truth
  • Automatic failover between providers
  • Simplified content management
Each relation stores provider-specific information:
class M3UMovieRelation:
    m3u_account = ForeignKey(M3UAccount)
    movie = ForeignKey(Movie)
    stream_id = CharField()  # Provider's ID
    container_extension = CharField()  # mp4, mkv, avi
    category = ForeignKey(VODCategory)
    custom_properties = JSONField()  # Quality, language, etc.
Allows different categories, qualities, and metadata per provider.

Movie Relations

class M3UMovieRelation:
    m3u_account = ForeignKey(M3UAccount)
    movie = ForeignKey(Movie)
    category = ForeignKey(VODCategory)
    
    # Streaming info
    stream_id = CharField()  # Provider's stream ID
    container_extension = CharField()  # mp4, mkv, etc.
    
    # Provider data
    custom_properties = JSONField()
    
    # Timestamps
    last_seen = DateTimeField()  # Stale detection
    last_advanced_refresh = DateTimeField()  # Metadata refresh
    
    def get_stream_url(self):
        """Build stream URL for Xtream Codes provider"""
        if self.m3u_account.account_type == 'XC':
            return f"{base_url}/movie/{username}/{password}/{self.stream_id}.{self.container_extension}"

Series Relations

class M3USeriesRelation:
    m3u_account = ForeignKey(M3UAccount)
    series = ForeignKey(Series)
    category = ForeignKey(VODCategory)
    external_series_id = CharField()  # Provider's series ID
    last_episode_refresh = DateTimeField()  # When episodes were last synced

Episode Relations

class M3UEpisodeRelation:
    m3u_account = ForeignKey(M3UAccount)
    episode = ForeignKey(Episode)
    series_relation = ForeignKey(M3USeriesRelation)  # Parent series
    stream_id = CharField()  # Provider's episode stream ID
    container_extension = CharField()
    
    def get_stream_url(self):
        """Build stream URL for episode"""
        if self.m3u_account.account_type == 'XC':
            return f"{base_url}/series/{username}/{password}/{self.stream_id}.{self.container_extension}"

Categories

Organize VOD content into categories:
class VODCategory:
    name = CharField()
    category_type = CharField()  # 'movie' or 'series'
    
    class Meta:
        unique_together = [('name', 'category_type')]

Category Types

Movie Categories

Action, Comedy, Drama, Horror, Sci-Fi, etc.

Series Categories

TV Shows, Documentaries, Anime, Kids, etc.

Provider Category Mapping

class M3UVODCategoryRelation:
    m3u_account = ForeignKey(M3UAccount)
    category = ForeignKey(VODCategory)
    enabled = BooleanField(default=False)
    custom_properties = JSONField()
Control which categories are enabled per provider:
Provider A:
├── Action Movies (enabled)
├── Comedy Movies (enabled)
└── Horror Movies (disabled)

Provider B:
├── Action Movies (enabled)
├── Drama Movies (enabled)
└── Sci-Fi Movies (enabled)

Content Import

Xtream Codes VOD Sync

Import VOD content from Xtream Codes providers:
1

Connect M3U Account

Add Xtream Codes account with credentials
2

Fetch VOD Categories

Retrieve available movie and series categories
3

Enable Categories

Select which categories to import
4

Sync Content

Import movies and series with metadata:
  • Basic info from provider
  • TMDB/IMDB IDs if available
  • Categories and organization
5

Fetch Episode Data

For series, retrieve season/episode structure

Deduplication Strategy

When TMDB or IMDB ID is present:
# Check for existing movie with same metadata ID
existing_movie = Movie.objects.filter(
    Q(tmdb_id=tmdb_id) | Q(imdb_id=imdb_id)
).first()

if existing_movie:
    # Create relation to existing movie
    M3UMovieRelation.objects.create(
        movie=existing_movie,
        m3u_account=account,
        stream_id=provider_stream_id
    )
Ensures global uniqueness across providers.
When no metadata IDs available:
# Fallback to name + year matching
existing_movie = Movie.objects.filter(
    name=movie_name,
    year=release_year,
    tmdb_id__isnull=True,
    imdb_id__isnull=True
).first()
Name+year uniqueness only enforced when no external IDs present to allow flexibility.

Metadata Management

TMDB Integration

class Movie:
    tmdb_id = CharField(unique=True)
    
class Series:
    tmdb_id = CharField(unique=True)
    
class Episode:
    tmdb_id = CharField(db_index=True)
Use Cases:
  • Enrichment: Fetch additional metadata from TMDB
  • Deduplication: Match content across providers
  • Organization: Group content by metadata

IMDB Integration

class Movie:
    imdb_id = CharField(unique=True)
    
class Series:
    imdb_id = CharField(unique=True)
    
class Episode:
    imdb_id = CharField(db_index=True)
Benefits:
  • Cross-reference with other services
  • Ratings and reviews integration
  • Enhanced metadata accuracy

Custom Properties

class Movie:
    custom_properties = JSONField()
    
# Example data:
{
    "quality": "4K",
    "audio": ["English 5.1", "Spanish 2.0"],
    "subtitles": ["English", "Spanish", "French"],
    "file_size_mb": 15360,
    "provider_rating": 8.5
}
Use custom properties to store provider-specific metadata that doesn’t fit standard fields.

Streaming & Playback

Stream URL Generation

For Xtream Codes providers, URLs are built dynamically:
# Movie URL format
http://server:port/movie/{username}/{password}/{stream_id}.{extension}

# Episode URL format  
http://server:port/series/{username}/{password}/{stream_id}.{extension}

# Example
http://provider.com:8080/movie/user123/pass456/12345.mp4

Container Extensions

class M3UMovieRelation:
    container_extension = CharField()  # mp4, mkv, avi, etc.
Common formats:
  • mp4: H.264/H.265, broad compatibility
  • mkv: Matroska, high quality
  • avi: Legacy format
  • ts: Transport stream

Advanced Features

Stale Content Detection

class M3UMovieRelation:
    last_seen = DateTimeField()
    
class M3USeriesRelation:
    last_seen = DateTimeField()
    
class M3UEpisodeRelation:
    last_seen = DateTimeField()
How it works:
  1. During VOD sync, last_seen is updated
  2. Content not seen in recent sync is marked stale
  3. Stale content can be auto-cleaned or flagged
Configure stale detection timeframe carefully to avoid accidentally removing content during provider outages.

Episode Refresh

class M3USeriesRelation:
    last_episode_refresh = DateTimeField()
Track when episodes were last synced:
  • Initial Import: Fetches all episodes
  • Incremental Update: Only new episodes
  • Full Refresh: Re-sync entire series

Category Management

class VODCategory:
    @classmethod
    def bulk_create_and_fetch(cls, objects, ignore_conflicts=False):
        """Efficiently create categories in bulk"""
        cls.objects.bulk_create(objects, ignore_conflicts=ignore_conflicts)
        # Fetch created objects for relation mapping
        return cls.objects.filter(...)
Optimized bulk operations for large content imports.

Real-World Use Cases

Multi-Provider Content Aggregation

Movie: "Inception" (2010)
├── Provider A: 1080p MP4 (Action category)
├── Provider B: 4K MKV (Sci-Fi category)
└── Provider C: 720p MP4 (Thriller category)

Result: Single movie with multiple streaming options
Failover: Automatic switch if one provider fails

Series Management

Series: "Breaking Bad"
├── Season 1 (13 episodes)
│   ├── Episode 1: Provider A, Provider B
│   ├── Episode 2: Provider A, Provider B
│   └── ...
├── Season 2 (13 episodes)
└── Season 3 (13 episodes)

Total: 3 seasons, 39 episodes
Providers: 2 sources with automatic failover

Category-Based Access Control

User Profile: Family Account
├── Enabled Categories:
│   ├── Family Movies
│   ├── Kids Series
│   └── Documentaries
└── Disabled Categories:
    ├── Horror Movies
    ├── Adult Content
    └── Violent Series

Result: Filtered content based on user preferences

Metadata Enrichment Workflow

1. Import movie from provider (basic info only)
2. Detect TMDB ID in provider metadata
3. Fetch full metadata from TMDB:
   - High-res posters
   - Plot summaries
   - Cast & crew
   - User ratings
4. Store in custom_properties
5. Display enriched content in UI

API Operations

List Movies

GET /api/vod/movies/Filter by category, year, rating

List Series

GET /api/vod/series/Browse TV shows with metadata

Get Episodes

GET /api/vod/series/{id}/episodes/Retrieve all episodes for a series

Stream URLs

GET /api/vod/movies/{id}/stream/Get streaming URL for playback

Bulk Operations

  • Sync VOD: Refresh content from all M3U accounts
  • Clean Stale: Remove content not seen recently
  • Update Metadata: Bulk refresh from TMDB/IMDB
  • Category Management: Enable/disable categories
VOD operations can be long-running. Use WebSocket updates to monitor progress in real-time.