feat: add English fallback for empty German TMDB overview in NFO creation

When TMDB returns an empty German (de-DE) overview for anime (e.g.
Basilisk), the NFO plot tag was missing. Now both create and update
paths call _enrich_details_with_fallback() which fetches the English
(en-US) overview as a fallback.

Additionally, the <plot> XML element is always written (even when
empty) via the always_write parameter on _add_element(), ensuring
consistent NFO structure regardless of creation path.

Changes:
- nfo_service.py: add _enrich_details_with_fallback() method, call it
  in create_tvshow_nfo and update_tvshow_nfo
- nfo_generator.py: add always_write param to _add_element(), use it
  for <plot> tag
- test_nfo_service.py: add TestEnrichDetailsWithFallback with 4 tests
This commit is contained in:
2026-02-26 20:48:47 +01:00
parent fc8cdc538d
commit e6d9f9f342
3 changed files with 280 additions and 4 deletions

View File

@@ -169,6 +169,9 @@ class NFOService:
# Get content ratings for FSK
content_ratings = await self.tmdb_client.get_tv_show_content_ratings(tv_id)
# Enrich with English fallback for empty overview/tagline
details = await self._enrich_details_with_fallback(details)
# Convert TMDB data to TVShowNFO model
nfo_model = tmdb_to_nfo_model(
details,
@@ -264,6 +267,9 @@ class NFOService:
# Get content ratings for FSK
content_ratings = await self.tmdb_client.get_tv_show_content_ratings(tmdb_id)
# Enrich with English fallback for empty overview/tagline
details = await self._enrich_details_with_fallback(details)
# Convert TMDB data to TVShowNFO model
nfo_model = tmdb_to_nfo_model(
details,
@@ -372,6 +378,61 @@ class NFOService:
return result
async def _enrich_details_with_fallback(
self,
details: Dict[str, Any],
) -> Dict[str, Any]:
"""Enrich TMDB details with English fallback for empty fields.
When requesting details in ``de-DE``, some anime have an empty
``overview`` (and potentially other translatable fields). This
method detects empty values and fills them from the English
(``en-US``) endpoint so that NFO files always contain a ``plot``
regardless of whether the German translation exists.
Args:
details: TMDB TV show details (language ``de-DE``).
Returns:
The *same* dict, mutated in-place with English fallbacks
where needed.
"""
overview = details.get("overview") or ""
if overview:
# Overview already populated nothing to do.
return details
logger.debug(
"German overview empty for TMDB ID %s, fetching English fallback",
details.get("id"),
)
try:
en_details = await self.tmdb_client.get_tv_show_details(
details["id"],
language="en-US",
)
if en_details.get("overview"):
details["overview"] = en_details["overview"]
logger.info(
"Used English overview fallback for TMDB ID %s",
details.get("id"),
)
# Also fill tagline if missing
if not details.get("tagline") and en_details.get("tagline"):
details["tagline"] = en_details["tagline"]
except Exception as exc: # pylint: disable=broad-except
logger.warning(
"Failed to fetch English fallback for TMDB ID %s: %s",
details.get("id"),
exc,
)
return details
def _find_best_match(
self,
results: List[Dict[str, Any]],

View File

@@ -43,8 +43,10 @@ def generate_tvshow_nfo(tvshow: TVShowNFO, pretty_print: bool = True) -> str:
_add_element(root, "sorttitle", tvshow.sorttitle)
_add_element(root, "year", str(tvshow.year) if tvshow.year else None)
# Plot and description
_add_element(root, "plot", tvshow.plot)
# Plot and description always write <plot> even when empty so that
# all NFO files have a consistent set of tags regardless of whether they
# were produced by create or update.
_add_element(root, "plot", tvshow.plot, always_write=True)
_add_element(root, "outline", tvshow.outline)
_add_element(root, "tagline", tvshow.tagline)
@@ -164,13 +166,23 @@ def generate_tvshow_nfo(tvshow: TVShowNFO, pretty_print: bool = True) -> str:
return xml_declaration + xml_str
def _add_element(parent: etree.Element, tag: str, text: Optional[str]) -> Optional[etree.Element]:
def _add_element(
parent: etree.Element,
tag: str,
text: Optional[str],
always_write: bool = False,
) -> Optional[etree.Element]:
"""Add a child element to parent if text is not None or empty.
Args:
parent: Parent XML element
tag: Tag name for child element
text: Text content (None or empty strings are skipped)
text: Text content (None or empty strings are skipped
unless *always_write* is True)
always_write: When True the element is created even when
*text* is None/empty (the element will have
no text content). Useful for tags like
``<plot>`` that should always be present.
Returns:
Created element or None if skipped
@@ -179,6 +191,8 @@ def _add_element(parent: etree.Element, tag: str, text: Optional[str]) -> Option
elem = etree.SubElement(parent, tag)
elem.text = text
return elem
if always_write:
return etree.SubElement(parent, tag)
return None