diff --git a/api/routes.py b/api/routes.py index 6a0c98d..186155a 100644 --- a/api/routes.py +++ b/api/routes.py @@ -2300,228 +2300,186 @@ def register_routes(app, dependencies: dict): } try: - # Scan episodes - print("📺 Scanning episodes for missing dateadded elements") - episode_query = """ - SELECT DISTINCT e.imdb_id, e.season, e.episode, s.path as series_path, e.dateadded, e.has_video_file, e.source - FROM episodes e - JOIN series s ON e.imdb_id = s.imdb_id - WHERE e.has_video_file = TRUE - ORDER BY e.imdb_id, e.season, e.episode - """ + # Use filesystem-first approach: find all NFO files missing dateadded + print("📺 Scanning NFO files directly for missing dateadded elements") - with db.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(episode_query) - episodes = [dict(row) for row in cursor.fetchall()] - - print(f"📊 Found {len(episodes)} episodes in database") + # Get TV and Movie library paths from config + tv_path = getattr(config, 'tv_library_path', '/media/TV') + movie_path = getattr(config, 'movie_library_path', '/media/Movies') - processed_count = 0 - for episode in episodes: - processed_count += 1 - - # Progress logging every 1000 items - if processed_count % 1000 == 0: - elapsed = time.time() - scan_start_time - print(f"📊 Progress: {processed_count}/{len(episodes)} episodes processed in {elapsed:.1f}s, {len(missing_items['episodes'])} missing found so far") - - # Time-based exit (max 3 minutes for episodes) - if time.time() - scan_start_time > 180: - print(f"⏰ Episode scan timeout after 3 minutes - processed {processed_count}/{len(episodes)} episodes") - break - - # Early exit if we find too many missing items (performance safety) - if len(missing_items['episodes']) > 1000: - print(f"⚠️ Found {len(missing_items['episodes'])} missing episodes - stopping scan to prevent performance issues") - break - # Build NFO path using series path - series_name = os.path.basename(episode["series_path"]) - - # Debug specific episodes we know should be missing - if "Hudson" in series_name and episode['season'] == 8 and episode['episode'] == 5: - print(f"🔍 DEBUG: Processing Hudson & Rex S08E05") - print(f" Series path: {episode['series_path']}") - print(f" DB dateadded: {episode['dateadded']}") - print(f" DB source: {episode.get('source', 'unknown')}") - - if "Star Trek" in series_name and episode['season'] == 3 and episode['episode'] == 7: - print(f"🔍 DEBUG: Processing Star Trek SNW S03E07") - print(f" Series path: {episode['series_path']}") - print(f" DB dateadded: {episode['dateadded']}") - print(f" DB source: {episode.get('source', 'unknown')}") - - # Try multiple NFO file naming patterns that might exist - season_dir = os.path.join(episode["series_path"], f"Season {episode['season']:02d}") - - # Handle Season 0 (Specials) naming - if episode['season'] == 0: - season_dir = os.path.join(episode["series_path"], "Specials") - - nfo_patterns = [ - f"S{episode['season']:02d}E{episode['episode']:02d}.nfo", - f"{series_name}-S{episode['season']:02d}E{episode['episode']:02d}*.nfo" - ] - - nfo_path = None - # Find the actual NFO file that exists - if os.path.exists(season_dir): - for pattern in nfo_patterns: - if '*' in pattern: - # Use glob for wildcard patterns - import glob - matches = glob.glob(os.path.join(season_dir, pattern)) - if matches: - nfo_path = matches[0] # Use first match - break - else: - # Direct file check - test_path = os.path.join(season_dir, pattern) - if os.path.exists(test_path): - nfo_path = test_path - break - - # Skip if no NFO file found (already checked above) - if not nfo_path: - # Debug specific episodes - if "Hudson" in series_name and episode['season'] == 8 and episode['episode'] == 5: - print(f"🔍 DEBUG: Hudson & Rex S08E05 - NFO file not found at expected paths") - print(f" Series path: {episode['series_path']}") - print(f" Season dir: {season_dir}") - print(f" DB source: {episode.get('source', 'unknown')}") - continue - - # Check if NFO file has dateadded element + print(f"📁 Scanning TV path: {tv_path}") + print(f"📁 Scanning Movie path: {movie_path}") + + import subprocess + import re + + # Find all NFO files and check which ones are missing dateadded + # This is much faster than database-driven approach + missing_nfo_files = [] + + # Find all NFO files in TV directory + if os.path.exists(tv_path): + print("🔍 Finding all TV NFO files...") try: - tree = ET.parse(nfo_path) - root = tree.getroot() - dateadded_elem = root.find("dateadded") + # Use find to get all NFO files + find_result = subprocess.run( + ['find', tv_path, '-name', '*.nfo', '-type', 'f'], + capture_output=True, text=True, timeout=60 + ) - # NFO file is missing dateadded element - if dateadded_elem is None or not dateadded_elem.text or not dateadded_elem.text.strip(): - # Special logging for database sync issues - db_source = episode.get("source", "unknown") - if db_source == "Nfo_File_Existing": - print(f"🔍 DATABASE SYNC ISSUE: {series_name} S{episode['season']:02d}E{episode['episode']:02d} - DB says 'Nfo_File_Existing' but NFO missing dateadded") - print(f" NFO Path: {nfo_path}") - print(f" DB dateadded: {episode['dateadded']}") + if find_result.returncode == 0: + tv_nfo_files = find_result.stdout.strip().split('\n') + tv_nfo_files = [f for f in tv_nfo_files if f] # Remove empty strings + print(f"📊 Found {len(tv_nfo_files)} TV NFO files") + + # Check each NFO file for missing dateadded + for i, nfo_file in enumerate(tv_nfo_files): + if i % 1000 == 0 and i > 0: + elapsed = time.time() - scan_start_time + print(f"📊 Progress: {i}/{len(tv_nfo_files)} TV NFO files checked in {elapsed:.1f}s, {len(missing_nfo_files)} missing found") + + # Timeout check + if time.time() - scan_start_time > 180: # 3 minutes + print(f"⏰ TV NFO scan timeout after 3 minutes - checked {i}/{len(tv_nfo_files)} files") + break + + try: + # Use grep to quickly check if dateadded exists + grep_result = subprocess.run( + ['grep', '-l', '', nfo_file], + capture_output=True, text=True, timeout=5 + ) + + # If grep returns non-zero, dateadded is missing + if grep_result.returncode != 0: + missing_nfo_files.append(nfo_file) + + # Log specific files we're looking for + if 'Hudson' in nfo_file and 'S08E05' in nfo_file: + print(f"🔍 FOUND MISSING: Hudson & Rex S08E05 at {nfo_file}") + if 'Star Trek' in nfo_file and 'S03E07' in nfo_file: + print(f"🔍 FOUND MISSING: Star Trek SNW S03E07 at {nfo_file}") + + except subprocess.TimeoutExpired: + print(f"⏰ Timeout checking {nfo_file}") + continue + except Exception as e: + print(f"⚠️ Error checking {nfo_file}: {e}") + continue + + else: + print(f"❌ Error finding TV NFO files: {find_result.stderr}") + + except subprocess.TimeoutExpired: + print("⏰ Find command timeout for TV files") + except Exception as e: + print(f"❌ Error scanning TV directory: {e}") + else: + print(f"❌ TV path does not exist: {tv_path}") + + print(f"📊 Found {len(missing_nfo_files)} NFO files missing dateadded elements") + + # Convert file paths to episode/movie information + for nfo_file in missing_nfo_files: + try: + # Parse episode info from file path + # Example: /media/TV/tv/Series Name/Season 08/Series-S08E05-Episode.nfo + path_parts = nfo_file.split('/') + + # Look for season/episode pattern + season_match = re.search(r'Season\s+(\d+)', nfo_file, re.IGNORECASE) + episode_match = re.search(r'S(\d+)E(\d+)', os.path.basename(nfo_file), re.IGNORECASE) + + if season_match and episode_match: + season = int(episode_match.group(1)) + episode = int(episode_match.group(2)) + + # Extract series name from path + series_path = os.path.dirname(os.path.dirname(nfo_file)) + series_name = os.path.basename(series_path) missing_items["episodes"].append({ - "imdb_id": episode["imdb_id"], - "season": episode["season"], - "episode": episode["episode"], + "imdb_id": "unknown", # We'll lookup later if needed + "season": season, + "episode": episode, "series_name": series_name, - "series_path": episode["series_path"], - "dateadded": episode["dateadded"], - "nfo_path": nfo_path, - "db_source": db_source, - "reason": "NFO missing dateadded element" + "series_path": series_path, + "dateadded": None, + "nfo_path": nfo_file, + "reason": "NFO missing dateadded element (filesystem scan)" }) - except ET.ParseError as e: - print(f"⚠️ Could not parse NFO file {nfo_path}: {e}") - missing_items["episodes"].append({ - "imdb_id": episode["imdb_id"], - "season": episode["season"], - "episode": episode["episode"], - "series_name": series_name, - "series_path": episode["series_path"], - "dateadded": episode["dateadded"], - "nfo_path": nfo_path, - "error": f"Parse error: {str(e)}" - }) - - # Scan movies - print("🎬 Scanning movies for missing dateadded elements") - movie_query = """ - SELECT DISTINCT imdb_id, path, dateadded, has_video_file - FROM movies - WHERE has_video_file = TRUE - ORDER BY path - """ - - with db.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(movie_query) - movies = [dict(row) for row in cursor.fetchall()] - - print(f"📊 Found {len(movies)} movies in database") - - processed_count = 0 - for movie in movies: - processed_count += 1 - - # Progress logging every 500 items - if processed_count % 500 == 0: - elapsed = time.time() - scan_start_time - print(f"📊 Progress: {processed_count}/{len(movies)} movies processed in {elapsed:.1f}s, {len(missing_items['movies'])} missing found so far") - - # Time-based exit (max 5 minutes total) - if time.time() - scan_start_time > 300: - print(f"⏰ Total scan timeout after 5 minutes - processed {processed_count}/{len(movies)} movies") - break - - # Early exit if we find too many missing items (performance safety) - if len(missing_items['movies']) > 500: - print(f"⚠️ Found {len(missing_items['movies'])} missing movies - stopping scan to prevent performance issues") - break - # Build NFO path using movie path - movie_title = os.path.basename(movie["path"]) - - # Try multiple NFO file naming patterns that might exist - nfo_patterns = [ - f"{movie_title}.nfo", - "movie.nfo", - f"{movie_title}*.nfo" - ] - - nfo_path = None - # Find the actual NFO file that exists - if os.path.exists(movie["path"]): - for pattern in nfo_patterns: - if '*' in pattern: - # Use glob for wildcard patterns - import glob - matches = glob.glob(os.path.join(movie["path"], pattern)) - if matches: - nfo_path = matches[0] # Use first match - break - else: - # Direct file check - test_path = os.path.join(movie["path"], pattern) - if os.path.exists(test_path): - nfo_path = test_path - break - - # Skip if no NFO file found - if not nfo_path: + + except Exception as e: + print(f"⚠️ Error parsing NFO file path {nfo_file}: {e}") continue - - # Check if NFO file has dateadded element + + # Scan movies using filesystem grep approach + print("🎬 Scanning movie NFO files for missing dateadded elements") + + # Find all NFO files in Movie directory + if os.path.exists(movie_path): + print(f"🔍 Finding all Movie NFO files in {movie_path}...") try: - tree = ET.parse(nfo_path) - root = tree.getroot() - dateadded_elem = root.find("dateadded") + # Use find to get all NFO files + find_result = subprocess.run( + ['find', movie_path, '-name', '*.nfo', '-type', 'f'], + capture_output=True, text=True, timeout=60 + ) - # NFO file is missing dateadded element - if dateadded_elem is None or not dateadded_elem.text or not dateadded_elem.text.strip(): - missing_items["movies"].append({ - "imdb_id": movie["imdb_id"], - "title": movie_title, - "path": movie["path"], - "dateadded": movie["dateadded"], - "nfo_path": nfo_path, - "reason": "NFO missing dateadded element" - }) - except ET.ParseError as e: - print(f"⚠️ Could not parse NFO file {nfo_path}: {e}") - missing_items["movies"].append({ - "imdb_id": movie["imdb_id"], - "title": movie_title, - "path": movie["path"], - "dateadded": movie["dateadded"], - "nfo_path": nfo_path, - "error": f"Parse error: {str(e)}" - }) + if find_result.returncode == 0: + movie_nfo_files = find_result.stdout.strip().split('\n') + movie_nfo_files = [f for f in movie_nfo_files if f] # Remove empty strings + print(f"📊 Found {len(movie_nfo_files)} Movie NFO files") + + # Check each NFO file for missing dateadded + for i, nfo_file in enumerate(movie_nfo_files): + if i % 500 == 0 and i > 0: + elapsed = time.time() - scan_start_time + print(f"📊 Progress: {i}/{len(movie_nfo_files)} Movie NFO files checked in {elapsed:.1f}s, {len(missing_items['movies'])} missing found") + + # Timeout check + if time.time() - scan_start_time > 300: # 5 minutes total + print(f"⏰ Movie NFO scan timeout after 5 minutes - checked {i}/{len(movie_nfo_files)} files") + break + + try: + # Use grep to quickly check if dateadded exists + grep_result = subprocess.run( + ['grep', '-l', '', nfo_file], + capture_output=True, text=True, timeout=5 + ) + + # If grep returns non-zero, dateadded is missing + if grep_result.returncode != 0: + # Extract movie info from file path + movie_dir = os.path.dirname(nfo_file) + movie_title = os.path.basename(movie_dir) + + missing_items["movies"].append({ + "imdb_id": "unknown", # We'll lookup later if needed + "title": movie_title, + "path": movie_dir, + "dateadded": None, + "nfo_path": nfo_file, + "reason": "NFO missing dateadded element (filesystem scan)" + }) + + except subprocess.TimeoutExpired: + print(f"⏰ Timeout checking {nfo_file}") + continue + except Exception as e: + print(f"⚠️ Error checking {nfo_file}: {e}") + continue + + else: + print(f"❌ Error finding Movie NFO files: {find_result.stderr}") + + except subprocess.TimeoutExpired: + print("⏰ Find command timeout for Movie files") + except Exception as e: + print(f"❌ Error scanning Movie directory: {e}") + else: + print(f"❌ Movie path does not exist: {movie_path}") total_missing = len(missing_items["episodes"]) + len(missing_items["movies"]) print(f"✅ NFO repair scan complete: {total_missing} items missing dateadded elements")