Add German FSK rating support for NFO files
- Add optional fsk field to TVShowNFO model - Implement TMDB content ratings API integration - Add FSK extraction and mapping (FSK 0/6/12/16/18) - Update XML generation to prefer FSK over MPAA - Add nfo_prefer_fsk_rating config setting - Add 31 comprehensive tests for FSK functionality - All 112 NFO tests passing
This commit is contained in:
@@ -22,7 +22,7 @@ from src.core.entities.nfo_models import (
|
||||
UniqueID,
|
||||
)
|
||||
from src.core.services.tmdb_client import TMDBAPIError, TMDBClient
|
||||
from src.core.utils.image_downloader import ImageDownloader, ImageDownloadError
|
||||
from src.core.utils.image_downloader import ImageDownloader
|
||||
from src.core.utils.nfo_generator import generate_tvshow_nfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -122,8 +122,11 @@ class NFOService:
|
||||
append_to_response="credits,external_ids,images"
|
||||
)
|
||||
|
||||
# Get content ratings for FSK
|
||||
content_ratings = await self.tmdb_client.get_tv_show_content_ratings(tv_id)
|
||||
|
||||
# Convert TMDB data to TVShowNFO model
|
||||
nfo_model = self._tmdb_to_nfo_model(details)
|
||||
nfo_model = self._tmdb_to_nfo_model(details, content_ratings)
|
||||
|
||||
# Generate XML
|
||||
nfo_xml = generate_tvshow_nfo(nfo_model)
|
||||
@@ -209,8 +212,11 @@ class NFOService:
|
||||
append_to_response="credits,external_ids,images"
|
||||
)
|
||||
|
||||
# Get content ratings for FSK
|
||||
content_ratings = await self.tmdb_client.get_tv_show_content_ratings(tmdb_id)
|
||||
|
||||
# Convert TMDB data to TVShowNFO model
|
||||
nfo_model = self._tmdb_to_nfo_model(details)
|
||||
nfo_model = self._tmdb_to_nfo_model(details, content_ratings)
|
||||
|
||||
# Generate XML
|
||||
nfo_xml = generate_tvshow_nfo(nfo_model)
|
||||
@@ -261,11 +267,16 @@ class NFOService:
|
||||
# Return first result (usually best match)
|
||||
return results[0]
|
||||
|
||||
def _tmdb_to_nfo_model(self, tmdb_data: Dict[str, Any]) -> TVShowNFO:
|
||||
def _tmdb_to_nfo_model(
|
||||
self,
|
||||
tmdb_data: Dict[str, Any],
|
||||
content_ratings: Optional[Dict[str, Any]] = None
|
||||
) -> TVShowNFO:
|
||||
"""Convert TMDB API data to TVShowNFO model.
|
||||
|
||||
Args:
|
||||
tmdb_data: TMDB TV show details
|
||||
content_ratings: TMDB content ratings data
|
||||
|
||||
Returns:
|
||||
TVShowNFO Pydantic model
|
||||
@@ -362,6 +373,9 @@ class NFOService:
|
||||
default=True
|
||||
))
|
||||
|
||||
# Extract FSK rating from content ratings
|
||||
fsk_rating = self._extract_fsk_rating(content_ratings) if content_ratings else None
|
||||
|
||||
# Create NFO model
|
||||
return TVShowNFO(
|
||||
title=title,
|
||||
@@ -375,6 +389,7 @@ class NFOService:
|
||||
studio=[n["name"] for n in tmdb_data.get("networks", [])],
|
||||
country=[c["name"] for c in tmdb_data.get("production_countries", [])],
|
||||
ratings=ratings,
|
||||
fsk=fsk_rating,
|
||||
tmdbid=tmdb_data.get("id"),
|
||||
imdbid=imdb_id,
|
||||
tvdbid=tvdb_id,
|
||||
@@ -444,6 +459,50 @@ class NFOService:
|
||||
logger.info(f"Media download results: {results}")
|
||||
return results
|
||||
|
||||
def _extract_fsk_rating(self, content_ratings: Dict[str, Any]) -> Optional[str]:
|
||||
"""Extract German FSK rating from TMDB content ratings.
|
||||
|
||||
Args:
|
||||
content_ratings: TMDB content ratings response
|
||||
|
||||
Returns:
|
||||
FSK rating string (e.g., 'FSK 12') or None
|
||||
"""
|
||||
if not content_ratings or "results" not in content_ratings:
|
||||
return None
|
||||
|
||||
# Find German rating (iso_3166_1: "DE")
|
||||
for rating in content_ratings["results"]:
|
||||
if rating.get("iso_3166_1") == "DE":
|
||||
rating_value = rating.get("rating", "")
|
||||
|
||||
# Map TMDB German ratings to FSK format
|
||||
fsk_mapping = {
|
||||
"0": "FSK 0",
|
||||
"6": "FSK 6",
|
||||
"12": "FSK 12",
|
||||
"16": "FSK 16",
|
||||
"18": "FSK 18",
|
||||
}
|
||||
|
||||
# Try direct mapping
|
||||
if rating_value in fsk_mapping:
|
||||
return fsk_mapping[rating_value]
|
||||
|
||||
# Try to extract number from rating string (ordered from highest to lowest to avoid partial matches)
|
||||
for key in ["18", "16", "12", "6", "0"]:
|
||||
if key in rating_value:
|
||||
return fsk_mapping[key]
|
||||
|
||||
# Return as-is if it already starts with FSK
|
||||
if rating_value.startswith("FSK"):
|
||||
return rating_value
|
||||
|
||||
logger.debug(f"Unmapped German rating: {rating_value}")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
async def close(self):
|
||||
"""Clean up resources."""
|
||||
await self.tmdb_client.close()
|
||||
|
||||
@@ -191,6 +191,17 @@ class TMDBClient:
|
||||
|
||||
return await self._request(f"tv/{tv_id}", params)
|
||||
|
||||
async def get_tv_show_content_ratings(self, tv_id: int) -> Dict[str, Any]:
|
||||
"""Get content ratings for a TV show.
|
||||
|
||||
Args:
|
||||
tv_id: TMDB TV show ID
|
||||
|
||||
Returns:
|
||||
Content ratings by country
|
||||
"""
|
||||
return await self._request(f"tv/{tv_id}/content_ratings")
|
||||
|
||||
async def get_tv_show_external_ids(self, tv_id: int) -> Dict[str, Any]:
|
||||
"""Get external IDs (IMDB, TVDB) for a TV show.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user