From 912ea8bfacc18e3bb6fe8d76653a839247586993 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 7 Jun 2026 09:47:52 -0400 Subject: [PATCH] [videos] Add OMDB enrichment when TMDb fails --- PROJECT.org | 4 +- vrobbler/apps/videos/models.py | 39 ++++++++++++--- vrobbler/apps/videos/sources/omdb.py | 72 ++++++++++++++++++++++++++++ vrobbler/apps/videos/sources/tmdb.py | 7 +-- vrobbler/settings.py | 1 + 5 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 vrobbler/apps/videos/sources/omdb.py diff --git a/PROJECT.org b/PROJECT.org index 0eeaa82..0cafcee 100644 --- a/PROJECT.org +++ b/PROJECT.org @@ -86,7 +86,7 @@ fetching and simple saving. **** Bookmarklet *** Metadata sources **** Scraper -* Backlog [0/13] :vrobbler:project:personal: +* Backlog [1/13] :vrobbler:project:personal: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles: :PROPERTIES: :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb @@ -498,7 +498,7 @@ needed import celery task. This is how the WebDAV celery task currently works. This would also be an opporunity to clean up the code around WebDAV imports and make them more re-usable for other import services. -** TODO [#B] Add OMDB source as backup when TMDB returns nothing :videos:metadata:imdb: +** DONE [#B] Add OMDB source as backup when TMDB returns nothing :videos:metadata:imdb: :PROPERTIES: :ID: 20195445-7fdd-49be-9767-103b12da0bfb :END: diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index 17cf9ce..daf8714 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -20,6 +20,7 @@ from scrobbles.mixins import ( ) from taggit.managers import TaggableManager from videos.metadata import VideoMetadata +from videos.sources.omdb import lookup_video_from_omdb from videos.sources.tmdb import lookup_video_from_tmdb from videos.sources.youtube import lookup_video_from_youtube @@ -255,9 +256,22 @@ class Series(TimeStampedModel): logger.info("Series not created and overwrite=False, returning") return series - vdict, _, cover, genres = lookup_video_from_tmdb( - imdb_id - ).as_dict_with_cover_and_genres() + try: + metadata = lookup_video_from_tmdb(imdb_id) + except Exception as e: + logger.warning(f"TMDB lookup failed for series {imdb_id}: {e}") + metadata = None + + if not metadata or not metadata.title: + metadata = lookup_video_from_omdb(imdb_id) + + if not metadata or not metadata.title: + logger.warning( + f"No metadata found for series {imdb_id} from TMDB or OMDB" + ) + return series + + vdict, _, cover, genres = metadata.as_dict_with_cover_and_genres() vdict.pop("video_type") vdict["name"] = vdict.pop("title") @@ -432,9 +446,22 @@ class Video(ScrobblableMixin): if not created and not overwrite: return video - vdict, series_id, cover, genres = lookup_video_from_tmdb( - imdb_id - ).as_dict_with_cover_and_genres() + try: + metadata = lookup_video_from_tmdb(imdb_id) + except Exception as e: + logger.warning(f"TMDB lookup failed for {imdb_id}: {e}") + metadata = None + + if not metadata or not metadata.title: + metadata = lookup_video_from_omdb(imdb_id) + + if not metadata or not metadata.title: + logger.warning( + f"No metadata found for {imdb_id} from TMDB or OMDB" + ) + return video + + vdict, series_id, cover, genres = metadata.as_dict_with_cover_and_genres() if created or overwrite: for k, v in vdict.items(): diff --git a/vrobbler/apps/videos/sources/omdb.py b/vrobbler/apps/videos/sources/omdb.py new file mode 100644 index 0000000..eb661ae --- /dev/null +++ b/vrobbler/apps/videos/sources/omdb.py @@ -0,0 +1,72 @@ +import logging +import re +from typing import Optional + +import requests +from django.conf import settings +from videos.metadata import VideoMetadata, VideoType + +logger = logging.getLogger(__name__) + +OMDB_API_KEY = getattr(settings, "OMDB_API_KEY", "") + +OMDB_URL = "https://www.omdbapi.com/" +RUNTIME_RE = re.compile(r"(\d+)\s*min") + + +def lookup_video_from_omdb(imdb_id: str) -> Optional[VideoMetadata]: + if not imdb_id.startswith("tt"): + imdb_id = f"tt{imdb_id}" + + if not OMDB_API_KEY: + logger.warning("No OMDB API key configured") + return None + + params = {"apikey": OMDB_API_KEY, "i": imdb_id, "plot": "full"} + + try: + response = requests.get(OMDB_URL, params=params) + response.raise_for_status() + data = response.json() + except requests.exceptions.RequestException as e: + logger.error(f"OMDB API error for {imdb_id}: {e}") + return None + + if data.get("Response") == "False": + logger.info(f"OMDB no result for {imdb_id}: {data.get('Error')}") + return None + + metadata = VideoMetadata(imdb_id=imdb_id) + metadata.title = data.get("Title") + metadata.plot = data.get("Plot") + metadata.overview = data.get("Plot") + + raw_year = data.get("Year") + if raw_year and raw_year.isdigit(): + metadata.year = int(raw_year) + + raw_rating = data.get("imdbRating") + if raw_rating and raw_rating != "N/A": + metadata.imdb_rating = raw_rating + + raw_cover = data.get("Poster") + if raw_cover and raw_cover != "N/A": + metadata.cover_url = raw_cover + + raw_runtime = data.get("Runtime") + if raw_runtime: + match = RUNTIME_RE.match(raw_runtime) + if match: + metadata.base_run_time_seconds = int(match.group(1)) * 60 + + media_type = data.get("Type") + if media_type == "movie": + metadata.video_type = VideoType.MOVIE.value + elif media_type in ("series", "episode"): + metadata.video_type = VideoType.TV_EPISODE.value + + raw_genres = data.get("Genre") + if raw_genres: + metadata.genres = [g.strip() for g in raw_genres.split(",") if g.strip()] + + return metadata diff --git a/vrobbler/apps/videos/sources/tmdb.py b/vrobbler/apps/videos/sources/tmdb.py index 5b0f6c2..82ca9b8 100644 --- a/vrobbler/apps/videos/sources/tmdb.py +++ b/vrobbler/apps/videos/sources/tmdb.py @@ -3,14 +3,11 @@ import logging import pendulum from django.conf import settings from themoviedb import TMDb -from tmdbv3api import TV, Movie, TMDb as TMDb_direct +from tmdbv3api import TV, Movie from videos.metadata import VideoMetadata, VideoType TMDB_KEY = getattr(settings, "TMDB_API_KEY", "") -tmdb_direct = TMDb_direct() -tmdb_direct.api_key = TMDB_KEY - tmdb = TMDb(key=TMDB_KEY, language="en-US", region="US") TMDB_IMAGE_URL = "https://image.tmdb.org/t/p/original" @@ -36,7 +33,7 @@ def lookup_video_from_tmdb(name_or_id: str, kind: str = "movie") -> VideoMetadat video_metadata = VideoMetadata(imdb_id=imdb_id) media = None - show = None + show_data = None if len(tmdb_result.movie_results) > 0: media = Movie().details(tmdb_result.movie_results[0].id) video_metadata.video_type = VideoType.MOVIE.value diff --git a/vrobbler/settings.py b/vrobbler/settings.py index cdeb8a0..21a44c6 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -60,6 +60,7 @@ THEAUDIODB_API_KEY = os.getenv("VROBBLER_THEAUDIODB_API_KEY", "2") PODCASTINDEX_API_KEY = os.getenv("VROBBLER_PODCASTINDEX_API_KEY", "") PODCASTINDEX_API_SECRET = os.getenv("VROBBLER_PODCASTINDEX_API_SECRET", "") TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "") +OMDB_API_KEY = os.getenv("VROBBLER_OMDB_API_KEY", "") LASTFM_API_KEY = os.getenv("VROBBLER_LASTFM_API_KEY") LASTFM_SECRET_KEY = os.getenv("VROBBLER_LASTFM_SECRET_KEY") IGDB_CLIENT_ID = os.getenv("VROBBLER_IGDB_CLIENT_ID")