#!/usr/bin/env python3 """ Analyze TV series missing from Plex to identify common patterns. """ import json from pathlib import Path from collections import defaultdict import re def analyze_missing_series(report_file='series_comparison_report.json'): with open(report_file, 'r') as f: data = json.load(f) missing = data.get('missing_from_plex', {}) if not missing: print("No missing TV series found!") return print(f"Analyzing {len(missing)} TV series missing from Plex...\n") # Analyze various attributes has_special_chars = [] has_brackets = [] has_edition_tag = [] has_imdb_tag = [] has_tvdb_tag = [] recently_released = [] directory_patterns = defaultdict(int) episode_counts = [] for title, info in missing.items(): path = Path(info['path']) dirname = path.name episode_count = info.get('episode_count', 0) episode_counts.append(episode_count) # Special characters in directory name if re.search(r'[^a-zA-Z0-9\s\-_\.\(\)\[\]\{\}]', dirname): has_special_chars.append((title, dirname)) # Brackets/braces patterns if '{' in dirname or '}' in dirname: has_brackets.append((title, dirname)) # Edition tags if '{edition-' in dirname.lower(): has_edition_tag.append((title, dirname)) # IMDB/TVDB tags if '{imdb-' in dirname.lower(): has_imdb_tag.append((title, dirname)) if '{tvdb-' in dirname.lower(): has_tvdb_tag.append((title, dirname)) # Year extraction year_match = re.search(r'\((\d{4})\)', dirname) if year_match: year = int(year_match.group(1)) if year >= 2020: recently_released.append((title, year, dirname)) # Directory naming patterns # Check for patterns like "Show (Year)" or "Show Name - [Quality]" if re.search(r'\((\d{4})\)', dirname): directory_patterns['has_year'] += 1 if re.search(r'\{[^}]+\}', dirname): directory_patterns['has_curly_braces'] += 1 if re.search(r'\[[^\]]+\]', dirname): directory_patterns['has_square_brackets'] += 1 # Print analysis print("="*80) print("ANALYSIS RESULTS") print("="*80) print(f"\nšŸ“Š EPISODE COUNTS:") if episode_counts: print(f" Total episodes: {sum(episode_counts)}") print(f" Average per series: {sum(episode_counts) / len(episode_counts):.1f}") print(f" Min: {min(episode_counts)}, Max: {max(episode_counts)}") # Count series with no episodes no_episodes = sum(1 for c in episode_counts if c == 0) if no_episodes > 0: print(f" āš ļø Series with 0 episodes: {no_episodes}") print(f"\nšŸ”¤ NAMING PATTERNS:") for pattern, count in sorted(directory_patterns.items(), key=lambda x: x[1], reverse=True): pct = (count / len(missing)) * 100 print(f" {pattern:30} {count:4} ({pct:.1f}%)") if has_imdb_tag: print(f"\nšŸŽ¬ IMDB TAGS: {len(has_imdb_tag)}") print(f" Series with {{imdb-...}} tags") if has_tvdb_tag: print(f"\nšŸ“ŗ TVDB TAGS: {len(has_tvdb_tag)}") print(f" Series with {{tvdb-...}} tags") if has_edition_tag: print(f"\nšŸ·ļø EDITION TAGS: {len(has_edition_tag)}") print(f" Series with {{edition-...}} tags") if recently_released: print(f"\nšŸ“… RECENT SERIES (2020+): {len(recently_released)}") recent_sorted = sorted(recently_released, key=lambda x: x[1], reverse=True) for title, year, dirname in recent_sorted[:10]: print(f" {year} - {title[:60]}") if len(recently_released) > 10: print(f" ... and {len(recently_released) - 10} more") if has_special_chars: print(f"\nāš ļø SPECIAL CHARACTERS: {len(has_special_chars)}") special_chars_found = set() for title, dirname in has_special_chars: chars = re.findall(r'[^a-zA-Z0-9\s\-_\.\(\)\[\]\{\}]', dirname) special_chars_found.update(chars) print(f" Characters found: {', '.join(repr(c) for c in sorted(special_chars_found))}") print(f" Sample directories:") for title, dirname in has_special_chars[:5]: print(f" • {dirname[:75]}") # Check if all in Jellyfin all_in_jellyfin = all(info['in_jellyfin'] for info in missing.values()) some_in_jellyfin = sum(1 for info in missing.values() if info['in_jellyfin']) print(f"\nšŸ“ŗ JELLYFIN STATUS:") print(f" Series also in Jellyfin: {some_in_jellyfin}/{len(missing)} ({(some_in_jellyfin/len(missing)*100):.1f}%)") if some_in_jellyfin == len(missing): print(" āœ“ ALL missing series are visible in Jellyfin") print(" → This suggests a Plex scanning/indexing issue") elif some_in_jellyfin > 0: print(f" ⚠ {len(missing) - some_in_jellyfin} series not in Jellyfin either") print(" → These might have filesystem/permission issues") # Check for .plexignore hints print(f"\nšŸ” CHECK FOR .PLEXIGNORE:") # Check all unique parent directories parent_dirs = set() for info in missing.values(): path = Path(info['path']) parent_dirs.add(path.parent) found_plexignore = False for parent_dir in parent_dirs: plexignore_path = parent_dir / '.plexignore' if plexignore_path.exists(): print(f" āš ļø Found .plexignore at: {plexignore_path}") print(f" → Check if these series are listed in it!") found_plexignore = True if not found_plexignore: print(f" No .plexignore files found in series directories") print("\n" + "="*80) print("RECOMMENDATIONS:") print("="*80) recommendations = [] # Check for .plexignore first if plexignore_path.exists(): recommendations.append( "• .plexignore file detected!\n" f" Check: {plexignore_path}\n" " These series might be explicitly excluded." ) if some_in_jellyfin == len(missing): recommendations.append( "• ALL missing series are visible in Jellyfin\n" " Actions:\n" " 1. Force a full library refresh in Plex\n" " 2. Check Plex's TV Shows library scanner settings\n" " 3. Verify series naming follows Plex conventions\n" " 4. Check Plex server logs for scanner errors" ) no_episodes = sum(1 for c in episode_counts if c == 0) if no_episodes > 0: recommendations.append( f"• {no_episodes} series have 0 episodes detected\n" " These directories might be empty or improperly structured.\n" " Plex requires proper season/episode folder structure." ) if has_special_chars: recommendations.append( f"• {len(has_special_chars)} series have special characters in names\n" " Some characters might cause issues with Plex scanner." ) for i, rec in enumerate(recommendations, 1): print(f"\n{i}. {rec}") if not recommendations: print("\n• Series appear normal. Try forcing a Plex library refresh.") print("\n" + "="*80) # List all missing series print("\nšŸ“‹ COMPLETE LIST OF MISSING SERIES:") print("="*80) for i, (title, info) in enumerate(sorted(missing.items()), 1): jf_status = "āœ“" if info['in_jellyfin'] else "āœ—" ep_count = info.get('episode_count', 0) print(f"{i:3}. [{jf_status}] {title} ({ep_count} episodes)") print("="*80) if __name__ == '__main__': analyze_missing_series()