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 metadata Direct streaming URLs
Series TV shows with hierarchical structure Seasons → Episodes
Episodes Individual episodes of series Season/episode numbering
Categories Genre and type organization Movie 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:
Connect M3U Account
Add Xtream Codes account with credentials
Fetch VOD Categories
Retrieve available movie and series categories
Enable Categories
Select which categories to import
Sync Content
Import movies and series with metadata:
Basic info from provider
TMDB/IMDB IDs if available
Categories and organization
Fetch Episode Data
For series, retrieve season/episode structure
Deduplication Strategy
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.
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:
During VOD sync, last_seen is updated
Content not seen in recent sync is marked stale
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
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.