From b4180afbed4727572bbd4f9dd0c6b8cbfebc1d8e Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Wed, 17 Jun 2026 12:05:22 -0400 Subject: [PATCH] [videos] Fix metadata for series script --- PROJECT.org | 6 +++- .../commands/cleanup_series_metadata.py | 21 +++++++++-- vrobbler/apps/videos/models.py | 30 +++++++++++----- vrobbler/apps/videos/sources/tmdb.py | 36 +++++++++++++++---- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/PROJECT.org b/PROJECT.org index 9427137..5f43c2f 100644 --- a/PROJECT.org +++ b/PROJECT.org @@ -88,7 +88,7 @@ fetching and simple saving. *** Metadata sources **** Scraper -* Backlog [0/20] :vrobbler:project:personal: +* Backlog [1/21] :vrobbler:project:personal: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles: :PROPERTIES: :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb @@ -590,6 +590,10 @@ We should rename `email_scrobble_board_game` to reflect the fact that it's just a helper method to create board game scrobbles given a json blob. It's independent of the email flow it was originally creatdd for +** DONE [#B] Fix bug in series metadata cleanup script :videos:metadta: +:PROPERTIES: +:ID: 85448702-907c-5d63-f5af-7795661d7c46 +:END: * Version 54.2 [4/4] ** DONE [#B] Add script to clean up TV series metadata :videos:metadata: :PROPERTIES: diff --git a/vrobbler/apps/videos/management/commands/cleanup_series_metadata.py b/vrobbler/apps/videos/management/commands/cleanup_series_metadata.py index 94bb7eb..b70659c 100644 --- a/vrobbler/apps/videos/management/commands/cleanup_series_metadata.py +++ b/vrobbler/apps/videos/management/commands/cleanup_series_metadata.py @@ -1,7 +1,7 @@ import logging from django.core.management.base import BaseCommand -from django.db import transaction +from django.db import models, transaction logger = logging.getLogger(__name__) @@ -25,6 +25,11 @@ class Command(BaseCommand): type=str, help="Only process series with this imdb_id", ) + parser.add_argument( + "--needs-metadata", + action="store_true", + help="Only process series missing imdb_id or cover image", + ) def handle(self, *args, **options): from videos.models import Series @@ -32,18 +37,30 @@ class Command(BaseCommand): force = options["force"] dry_run = options["dry_run"] imdb_id = options["imdb_id"] + needs_metadata = options["needs_metadata"] qs = Series.objects.all() if imdb_id: qs = qs.filter(imdb_id=imdb_id) + if needs_metadata: + qs = qs.filter( + models.Q(imdb_id__isnull=True) + | models.Q(imdb_id="") + | models.Q(cover_image__isnull=True) + | models.Q(cover_image="") + ) total = qs.count() self.stdout.write(f"Processing {total} series") if dry_run: for series in qs.iterator(): + has_imdb = bool(series.imdb_id) + has_image = bool(series.cover_image) self.stdout.write( - f" [DRY RUN] Would fix {series.name} (imdb_id={series.imdb_id})" + f" [DRY RUN] Would fix {series.name}" + f" (imdb_id={'✓' if has_imdb else '✗'}" + f", image={'✓' if has_image else '✗'})" ) return diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index be82333..0306a8a 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -321,13 +321,27 @@ class Series(TimeStampedModel): return not last_scrobble.played_to_completion def fix_metadata(self, force_update=False): - name_or_id = self.name - if self.imdb_id: - name_or_id = self.imdb_id - video_metadata: VideoMetadata = lookup_video_from_tmdb(name_or_id) + from tmdbv3api import TV - if not video_metadata.title: - logger.warning(f"No imdb data for {self}") + if not self.imdb_id: + tv = TV() + results = tv.search(self.name) + if results: + show_id = results[0].id + external_ids = tv.external_ids(show_id) + if external_ids and external_ids.imdb_id: + self.imdb_id = external_ids.imdb_id + self.save(update_fields=["imdb_id"]) + else: + logger.warning(f"No IMDB ID found on TMDB for {self}") + return + else: + logger.warning(f"No results on TMDB for {self.name}") + return + + video_metadata = lookup_video_from_tmdb(self.imdb_id) + if not video_metadata or not video_metadata.title: + logger.warning(f"No metadata for {self}") return if video_metadata.cover_url and (not self.cover_image or force_update): @@ -336,8 +350,8 @@ class Series(TimeStampedModel): fname = f"{self.name}_{self.uuid}.jpg" self.cover_image.save(fname, ContentFile(r.content), save=True) - self.plot = video_metadata.plot - self.imdb_rating = video_metadata.imdb_rating + self.plot = video_metadata.plot or "" + self.imdb_rating = getattr(video_metadata, "imdb_rating", None) self.save() if video_metadata.genres: diff --git a/vrobbler/apps/videos/sources/tmdb.py b/vrobbler/apps/videos/sources/tmdb.py index 82ca9b8..e34c4fb 100644 --- a/vrobbler/apps/videos/sources/tmdb.py +++ b/vrobbler/apps/videos/sources/tmdb.py @@ -1,4 +1,5 @@ import logging +import os import pendulum from django.conf import settings @@ -8,6 +9,8 @@ from videos.metadata import VideoMetadata, VideoType TMDB_KEY = getattr(settings, "TMDB_API_KEY", "") +os.environ.setdefault("TMDB_API_KEY", TMDB_KEY) + tmdb = TMDb(key=TMDB_KEY, language="en-US", region="US") TMDB_IMAGE_URL = "https://image.tmdb.org/t/p/original" @@ -43,6 +46,28 @@ def lookup_video_from_tmdb(name_or_id: str, kind: str = "movie") -> VideoMetadat ) # TODO: enrich this with TMDB url video_metadata.year = pendulum.parse(media.release_date).year video_metadata.genres = [g.get("name", "") for g in media.genres] + video_metadata.tmdb_id = media.id + video_metadata.base_run_time_seconds = media.runtime * 60 + video_metadata.plot = media.overview + video_metadata.overview = media.overview + video_metadata.tmdb_rating = media.vote_average + + if len(tmdb_result.tv_results) > 0: + media = TV().details(tmdb_result.tv_results[0].id) + video_metadata.video_type = VideoType.TV_EPISODE.value + video_metadata.title = media.name + video_metadata.cover_url = ( + TMDB_IMAGE_URL + media.poster_path + ) + video_metadata.year = pendulum.parse(media.first_air_date).year if media.first_air_date else None + video_metadata.genres = [g.get("name", "") for g in media.genres] + video_metadata.tmdb_id = media.id + video_metadata.base_run_time_seconds = ( + media.episode_run_time[0] * 60 if media.episode_run_time else 1800 + ) + video_metadata.plot = media.overview + video_metadata.overview = media.overview + video_metadata.tmdb_rating = media.vote_average if len(tmdb_result.tv_episode_results) > 0: video_metadata.video_type = VideoType.TV_EPISODE.value @@ -63,15 +88,12 @@ def lookup_video_from_tmdb(name_or_id: str, kind: str = "movie") -> VideoMetadat series.save() video_metadata.tv_series_id = series.id + video_metadata.tmdb_id = media.id + video_metadata.plot = media.overview + video_metadata.overview = media.overview + if not media: logger.warning("Video not found on TMDB", extra={"imdb_id": imdb_id}) return video_metadata - video_metadata.tmdb_id = media.id - video_metadata.base_run_time_seconds = media.runtime * 60 - video_metadata.plot = media.overview - video_metadata.overview = media.overview - video_metadata.tmdb_rating = media.vote_average - # video_metadata.next_imdb_id = imdb_result.get("next episode", None) - return video_metadata