Add NFO file support to Serie and SerieList entities

- Add nfo_path property to Serie class
- Add has_nfo(), has_poster(), has_logo(), has_fanart() methods
- Update to_dict()/from_dict() to include nfo metadata
- Modify SerieList.load_series() to detect NFO and media files
- Add logging for missing NFO and media files with statistics
- Comprehensive unit tests with 100% coverage
- All 67 tests passing
This commit is contained in:
2026-01-11 20:12:23 +01:00
parent 9a1c9b39ee
commit 65b116c39f
5 changed files with 745 additions and 57 deletions

View File

@@ -1,5 +1,8 @@
import json
import os
import warnings
from pathlib import Path
from typing import Optional
from src.server.utils.filesystem import sanitize_folder_name
@@ -35,7 +38,8 @@ class Serie:
site: str,
folder: str,
episodeDict: dict[int, list[int]],
year: int | None = None
year: int | None = None,
nfo_path: Optional[str] = None
):
if not key or not key.strip():
raise ValueError("Serie key cannot be None or empty")
@@ -46,6 +50,7 @@ class Serie:
self._folder = folder
self._episodeDict = episodeDict
self._year = year
self._nfo_path = nfo_path
def __str__(self):
"""String representation of Serie object"""
@@ -148,6 +153,93 @@ class Serie:
"""Set the release year of the series."""
self._year = value
@property
def nfo_path(self) -> Optional[str]:
"""
Path to the tvshow.nfo metadata file.
Returns:
str or None: Path to the NFO file, or None if not set
"""
return self._nfo_path
@nfo_path.setter
def nfo_path(self, value: Optional[str]):
"""Set the path to the NFO file."""
self._nfo_path = value
def has_nfo(self, base_directory: Optional[str] = None) -> bool:
"""
Check if tvshow.nfo file exists for this series.
Args:
base_directory: Base anime directory path. If provided, checks
relative to base_directory/folder/tvshow.nfo. If not provided,
uses nfo_path directly.
Returns:
bool: True if tvshow.nfo exists, False otherwise
"""
if base_directory:
nfo_file = Path(base_directory) / self.folder / "tvshow.nfo"
elif self._nfo_path:
nfo_file = Path(self._nfo_path)
else:
return False
return nfo_file.exists() and nfo_file.is_file()
def has_poster(self, base_directory: Optional[str] = None) -> bool:
"""
Check if poster.jpg file exists for this series.
Args:
base_directory: Base anime directory path. If provided, checks
relative to base_directory/folder/poster.jpg.
Returns:
bool: True if poster.jpg exists, False otherwise
"""
if not base_directory:
return False
poster_file = Path(base_directory) / self.folder / "poster.jpg"
return poster_file.exists() and poster_file.is_file()
def has_logo(self, base_directory: Optional[str] = None) -> bool:
"""
Check if logo.png file exists for this series.
Args:
base_directory: Base anime directory path. If provided, checks
relative to base_directory/folder/logo.png.
Returns:
bool: True if logo.png exists, False otherwise
"""
if not base_directory:
return False
logo_file = Path(base_directory) / self.folder / "logo.png"
return logo_file.exists() and logo_file.is_file()
def has_fanart(self, base_directory: Optional[str] = None) -> bool:
"""
Check if fanart.jpg file exists for this series.
Args:
base_directory: Base anime directory path. If provided, checks
relative to base_directory/folder/fanart.jpg.
Returns:
bool: True if fanart.jpg exists, False otherwise
"""
if not base_directory:
return False
fanart_file = Path(base_directory) / self.folder / "fanart.jpg"
return fanart_file.exists() and fanart_file.is_file()
@property
def name_with_year(self) -> str:
"""
@@ -208,7 +300,8 @@ class Serie:
"episodeDict": {
str(k): list(v) for k, v in self.episodeDict.items()
},
"year": self.year
"year": self.year,
"nfo_path": self.nfo_path
}
@staticmethod
@@ -224,7 +317,8 @@ class Serie:
data["site"],
data["folder"],
episode_dict,
data.get("year") # Optional year field for backward compatibility
data.get("year"), # Optional year field for backward compatibility
data.get("nfo_path") # Optional nfo_path field
)
def save_to_file(self, filename: str):