Compare commits

..

5 Commits
47.0 ... 47.2

Author SHA1 Message Date
5a4ef678a8 [release] Bump to version 47.2
All checks were successful
build / test (push) Successful in 1m58s
deploy / test (push) Successful in 1m58s
deploy / build-and-deploy (push) Successful in 53s
- Add OMDB source as backup when TMDB returns nothing
2026-06-07 09:59:05 -04:00
5ca22efeaa [videos] Fix full metadata from OMDB
We were missing the series and episode number info.
2026-06-07 09:58:28 -04:00
912ea8bfac [videos] Add OMDB enrichment when TMDb fails 2026-06-07 09:47:52 -04:00
b541e1084d [release] Bump to version 47.1
All checks were successful
build / test (push) Successful in 2m0s
deploy / test (push) Successful in 2m1s
deploy / build-and-deploy (push) Successful in 32s
- Untangle the sports migrations errors
2026-06-06 23:47:25 -04:00
c9b9da4abc [sports] Fix migrations 2026-06-06 23:47:10 -04:00
9 changed files with 157 additions and 29 deletions

View File

@ -498,7 +498,8 @@ 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 This would also be an opporunity to clean up the code around WebDAV imports
and make them more re-usable for other import services. and make them more re-usable for other import services.
** TODO [#B] Add OMDB source as backup when TMDB returns nothing :videos:metadata:imdb: * Version 47.2 [1/1]
** DONE [#B] Add OMDB source as backup when TMDB returns nothing :videos:metadata:imdb:
:PROPERTIES: :PROPERTIES:
:ID: 20195445-7fdd-49be-9767-103b12da0bfb :ID: 20195445-7fdd-49be-9767-103b12da0bfb
:END: :END:
@ -512,6 +513,13 @@ epiodes up in three parts, while they were always broadcast three-in-one, and
that's how IMDB lists them. Thus, the IMDB ID means nothing, and the videos end that's how IMDB lists them. Thus, the IMDB ID means nothing, and the videos end
up unenriched. up unenriched.
* Version 47.1 [1/1]
** DONE [#A] Untangle the sports migrations errors :sports:bug:migrations:
:PROPERTIES:
:ID: 4d50ca2e-f45b-dde8-e3c9-cd84f353b349
:END:
* Version 47.0 [1/1] * Version 47.0 [1/1]
** DONE [#B] Change sports scrobbling a bit :feature:sports:scrobbles: ** DONE [#B] Change sports scrobbling a bit :feature:sports:scrobbles:
:PROPERTIES: :PROPERTIES:

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "47.0" version = "47.2"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]

View File

@ -108,20 +108,4 @@ class Migration(migrations.Migration):
migrations.RunPython( migrations.RunPython(
migrate_sport_event_data, reverse_code=migrations.RunPython.noop migrate_sport_event_data, reverse_code=migrations.RunPython.noop
), ),
migrations.RemoveField(
model_name="sportevent",
name="home_team",
),
migrations.RemoveField(
model_name="sportevent",
name="away_team",
),
migrations.RemoveField(
model_name="sportevent",
name="player_one",
),
migrations.RemoveField(
model_name="sportevent",
name="player_two",
),
] ]

View File

@ -0,0 +1,27 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("sports", "0021_team_logo"),
]
operations = [
migrations.RemoveField(
model_name="sportevent",
name="home_team",
),
migrations.RemoveField(
model_name="sportevent",
name="away_team",
),
migrations.RemoveField(
model_name="sportevent",
name="player_one",
),
migrations.RemoveField(
model_name="sportevent",
name="player_two",
),
]

View File

@ -71,6 +71,7 @@ class VideoMetadata:
twitch_id: Optional[str] = "", twitch_id: Optional[str] = "",
base_run_time_seconds: int = 900, base_run_time_seconds: int = 900,
): ):
self.title = ""
self.imdb_id = imdb_id self.imdb_id = imdb_id
self.youtube_id = youtube_id self.youtube_id = youtube_id
self.twitch_id = twitch_id self.twitch_id = twitch_id

View File

@ -20,6 +20,7 @@ from scrobbles.mixins import (
) )
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from videos.metadata import VideoMetadata 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.tmdb import lookup_video_from_tmdb
from videos.sources.youtube import lookup_video_from_youtube 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") logger.info("Series not created and overwrite=False, returning")
return series return series
vdict, _, cover, genres = lookup_video_from_tmdb( try:
imdb_id metadata = lookup_video_from_tmdb(imdb_id)
).as_dict_with_cover_and_genres() 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.pop("video_type")
vdict["name"] = vdict.pop("title") vdict["name"] = vdict.pop("title")
@ -432,9 +446,22 @@ class Video(ScrobblableMixin):
if not created and not overwrite: if not created and not overwrite:
return video return video
vdict, series_id, cover, genres = lookup_video_from_tmdb( try:
imdb_id metadata = lookup_video_from_tmdb(imdb_id)
).as_dict_with_cover_and_genres() 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: if created or overwrite:
for k, v in vdict.items(): for k, v in vdict.items():

View File

@ -0,0 +1,83 @@
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
if media_type == "episode":
raw_season = data.get("Season")
if raw_season and raw_season != "N/A":
metadata.season_number = int(raw_season)
raw_episode = data.get("Episode")
if raw_episode and raw_episode != "N/A":
metadata.episode_number = int(raw_episode)
series_imdb_id = data.get("seriesID")
if series_imdb_id and series_imdb_id != "N/A":
metadata.tv_series_imdb_id = series_imdb_id
raw_genres = data.get("Genre")
if raw_genres:
metadata.genres = [g.strip() for g in raw_genres.split(",") if g.strip()]
return metadata

View File

@ -3,14 +3,11 @@ import logging
import pendulum import pendulum
from django.conf import settings from django.conf import settings
from themoviedb import TMDb from themoviedb import TMDb
from tmdbv3api import TV, Movie, TMDb as TMDb_direct from tmdbv3api import TV, Movie
from videos.metadata import VideoMetadata, VideoType from videos.metadata import VideoMetadata, VideoType
TMDB_KEY = getattr(settings, "TMDB_API_KEY", "") 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 = TMDb(key=TMDB_KEY, language="en-US", region="US")
TMDB_IMAGE_URL = "https://image.tmdb.org/t/p/original" 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) video_metadata = VideoMetadata(imdb_id=imdb_id)
media = None media = None
show = None show_data = None
if len(tmdb_result.movie_results) > 0: if len(tmdb_result.movie_results) > 0:
media = Movie().details(tmdb_result.movie_results[0].id) media = Movie().details(tmdb_result.movie_results[0].id)
video_metadata.video_type = VideoType.MOVIE.value video_metadata.video_type = VideoType.MOVIE.value

View File

@ -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_KEY = os.getenv("VROBBLER_PODCASTINDEX_API_KEY", "")
PODCASTINDEX_API_SECRET = os.getenv("VROBBLER_PODCASTINDEX_API_SECRET", "") PODCASTINDEX_API_SECRET = os.getenv("VROBBLER_PODCASTINDEX_API_SECRET", "")
TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "") 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_API_KEY = os.getenv("VROBBLER_LASTFM_API_KEY")
LASTFM_SECRET_KEY = os.getenv("VROBBLER_LASTFM_SECRET_KEY") LASTFM_SECRET_KEY = os.getenv("VROBBLER_LASTFM_SECRET_KEY")
IGDB_CLIENT_ID = os.getenv("VROBBLER_IGDB_CLIENT_ID") IGDB_CLIENT_ID = os.getenv("VROBBLER_IGDB_CLIENT_ID")