Dispatcharr/apps/vod/models.py
2025-08-08 08:06:28 -05:00

238 lines
10 KiB
Python

from django.db import models
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from apps.m3u.models import M3UAccount
from apps.channels.models import Logo
import uuid
class VODCategory(models.Model):
"""Categories for organizing VODs (e.g., Action, Comedy, Drama)"""
CATEGORY_TYPE_CHOICES = [
('movie', 'Movie'),
('series', 'Series'),
]
name = models.CharField(max_length=255)
category_type = models.CharField(
max_length=10,
choices=CATEGORY_TYPE_CHOICES,
default='movie',
help_text="Type of content this category contains"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'VOD Category'
verbose_name_plural = 'VOD Categories'
ordering = ['name']
unique_together = [('name', 'category_type')]
def __str__(self):
return f"{self.name} ({self.get_category_type_display()})"
class Series(models.Model):
"""Series information for TV shows"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
year = models.IntegerField(blank=True, null=True)
rating = models.CharField(max_length=10, blank=True, null=True)
genre = models.CharField(max_length=255, blank=True, null=True)
logo = models.ForeignKey(Logo, on_delete=models.SET_NULL, null=True, blank=True)
# Metadata IDs for deduplication
tmdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="TMDB ID for metadata", db_index=True)
imdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="IMDB ID for metadata", db_index=True)
# Additional metadata and properties
custom_properties = models.JSONField(blank=True, null=True, help_text='Additional metadata and properties for the series')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Series'
verbose_name_plural = 'Series'
ordering = ['name']
# Create unique constraint for deduplication
unique_together = [
('name', 'year', 'tmdb_id'),
('name', 'year', 'imdb_id'),
]
def __str__(self):
year_str = f" ({self.year})" if self.year else ""
return f"{self.name}{year_str}"
class Movie(models.Model):
"""Movie content"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
year = models.IntegerField(blank=True, null=True)
rating = models.CharField(max_length=10, blank=True, null=True)
genre = models.CharField(max_length=255, blank=True, null=True)
duration = models.IntegerField(blank=True, null=True, help_text="Duration in minutes")
logo = models.ForeignKey(Logo, on_delete=models.SET_NULL, null=True, blank=True)
# Metadata IDs for deduplication
tmdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="TMDB ID for metadata", db_index=True)
imdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="IMDB ID for metadata", db_index=True)
# Additional metadata and properties
custom_properties = models.JSONField(blank=True, null=True, help_text='Additional metadata and properties for the movie')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Movie'
verbose_name_plural = 'Movies'
ordering = ['name']
# Create unique constraint for deduplication
unique_together = [
('name', 'year', 'tmdb_id'),
('name', 'year', 'imdb_id'),
]
def __str__(self):
year_str = f" ({self.year})" if self.year else ""
return f"{self.name}{year_str}"
class Episode(models.Model):
"""Episode content for TV series"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
air_date = models.DateField(blank=True, null=True)
rating = models.CharField(max_length=10, blank=True, null=True)
duration = models.IntegerField(blank=True, null=True, help_text="Duration in minutes")
# Episode specific fields
series = models.ForeignKey(Series, on_delete=models.CASCADE, related_name='episodes')
season_number = models.IntegerField(blank=True, null=True)
episode_number = models.IntegerField(blank=True, null=True)
# Metadata IDs
tmdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="TMDB ID for metadata", db_index=True)
imdb_id = models.CharField(max_length=50, blank=True, null=True, help_text="IMDB ID for metadata", db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Episode'
verbose_name_plural = 'Episodes'
ordering = ['series__name', 'season_number', 'episode_number']
unique_together = [
('series', 'season_number', 'episode_number'),
]
def __str__(self):
season_ep = f"S{self.season_number or 0:02d}E{self.episode_number or 0:02d}"
return f"{self.series.name} - {season_ep} - {self.name}"
# New relation models to link M3U accounts with VOD content
class M3USeriesRelation(models.Model):
"""Links M3U accounts to Series with provider-specific information"""
m3u_account = models.ForeignKey(M3UAccount, on_delete=models.CASCADE, related_name='series_relations')
series = models.ForeignKey(Series, on_delete=models.CASCADE, related_name='m3u_relations')
category = models.ForeignKey(VODCategory, on_delete=models.SET_NULL, null=True, blank=True)
# Provider-specific fields - renamed to avoid clash with series ForeignKey
external_series_id = models.CharField(max_length=255, help_text="External series ID from M3U provider")
custom_properties = models.JSONField(blank=True, null=True, help_text="Provider-specific data")
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_episode_refresh = models.DateTimeField(blank=True, null=True, help_text="Last time episodes were refreshed")
class Meta:
verbose_name = 'M3U Series Relation'
verbose_name_plural = 'M3U Series Relations'
unique_together = [('m3u_account', 'external_series_id')]
def __str__(self):
return f"{self.m3u_account.name} - {self.series.name}"
class M3UMovieRelation(models.Model):
"""Links M3U accounts to Movies with provider-specific information"""
m3u_account = models.ForeignKey(M3UAccount, on_delete=models.CASCADE, related_name='movie_relations')
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='m3u_relations')
category = models.ForeignKey(VODCategory, on_delete=models.SET_NULL, null=True, blank=True)
# Streaming information (provider-specific)
url = models.URLField(max_length=2048)
stream_id = models.CharField(max_length=255, help_text="External stream ID from M3U provider")
container_extension = models.CharField(max_length=10, blank=True, null=True)
# Provider-specific data
custom_properties = models.JSONField(blank=True, null=True, help_text="Provider-specific data like quality, language, etc.")
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_advanced_refresh = models.DateTimeField(blank=True, null=True, help_text="Last time advanced data was fetched from provider")
class Meta:
verbose_name = 'M3U Movie Relation'
verbose_name_plural = 'M3U Movie Relations'
unique_together = [('m3u_account', 'stream_id')]
def __str__(self):
return f"{self.m3u_account.name} - {self.movie.name}"
def get_stream_url(self):
"""Get the full stream URL for this movie from this provider"""
return self.url
class M3UEpisodeRelation(models.Model):
"""Links M3U accounts to Episodes with provider-specific information"""
m3u_account = models.ForeignKey(M3UAccount, on_delete=models.CASCADE, related_name='episode_relations')
episode = models.ForeignKey(Episode, on_delete=models.CASCADE, related_name='m3u_relations')
# Streaming information (provider-specific)
stream_id = models.CharField(max_length=255, help_text="External stream ID from M3U provider")
container_extension = models.CharField(max_length=10, blank=True, null=True)
# Provider-specific data
custom_properties = models.JSONField(blank=True, null=True, help_text="Provider-specific data like quality, language, etc.")
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'M3U Episode Relation'
verbose_name_plural = 'M3U Episode Relations'
unique_together = [('m3u_account', 'stream_id')]
def __str__(self):
return f"{self.m3u_account.name} - {self.episode}"
def get_stream_url(self):
"""Get the full stream URL for this episode from this provider"""
from core.xtream_codes import Client as XtreamCodesClient
if self.m3u_account.account_type == 'XC':
# For XtreamCodes accounts, build the URL dynamically
server_url = self.m3u_account.server_url.rstrip('/')
username = self.m3u_account.username
password = self.m3u_account.password
return f"{server_url}/series/{username}/{password}/{self.stream_id}.{self.container_extension or 'mp4'}"
else:
# We might support non XC accounts in the future
# For now, return None
return None