diff --git a/src/server/nfo/tmdb_client.py b/src/server/nfo/tmdb_client.py index aef3e61..8fddaec 100644 --- a/src/server/nfo/tmdb_client.py +++ b/src/server/nfo/tmdb_client.py @@ -422,3 +422,32 @@ class TMDBClient: if expired_keys: logger.debug("Removed %d expired negative cache entries", len(expired_keys)) return len(expired_keys) + + +def get_tmdb_client() -> TMDBClient: + """Factory function to create a TMDBClient with settings configuration. + + Returns: + TMDBClient instance configured with settings.tmdb_api_key + + Raises: + ValueError: If TMDB API key is not configured + """ + from src.config.settings import settings + + if not settings.tmdb_api_key: + raise ValueError("TMDB API key is not configured") + + return TMDBClient(api_key=settings.tmdb_api_key) + + +def get_tmdb_image_base_url(tmdb_id: int) -> str: + """Get the base URL for TMDB images. + + Args: + tmdb_id: TMDB show ID (used for account-specific URLs) + + Returns: + Base URL string for TMDB images + """ + return "https://image.tmdb.org/t/p/" diff --git a/src/server/services/initialization_service.py b/src/server/services/initialization_service.py index 17356f8..cccfecc 100644 --- a/src/server/services/initialization_service.py +++ b/src/server/services/initialization_service.py @@ -541,6 +541,8 @@ async def perform_nfo_scan_if_needed(progress_service=None): # Execute the NFO scan try: + # Ensure any newly created series are loaded from DB into SeriesApp memory + await _load_series_into_memory(progress_service=None) await _execute_nfo_scan(progress_service) await _mark_nfo_scan_completed() except Exception as e: @@ -615,6 +617,9 @@ async def perform_nfo_scan_phase(progress_service=None): # Execute the NFO scan try: + # Ensure any newly created series (e.g., from resolving unresolved folders) + # are loaded from DB into SeriesApp memory before scanning + await _load_series_into_memory(progress_service=None) await _execute_nfo_scan(progress_service) await _mark_nfo_scan_completed() diff --git a/src/server/services/nfo_scan_service.py b/src/server/services/nfo_scan_service.py index d9b928b..11b5eba 100644 --- a/src/server/services/nfo_scan_service.py +++ b/src/server/services/nfo_scan_service.py @@ -326,6 +326,22 @@ class NfoScanService: nfo_exists = os.path.isfile(nfo_path) + # If tmdb_id is missing, try to look it up by series name + if not series_data.get("tmdb_id"): + logger.debug("No tmdb_id for %s — attempting TMDB lookup", key) + name = series_data.get("name", "") + found_tmdb_id = await self._lookup_tmdb_id_by_name(name) + if found_tmdb_id: + series_data["tmdb_id"] = found_tmdb_id + await self._save_tmdb_id(key, found_tmdb_id) + logger.info("Found and saved tmdb_id %s for %s", found_tmdb_id, key) + else: + logger.warning( + "Could not resolve tmdb_id for %s (%s)", + key, + name, + ) + if not nfo_exists: # Create new NFO logger.info("Creating NFO for series: %s (%s)", key, folder) @@ -526,6 +542,53 @@ class NfoScanService: logger.info("Regenerated NFO for %s", key) return True + async def _save_tmdb_id(self, key: str, tmdb_id: int) -> None: + """Save tmdb_id to the database for a series. + + Args: + key: Series key (primary identifier) + tmdb_id: TMDB series ID to save + """ + try: + from src.server.database.connection import get_db_session + from src.server.database.service import AnimeSeriesService + + async with get_db_session() as db: + series = await AnimeSeriesService.get_by_key(db, key) + if series: + series.tmdb_id = tmdb_id + await db.flush() + logger.debug("Saved tmdb_id %s for series: %s", tmdb_id, key) + else: + logger.warning("Series not found for tmdb_id save: %s", key) + except Exception as exc: + logger.warning("Failed to save tmdb_id for %s: %s", key, exc) + + async def _lookup_tmdb_id_by_name(self, name: str) -> Optional[int]: + """Look up a TMDB series ID by series name. + + Args: + name: Series name to search for + + Returns: + TMDB series ID or None if not found. + """ + if not name: + return None + + try: + from src.server.nfo.tmdb_client import get_tmdb_client + + client = get_tmdb_client() + results = await client.search_tv_show(name) + if results and results.get("results"): + first_result = results["results"][0] + return first_result.get("id") + return None + except Exception as exc: + logger.warning("TMDB lookup failed for %s: %s", name, exc) + return None + async def _fetch_tmdb_data(self, tmdb_id: int) -> Optional[Dict[str, Any]]: """Fetch series metadata from TMDB API. @@ -539,7 +602,7 @@ class NfoScanService: from src.server.nfo.tmdb_client import get_tmdb_client client = get_tmdb_client() - data = await client.get_series_details(tmdb_id) + data = await client.get_tv_show_details(tmdb_id) return data except Exception as exc: logger.warning("TMDB fetch failed for TMDB ID %s: %s", tmdb_id, exc) diff --git a/tests/unit/test_initialization_service.py b/tests/unit/test_initialization_service.py index 2205fc9..61c8fd7 100644 --- a/tests/unit/test_initialization_service.py +++ b/tests/unit/test_initialization_service.py @@ -532,6 +532,8 @@ class TestPerformNFOScan: new_callable=AsyncMock, return_value=False), \ patch('src.server.services.initialization_service._is_nfo_scan_configured', new_callable=AsyncMock, return_value=True), \ + patch('src.server.services.initialization_service._load_series_into_memory', + new_callable=AsyncMock), \ patch('src.server.services.initialization_service._execute_nfo_scan', new_callable=AsyncMock), \ patch('src.server.services.initialization_service._mark_nfo_scan_completed', @@ -549,6 +551,8 @@ class TestPerformNFOScan: new_callable=AsyncMock, return_value=False), \ patch('src.server.services.initialization_service._is_nfo_scan_configured', new_callable=AsyncMock, return_value=True), \ + patch('src.server.services.initialization_service._load_series_into_memory', + new_callable=AsyncMock), \ patch('src.server.services.initialization_service._execute_nfo_scan', new_callable=AsyncMock), \ patch('src.server.services.initialization_service._mark_nfo_scan_completed',