"""Media file utilities for AniWorld. This module provides utilities for checking and validating media files (videos, images, NFO files) in the anime directory. """ from pathlib import Path from typing import Dict, Optional, Union # Standard media file names as defined by Kodi/Plex conventions POSTER_FILENAME = "poster.jpg" LOGO_FILENAME = "logo.png" FANART_FILENAME = "fanart.jpg" NFO_FILENAME = "tvshow.nfo" # Video file extensions supported by most media players VIDEO_EXTENSIONS = {".mp4", ".mkv", ".avi", ".webm", ".mov", ".m4v", ".flv", ".wmv"} def check_media_files( series_folder: Union[str, Path], check_poster: bool = True, check_logo: bool = True, check_fanart: bool = True, check_nfo: bool = True ) -> Dict[str, bool]: """Check existence of standard media files for a series. Checks for standard Kodi/Plex media files in the series folder: - poster.jpg: Series poster image - logo.png: Series logo/clearlogo - fanart.jpg: Series fanart/background image - tvshow.nfo: Series NFO metadata file Args: series_folder: Path to the series folder (string or Path object) check_poster: Whether to check for poster.jpg check_logo: Whether to check for logo.png check_fanart: Whether to check for fanart.jpg check_nfo: Whether to check for tvshow.nfo Returns: Dict mapping file types to existence status: { "poster": bool, "logo": bool, "fanart": bool, "nfo": bool } Example: >>> from pathlib import Path >>> series_path = Path("/anime/Attack on Titan (2013)") >>> status = check_media_files(series_path) >>> print(status["poster"]) # True if poster.jpg exists """ # Convert to Path object if string folder_path = Path(series_folder) if isinstance(series_folder, str) else series_folder result = {} if check_poster: poster_path = folder_path / POSTER_FILENAME result["poster"] = poster_path.exists() if check_logo: logo_path = folder_path / LOGO_FILENAME result["logo"] = logo_path.exists() if check_fanart: fanart_path = folder_path / FANART_FILENAME result["fanart"] = fanart_path.exists() if check_nfo: nfo_path = folder_path / NFO_FILENAME result["nfo"] = nfo_path.exists() return result def get_media_file_paths( series_folder: Union[str, Path], include_poster: bool = True, include_logo: bool = True, include_fanart: bool = True, include_nfo: bool = True ) -> Dict[str, Optional[Path]]: """Get paths to standard media files for a series. Returns paths only if the files exist. Useful for operations that need the actual file paths (e.g., reading, copying, moving). Args: series_folder: Path to the series folder (string or Path object) include_poster: Whether to include poster.jpg path include_logo: Whether to include logo.png path include_fanart: Whether to include fanart.jpg path include_nfo: Whether to include tvshow.nfo path Returns: Dict mapping file types to paths (None if file doesn't exist): { "poster": Optional[Path], "logo": Optional[Path], "fanart": Optional[Path], "nfo": Optional[Path] } Example: >>> from pathlib import Path >>> series_path = Path("/anime/Attack on Titan (2013)") >>> paths = get_media_file_paths(series_path) >>> if paths["poster"]: ... print(f"Poster found at: {paths['poster']}") """ # Convert to Path object if string folder_path = Path(series_folder) if isinstance(series_folder, str) else series_folder result = {} if include_poster: poster_path = folder_path / POSTER_FILENAME result["poster"] = poster_path if poster_path.exists() else None if include_logo: logo_path = folder_path / LOGO_FILENAME result["logo"] = logo_path if logo_path.exists() else None if include_fanart: fanart_path = folder_path / FANART_FILENAME result["fanart"] = fanart_path if fanart_path.exists() else None if include_nfo: nfo_path = folder_path / NFO_FILENAME result["nfo"] = nfo_path if nfo_path.exists() else None return result def has_all_images(series_folder: Union[str, Path]) -> bool: """Check if series has all standard image files (poster, logo, fanart). Args: series_folder: Path to the series folder (string or Path object) Returns: True if all image files exist, False otherwise Example: >>> from pathlib import Path >>> series_path = Path("/anime/Attack on Titan (2013)") >>> if has_all_images(series_path): ... print("Series has complete image set") """ # Convert to Path object if string folder_path = Path(series_folder) if isinstance(series_folder, str) else series_folder poster_path = folder_path / POSTER_FILENAME logo_path = folder_path / LOGO_FILENAME fanart_path = folder_path / FANART_FILENAME return ( poster_path.exists() and logo_path.exists() and fanart_path.exists() ) def count_video_files(series_folder: Union[str, Path], recursive: bool = True) -> int: """Count video files in the series folder. Counts files with standard video extensions (.mp4, .mkv, .avi, etc.). Args: series_folder: Path to the series folder (string or Path object) recursive: Whether to search subdirectories (for season folders) Returns: Number of video files found Example: >>> from pathlib import Path >>> series_path = Path("/anime/Attack on Titan (2013)") >>> video_count = count_video_files(series_path) >>> print(f"Found {video_count} episodes") """ # Convert to Path object if string folder_path = Path(series_folder) if isinstance(series_folder, str) else series_folder if not folder_path.exists(): return 0 count = 0 pattern = "**/*" if recursive else "*" for file_path in folder_path.glob(pattern): if file_path.is_file() and file_path.suffix.lower() in VIDEO_EXTENSIONS: count += 1 return count def has_video_files(series_folder: Union[str, Path]) -> bool: """Check if series folder contains any video files. Args: series_folder: Path to the series folder (string or Path object) Returns: True if at least one video file exists, False otherwise Example: >>> from pathlib import Path >>> series_path = Path("/anime/Attack on Titan (2013)") >>> if not has_video_files(series_path): ... print("No episodes found") """ # Convert to Path object if string folder_path = Path(series_folder) if isinstance(series_folder, str) else series_folder if not folder_path.exists(): return False for file_path in folder_path.rglob("*"): if file_path.is_file() and file_path.suffix.lower() in VIDEO_EXTENSIONS: return True return False