diff --git a/mnamer/endpoints.py b/mnamer/endpoints.py index f11b56ec..aac8a8a8 100644 --- a/mnamer/endpoints.py +++ b/mnamer/endpoints.py @@ -165,7 +165,7 @@ def tmdb_movies( def tmdb_search_movies( api_key: str, title: str, - year: int | str | None = None, + year: int | None = None, language: Language | None = None, region: str | None = None, adult: bool = False, diff --git a/mnamer/metadata.py b/mnamer/metadata.py index df43382a..b5aaff09 100644 --- a/mnamer/metadata.py +++ b/mnamer/metadata.py @@ -16,7 +16,6 @@ str_fix_padding, str_replace_slashes, str_title_case, - year_parse, ) @@ -111,13 +110,17 @@ class MetadataMovie(Metadata): """ name: str | None = None - year: str | None = None + date: dt.date | None = None id_imdb: str | None = None id_tmdb: str | None = None + def __post_init__(self): + if isinstance(self.date, str): + self.date = parse_date(self.date) + def __format__(self, format_spec: str | None): - default = "{name} ({year})" - re_pattern = r"({(\w+)(?:\[[\w:]+\])?(?:\:\d{1,2})?})" + default = "{name} ({date.year})" + re_pattern = r"({(\w+)(?:\[[\w:]+\]|\.\w+)?(?:\:\d{1,2})?})" s = re.sub(re_pattern, self._format_repl, format_spec or default) s = str_fix_padding(s) return s @@ -125,7 +128,7 @@ def __format__(self, format_spec: str | None): def __setattr__(self, key: str, value: Any): converter_map: dict[str, Callable] = { "name": fn_pipe(str_replace_slashes, str_title_case), - "year": year_parse, + "date": parse_date, } converter: Callable | None = converter_map.get(key) if value is not None and converter: @@ -141,6 +144,7 @@ class MetadataEpisode(Metadata): """ series: str | None = None + series_date: dt.date | None = None season: int | None = None episode: int | None = None date: dt.date | None = None @@ -155,10 +159,12 @@ def __post_init__(self): self.episode = int(self.episode) if isinstance(self.date, str): self.date = parse_date(self.date) + if isinstance(self.series_date, str): + self.series_date = parse_date(self.series_date) def __format__(self, format_spec: str | None): default = "{series} - {season:02}x{episode:02} - {title}" - re_pattern = r"({(\w+)(?:\[[\w:]+\])?(?:\:\d{1,2})?})" + re_pattern = r"({(\w+)(?:\[[\w:]+\]|\.\w+)?(?:\:\d{1,2})?})" s = re.sub(re_pattern, self._format_repl, format_spec or default) s = str_fix_padding(s) return s @@ -169,6 +175,7 @@ def __setattr__(self, key: str, value: Any): "episode": int, "season": int, "series": fn_pipe(str_replace_slashes, str_title_case), + "series_date": parse_date, "title": fn_pipe(str_replace_slashes, str_title_case), } converter: Callable | None = converter_map.get(key) diff --git a/mnamer/providers.py b/mnamer/providers.py index e4e83c04..4ecb28cf 100644 --- a/mnamer/providers.py +++ b/mnamer/providers.py @@ -28,7 +28,7 @@ from mnamer.metadata import Metadata, MetadataEpisode, MetadataMovie from mnamer.setting_store import SettingStore from mnamer.types import MediaType, ProviderType -from mnamer.utils import parse_date, year_range_parse +from mnamer.utils import parse_date class Provider(ABC): @@ -83,7 +83,7 @@ def search(self, query: MetadataMovie) -> Iterator[MetadataMovie]: if query.id_imdb: results = self._lookup_movie(query.id_imdb) elif query.name: - results = self._search_movie(query.name, query.year) + results = self._search_movie(query.name, None if query.date is None else query.date.year) else: raise MnamerNotFoundException yield from results @@ -94,15 +94,17 @@ def _lookup_movie(self, id_imdb: str) -> Iterator[MetadataMovie]: try: release_date = dt.datetime.strptime( response["Released"], "%d %b %Y" - ).strftime("%Y-%m-%d") + ) except (KeyError, ValueError): if response.get("Year") in (None, "N/A"): release_date = None else: - release_date = "{}-01-01".format(response["Year"]) + release_date = dt.datetime.strptime( + "{}-01-01".format(response["Year"]), "%Y-%m-%d" + ) meta = MetadataMovie( name=response["Title"], - year=release_date, + date=release_date, synopsis=response["Plot"], id_imdb=response["imdbID"], ) @@ -110,9 +112,9 @@ def _lookup_movie(self, id_imdb: str) -> Iterator[MetadataMovie]: meta.synopsis = None yield meta - def _search_movie(self, name: str, year: str | None) -> Iterator[MetadataMovie]: + def _search_movie(self, name: str, year: int | None) -> Iterator[MetadataMovie]: assert self.api_key - year_from, year_to = year_range_parse(year, 5) + year_from, year_to = year - 5, year + 5 found = False page = 1 page_max = 10 # each page yields a maximum of 10 results @@ -153,7 +155,7 @@ def search(self, query: MetadataMovie) -> Iterator[MetadataMovie]: if query.id_tmdb: results = self._search_id(query.id_tmdb, query.language) elif query.name: - results = self._search_name(query.name, query.year, query.language) + results = self._search_name(query.name, None if query.date is None else query.date.year, query.language) else: raise MnamerNotFoundException yield from results @@ -166,13 +168,13 @@ def _search_id( yield MetadataMovie( name=response["title"], language=language, - year=response["release_date"], + date=response["release_date"], synopsis=response["overview"], id_tmdb=response["id"], id_imdb=response["imdb_id"], ) - def _search_name(self, name: str, year: str | None, language: Language | None): + def _search_name(self, name: str, year: int | None, language: Language | None): assert self.api_key page = 1 page_max = 5 # each page yields a maximum of 20 results @@ -193,9 +195,9 @@ def _search_name(self, name: str, year: str | None, language: Language | None): name=entry["title"], language=language, synopsis=entry["overview"], - year=entry["release_date"], + date=entry["release_date"], ) - if not meta.year: + if not meta.date: continue yield meta found = True @@ -275,6 +277,7 @@ def _search_id( id_tvdb=id_tvdb, season=entry["airedSeason"], series=series_data["data"]["seriesName"], + series_date=series_data["data"]["firstAired"], language=language, synopsis=(entry["overview"] or "") .replace("\r\n", "") @@ -480,6 +483,7 @@ def _transform_meta( id_tvmaze=id_tvmaze or None, season=episode_entry["season"], series=series_entry["name"], + series_date=series_entry["premiered"], synopsis=episode_entry["summary"] or None, title=episode_entry["name"] or None, ) diff --git a/mnamer/setting_store.py b/mnamer/setting_store.py index 8981d335..b255fd2d 100644 --- a/mnamer/setting_store.py +++ b/mnamer/setting_store.py @@ -177,7 +177,7 @@ class SettingStore: ).as_dict(), ) movie_format: str = dataclasses.field( - default="{name} ({year}).{extension}", + default="{name} ({date.year}).{extension}", metadata=SettingSpec( dest="movie_format", flags=["--movie_format", "--movie-format", "--movieformat"], diff --git a/mnamer/target.py b/mnamer/target.py index cd9927e2..e6ab5f41 100644 --- a/mnamer/target.py +++ b/mnamer/target.py @@ -167,6 +167,10 @@ def _parse(self, file_path: Path): self.metadata.language = path_data.get("language") self.metadata.group = path_data.get("release_group") self.metadata.container = file_path.suffix or None + if "date" in path_data: + self.metadata.date = path_data.get("date") + elif "year" in path_data: + self.metadata.date = "{}-01-01".format(path_data.get("year")) if not self.metadata.language: try: self.metadata.language = path_data.get("language") @@ -178,9 +182,7 @@ def _parse(self, file_path: Path): pass if isinstance(self.metadata, MetadataMovie): self.metadata.name = path_data.get("title") - self.metadata.year = path_data.get("year") elif isinstance(self.metadata, MetadataEpisode): - self.metadata.date = path_data.get("date") self.metadata.episode = path_data.get("episode") self.metadata.season = path_data.get("season") self.metadata.series = path_data.get("title") diff --git a/mnamer/utils.py b/mnamer/utils.py index 84df7870..1a3b03f0 100644 --- a/mnamer/utils.py +++ b/mnamer/utils.py @@ -476,30 +476,3 @@ def str_title_case(s: str) -> str: s = s[:pos] + exception.upper() + s[pos + word_length :] return s - - -def year_parse(s: str) -> int | None: - """Parses a year from a string.""" - regex = r"((?:19|20)\d{2})(?:$|[-/]\d{2}[-/]\d{2})" - try: - return int(re.findall(regex, str(s))[0]) - except IndexError: - return None - - -def year_range_parse(years: str | int | None, tolerance: int = 1) -> tuple[int, int]: - """Parses a year or dash-delimited year range.""" - regex = r"^((?:19|20)\d{2})?([-,: ]*)?((?:19|20)\d{2})?$" - default_start = 1900 - default_end = CURRENT_YEAR - try: - start, dash, end = re.match(regex, str(years).strip()).groups() # type: ignore - except AttributeError: - start, end, dash = None, None, True - if not start and not end: - start, end, dash = None, None, True - start = int(start or default_start) - end = int(end or default_end) - if not dash: - end = start - return start - tolerance, end + tolerance