"""CLI command for NFO management. This script provides command-line interface for creating, updating, and checking NFO metadata files. """ import asyncio import sys from pathlib import Path # Add src to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from src.config.settings import settings from src.core.services.series_manager_service import SeriesManagerService async def scan_and_create_nfo(): """Scan all series and create missing NFO files.""" print("=" * 70) print("NFO Auto-Creation Tool") print("=" * 70) if not settings.tmdb_api_key: print("\n❌ Error: TMDB_API_KEY not configured") print(" Set TMDB_API_KEY in .env file or environment") print(" Get API key from: https://www.themoviedb.org/settings/api") return 1 if not settings.anime_directory: print("\n❌ Error: ANIME_DIRECTORY not configured") return 1 print(f"\nAnime Directory: {settings.anime_directory}") print(f"Auto-create NFO: {settings.nfo_auto_create}") print(f"Update on scan: {settings.nfo_update_on_scan}") print(f"Download poster: {settings.nfo_download_poster}") print(f"Download logo: {settings.nfo_download_logo}") print(f"Download fanart: {settings.nfo_download_fanart}") if not settings.nfo_auto_create: print("\n⚠️ Warning: NFO_AUTO_CREATE is set to False") print(" Enable it in .env to auto-create NFO files") print("\n Continuing anyway to demonstrate functionality...") # Override for demonstration settings.nfo_auto_create = True print("\nInitializing series manager...") manager = SeriesManagerService.from_settings() # Get series list first serie_list = manager.get_serie_list() all_series = serie_list.get_all() print(f"Found {len(all_series)} series in directory") if not all_series: print("\n⚠️ No series found. Add some anime series first.") return 0 # Show series without NFO series_without_nfo = [] for serie in all_series: if not serie.has_nfo(): series_without_nfo.append(serie) if series_without_nfo: print(f"\nSeries without NFO: {len(series_without_nfo)}") for serie in series_without_nfo[:5]: # Show first 5 print(f" - {serie.name} ({serie.folder})") if len(series_without_nfo) > 5: print(f" ... and {len(series_without_nfo) - 5} more") else: print("\n✅ All series already have NFO files!") if not settings.nfo_update_on_scan: print("\nNothing to do. Enable NFO_UPDATE_ON_SCAN to update existing NFOs.") return 0 print("\nProcessing NFO files...") print("(This may take a while depending on the number of series)") try: await manager.scan_and_process_nfo() print("\n✅ NFO processing complete!") # Show updated stats serie_list.load_series() # Reload to get updated stats all_series = serie_list.get_all() series_with_nfo = [s for s in all_series if s.has_nfo()] series_with_poster = [s for s in all_series if s.has_poster()] series_with_logo = [s for s in all_series if s.has_logo()] series_with_fanart = [s for s in all_series if s.has_fanart()] print("\nFinal Statistics:") print(f" Series with NFO: {len(series_with_nfo)}/{len(all_series)}") print(f" Series with poster: {len(series_with_poster)}/{len(all_series)}") print(f" Series with logo: {len(series_with_logo)}/{len(all_series)}") print(f" Series with fanart: {len(series_with_fanart)}/{len(all_series)}") except Exception as e: print(f"\n❌ Error: {e}") import traceback traceback.print_exc() return 1 finally: await manager.close() return 0 async def check_nfo_status(): """Check NFO status for all series.""" print("=" * 70) print("NFO Status Check") print("=" * 70) if not settings.anime_directory: print("\n❌ Error: ANIME_DIRECTORY not configured") return 1 print(f"\nAnime Directory: {settings.anime_directory}") # Create series list (no NFO service needed for status check) from src.core.entities.SerieList import SerieList serie_list = SerieList(settings.anime_directory) all_series = serie_list.get_all() if not all_series: print("\n⚠️ No series found") return 0 print(f"\nTotal series: {len(all_series)}") # Categorize series with_nfo = [] without_nfo = [] for serie in all_series: if serie.has_nfo(): with_nfo.append(serie) else: without_nfo.append(serie) print(f"\nWith NFO: {len(with_nfo)} ({len(with_nfo) * 100 // len(all_series)}%)") print(f"Without NFO: {len(without_nfo)} ({len(without_nfo) * 100 // len(all_series)}%)") if without_nfo: print("\nSeries missing NFO:") for serie in without_nfo[:10]: print(f" ❌ {serie.name} ({serie.folder})") if len(without_nfo) > 10: print(f" ... and {len(without_nfo) - 10} more") # Media file statistics with_poster = sum(1 for s in all_series if s.has_poster()) with_logo = sum(1 for s in all_series if s.has_logo()) with_fanart = sum(1 for s in all_series if s.has_fanart()) print("\nMedia Files:") print(f" Posters: {with_poster}/{len(all_series)} ({with_poster * 100 // len(all_series)}%)") print(f" Logos: {with_logo}/{len(all_series)} ({with_logo * 100 // len(all_series)}%)") print(f" Fanart: {with_fanart}/{len(all_series)} ({with_fanart * 100 // len(all_series)}%)") return 0 async def update_nfo_files(): """Update existing NFO files with fresh data from TMDB.""" print("=" * 70) print("NFO Update Tool") print("=" * 70) if not settings.tmdb_api_key: print("\n❌ Error: TMDB_API_KEY not configured") print(" Set TMDB_API_KEY in .env file or environment") print(" Get API key from: https://www.themoviedb.org/settings/api") return 1 if not settings.anime_directory: print("\n❌ Error: ANIME_DIRECTORY not configured") return 1 print(f"\nAnime Directory: {settings.anime_directory}") print(f"Download media: {settings.nfo_download_poster or settings.nfo_download_logo or settings.nfo_download_fanart}") # Get series with NFO from src.core.entities.SerieList import SerieList serie_list = SerieList(settings.anime_directory) all_series = serie_list.get_all() series_with_nfo = [s for s in all_series if s.has_nfo()] if not series_with_nfo: print("\n⚠️ No series with NFO files found") print(" Run 'scan' command first to create NFO files") return 0 print(f"\nFound {len(series_with_nfo)} series with NFO files") print("Updating NFO files with fresh data from TMDB...") print("(This may take a while)") # Initialize NFO service from src.core.services.nfo_service import NFOService nfo_service = NFOService( tmdb_api_key=settings.tmdb_api_key, anime_directory=settings.anime_directory, image_size=settings.nfo_image_size ) success_count = 0 error_count = 0 try: for i, serie in enumerate(series_with_nfo, 1): print(f"\n[{i}/{len(series_with_nfo)}] Updating: {serie.name}") try: await nfo_service.update_tvshow_nfo( serie_folder=serie.folder, download_media=( settings.nfo_download_poster or settings.nfo_download_logo or settings.nfo_download_fanart ) ) print(f" ✅ Updated successfully") success_count += 1 # Small delay to respect API rate limits await asyncio.sleep(0.5) except Exception as e: print(f" ❌ Error: {e}") error_count += 1 print("\n" + "=" * 70) print(f"✅ Update complete!") print(f" Success: {success_count}") print(f" Errors: {error_count}") except Exception as e: print(f"\n❌ Fatal error: {e}") import traceback traceback.print_exc() return 1 finally: await nfo_service.close() return 0 def main(): """Main CLI entry point.""" if len(sys.argv) < 2: print("NFO Management Tool") print("\nUsage:") print(" python -m src.cli.nfo_cli scan # Scan and create missing NFO files") print(" python -m src.cli.nfo_cli status # Check NFO status for all series") print(" python -m src.cli.nfo_cli update # Update existing NFO files with fresh data") print("\nConfiguration:") print(" Set TMDB_API_KEY in .env file") print(" Set NFO_AUTO_CREATE=true to enable auto-creation") print(" Set NFO_UPDATE_ON_SCAN=true to update existing NFOs during scan") return 1 command = sys.argv[1].lower() if command == "scan": return asyncio.run(scan_and_create_nfo()) elif command == "status": return asyncio.run(check_nfo_status()) elif command == "update": return asyncio.run(update_nfo_files()) else: print(f"Unknown command: {command}") print("Use 'scan', 'status', or 'update'") return 1 if __name__ == "__main__": sys.exit(main())