From 325d832c1bc3ac83c3c4be3dd15b95ef67f296ba Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Thu, 7 Aug 2025 20:42:20 -0500 Subject: [PATCH] Fixes movie details after db changes. --- apps/vod/api_views.py | 180 +++++++++++++----------------------------- apps/vod/models.py | 1 + apps/vod/tasks.py | 106 ++++++++++++++++++++++++- 3 files changed, 160 insertions(+), 127 deletions(-) diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py index 3d3f45f2..7f6f65aa 100644 --- a/apps/vod/api_views.py +++ b/apps/vod/api_views.py @@ -23,7 +23,7 @@ from .serializers import ( M3USeriesRelationSerializer, M3UEpisodeRelationSerializer ) -from .tasks import refresh_series_episodes +from .tasks import refresh_series_episodes, refresh_movie_advanced_data from django.utils import timezone from datetime import timedelta @@ -88,7 +88,7 @@ class MovieViewSet(viewsets.ReadOnlyModelViewSet): @action(detail=True, methods=['get'], url_path='provider-info') def provider_info(self, request, pk=None): - """Get detailed movie information from the original provider""" + """Get detailed movie information from the original provider, throttled to 24h.""" movie = self.get_object() # Get the first active relation @@ -103,130 +103,62 @@ class MovieViewSet(viewsets.ReadOnlyModelViewSet): status=status.HTTP_400_BAD_REQUEST ) - # Check if detailed data has been fetched + force_refresh = request.query_params.get('force_refresh', 'false').lower() == 'true' + now = timezone.now() + needs_refresh = ( + force_refresh or + not relation.last_advanced_refresh or + (now - relation.last_advanced_refresh).total_seconds() > 86400 + ) + + if needs_refresh: + # Trigger advanced data refresh + logger.debug(f"Refreshing advanced data for movie {movie.id} (relation ID: {relation.id})") + refresh_movie_advanced_data(relation.id, force_refresh=force_refresh) + + # Use cached advanced data custom_props = relation.custom_properties or {} - detailed_fetched = custom_props.get('detailed_fetched', False) - - # If detailed data hasn't been fetched, fetch it now - if not detailed_fetched: - try: - from core.xtream_codes import Client as XtreamCodesClient - - with XtreamCodesClient( - server_url=relation.m3u_account.server_url, - username=relation.m3u_account.username, - password=relation.m3u_account.password, - user_agent=relation.m3u_account.get_user_agent().user_agent - ) as client: - # Get detailed VOD info from provider - vod_info = client.get_vod_info(relation.stream_id) - - if vod_info and 'info' in vod_info: - # Update movie with detailed info - info = vod_info.get('info', {}) - movie_data = vod_info.get('movie_data', {}) - - movie.description = info.get('plot', movie.description) - movie.rating = info.get('rating', movie.rating) - movie.genre = info.get('genre', movie.genre) - movie.duration = self._convert_duration_to_minutes(info.get('duration_secs')) - if info.get('releasedate'): - movie.year = self._extract_year(info.get('releasedate')) - movie.save() - - # Update relation with detailed data - custom_props['detailed_info'] = info - custom_props['movie_data'] = movie_data - custom_props['detailed_fetched'] = True - relation.custom_properties = custom_props - relation.save() - - except Exception as e: - logger.error(f"Error fetching detailed VOD info for movie {pk}: {str(e)}") - # Continue with available data - - try: - from core.xtream_codes import Client as XtreamCodesClient - - # Create XtreamCodes client for final response (minimal call) - with XtreamCodesClient( - server_url=relation.m3u_account.server_url, - username=relation.m3u_account.username, - password=relation.m3u_account.password, - user_agent=relation.m3u_account.get_user_agent().user_agent - ) as client: - - # Use cached detailed data if available - custom_props = relation.custom_properties or {} - info = custom_props.get('detailed_info', {}) - movie_data = custom_props.get('movie_data', {}) - - # If no cached data, use basic data - if not info: - basic_data = custom_props.get('basic_data', {}) - info = { - 'name': movie.name, - 'plot': movie.description, - 'rating': movie.rating, - 'genre': movie.genre, - } - movie_data = { - 'container_extension': basic_data.get('container_extension', 'mp4'), - 'added': basic_data.get('added', ''), - } - - # Build response with available data - response_data = { - 'id': movie.id, - 'stream_id': relation.stream_id, - 'name': info.get('name', movie.name), - 'o_name': info.get('o_name', ''), - 'description': info.get('description', info.get('plot', movie.description)), - 'plot': info.get('plot', info.get('description', movie.description)), - 'year': movie.year or self._extract_year(info.get('releasedate', '')), - 'release_date': info.get('release_date', ''), - 'releasedate': info.get('releasedate', ''), - 'genre': info.get('genre', movie.genre), - 'director': info.get('director', ''), - 'actors': info.get('actors', info.get('cast', '')), - 'cast': info.get('cast', info.get('actors', '')), - 'country': info.get('country', ''), - 'rating': info.get('rating', movie.rating or 0), - 'tmdb_id': info.get('tmdb_id', movie.tmdb_id or ''), - 'youtube_trailer': info.get('youtube_trailer') or info.get('trailer', ''), - 'duration': movie.duration or self._convert_duration_to_minutes(info.get('duration_secs', 0)), - 'duration_secs': info.get('duration_secs', (movie.duration or 0) * 60), - 'episode_run_time': info.get('episode_run_time', 0), - 'age': info.get('age', ''), - 'backdrop_path': info.get('backdrop_path', []), - 'cover': info.get('cover_big', ''), - 'cover_big': info.get('cover_big', ''), - 'movie_image': info.get('movie_image', ''), - 'bitrate': info.get('bitrate', 0), - 'video': info.get('video', {}), - 'audio': info.get('audio', {}), - # Include movie_data fields - 'container_extension': movie_data.get('container_extension', 'mp4'), - 'direct_source': movie_data.get('direct_source', ''), - 'category_id': movie_data.get('category_id', ''), - 'added': movie_data.get('added', ''), - # Include M3U account info - 'm3u_account': { - 'id': relation.m3u_account.id, - 'name': relation.m3u_account.name, - 'account_type': relation.m3u_account.account_type - } - } - - return Response(response_data) - - except Exception as e: - logger.error(f"Error in provider info for movie {pk}: {str(e)}") - return Response( - {'error': f'Failed to fetch information from provider: {str(e)}'}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) + info = custom_props.get('detailed_info', {}) + movie_data = custom_props.get('movie_data', {}) + # Build response with available data + response_data = { + 'id': movie.id, + 'stream_id': relation.stream_id, + 'name': info.get('name', movie.name), + 'o_name': info.get('o_name', ''), + 'description': info.get('description', info.get('plot', movie.description)), + 'plot':info.get('plot', info.get('description', movie.description)), + 'year': movie.year or info.get('year'), + 'release_date': (movie.custom_properties or {}).get('release_date') or info.get('release_date') or info.get('releasedate', ''), + 'genre': movie.genre or info.get('genre', ''), + 'director': (movie.custom_properties or {}).get('director') or info.get('director', ''), + 'actors': (movie.custom_properties or {}).get('actors') or info.get('actors', ''), + 'country': (movie.custom_properties or {}).get('country') or info.get('country', ''), + 'rating': movie.rating or info.get('rating', movie.rating or 0), + 'tmdb_id': movie.tmdb_id or info.get('tmdb_id', ''), + 'youtube_trailer': (movie.custom_properties or {}).get('youtube_trailer') or info.get('youtube_trailer') or info.get('trailer', ''), + 'duration': movie.duration or (int(info.get('duration_secs', 0)) // 60 if info.get('duration_secs') else None), + 'duration_secs': info.get('duration_secs', (movie.duration or 0) * 60), + 'age': info.get('age', ''), + 'backdrop_path': (movie.custom_properties or {}).get('backdrop_path') or info.get('backdrop_path', []), + 'cover': info.get('cover_big', ''), + 'cover_big': info.get('cover_big', ''), + 'movie_image': movie.logo.url or info.get('movie_image', ''), + 'bitrate': info.get('bitrate', 0), + 'video': info.get('video', {}), + 'audio': info.get('audio', {}), + 'container_extension': movie_data.get('container_extension', 'mp4'), + 'direct_source': movie_data.get('direct_source', ''), + 'category_id': movie_data.get('category_id', ''), + 'added': movie_data.get('added', ''), + 'm3u_account': { + 'id': relation.m3u_account.id, + 'name': relation.m3u_account.name, + 'account_type': relation.m3u_account.account_type + } + } + return Response(response_data) class EpisodeFilter(django_filters.FilterSet): name = django_filters.CharFilter(lookup_expr="icontains") diff --git a/apps/vod/models.py b/apps/vod/models.py index 711cba0c..092e0034 100644 --- a/apps/vod/models.py +++ b/apps/vod/models.py @@ -183,6 +183,7 @@ class M3UMovieRelation(models.Model): # 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' diff --git a/apps/vod/tasks.py b/apps/vod/tasks.py index 6c08de15..e37b6892 100644 --- a/apps/vod/tasks.py +++ b/apps/vod/tasks.py @@ -424,8 +424,8 @@ def find_or_create_movie(name, year, tmdb_id, imdb_id, info): # If we found an existing movie, update it if movie: updated = False - if info.get('plot') and info.get('plot') != movie.description: - movie.description = info.get('plot') + if (info.get('plot') or info.get('description')) and (info.get('plot') or info.get('description')) != movie.description: + movie.description = info.get('plot') or info.get('description') updated = True if info.get('rating') and info.get('rating') != movie.rating: movie.rating = info.get('rating') @@ -478,7 +478,7 @@ def find_or_create_movie(name, year, tmdb_id, imdb_id, info): year=year, tmdb_id=tmdb_id, imdb_id=imdb_id, - description=info.get('plot', ''), + description=info.get('plot') or info.get('description', ''), rating=info.get('rating', ''), genre=info.get('genre', ''), duration=convert_duration_to_minutes(info.get('duration_secs')), @@ -692,3 +692,103 @@ def parse_date(date_string): return datetime.strptime(date_string, '%Y-%m-%d') except ValueError: return None # Return None if parsing fails + +from django.utils import timezone +from apps.vod.models import M3UMovieRelation, Movie + +@shared_task +def refresh_movie_advanced_data(m3u_movie_relation_id, force_refresh=False): + """ + Fetch advanced movie data from provider and update Movie and M3UMovieRelation. + Only fetch if last_advanced_refresh > 24h ago, unless force_refresh is True. + """ + try: + relation = M3UMovieRelation.objects.select_related('movie', 'm3u_account').get(id=m3u_movie_relation_id) + now = timezone.now() + if not force_refresh and relation.last_advanced_refresh and (now - relation.last_advanced_refresh).total_seconds() < 86400: + return "Advanced data recently fetched, skipping." + + account = relation.m3u_account + movie = relation.movie + + from core.xtream_codes import Client as XtreamCodesClient + + with XtreamCodesClient( + server_url=account.server_url, + username=account.username, + password=account.password, + user_agent=account.get_user_agent().user_agent + ) as client: + vod_info = client.get_vod_info(relation.stream_id) + if vod_info and 'info' in vod_info: + info = vod_info.get('info', {}) + movie_data = vod_info.get('movie_data', {}) + + # Update Movie fields if changed + updated = False + custom_props = movie.custom_properties or {} + if info.get('plot') and info.get('plot') != movie.description: + movie.description = info.get('plot') + updated = True + if info.get('rating') and info.get('rating') != movie.rating: + movie.rating = info.get('rating') + updated = True + if info.get('genre') and info.get('genre') != movie.genre: + movie.genre = info.get('genre') + updated = True + if info.get('duration_secs'): + duration = int(info.get('duration_secs')) // 60 + if duration != movie.duration: + movie.duration = duration + updated = True + # Check for releasedate or release_date + release_date_value = info.get('releasedate') or info.get('release_date') + if release_date_value: + try: + year = int(str(release_date_value).split('-')[0]) + if year != movie.year: + movie.year = year + updated = True + except Exception: + pass + if info.get('tmdb_id') and info.get('tmdb_id') != movie.tmdb_id: + movie.tmdb_id = info.get('tmdb_id') + updated = True + if info.get('imdb_id') and info.get('imdb_id') != movie.imdb_id: + movie.imdb_id = info.get('imdb_id') + updated = True + if info.get('trailer') and info.get('trailer') != custom_props.get('youtube_trailer'): + custom_props['youtube_trailer'] = info.get('trailer') + updated = True + if info.get('youtube_trailer') and info.get('youtube_trailer') != custom_props.get('youtube_trailer'): + custom_props['youtube_trailer'] = info.get('youtube_trailer') + updated = True + if info.get('backdrop_path') and info.get('backdrop_path') != custom_props.get('backdrop_path'): + custom_props['backdrop_path'] = info.get('backdrop_path') + updated = True + if info.get('actors') and info.get('actors') != custom_props.get('actors'): + custom_props['actors'] = info.get('actors') + updated = True + if info.get('cast') and info.get('cast') != custom_props.get('actors'): + custom_props['actors'] = info.get('cast') + updated = True + if info.get('director') and info.get('director') != custom_props.get('director'): + custom_props['director'] = info.get('director') + updated = True + if updated: + movie.custom_properties = custom_props + movie.save() + + # Update relation custom_properties and last_advanced_refresh + custom_props = relation.custom_properties or {} + custom_props['detailed_info'] = info + custom_props['movie_data'] = movie_data + custom_props['detailed_fetched'] = True + relation.custom_properties = custom_props + relation.last_advanced_refresh = now + relation.save(update_fields=['custom_properties', 'last_advanced_refresh']) + + return "Advanced data refreshed." + except Exception as e: + logger.error(f"Error refreshing advanced movie data for relation {m3u_movie_relation_id}: {str(e)}") + return f"Error: {str(e)}"