- Moved RuntimeError catch to encompass get_db_session() call - Previously only caught during import, not during execution - Now properly yields None when database not initialized - Fixes test_add_series_endpoint_authenticated test failure
229 lines
7.2 KiB
Python
229 lines
7.2 KiB
Python
"""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
|