feat: Implement NFOService.update_tvshow_nfo()

- Parse existing NFO to extract TMDB ID from uniqueid or tmdbid element
- Fetch fresh metadata from TMDB API
- Regenerate NFO with updated data
- Optionally re-download media files
- Add comprehensive error handling (missing NFO, no TMDB ID, invalid XML)
- Add unit tests for XML parsing logic (4 tests, all passing)
- Add integration test script (requires TMDB API key)
This commit is contained in:
2026-01-11 21:10:44 +01:00
parent 67119d0627
commit e32098fb94
4 changed files with 428 additions and 10 deletions

View File

@@ -12,6 +12,8 @@ import logging
from pathlib import Path
from typing import Any, Dict, List, Optional
from lxml import etree
from src.core.entities.nfo_models import (
ActorInfo,
ImageInfo,
@@ -158,21 +160,76 @@ class NFOService:
Raises:
FileNotFoundError: If NFO file doesn't exist
TMDBAPIError: If TMDB API fails
TMDBAPIError: If TMDB API fails or no TMDB ID found in NFO
"""
nfo_path = self.anime_directory / serie_folder / "tvshow.nfo"
folder_path = self.anime_directory / serie_folder
nfo_path = folder_path / "tvshow.nfo"
if not nfo_path.exists():
raise FileNotFoundError(f"NFO file not found: {nfo_path}")
# Parse existing NFO to get TMDB ID
# For simplicity, we'll recreate from scratch
# In production, you'd parse the XML to extract the ID
logger.info(f"Updating NFO for {serie_folder}")
# Implementation would extract serie name and call create_tvshow_nfo
# This is a simplified version
raise NotImplementedError("Update NFO not yet implemented")
# Parse existing NFO to extract TMDB ID
try:
tree = etree.parse(str(nfo_path))
root = tree.getroot()
# Try to find TMDB ID from uniqueid elements
tmdb_id = None
for uniqueid in root.findall(".//uniqueid"):
if uniqueid.get("type") == "tmdb":
tmdb_id = int(uniqueid.text)
break
# Fallback: check for tmdbid element
if tmdb_id is None:
tmdbid_elem = root.find(".//tmdbid")
if tmdbid_elem is not None and tmdbid_elem.text:
tmdb_id = int(tmdbid_elem.text)
if tmdb_id is None:
raise TMDBAPIError(
f"No TMDB ID found in existing NFO. "
f"Delete the NFO and create a new one instead."
)
logger.debug(f"Found TMDB ID: {tmdb_id}")
except etree.XMLSyntaxError as e:
raise TMDBAPIError(f"Invalid XML in NFO file: {e}")
except ValueError as e:
raise TMDBAPIError(f"Invalid TMDB ID format in NFO: {e}")
# Fetch fresh data from TMDB
async with self.tmdb_client:
logger.debug(f"Fetching fresh data for TMDB ID: {tmdb_id}")
details = await self.tmdb_client.get_tv_show_details(
tmdb_id,
append_to_response="credits,external_ids,images"
)
# Convert TMDB data to TVShowNFO model
nfo_model = self._tmdb_to_nfo_model(details)
# Generate XML
nfo_xml = generate_tvshow_nfo(nfo_model)
# Save updated NFO file
nfo_path.write_text(nfo_xml, encoding="utf-8")
logger.info(f"Updated NFO: {nfo_path}")
# Re-download media files if requested
if download_media:
await self._download_media_files(
details,
folder_path,
download_poster=True,
download_logo=True,
download_fanart=True
)
return nfo_path
def _find_best_match(
self,