diff --git a/src/core/services/nfo_service.py b/src/core/services/nfo_service.py index 6c855fd..46d05f2 100644 --- a/src/core/services/nfo_service.py +++ b/src/core/services/nfo_service.py @@ -174,6 +174,32 @@ class NFOService: # Enrich with fallback languages for empty overview/tagline # Pass search result overview as last resort fallback search_overview = tv_show.get("overview") or None + if not search_overview: + try: + logger.debug( + "No overview in German search result, trying en-US search fallback for: %s", + search_name, + ) + en_search_results = await self.tmdb_client.search_tv_show( + search_name, + language="en-US", + ) + if en_search_results.get("results"): + en_match = self._find_best_match( + en_search_results["results"], search_name, year + ) + search_overview = en_match.get("overview") or None + if search_overview: + logger.info( + "Using en-US search overview fallback for %s", + search_name, + ) + except (TMDBAPIError, Exception) as exc: + logger.warning( + "Failed en-US search fallback for overview: %s", + exc, + ) + details = await self._enrich_details_with_fallback( details, search_overview=search_overview ) diff --git a/tests/unit/test_nfo_service.py b/tests/unit/test_nfo_service.py index 20d2733..1824984 100644 --- a/tests/unit/test_nfo_service.py +++ b/tests/unit/test_nfo_service.py @@ -424,7 +424,14 @@ class TestCreateTVShowNFO: patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \ patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock): - mock_search.return_value = {"results": [{"id": 1429, "name": "Attack on Titan", "first_air_date": "2013-04-07"}]} + mock_search.return_value = { + "results": [{ + "id": 1429, + "name": "Attack on Titan", + "first_air_date": "2013-04-07", + "overview": "Several hundred years ago, humans were nearly...", + }] + } mock_details.return_value = mock_tmdb_data mock_ratings.return_value = mock_content_ratings_de @@ -463,7 +470,14 @@ class TestCreateTVShowNFO: patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \ patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock): - mock_search.return_value = {"results": [{"id": 1429, "name": "Attack on Titan", "first_air_date": "2013-04-07"}]} + mock_search.return_value = { + "results": [{ + "id": 1429, + "name": "Attack on Titan", + "first_air_date": "2013-04-07", + "overview": "Several hundred years ago, humans were nearly...", + }] + } mock_details.return_value = mock_tmdb_data mock_ratings.return_value = mock_content_ratings_no_de @@ -749,7 +763,14 @@ class TestNFOServiceEdgeCases: "poster_path": None, "backdrop_path": None } - mock_search.return_value = {"results": [{"id": 1, "name": "Series", "first_air_date": "2020-01-01"}]} + mock_search.return_value = { + "results": [{ + "id": 1, + "name": "Series", + "first_air_date": "2020-01-01", + "overview": "Test overview.", + }] + } mock_details.return_value = tmdb_data mock_ratings.return_value = {"results": []} @@ -1486,6 +1507,67 @@ class TestEnrichFallbackLanguages: content = nfo_path.read_text(encoding="utf-8") assert "Search result overview text." in content + @pytest.mark.asyncio + async def test_en_us_search_fallback_when_german_search_overview_empty( + self, nfo_service, tmp_path + ): + """When the German search overview is empty, fallback to en-US search overview.""" + series_folder = tmp_path / "Rare Anime" + series_folder.mkdir() + + empty_data = { + "id": 77777, "name": "Rare Anime", + "original_name": "新しいアニメ", "first_air_date": "2025-01-01", + "overview": "", + "vote_average": 0, "vote_count": 0, + "status": "Continuing", "episode_run_time": [], + "genres": [], "networks": [], "production_countries": [], + "poster_path": None, "backdrop_path": None, + "external_ids": {}, "credits": {"cast": []}, + "images": {"logos": []}, + } + + async def search_side_effect(query, language="de-DE", page=1): + if language == "en-US": + return { + "results": [{ + "id": 77777, + "name": "Rare Anime", + "first_air_date": "2025-01-01", + "overview": "English search overview text.", + }], + } + return { + "results": [{ + "id": 77777, + "name": "Rare Anime", + "first_air_date": "2025-01-01", + "overview": "", + }], + } + + async def details_side_effect(tv_id, **kwargs): + return empty_data + + with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \ + patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \ + patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \ + patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock): + + mock_search.side_effect = search_side_effect + mock_details.side_effect = details_side_effect + mock_ratings.return_value = {"results": []} + + nfo_path = await nfo_service.create_tvshow_nfo( + "Rare Anime", "Rare Anime", + download_poster=False, download_logo=False, download_fanart=False, + ) + + content = nfo_path.read_text(encoding="utf-8") + assert "English search overview text." in content + assert mock_search.call_count == 2 + assert mock_search.call_args_list[1].kwargs['language'] == 'en-US' + @pytest.mark.asyncio async def test_no_japanese_fallback_when_english_succeeds( self, nfo_service, tmp_path,