mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 15:13:24 +02:00
feat: Add multi-language metadata support
- Add primary_languages config setting (ISO 639-1 codes) - Fetch metadata in original language when in primary_languages - Display original titles in search results for configured languages - Download language-specific posters when available
This commit is contained in:
@@ -86,6 +86,13 @@ enabled = false
|
||||
api_key = ""
|
||||
user = ""
|
||||
|
||||
[metadata]
|
||||
# Primary languages for metadata fetching (ISO 639-1 codes)
|
||||
# When a TV show or movie's original language matches one of these languages,
|
||||
# MediaManager will display the original title and fetch metadata in that language.
|
||||
# Examples: ["en", "no", "da"]
|
||||
primary_languages = ["en"]
|
||||
|
||||
[torrents]
|
||||
# qBittorrent settings
|
||||
[torrents.qbittorrent]
|
||||
|
||||
@@ -12,3 +12,6 @@ class TvdbConfig(BaseSettings):
|
||||
class MetadataProviderConfig(BaseSettings):
|
||||
tvdb: TvdbConfig = TvdbConfig()
|
||||
tmdb: TmdbConfig = TmdbConfig()
|
||||
# ISO 639-1 language codes (e.g., ["en", "no", "sv"])
|
||||
# When media's original language matches one of these, original title and metadata will be used
|
||||
primary_languages: list[str] = ["en"]
|
||||
|
||||
@@ -22,12 +22,28 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
name = "tmdb"
|
||||
|
||||
def __init__(self):
|
||||
config = AllEncompassingConfig().metadata.tmdb
|
||||
self.url = config.tmdb_relay_url
|
||||
config = AllEncompassingConfig()
|
||||
self.url = config.metadata.tmdb.tmdb_relay_url
|
||||
self.primary_languages = config.metadata.primary_languages
|
||||
|
||||
def __get_show_metadata(self, id: int) -> dict:
|
||||
def __get_language_param(self, original_language: str | None) -> str:
|
||||
"""
|
||||
Determine the language parameter to use for TMDB API calls.
|
||||
Returns the original language if it's in primary_languages, otherwise returns English.
|
||||
|
||||
:param original_language: The original language code (ISO 639-1) of the media
|
||||
:return: Language parameter (ISO 639-1 format, e.g., 'en', 'no')
|
||||
"""
|
||||
if original_language and original_language in self.primary_languages:
|
||||
return original_language
|
||||
return "en"
|
||||
|
||||
def __get_show_metadata(self, id: int, language: str = "en") -> dict:
|
||||
try:
|
||||
response = requests.get(url=f"{self.url}/tv/shows/{id}")
|
||||
response = requests.get(
|
||||
url=f"{self.url}/tv/shows/{id}",
|
||||
params={"language": language}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
@@ -39,10 +55,11 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
)
|
||||
raise
|
||||
|
||||
def __get_season_metadata(self, show_id: int, season_number: int) -> dict:
|
||||
def __get_season_metadata(self, show_id: int, season_number: int, language: str = "en") -> dict:
|
||||
try:
|
||||
response = requests.get(
|
||||
url=f"{self.url}/tv/shows/{show_id}/{season_number}"
|
||||
url=f"{self.url}/tv/shows/{show_id}/{season_number}",
|
||||
params={"language": language}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -87,9 +104,12 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
)
|
||||
raise
|
||||
|
||||
def __get_movie_metadata(self, id: int) -> dict:
|
||||
def __get_movie_metadata(self, id: int, language: str = "en") -> dict:
|
||||
try:
|
||||
response = requests.get(url=f"{self.url}/movies/{id}")
|
||||
response = requests.get(
|
||||
url=f"{self.url}/movies/{id}",
|
||||
params={"language": language}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
@@ -132,7 +152,17 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
raise
|
||||
|
||||
def download_show_poster_image(self, show: Show) -> bool:
|
||||
# First fetch to get original_language
|
||||
show_metadata = self.__get_show_metadata(show.external_id)
|
||||
original_language = show_metadata.get("original_language")
|
||||
|
||||
# Determine which language to use
|
||||
language = self.__get_language_param(original_language)
|
||||
|
||||
# Fetch metadata in the appropriate language to get localized poster
|
||||
if language != "en":
|
||||
show_metadata = self.__get_show_metadata(show.external_id, language=language)
|
||||
|
||||
# downloading the poster
|
||||
# all pictures from TMDB should already be jpeg, so no need to convert
|
||||
if show_metadata["poster_path"] is not None:
|
||||
@@ -159,12 +189,23 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
:return: returns a ShowMetadata object
|
||||
:rtype: ShowMetadata
|
||||
"""
|
||||
|
||||
show_metadata = self.__get_show_metadata(id)
|
||||
original_language = show_metadata.get("original_language")
|
||||
|
||||
# Determine which language to use for metadata
|
||||
language = self.__get_language_param(original_language)
|
||||
|
||||
# Fetch show metadata in the appropriate language
|
||||
show_metadata = self.__get_show_metadata(id, language=language)
|
||||
|
||||
season_list = []
|
||||
# inserting all the metadata into the objects
|
||||
for season in show_metadata["seasons"]:
|
||||
season_metadata = self.__get_season_metadata(
|
||||
show_id=show_metadata["id"], season_number=season["season_number"]
|
||||
show_id=show_metadata["id"],
|
||||
season_number=season["season_number"],
|
||||
language=language
|
||||
)
|
||||
episode_list = []
|
||||
|
||||
@@ -231,11 +272,21 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
)
|
||||
else:
|
||||
poster_url = None
|
||||
|
||||
# Determine which name to use based on primary_languages
|
||||
original_language = result.get("original_language")
|
||||
original_name = result.get("original_name")
|
||||
display_name = result["name"]
|
||||
|
||||
# Use original name if language is in primary_languages
|
||||
if original_language and original_language in self.primary_languages:
|
||||
display_name = original_name
|
||||
|
||||
formatted_results.append(
|
||||
MetaDataProviderSearchResult(
|
||||
poster_path=poster_url,
|
||||
overview=result["overview"],
|
||||
name=result["name"],
|
||||
name=display_name,
|
||||
external_id=result["id"],
|
||||
year=media_manager.metadataProvider.utils.get_year_from_date(
|
||||
result["first_air_date"]
|
||||
@@ -243,6 +294,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
metadata_provider=self.name,
|
||||
added=False,
|
||||
vote_average=result["vote_average"],
|
||||
original_language=original_language,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -252,12 +304,21 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
def get_movie_metadata(self, id: int = None) -> Movie:
|
||||
"""
|
||||
|
||||
:param id: the external id of the show
|
||||
:param id: the external id of the movie
|
||||
:type id: int
|
||||
:return: returns a ShowMetadata object
|
||||
:rtype: ShowMetadata
|
||||
:return: returns a Movie object
|
||||
:rtype: Movie
|
||||
"""
|
||||
|
||||
movie_metadata = self.__get_movie_metadata(id=id)
|
||||
original_language = movie_metadata.get("original_language")
|
||||
|
||||
# Determine which language to use for metadata
|
||||
language = self.__get_language_param(original_language)
|
||||
|
||||
# Fetch movie metadata in the appropriate language
|
||||
movie_metadata = self.__get_movie_metadata(id=id, language=language)
|
||||
|
||||
year = media_manager.metadataProvider.utils.get_year_from_date(
|
||||
movie_metadata["release_date"]
|
||||
)
|
||||
@@ -300,11 +361,21 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
)
|
||||
else:
|
||||
poster_url = None
|
||||
|
||||
# Determine which name to use based on primary_languages
|
||||
original_language = result.get("original_language")
|
||||
original_title = result.get("original_title")
|
||||
display_name = result["title"]
|
||||
|
||||
# Use original title if language is in primary_languages
|
||||
if original_language and original_language in self.primary_languages and original_title:
|
||||
display_name = original_title
|
||||
|
||||
formatted_results.append(
|
||||
MetaDataProviderSearchResult(
|
||||
poster_path=poster_url,
|
||||
overview=result["overview"],
|
||||
name=result["title"],
|
||||
name=display_name,
|
||||
external_id=result["id"],
|
||||
year=media_manager.metadataProvider.utils.get_year_from_date(
|
||||
result["release_date"]
|
||||
@@ -312,6 +383,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
metadata_provider=self.name,
|
||||
added=False,
|
||||
vote_average=result["vote_average"],
|
||||
original_language=original_language,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -319,7 +391,17 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
return formatted_results
|
||||
|
||||
def download_movie_poster_image(self, movie: Movie) -> bool:
|
||||
# First fetch to get original_language
|
||||
movie_metadata = self.__get_movie_metadata(id=movie.external_id)
|
||||
original_language = movie_metadata.get("original_language")
|
||||
|
||||
# Determine which language to use
|
||||
language = self.__get_language_param(original_language)
|
||||
|
||||
# Fetch metadata in the appropriate language to get localized poster
|
||||
if language != "en":
|
||||
movie_metadata = self.__get_movie_metadata(id=movie.external_id, language=language)
|
||||
|
||||
# downloading the poster
|
||||
# all pictures from TMDB should already be jpeg, so no need to convert
|
||||
if movie_metadata["poster_path"] is not None:
|
||||
@@ -329,11 +411,11 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
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)
|
||||
log.info("Successfully downloaded poster image for movie " + movie.name)
|
||||
else:
|
||||
log.warning(f"download for image of show {movie.name} failed")
|
||||
log.warning(f"download for image of movie {movie.name} failed")
|
||||
return False
|
||||
else:
|
||||
log.warning(f"image for show {movie.name} could not be downloaded")
|
||||
log.warning(f"image for movie {movie.name} could not be downloaded")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -24,12 +24,12 @@ else:
|
||||
return Search().tv(page=page, query=query, include_adult=True)
|
||||
|
||||
@router.get("/tv/shows/{show_id}")
|
||||
async def get_tmdb_show(show_id: int):
|
||||
return TV(show_id).info()
|
||||
async def get_tmdb_show(show_id: int, language: str = "en"):
|
||||
return TV(show_id).info(language=language)
|
||||
|
||||
@router.get("/tv/shows/{show_id}/{season_number}")
|
||||
async def get_tmdb_season(season_number: int, show_id: int):
|
||||
return TV_Seasons(season_number=season_number, tv_id=show_id).info()
|
||||
async def get_tmdb_season(season_number: int, show_id: int, language: str = "en"):
|
||||
return TV_Seasons(season_number=season_number, tv_id=show_id).info(language=language)
|
||||
|
||||
@router.get("/movies/trending")
|
||||
async def get_tmdb_trending_movies():
|
||||
@@ -40,5 +40,5 @@ else:
|
||||
return Search().movie(page=page, query=query, include_adult=True)
|
||||
|
||||
@router.get("/movies/{movie_id}")
|
||||
async def get_tmdb_movie(movie_id: int):
|
||||
return Movies(movie_id).info()
|
||||
async def get_tmdb_movie(movie_id: int, language: str = "en"):
|
||||
return Movies(movie_id).info(language=language)
|
||||
|
||||
Reference in New Issue
Block a user