From 24f876d09f0bd90996eac48054d03cceee3ca03e Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Wed, 20 Aug 2025 17:38:21 -0500 Subject: [PATCH] Add priority for providers so VOD's can be auto selected based on the priority. --- apps/m3u/admin.py | 1 + .../migrations/0016_m3uaccount_priority.py | 18 ++++++++++++ apps/m3u/models.py | 4 +++ apps/m3u/serializers.py | 3 +- apps/output/views.py | 10 ++++--- apps/proxy/vod_proxy/views.py | 29 +++++++++++++++---- apps/vod/api_views.py | 8 ++--- frontend/src/components/forms/M3U.jsx | 13 +++++++++ 8 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 apps/m3u/migrations/0016_m3uaccount_priority.py diff --git a/apps/m3u/admin.py b/apps/m3u/admin.py index 5ddce119..dd5986eb 100644 --- a/apps/m3u/admin.py +++ b/apps/m3u/admin.py @@ -18,6 +18,7 @@ class M3UAccountAdmin(admin.ModelAdmin): "server_url", "server_group", "max_streams", + "priority", "is_active", "user_agent_display", "uploaded_file_link", diff --git a/apps/m3u/migrations/0016_m3uaccount_priority.py b/apps/m3u/migrations/0016_m3uaccount_priority.py new file mode 100644 index 00000000..55e0e95b --- /dev/null +++ b/apps/m3u/migrations/0016_m3uaccount_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-20 22:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('m3u', '0015_alter_m3ufilter_options_m3ufilter_custom_properties'), + ] + + operations = [ + migrations.AddField( + model_name='m3uaccount', + name='priority', + field=models.PositiveIntegerField(default=0, help_text='Priority for VOD provider selection (higher numbers = higher priority). Used when multiple providers offer the same content.'), + ), + ] diff --git a/apps/m3u/models.py b/apps/m3u/models.py index 2a7846c6..cfcc3646 100644 --- a/apps/m3u/models.py +++ b/apps/m3u/models.py @@ -94,6 +94,10 @@ class M3UAccount(models.Model): default=7, help_text="Number of days after which a stream will be removed if not seen in the M3U source.", ) + priority = models.PositiveIntegerField( + default=0, + help_text="Priority for VOD provider selection (higher numbers = higher priority). Used when multiple providers offer the same content.", + ) def __str__(self): return self.name diff --git a/apps/m3u/serializers.py b/apps/m3u/serializers.py index b963db7b..1a62080b 100644 --- a/apps/m3u/serializers.py +++ b/apps/m3u/serializers.py @@ -1,5 +1,5 @@ from core.utils import validate_flexible_url -from rest_framework import serializers +from rest_framework import serializers, status from rest_framework.response import Response from .models import M3UAccount, M3UFilter, ServerGroup, M3UAccountProfile from core.models import UserAgent @@ -113,6 +113,7 @@ class M3UAccountSerializer(serializers.ModelSerializer): "username", "password", "stale_stream_days", + "priority", "status", "last_message", "enable_vod", diff --git a/apps/output/views.py b/apps/output/views.py index bb54d3c3..20d3c206 100644 --- a/apps/output/views.py +++ b/apps/output/views.py @@ -1077,10 +1077,10 @@ def xc_get_vod_streams(request, user, category_id=None): movies = Movie.objects.filter(**filters).select_related('logo').distinct() for movie in movies: - # Get the first relation for this movie (for metadata like container_extension) + # Get the highest priority relation for this movie (for metadata like container_extension) relation = movie.m3u_relations.filter( m3u_account__is_active=True - ).first() + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() if relation: relation_custom = relation.custom_properties or {} @@ -1282,9 +1282,11 @@ def xc_get_series_info(request, user, series_id): if season_num not in seasons: seasons[season_num] = [] - # Try to get the first related M3UEpisodeRelation for this episode (for video/audio/bitrate) + # Try to get the highest priority related M3UEpisodeRelation for this episode (for video/audio/bitrate) from apps.vod.models import M3UEpisodeRelation - first_relation = M3UEpisodeRelation.objects.filter(episode=episode).order_by('id').first() + first_relation = M3UEpisodeRelation.objects.filter( + episode=episode + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() video = audio = bitrate = None if first_relation and first_relation.custom_properties: info = first_relation.custom_properties.get('info') diff --git a/apps/proxy/vod_proxy/views.py b/apps/proxy/vod_proxy/views.py index 8a5abd88..00ab72ea 100644 --- a/apps/proxy/vod_proxy/views.py +++ b/apps/proxy/vod_proxy/views.py @@ -345,16 +345,28 @@ class VODStreamView(View): content_obj = get_object_or_404(Movie, uuid=content_id) logger.info(f"[CONTENT-FOUND] Movie: {content_obj.name} (ID: {content_obj.id})") - # Get the first active relation - relation = content_obj.m3u_relations.filter(m3u_account__is_active=True).first() + # Get the highest priority active relation + relation = content_obj.m3u_relations.filter( + m3u_account__is_active=True + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() + + if relation: + logger.info(f"[PROVIDER-SELECTED] Using provider: {relation.m3u_account.name} (priority: {relation.m3u_account.priority})") + return content_obj, relation elif content_type == 'episode': content_obj = get_object_or_404(Episode, uuid=content_id) logger.info(f"[CONTENT-FOUND] Episode: {content_obj.name} (ID: {content_obj.id}, Series: {content_obj.series.name})") - # Get the first active relation - relation = content_obj.m3u_relations.filter(m3u_account__is_active=True).first() + # Get the highest priority active relation + relation = content_obj.m3u_relations.filter( + m3u_account__is_active=True + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() + + if relation: + logger.info(f"[PROVIDER-SELECTED] Using provider: {relation.m3u_account.name} (priority: {relation.m3u_account.priority})") + return content_obj, relation elif content_type == 'series': @@ -367,7 +379,14 @@ class VODStreamView(View): return None, None logger.info(f"[CONTENT-FOUND] First episode: {episode.name} (ID: {episode.id})") - relation = episode.m3u_relations.filter(m3u_account__is_active=True).first() + # Get the highest priority active relation + relation = episode.m3u_relations.filter( + m3u_account__is_active=True + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() + + if relation: + logger.info(f"[PROVIDER-SELECTED] Using provider: {relation.m3u_account.name} (priority: {relation.m3u_account.priority})") + return episode, relation else: logger.error(f"[CONTENT-ERROR] Invalid content type: {content_type}") diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py index d93dc6cf..df686c9c 100644 --- a/apps/vod/api_views.py +++ b/apps/vod/api_views.py @@ -108,11 +108,11 @@ class MovieViewSet(viewsets.ReadOnlyModelViewSet): """Get detailed movie information from the original provider, throttled to 24h.""" movie = self.get_object() - # Get the first active relation + # Get the highest priority active relation relation = M3UMovieRelation.objects.filter( movie=movie, m3u_account__is_active=True - ).select_related('m3u_account').first() + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() if not relation: return Response( @@ -314,11 +314,11 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet): series = self.get_object() logger.debug(f"Retrieved series: {series.name} (ID: {series.id})") - # Get the first active relation + # Get the highest priority active relation relation = M3USeriesRelation.objects.filter( series=series, m3u_account__is_active=True - ).select_related('m3u_account').first() + ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() if not relation: return Response( diff --git a/frontend/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx index a317a7fe..ff25ed92 100644 --- a/frontend/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -64,6 +64,7 @@ const M3U = ({ username: '', password: '', stale_stream_days: 7, + priority: 0, enable_vod: false, }, @@ -93,6 +94,9 @@ const M3U = ({ m3uAccount.stale_stream_days !== null ? m3uAccount.stale_stream_days : 7, + priority: m3uAccount.priority !== undefined && m3uAccount.priority !== null + ? m3uAccount.priority + : 0, enable_vod: m3uAccount.enable_vod || false, }); @@ -366,6 +370,15 @@ const M3U = ({ {...form.getInputProps('stale_stream_days')} /> + +