modify metadataproviders to support movies

This commit is contained in:
maxDorninger
2025-06-22 20:59:58 +02:00
parent 6784a800cf
commit 5a8d3b1ef9
4 changed files with 225 additions and 16 deletions

View File

@@ -2,8 +2,9 @@ import logging
from abc import ABC, abstractmethod
import media_manager.config
from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult
from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult
from media_manager.tv.schemas import Show
from media_manager.movies.schemas import Movie
log = logging.getLogger(__name__)
@@ -20,10 +21,21 @@ class AbstractMetadataProvider(ABC):
def get_show_metadata(self, id: int = None) -> Show:
raise NotImplementedError()
@abstractmethod
def get_movie_metadata(self, id: int = None) -> Movie:
raise NotImplementedError()
@abstractmethod
def search_show(
self, query: str | None = None
) -> list[MetaDataProviderShowSearchResult]:
) -> list[MetaDataProviderSearchResult]:
raise NotImplementedError()
@abstractmethod
def search_movie(
self, query: str | None = None
) -> list[MetaDataProviderSearchResult]:
raise NotImplementedError()
@abstractmethod
@@ -35,6 +47,14 @@ class AbstractMetadataProvider(ABC):
"""
raise NotImplementedError()
@abstractmethod
def download_movie_poster_image(self, show: Show) -> bool:
"""
Downloads the poster image for a show.
:param show: The show to download the poster image for.
:return: True if the image was downloaded successfully, False otherwise.
"""
raise NotImplementedError()
metadata_providers = {}

View File

@@ -9,8 +9,9 @@ from media_manager.exceptions import InvalidConfigError
from media_manager.metadataProvider.abstractMetaDataProvider import (
AbstractMetadataProvider,
)
from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult
from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult
from media_manager.tv.schemas import Episode, Season, Show, SeasonNumber, EpisodeNumber
from media_manager.movies.schemas import Movie
class TmdbConfig(BaseSettings):
@@ -40,7 +41,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
"https://image.tmdb.org/t/p/original" + show_metadata["poster_path"]
)
if media_manager.metadataProvider.utils.download_poster_image(
storage_path=self.storage_path, poster_url=poster_url, show=show
storage_path=self.storage_path, poster_url=poster_url, id=show.id
):
log.info("Successfully downloaded poster image for show " + show.name)
else:
@@ -87,7 +88,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
)
)
year = media_manager.metadataProvider.utils.get_year_from_first_air_date(
year = media_manager.metadataProvider.utils.get_year_from_date(
show_metadata["first_air_date"]
)
@@ -105,7 +106,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
def search_show(
self, query: str | None = None, max_pages: int = 5
) -> list[MetaDataProviderShowSearchResult]:
) -> list[MetaDataProviderSearchResult]:
"""
Search for shows using TMDB API.
If no query is provided, it will return the most popular shows.
@@ -136,12 +137,12 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
else:
poster_url = None
formatted_results.append(
MetaDataProviderShowSearchResult(
MetaDataProviderSearchResult(
poster_path=poster_url,
overview=result["overview"],
name=result["name"],
external_id=result["id"],
year=media_manager.metadataProvider.utils.get_year_from_first_air_date(
year=media_manager.metadataProvider.utils.get_year_from_date(
result["first_air_date"]
),
metadata_provider=self.name,
@@ -152,3 +153,117 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
def get_movie_metadata(self, id: int = None) -> Movie:
"""
:param id: the external id of the show
:type id: int
:return: returns a ShowMetadata object
:rtype: ShowMetadata
"""
movie_metadata = tmdbsimple.Movies(id).info()
year = media_manager.metadataProvider.utils.get_year_from_date(
movie_metadata["release_date"]
)
movie = Movie(
external_id=id,
name=movie_metadata["title"],
overview=movie_metadata["overview"],
year=year,
metadata_provider=self.name,
)
return movie
def search_movie(
self, query: str | None = None, max_pages: int = 5
) -> list[MetaDataProviderSearchResult]:
"""
Search for movies using TMDB API.
If no query is provided, it will return the most popular movies.
"""
if query is None:
result_factory = lambda page: tmdbsimple.Trending(media_type="movie").info() # noqa: E731
else:
result_factory = lambda page: tmdbsimple.Search().movie( # noqa: E731
page=page, query=query, include_adult=True
)
results = []
for i in range(1, max_pages + 1):
result_page = result_factory(i)
if not result_page["results"]:
break
else:
results.extend(result_page["results"])
formatted_results = []
for result in results:
try:
if result["poster_path"] is not None:
poster_url = (
"https://image.tmdb.org/t/p/original" + result["poster_path"]
)
else:
poster_url = None
formatted_results.append(
MetaDataProviderSearchResult(
poster_path=poster_url,
overview=result["overview"],
name=result["title"],
external_id=result["id"],
year=media_manager.metadataProvider.utils.get_year_from_date(
result["release_date"]
),
metadata_provider=self.name,
added=False,
vote_average=result["vote_average"],
)
)
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
def download_movie_poster_image(self, movie: Movie) -> bool:
movie_metadata = tmdbsimple.Movies(movie.external_id).info()
# downloading the poster
# all pictures from TMDB should already be jpeg, so no need to convert
if movie_metadata["poster_path"] is not None:
poster_url = (
"https://image.tmdb.org/t/p/original" + movie_metadata["poster_path"]
)
if media_manager.metadataProvider.utils.download_poster_image(
storage_path=self.storage_path, poster_url=poster_url, id=movie.id
):
log.info("Successfully downloaded poster image for show " + movie.name)
else:
log.warning(f"download for image of show {movie.name} failed")
return False
else:
log.warning(f"image for show {movie.name} could not be downloaded")
return False
return True

View File

@@ -8,8 +8,9 @@ from media_manager.exceptions import InvalidConfigError
from media_manager.metadataProvider.abstractMetaDataProvider import (
AbstractMetadataProvider,
)
from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult
from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult
from media_manager.tv.schemas import Episode, Season, Show, SeasonNumber
from media_manager.movies.schemas import Movie
class TvdbConfig(BaseSettings):
@@ -24,7 +25,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
tvdb_client: tvdb_v4_official.TVDB
def __init__(self, api_key: str = None):
def __init__(self):
config = TvdbConfig()
if config.TVDB_API_KEY is None:
raise InvalidConfigError("TVDB_API_KEY is not set")
@@ -37,7 +38,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
media_manager.metadataProvider.utils.download_poster_image(
storage_path=self.storage_path,
poster_url=show_metadata["image"],
show=show,
id=show.id,
)
log.info("Successfully downloaded poster image for show " + show.name)
return True
@@ -105,7 +106,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
def search_show(
self, query: str | None = None
) -> list[MetaDataProviderShowSearchResult]:
) -> list[MetaDataProviderSearchResult]:
if query is None:
results = self.tvdb_client.get_all_series()
else:
@@ -120,7 +121,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
year = None
formatted_results.append(
MetaDataProviderShowSearchResult(
MetaDataProviderSearchResult(
poster_path=result["image_url"],
overview=result["overview"],
name=result["name"],
@@ -134,3 +135,72 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
def search_movie(self, query: str | None = None) -> list[MetaDataProviderSearchResult]:
if query is None:
results = self.tvdb_client.get_all_movies()
else:
results = self.tvdb_client.search(query)
formatted_results = []
for result in results:
try:
if result["type"] == "movie":
try:
year = result["year"]
except KeyError:
year = None
formatted_results.append(
MetaDataProviderSearchResult(
poster_path=result["image_url"],
overview="TVDB doesn't provide Movie Overviews",
name=result["name"],
external_id=result["tvdb_id"],
year=year,
metadata_provider=self.name,
added=False,
vote_average=None,
)
)
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
def download_movie_poster_image(self, movie: Movie) -> bool:
movie_metadata = self.tvdb_client.get_movie_extended(movie.external_id)
if movie_metadata["image"] is not None:
media_manager.metadataProvider.utils.download_poster_image(
storage_path=self.storage_path,
poster_url=movie_metadata["image"],
id=movie.id,
)
log.info("Successfully downloaded poster image for show " + movie.name)
return True
else:
log.warning(f"image for show {movie.name} could not be downloaded")
return False
def get_movie_metadata(self, id: int = None) -> Movie:
"""
:param id: the external id of the movie
:type id: int
:return: returns a Movie object
:rtype: Movie
"""
movie = self.tvdb_client.get_movie_extended(id)
try:
year = movie["year"]
except KeyError:
year = None
movie = Movie(
name=movie["name"],
overview="TVDB doesn't provide Movie Overviews",
year=year,
external_id=movie["id"],
metadata_provider=self.name,
)
return movie

View File

@@ -1,18 +1,22 @@
from uuid import UUID
from PIL import Image
import requests
import pillow_avif
pillow_avif
def get_year_from_first_air_date(first_air_date: str | None) -> int | None:
def get_year_from_date(first_air_date: str | None) -> int | None:
if first_air_date:
return int(first_air_date.split("-")[0])
else:
return None
def download_poster_image(storage_path=None, poster_url=None, show=None) -> bool:
def download_poster_image(storage_path=None, poster_url=None, id: UUID=None) -> bool:
res = requests.get(poster_url, stream=True)
if res.status_code == 200:
image_file_path = storage_path.joinpath(str(show.id))
image_file_path = storage_path.joinpath(str(id))
with open(str(image_file_path) + ".jpg", "wb") as f:
f.write(res.content)