From 8d37c678d388dfb446dc81627ef7bbca808caeed Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Thu, 7 Aug 2025 19:28:42 -0500 Subject: [PATCH] Fix air_date not populating for series. --- apps/vod/api_views.py | 7 +++-- apps/vod/models.py | 2 +- apps/vod/tasks.py | 45 ++++++++++++++++++++++++++---- frontend/src/pages/VODs.jsx | 2 +- frontend/src/store/useVODStore.jsx | 6 ++-- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py index b8cb3153..aeaec508 100644 --- a/apps/vod/api_views.py +++ b/apps/vod/api_views.py @@ -383,7 +383,6 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet): 'rating': series.rating, 'tmdb_id': series.tmdb_id, 'imdb_id': series.imdb_id, - 'youtube_trailer': (series.custom_properties or {}).get('trailer') or (series.custom_properties or {}).get('youtube_trailer', ''), 'category_id': relation.category.id if relation.category else None, 'category_name': relation.category.name if relation.category else None, 'cover': { @@ -426,11 +425,13 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet): 'episode_number': episode.episode_number, 'season_number': episode.season_number, 'description': episode.description, - 'release_date': episode.release_date, + 'air_date': episode.air_date, 'plot': episode.description, 'duration': episode.duration, 'rating': episode.rating, - 'movie_image': episode_relation.custom_properties.get('info', {}).get('movie_image') if episode_relation and episode_relation.custom_properties else None, + 'tmdb_id': episode.tmdb_id, + 'imdb_id': episode.imdb_id, + 'movie_image': episode_relation.custom_properties.get("info", {}).get('info', {}).get('movie_image') if episode_relation and episode_relation.custom_properties else None, 'container_extension': episode_relation.container_extension if episode_relation else 'mp4', 'type': 'episode', 'series': { diff --git a/apps/vod/models.py b/apps/vod/models.py index f3bf8833..711cba0c 100644 --- a/apps/vod/models.py +++ b/apps/vod/models.py @@ -111,7 +111,7 @@ class Episode(models.Model): uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) name = models.CharField(max_length=255) description = models.TextField(blank=True, null=True) - release_date = models.DateField(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") diff --git a/apps/vod/tasks.py b/apps/vod/tasks.py index a0109c0a..ae09cc74 100644 --- a/apps/vod/tasks.py +++ b/apps/vod/tasks.py @@ -8,6 +8,7 @@ from .models import ( M3USeriesRelation, M3UMovieRelation, M3UEpisodeRelation ) from apps.channels.models import Logo +from datetime import datetime import logging import json import re @@ -298,8 +299,7 @@ def refresh_series_episodes(account, series, external_series_id, episodes_data=N series.description = info.get('plot', series.description) series.rating = info.get('rating', series.rating) series.genre = info.get('genre', series.genre) - if info.get('releasedate'): - series.year = extract_year(info.get('releasedate')) + series.year = extract_year_from_data(info) series.save() episodes_data = series_info.get('episodes', {}) @@ -347,8 +347,10 @@ def process_episode(account, series, episode_data, season_number): if info: description = info.get('plot') or info.get('overview', '') - rating = episode_data.get('rating', '') - release_date = extract_year_from_data(episode_data.get('info')) + rating = info.get('rating', '') + + # Use helper function to parse air_date + air_date = extract_date_from_data(info) # Create or update episode episode, created = Episode.objects.update_or_create( @@ -359,7 +361,10 @@ def process_episode(account, series, episode_data, season_number): 'name': episode_name, 'description': description, 'rating': rating, - 'release_date': release_date, + 'air_date': air_date, + 'duration': convert_duration_to_minutes(info.get('duration_secs')), + 'tmdb_id': info.get('tmdb_id'), + 'imdb_id': info.get('imdb_id'), } ) @@ -658,4 +663,32 @@ def cleanup_orphaned_vod_content(): # Episodes will be cleaned up via CASCADE when series are deleted logger.info(f"Cleaned up {movie_count} orphaned movies and {series_count} orphaned series") - return f"Cleaned up {movie_count} movies and {series_count} series" \ No newline at end of file + return f"Cleaned up {movie_count} movies and {series_count} series" + +def extract_date_from_data(data): + """Extract date from various data sources with fallback options""" + try: + for date_field in ['air_date', 'releaseDate', 'release_date']: + date_value = data.get(date_field) + if date_value and isinstance(date_value, str) and date_value.strip(): + parsed = parse_date(date_value) + if parsed: + return parsed + except Exception: + # Don't fail processing if date extraction fails + pass + return None + +def parse_date(date_string): + """Parse date string into a datetime object""" + if not date_string: + return None + try: + # Try to parse ISO format first + return datetime.fromisoformat(date_string) + except ValueError: + # Fallback to parsing with strptime for common formats + try: + return datetime.strptime(date_string, '%Y-%m-%d') + except ValueError: + return None # Return None if parsing fails diff --git a/frontend/src/pages/VODs.jsx b/frontend/src/pages/VODs.jsx index 990a133a..cdf8ca20 100644 --- a/frontend/src/pages/VODs.jsx +++ b/frontend/src/pages/VODs.jsx @@ -626,7 +626,7 @@ const SeriesModal = ({ series, opened, onClose }) => { - {episode.release_date ? new Date(episode.release_date).toLocaleDateString() : 'N/A'} + {episode.air_date ? new Date(episode.air_date).toLocaleDateString() : 'N/A'} diff --git a/frontend/src/store/useVODStore.jsx b/frontend/src/store/useVODStore.jsx index 173fb395..5a026f61 100644 --- a/frontend/src/store/useVODStore.jsx +++ b/frontend/src/store/useVODStore.jsx @@ -308,7 +308,7 @@ const useVODStore = create((set, get) => ({ description: episode.plot || '', season_number: parseInt(seasonNumber) || 0, episode_number: episode.episode_number || 0, - duration: episode.duration ? Math.floor(episode.duration / 60) : null, + duration: episode.duration || null, rating: episode.rating || '', container_extension: episode.container_extension || '', series: { @@ -318,8 +318,10 @@ const useVODStore = create((set, get) => ({ type: 'episode', uuid: episode.id, // Use the stream ID as UUID for playback logo: episode.movie_image ? { url: episode.movie_image } : null, - release_date: episode.release_date || null, + air_date: episode.air_date || null, movie_image: episode.movie_image || null, + tmdb_id: episode.tmdb_id || '', + imdb_id: episode.imdb_id || '', }; episodesData[episode.id] = episodeData; });