diff --git a/scanner/matcher/matcher.py b/scanner/matcher/matcher.py index c4835f6f..0fd597d2 100644 --- a/scanner/matcher/matcher.py +++ b/scanner/matcher/matcher.py @@ -197,6 +197,11 @@ class Matcher: kind: Literal["collection", "movie", "episode", "show", "season"], kyoo_id: str, ): + async def id_movie(movie: dict, id: dict): + ret = await self._provider.identify_movie(id["dataId"]) + ret.path = movie["path"] + return ret + async def id_season(season: dict, id: dict): ret = await self._provider.identify_season( id["dataId"], season["seasonNumber"] @@ -217,7 +222,7 @@ class Matcher: "collection": lambda _, id: self._provider.identify_collection( id["dataId"] ), - "movie": lambda _, id: self._provider.identify_movie(id["dataId"]), + "movie": id_movie, "show": lambda _, id: self._provider.identify_show(id["dataId"]), "season": id_season, "episode": id_episode, diff --git a/scanner/matcher/parser/rules.py b/scanner/matcher/parser/rules.py index 5ff1b868..36f0da8c 100644 --- a/scanner/matcher/parser/rules.py +++ b/scanner/matcher/parser/rules.py @@ -157,7 +157,7 @@ class TitleNumberFixup(Rule): def when(self, matches: Matches, context) -> Any: episodes: List[Match] = matches.named("episode") # type: ignore - if len(episodes) < 2: + if len(episodes) < 2 or all(x.value == episodes[0].value for x in episodes): return to_remove = [] @@ -169,24 +169,25 @@ class TitleNumberFixup(Rule): continue # do not fixup if there was a - or any separator between the title and the episode number - holes = matches.holes(title.end, episode.start) + holes: List[Match] = matches.holes(title.end, episode.start) # type: ignore if holes: continue to_remove.extend([title, episode]) new_title = copy(title) new_title.end = episode.end - new_title.value = f"{title.value} {episode.value}" - # If an hole was created to parse the episode at the current pos, merge it back into the title - holes = matches.holes(episode.end) - if holes and holes[0].start == episode.end: - val: str = holes[0].value - if "-" in val: - val, *_ = val.split("-") - val = val.rstrip() - new_title.value = f"{new_title.value}{val}" - new_title.end += len(val) + nmatch: List[Match] = matches.next(episode) # type: ignore + if nmatch: + end = ( + nmatch[0].initiator.start + if isinstance(nmatch[0].initiator, Match) + else nmatch[0].start + ) + # If an hole was created to parse the episode at the current pos, merge it back into the title + holes: List[Match] = matches.holes(start=episode.end, end=end) # type: ignore + if holes and holes[0].start == episode.end: + new_title.end = holes[0].end to_add.append(new_title) return [to_remove, to_add] @@ -357,7 +358,9 @@ class SeasonYearDedup(Rule): ``` """ - priority = POST_PROCESS + # This rules does the opposite of the YearSeason rule of guessit (with POST_PROCESS priority) + # To overide it, we need the -1. (rule: https://github.com/guessit-io/guessit/blob/develop/guessit/rules/processors.py#L195) + priority = POST_PROCESS - 1 consequence = [RemoveMatch] def when(self, matches: Matches, context) -> Any: diff --git a/scanner/providers/implementations/themoviedatabase.py b/scanner/providers/implementations/themoviedatabase.py index fe227528..7d5d032a 100644 --- a/scanner/providers/implementations/themoviedatabase.py +++ b/scanner/providers/implementations/themoviedatabase.py @@ -559,6 +559,7 @@ class TheMovieDatabase(Provider): absolute-ordered group and return it """ + show = await self.identify_show(show_id) try: groups = await self.get(f"tv/{show_id}/episode_groups") ep_count = max((x["episode_count"] for x in groups["results"]), default=0) @@ -577,7 +578,22 @@ class TheMovieDatabase(Provider): if group_id is None: return None group = await self.get(f"tv/episode_group/{group_id}") - return [ep for grp in group["groups"] for ep in grp["episodes"]] + absgrp = [ep for grp in group["groups"] for ep in grp["episodes"]] + logger.warn( + f"Incomplete absolute group for show {show_id}. Filling missing values by assuming season/episode order is ascending" + ) + complete_abs = absgrp + [ + {"season_number": s.season_number, "episode_number": e} + for s in show.seasons + # ignore specials not specified in the absgrp + if s.season_number > 0 + for e in range(1, s.episodes_count) + if not any( + x["season_number"] == s.season_number and x["episode_number"] == e + for x in absgrp + ) + ] + return complete_abs except Exception as e: logger.exception( "Could not retrieve absolute ordering information", exc_info=e @@ -642,17 +658,17 @@ class TheMovieDatabase(Provider): if absolute is not None: return absolute # assume we use tmdb weird absolute by default (for example, One Piece S21E800, the first - # episode of S21 si not reset to 0 but keep increasing so it can be 800 + # episode of S21 is not reset to 0 but keep increasing so it can be 800 start = next( (x["episode_number"] for x in absgrp if x["season_number"] == season), None ) - if start is None or start <= episode_nbr: - raise ProviderError( - f"Could not guess absolute number of episode {show_id} s{season} e{episode_nbr}" - ) - # add back the continuous number (imagine the user has one piece S21e31 - # but tmdb registered it as S21E831 since S21's first ep is 800 - return await self.get_absolute_number(show_id, season, episode_nbr + start) + if start is not None and start <= episode_nbr: + # add back the continuous number (imagine the user has one piece S21e31 + # but tmdb registered it as S21E831 since S21's first ep is 800 + return await self.get_absolute_number(show_id, season, episode_nbr + start) + raise ProviderError( + f"Could not guess absolute number of episode {show_id} s{season} e{episode_nbr}" + ) async def identify_collection(self, provider_id: str) -> Collection: languages = self.get_languages()