diff --git a/core/async_nfo_manager.py b/core/async_nfo_manager.py index 33fb337..d0ab52f 100644 --- a/core/async_nfo_manager.py +++ b/core/async_nfo_manager.py @@ -15,7 +15,8 @@ from utils.async_file_utils import ( async_file_exists, async_set_file_mtime, async_batch_nfo_operations, - async_concurrent_episode_processing + async_concurrent_episode_processing, + aiofiles ) from utils.nfo_patterns import ( create_basic_nfo_structure, @@ -24,6 +25,7 @@ from utils.nfo_patterns import ( extract_imdb_id_from_text ) from utils.validation import validate_date_string +from utils.file_utils import VIDEO_EXTENSIONS class AsyncNFOManager: @@ -33,6 +35,54 @@ class AsyncNFOManager: self.manager_brand = manager_brand self.debug = debug + async def _async_get_target_nfo_path(self, season_dir: Path, season: int, episode: int) -> Path: + """ + Get the target NFO path using video filename matching to prevent concatenation + + Args: + season_dir: Path to season directory + season: Season number + episode: Episode number + + Returns: + Path to target NFO file (video-matching preferred, generic fallback) + """ + try: + # Find video files in the season directory + video_files = [] + if await async_file_exists(season_dir): + import re + try: + entries = await aiofiles.os.listdir(season_dir) + for entry in entries: + entry_path = season_dir / entry + if await aiofiles.os.path.isfile(entry_path): + if entry_path.suffix.lower() in VIDEO_EXTENSIONS: + # Parse episode info from filename + match = re.search(r'[Ss](\d{1,2})[Ee](\d{1,2})', entry) + if match: + s, e = int(match.group(1)), int(match.group(2)) + if s == season and e == episode: + # Found matching video file - use its name for NFO + target_nfo = season_dir / f"{entry_path.stem}.nfo" + if self.debug: + _log("DEBUG", f"Video-matching NFO path: {target_nfo.name}") + return target_nfo + except Exception as e: + if self.debug: + _log("WARNING", f"Failed to scan season directory {season_dir}: {e}") + + # Fallback to generic filename if no matching video found + target_nfo = season_dir / f"S{season:02d}E{episode:02d}.nfo" + if self.debug: + _log("WARNING", f"No video file found for S{season:02d}E{episode:02d}, using generic: {target_nfo.name}") + return target_nfo + + except Exception as e: + _log("ERROR", f"Failed to determine NFO path for S{season:02d}E{episode:02d}: {e}") + # Emergency fallback + return season_dir / f"S{season:02d}E{episode:02d}.nfo" + async def async_parse_imdb_from_path(self, path: Path) -> Optional[str]: """ Async extract IMDb ID from directory path or filename @@ -184,7 +234,7 @@ class AsyncNFOManager: lock_metadata: bool = True ) -> bool: """ - Async create episode NFO file + Async create episode NFO file with proper video filename matching Args: season_dir: Path to season directory @@ -199,8 +249,8 @@ class AsyncNFOManager: True if successful, False otherwise """ try: - nfo_filename = f"S{season:02d}E{episode:02d}.nfo" - nfo_path = season_dir / nfo_filename + # Use proper video filename matching to prevent NFO concatenation + nfo_path = await self._async_get_target_nfo_path(season_dir, season, episode) # Prepare dates dates = {}