diff --git a/modules/anidb.py b/modules/anidb.py index 06aa4748..315a9cd8 100644 --- a/modules/anidb.py +++ b/modules/anidb.py @@ -72,7 +72,7 @@ class AniDB: current_url = f"{base_url}{next_page_list[0]}" return anidb_ids[:limit] - def get_items(self, method, data, language): + def get_anidb_ids(self, method, data, language): anidb_ids = [] if method == "anidb_popular": logger.info(f"Processing AniDB Popular: {data} Anime") @@ -88,9 +88,6 @@ class AniDB: anidb_ids.extend(self._relations(data, language)) else: raise Failed(f"AniDB Error: Method {method} not supported") - movie_ids, show_ids = self.config.Convert.anidb_to_ids(anidb_ids) logger.debug("") logger.debug(f"{len(anidb_ids)} AniDB IDs Found: {anidb_ids}") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids + return anidb_ids diff --git a/modules/anilist.py b/modules/anilist.py index bdefc1fc..ef001832 100644 --- a/modules/anilist.py +++ b/modules/anilist.py @@ -207,7 +207,7 @@ class AniList: return anilist_values raise Failed(f"AniList Error: No valid AniList IDs in {anilist_ids}") - def get_items(self, method, data): + def get_anilist_ids(self, method, data): if method == "anilist_id": logger.info(f"Processing AniList ID: {data}") anilist_id, name = self._validate(data) @@ -235,9 +235,6 @@ class AniList: logger.info(f"Processing AniList Relations: ({data}) {name} ({len(anilist_ids)} Anime)") else: raise Failed(f"AniList Error: Method {method} not supported") - movie_ids, show_ids = self.config.Convert.anilist_to_ids(anilist_ids) logger.debug("") logger.debug(f"{len(anilist_ids)} AniList IDs Found: {anilist_ids}") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids + return anilist_ids diff --git a/modules/builder.py b/modules/builder.py index b1dfa546..6aeaf209 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -164,6 +164,7 @@ class CollectionBuilder: self.filters = [] self.tmdb_filters = [] self.rating_keys = [] + self.filtered_keys = [] self.run_again_movies = [] self.run_again_shows = [] self.posters = {} @@ -992,113 +993,112 @@ class CollectionBuilder: else: logger.error(message) - def collect_rating_keys(self): - filtered_keys = {} - name = self.obj.title if self.obj else self.name - def add_rating_keys(keys): - if not isinstance(keys, list): - keys = [keys] - total = len(keys) - max_length = len(str(total)) - if self.filters and self.details["show_filtered"] is True: - logger.info("") - logger.info("Filtering Builder:") - for i, key in enumerate(keys, 1): - if key not in self.rating_keys: - if key in filtered_keys: - if self.details["show_filtered"] is True: - logger.info(f"{name} Collection | X | {filtered_keys[key]}") - else: - try: - current = self.fetch_item(key) - except Failed as e: - logger.error(e) - continue - current_title = f"{current.title} ({current.year})" if current.year else current.title - if self.check_filters(current, f"{(' ' * (max_length - len(str(i))))}{i}/{total}"): - self.rating_keys.append(key) - else: - if key not in filtered_keys: - filtered_keys[key] = current_title - if self.details["show_filtered"] is True: - logger.info(f"{name} Collection | X | {current_title}") - def check_map(input_ids): - movie_ids, show_ids = input_ids - items_found_inside = 0 - if len(movie_ids) > 0: - items_found_inside += len(movie_ids) - movie_rating_keys = [] - for movie_id in movie_ids: - if movie_id in self.library.movie_map: - movie_rating_keys.append(self.library.movie_map[movie_id][0]) - elif movie_id not in self.missing_movies: - self.missing_movies.append(movie_id) - add_rating_keys(movie_rating_keys) - if len(show_ids) > 0: - items_found_inside += len(show_ids) - show_rating_keys = [] - for show_id in show_ids: - if show_id in self.library.show_map: - show_rating_keys.append(self.library.show_map[show_id][0]) - elif show_id not in self.missing_shows: - self.missing_shows.append(show_id) - add_rating_keys(show_rating_keys) - return items_found_inside + def find_rating_keys(self): for method, value in self.builders: + ids = [] + rating_keys = [] logger.debug("") logger.debug(f"Builder: {method}: {value}") logger.info("") - if "plex" in method: add_rating_keys(self.library.get_items(method, value)) - elif "tautulli" in method: add_rating_keys(self.library.Tautulli.get_items(self.library, value)) - elif "anidb" in method: check_map(self.config.AniDB.get_items(method, value, self.language)) - elif "anilist" in method: check_map(self.config.AniList.get_items(method, value)) - elif "mal" in method: check_map(self.config.MyAnimeList.get_items(method, value)) - elif "tvdb" in method: check_map(self.config.TVDb.get_items(method, value, self.language)) - elif "imdb" in method: check_map(self.config.IMDb.get_items(method, value, self.language, self.library.is_movie)) - elif "icheckmovies" in method: check_map(self.config.ICheckMovies.get_items(method, value, self.language)) - elif "letterboxd" in method: check_map(self.config.Letterboxd.get_items(method, value, self.language)) - elif "stevenlu" in method: check_map(self.config.StevenLu.get_items(method)) - elif "tmdb" in method: check_map(self.config.TMDb.get_items(method, value, self.library.is_movie)) - elif "trakt" in method: check_map(self.config.Trakt.get_items(method, value, self.library.is_movie)) - else: logger.error(f"Collection Error: {method} method not supported") + if "plex" in method: + rating_keys = self.library.get_rating_keys(method, value) + elif "tautulli" in method: + rating_keys = self.library.Tautulli.get_rating_keys(self.library, value) + elif "anidb" in method: + anidb_ids = self.config.AniDB.get_anidb_ids(method, value, self.language) + ids = self.config.Convert.anidb_to_ids(anidb_ids) + elif "anilist" in method: + anilist_ids = self.config.AniList.get_anilist_ids(method, value) + ids = self.config.Convert.anilist_to_ids(anilist_ids) + elif "mal" in method: + mal_ids = self.config.MyAnimeList.get_mal_ids(method, value) + ids = self.config.Convert.myanimelist_to_ids(mal_ids) + elif "tvdb" in method: + ids = self.config.TVDb.get_tvdb_ids(method, value, self.language) + elif "imdb" in method: + ids = self.config.IMDb.get_imdb_ids(method, value, self.language) + elif "icheckmovies" in method: + ids = self.config.ICheckMovies.get_icheckmovies_ids(method, value, self.language) + elif "letterboxd" in method: + ids = self.config.Letterboxd.get_tmdb_ids(method, value, self.language) + elif "stevenlu" in method: + ids = self.config.StevenLu.get_stevenlu_ids(method) + elif "tmdb" in method: + ids = self.config.TMDb.get_tmdb_ids(method, value, self.library.is_movie) + elif "trakt" in method: + ids = self.config.Trakt.get_trakt_ids(method, value, self.library.is_movie) + else: + logger.error(f"Collection Error: {method} method not supported") - def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False): - if self.tmdb_filters or check_released: - try: - if item is None: - item = self.config.TMDb.get_movie(item_id) if is_movie else self.config.TMDb.get_show(self.config.Convert.tvdb_to_tmdb(item_id)) - if check_released: - if util.validate_date(item.release_date if is_movie else item.first_air_date, "") > self.current_time: - return False - for filter_method, filter_data in self.tmdb_filters: - filter_attr, modifier, filter_final = self._split(filter_method) - if filter_attr == "original_language": - if (modifier == ".not" and item.original_language in filter_data) \ - or (modifier == "" and item.original_language not in filter_data): - return False - elif filter_attr in ["first_episode_aired", "last_episode_aired"]: - tmdb_date = None - if filter_attr == "first_episode_aired": - tmdb_date = item.first_air_date - elif filter_attr == "last_episode_aired": - tmdb_date = item.last_air_date - if not util.date_filter(tmdb_date, modifier, filter_data, filter_final, self.current_time): - return False - elif modifier in [".gt", ".gte", ".lt", ".lte"]: - attr = None - if filter_attr == "tmdb_vote_count": - attr = item.vote_count - elif filter_attr == "year" and is_movie: - attr = item.year - elif filter_attr == "year" and not is_movie: - air_date = item.first_air_date - if air_date: - attr = util.validate_date(air_date, "Year Filter").year - if util.number_filter(attr, modifier, filter_data): - return False - except Failed: - return False - return True + if len(ids) > 0: + logger.debug("") + logger.debug(f"{len(ids)} IDs Found: {ids}") + total_ids = len(ids) + if total_ids > 0: + for i, input_data in enumerate(ids, 1): + input_id, id_type = input_data + util.print_return(f"Parsing ID {i}/{total_ids}") + if id_type == "tmdb": + if input_id in self.library.movie_map: + rating_keys.append(self.library.movie_map[input_id][0]) + elif input_id not in self.missing_movies: + self.missing_movies.append(input_id) + elif id_type in ["tvdb", "tmdb_show"]: + if id_type == "tmdb_show": + try: + input_id = self.config.Convert.tmdb_to_tvdb(input_id, fail=True) + except Failed as e: + logger.error(e) + continue + if input_id in self.library.show_map: + rating_keys.append(self.library.show_map[input_id][0]) + elif input_id not in self.missing_shows: + self.missing_shows.append(input_id) + elif id_type == "imdb": + if input_id in self.library.imdb_map: + rating_keys.append(self.library.imdb_map[input_id][0]) + else: + try: + tmdb_id, tmdb_type = self.config.Convert.imdb_to_tmdb(input_id) + if tmdb_type == "movie": + if tmdb_id not in self.missing_movies: + self.missing_movies.append(tmdb_id) + else: + tvdb_id = self.config.Convert.tmdb_to_tvdb(tmdb_id) + if tvdb_id not in self.missing_shows: + self.missing_shows.append(tvdb_id) + except Failed as e: + logger.error(e) + continue + util.print_end() + + if len(rating_keys) > 0: + name = self.obj.title if self.obj else self.name + if not isinstance(rating_keys, list): + rating_keys = [rating_keys] + total = len(rating_keys) + max_length = len(str(total)) + if self.filters and self.details["show_filtered"] is True: + logger.info("") + logger.info("Filtering Builder:") + for i, key in enumerate(rating_keys, 1): + if key not in self.rating_keys: + if key in self.filtered_keys: + if self.details["show_filtered"] is True: + logger.info(f"{name} Collection | X | {self.filtered_keys[key]}") + else: + try: + current = self.fetch_item(key) + except Failed as e: + logger.error(e) + continue + current_title = f"{current.title} ({current.year})" if current.year else current.title + if self.check_filters(current, f"{(' ' * (max_length - len(str(i))))}{i}/{total}"): + self.rating_keys.append(key) + else: + self.filtered_keys[key] = current_title + if self.details["show_filtered"] is True: + logger.info(f"{name} Collection | X | {current_title}") def build_filter(self, method, plex_filter, smart=False): if smart: @@ -1385,6 +1385,44 @@ class CollectionBuilder: logger.info("") logger.info(f"{total} {media_type} Processed") + def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False): + if self.tmdb_filters or check_released: + try: + if item is None: + item = self.config.TMDb.get_movie(item_id) if is_movie else self.config.TMDb.get_show(self.config.Convert.tvdb_to_tmdb(item_id)) + if check_released: + if util.validate_date(item.release_date if is_movie else item.first_air_date, "") > self.current_time: + return False + for filter_method, filter_data in self.tmdb_filters: + filter_attr, modifier, filter_final = self._split(filter_method) + if filter_attr == "original_language": + if (modifier == ".not" and item.original_language in filter_data) \ + or (modifier == "" and item.original_language not in filter_data): + return False + elif filter_attr in ["first_episode_aired", "last_episode_aired"]: + tmdb_date = None + if filter_attr == "first_episode_aired": + tmdb_date = util.validate_date(item.first_air_date, "TMDB First Air Date") + elif filter_attr == "last_episode_aired": + tmdb_date = util.validate_date(item.last_air_date, "TMDB Last Air Date") + if util.is_date_filter(tmdb_date, modifier, filter_data, filter_final, self.current_time): + return False + elif modifier in [".gt", ".gte", ".lt", ".lte"]: + attr = None + if filter_attr == "tmdb_vote_count": + attr = item.vote_count + elif filter_attr == "year" and is_movie: + attr = item.year + elif filter_attr == "year" and not is_movie: + air_date = item.first_air_date + if air_date: + attr = util.validate_date(air_date, "Year Filter").year + if util.is_number_filter(attr, modifier, filter_data): + return False + except Failed: + return False + return True + def check_filters(self, current, display): if self.filters: util.print_return(f"Filtering {display} {current.title}") @@ -1406,41 +1444,19 @@ class CollectionBuilder: if not self.check_tmdb_filter(t_id, current.ratingKey in self.library.movie_rating_key_map): return False elif filter_attr in ["release", "added", "last_played"]: - if not util.date_filter(getattr(current, filter_actual), modifier, filter_data, filter_final, self.current_time): + if util.is_date_filter(getattr(current, filter_actual), modifier, filter_data, filter_final, self.current_time): return False - elif filter_attr == "audio_track_title": - jailbreak = False - for media in current.media: - for part in media.parts: - for audio in part.audioStreams(): - for check_title in filter_data: - title = audio.title if audio.title else "" - if util.string_filter(title, modifier, check_title): - jailbreak = True - break - if jailbreak: break - if jailbreak: break - if jailbreak: break - if (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"]): - return False - elif filter_attr == "filepath": - jailbreak = False - for location in current.locations: - for check_text in filter_data: - if util.string_filter(location, modifier, check_text): - jailbreak = True - break - if jailbreak: break - if (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"]): - return False - elif filter_attr in ["title", "studio"]: - jailbreak = False - current_data = getattr(current, filter_actual) - for check_data in filter_data: - if util.string_filter(current_data, modifier, check_data): - jailbreak = True - break - if (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"]): + elif filter_attr in ["audio_track_title", "filepath", "title", "studio"]: + values = [] + if filter_attr == "audio_track_title": + for media in current.media: + for part in media.parts: + values.extend([a.title for a in part.audioStreams() if a.title]) + elif filter_attr == "filepath": + values = [loc for loc in current.locations] + elif filter_attr in ["title", "studio"]: + values = [getattr(current, filter_actual)] + if util.is_string_filter(values, modifier, filter_data): return False elif filter_attr == "history": item_date = current.originallyAvailableAt @@ -1461,11 +1477,8 @@ class CollectionBuilder: if date_match is False: return False elif modifier in [".gt", ".gte", ".lt", ".lte"]: - if filter_attr == "duration": - attr = getattr(current, filter_actual) / 60000 - else: - attr = getattr(current, filter_actual) - if util.number_filter(attr, modifier, filter_data): + divider = 60000 if filter_attr == "duration" else 1 + if util.is_number_filter(getattr(current, filter_actual) / divider, modifier, filter_data): return False else: attrs = [] diff --git a/modules/cache.py b/modules/cache.py index 21a03d51..398bfbd5 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -18,14 +18,16 @@ class Cache: else: logger.info(f"Using cache database at {self.cache_path}") cursor.execute("DROP TABLE IF EXISTS guids") + cursor.execute("DROP TABLE IF EXISTS guid_map") cursor.execute("DROP TABLE IF EXISTS imdb_to_tvdb_map") cursor.execute("DROP TABLE IF EXISTS tmdb_to_tvdb_map") cursor.execute("DROP TABLE IF EXISTS imdb_map") cursor.execute( - """CREATE TABLE IF NOT EXISTS guid_map ( + """CREATE TABLE IF NOT EXISTS guids_map ( key INTEGER PRIMARY KEY, plex_guid TEXT UNIQUE, t_id TEXT, + imdb_id TEXT, media_type TEXT, expiration_date TEXT)""" ) @@ -100,27 +102,39 @@ class Cache: def query_guid_map(self, plex_guid): id_to_return = None + imdb_id = None media_type = None expired = None with sqlite3.connect(self.cache_path) as connection: connection.row_factory = sqlite3.Row with closing(connection.cursor()) as cursor: - cursor.execute(f"SELECT * FROM guid_map WHERE plex_guid = ?", (plex_guid,)) + cursor.execute(f"SELECT * FROM guids_map WHERE plex_guid = ?", (plex_guid,)) row = cursor.fetchone() if row: time_between_insertion = datetime.now() - datetime.strptime(row["expiration_date"], "%Y-%m-%d") id_to_return = util.get_list(row["t_id"], int_list=True) + imdb_id = util.get_list(row["imdb_id"]) media_type = row["media_type"] expired = time_between_insertion.days > self.expiration - return id_to_return, media_type, expired + return id_to_return, imdb_id, media_type, expired - def update_guid_map(self, media_type, plex_guid, t_id, expired): - self._update_map("guid_map", "plex_guid", plex_guid, "t_id", t_id, expired, media_type=media_type) + def update_guid_map(self, plex_guid, t_id, imdb_id, expired, media_type): + expiration_date = datetime.now() if expired is True else (datetime.now() - timedelta(days=random.randint(1, self.expiration))) + with sqlite3.connect(self.cache_path) as connection: + connection.row_factory = sqlite3.Row + with closing(connection.cursor()) as cursor: + cursor.execute(f"INSERT OR IGNORE INTO guids_map(plex_guid) VALUES(?)", (plex_guid,)) + if media_type is None: + sql = f"UPDATE guids_map SET t_id = ?, imdb_id = ?, expiration_date = ? WHERE plex_guid = ?" + cursor.execute(sql, (t_id, imdb_id, expiration_date.strftime("%Y-%m-%d"), plex_guid)) + else: + sql = f"UPDATE guids_map SET t_id = ?, imdb_id = ?, expiration_date = ?, media_type = ? WHERE plex_guid = ?" + cursor.execute(sql, (t_id, imdb_id, expiration_date.strftime("%Y-%m-%d"), media_type, plex_guid)) - def query_imdb_to_tmdb_map(self, media_type, _id, imdb=True): + def query_imdb_to_tmdb_map(self, _id, imdb=True, media_type=None, return_type=False): from_id = "imdb_id" if imdb else "tmdb_id" to_id = "tmdb_id" if imdb else "imdb_id" - return self._query_map("imdb_to_tmdb_map", _id, from_id, to_id, media_type=media_type) + return self._query_map("imdb_to_tmdb_map", _id, from_id, to_id, media_type=media_type, return_type=return_type) def update_imdb_to_tmdb_map(self, media_type, expired, imdb_id, tmdb_id): self._update_map("imdb_to_tmdb_map", "imdb_id", imdb_id, "tmdb_id", tmdb_id, expired, media_type=media_type) @@ -147,9 +161,10 @@ class Cache: def update_letterboxd_map(self, expired, letterboxd_id, tmdb_id): self._update_map("letterboxd_map", "letterboxd_id", letterboxd_id, "tmdb_id", tmdb_id, expired) - def _query_map(self, map_name, _id, from_id, to_id, media_type=None): + def _query_map(self, map_name, _id, from_id, to_id, media_type=None, return_type=False): id_to_return = None expired = None + out_type = None with sqlite3.connect(self.cache_path) as connection: connection.row_factory = sqlite3.Row with closing(connection.cursor()) as cursor: @@ -163,7 +178,11 @@ class Cache: time_between_insertion = datetime.now() - datetime_object id_to_return = row[to_id] if to_id == "imdb_id" else int(row[to_id]) expired = time_between_insertion.days > self.expiration - return id_to_return, expired + out_type = row["media_type"] if return_type else None + if out_type: + return id_to_return, out_type, expired + else: + return id_to_return, expired def _update_map(self, map_name, val1_name, val1, val2_name, val2, expired, media_type=None): expiration_date = datetime.now() if expired is True else (datetime.now() - timedelta(days=random.randint(1, self.expiration))) diff --git a/modules/convert.py b/modules/convert.py index 7cdd3680..d0395f40 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -21,8 +21,8 @@ class Convert: imdb_ids = util.get_list(imdbid[0]) tmdb_ids = [] for imdb in imdb_ids: - tmdb_id = self.imdb_to_tmdb(imdb) - if tmdb_id: + tmdb_id, tmdb_type = self.imdb_to_tmdb(imdb) + if tmdb_id and tmdb_type == "movie": tmdb_ids.append(tmdb_id) if tmdb_ids: return None, imdb_ids, tmdb_ids @@ -78,18 +78,17 @@ class Convert: return converted_ids def anidb_to_ids(self, anidb_list): - show_ids = [] - movie_ids = [] + ids = [] for anidb_id in anidb_list: try: tvdb_id, _, tmdb_ids = self._anidb(anidb_id, fail=True) if tvdb_id: - show_ids.append(tvdb_id) + ids.append((tvdb_id, "tvdb")) if tmdb_ids: - movie_ids.extend(tmdb_ids) + ids.extend((tmdb_ids, "tmdb")) except Failed as e: logger.error(e) - return movie_ids, show_ids + return ids def anilist_to_ids(self, anilist_ids): anidb_ids = [] @@ -113,43 +112,40 @@ class Convert: media_type = "movie" if is_movie else "show" expired = False if self.config.Cache and is_movie: - cache_id, expired = self.config.Cache.query_imdb_to_tmdb_map(media_type, tmdb_id, imdb=False) + cache_id, expired = self.config.Cache.query_imdb_to_tmdb_map(tmdb_id, imdb=False, media_type=media_type) if cache_id and not expired: return cache_id - imdb_id = None try: imdb_id = self.config.TMDb.convert_from(tmdb_id, "imdb_id", is_movie) + if imdb_id: + if self.config.Cache: + self.config.Cache.update_imdb_to_tmdb_map(media_type, expired, imdb_id, tmdb_id) + return imdb_id except Failed: pass - if imdb_id: - if self.config.Cache: - self.config.Cache.update_imdb_to_tmdb_map(media_type, expired, imdb_id, tmdb_id) - return imdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No IMDb ID Found for TMDb ID: {tmdb_id}") else: return None - def imdb_to_tmdb(self, imdb_id, is_movie=True, fail=False): - media_type = "movie" if is_movie else "show" + def imdb_to_tmdb(self, imdb_id, fail=False): expired = False - if self.config.Cache and is_movie: - cache_id, expired = self.config.Cache.query_imdb_to_tmdb_map(media_type, imdb_id, imdb=True) + if self.config.Cache: + cache_id, cache_type, expired = self.config.Cache.query_imdb_to_tmdb_map(imdb_id, imdb=True, return_type=True) if cache_id and not expired: - return cache_id - tmdb_id = None + return cache_id, cache_type try: - tmdb_id = self.config.TMDb.convert_to(imdb_id, "imdb_id", is_movie) + tmdb_id, tmdb_type = self.config.TMDb.convert_imdb_to(imdb_id) + if tmdb_id: + if self.config.Cache: + self.config.Cache.update_imdb_to_tmdb_map(tmdb_type, expired, imdb_id, tmdb_id) + return tmdb_id, tmdb_type except Failed: pass - if tmdb_id: - if self.config.Cache: - self.config.Cache.update_imdb_to_tmdb_map(media_type, expired, imdb_id, tmdb_id) - return tmdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No TMDb ID Found for IMDb ID: {imdb_id}") else: - return None + return None, None def tmdb_to_tvdb(self, tmdb_id, fail=False): expired = False @@ -157,16 +153,15 @@ class Convert: cache_id, expired = self.config.Cache.query_tmdb_to_tvdb_map(tmdb_id, tmdb=True) if cache_id and not expired: return cache_id - tvdb_id = None try: tvdb_id = self.config.TMDb.convert_from(tmdb_id, "tvdb_id", False) + if tvdb_id: + if self.config.Cache: + self.config.Cache.update_tmdb_to_tvdb_map(expired, tmdb_id, tvdb_id) + return tvdb_id except Failed: pass - if tvdb_id: - if self.config.Cache: - self.config.Cache.update_tmdb_to_tvdb_map(expired, tmdb_id, tvdb_id) - return tvdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No TVDb ID Found for TMDb ID: {tmdb_id}") else: return None @@ -177,16 +172,15 @@ class Convert: cache_id, expired = self.config.Cache.query_tmdb_to_tvdb_map(tvdb_id, tmdb=False) if cache_id and not expired: return cache_id - tmdb_id = None try: - tmdb_id = self.config.TMDb.convert_to(tvdb_id, "tvdb_id", False) + tmdb_id = self.config.TMDb.convert_tvdb_to(tvdb_id) + if tmdb_id: + if self.config.Cache: + self.config.Cache.update_tmdb_to_tvdb_map(expired, tmdb_id, tvdb_id) + return tmdb_id except Failed: pass - if tmdb_id: - if self.config.Cache: - self.config.Cache.update_tmdb_to_tvdb_map(expired, tmdb_id, tvdb_id) - return tmdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No TMDb ID Found for TVDb ID: {tvdb_id}") else: return None @@ -197,16 +191,15 @@ class Convert: cache_id, expired = self.config.Cache.query_imdb_to_tvdb_map(tvdb_id, imdb=False) if cache_id and not expired: return cache_id - imdb_id = None try: imdb_id = self.tmdb_to_imdb(self.tvdb_to_tmdb(tvdb_id, fail=True), is_movie=False, fail=True) + if imdb_id: + if self.config.Cache: + self.config.Cache.update_imdb_to_tvdb_map(expired, imdb_id, tvdb_id) + return imdb_id except Failed: pass - if imdb_id: - if self.config.Cache: - self.config.Cache.update_imdb_to_tvdb_map(expired, imdb_id, tvdb_id) - return imdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No IMDb ID Found for TVDb ID: {tvdb_id}") else: return None @@ -217,16 +210,17 @@ class Convert: cache_id, expired = self.config.Cache.query_imdb_to_tvdb_map(imdb_id, imdb=True) if cache_id and not expired: return cache_id - tvdb_id = None try: - tvdb_id = self.tmdb_to_tvdb(self.imdb_to_tmdb(imdb_id, is_movie=False, fail=True), fail=True) + tmdb_id, tmdb_type = self.imdb_to_tmdb(imdb_id, fail=True) + if tmdb_type == "show": + tvdb_id = self.tmdb_to_tvdb(tmdb_id, fail=True) + if tvdb_id: + if self.config.Cache: + self.config.Cache.update_imdb_to_tvdb_map(expired, imdb_id, tvdb_id) + return tvdb_id except Failed: pass - if tvdb_id: - if self.config.Cache: - self.config.Cache.update_imdb_to_tvdb_map(expired, imdb_id, tvdb_id) - return tvdb_id - elif fail: + if fail: raise Failed(f"Convert Error: No TVDb ID Found for IMDb ID: {imdb_id}") else: return None @@ -238,10 +232,10 @@ class Convert: imdb_id = [] anidb_id = None if self.config.Cache: - cache_id, media_type, expired = self.config.Cache.query_guid_map(item.guid) + cache_id, imdb_check, media_type, expired = self.config.Cache.query_guid_map(item.guid) if cache_id and not expired: media_id_type = "movie" if "movie" in media_type else "show" - return media_id_type, cache_id + return media_id_type, cache_id, imdb_check try: guid = requests.utils.urlparse(item.guid) item_type = guid.scheme.split(".")[-1] @@ -286,10 +280,16 @@ class Convert: else: if not tmdb_id and imdb_id: for imdb in imdb_id: - tmdb = self.imdb_to_tmdb(imdb, is_movie=library.is_movie) - if tmdb: + tmdb, tmdb_type = self.imdb_to_tmdb(imdb) + if tmdb and ((tmdb_type == "movie" and library.is_movie) or (tmdb_type == "show" and library.is_show)): tmdb_id.append(tmdb) + if not imdb_id and tmdb_id and library.is_movie: + for tmdb in tmdb_id: + imdb = self.tmdb_to_imdb(tmdb) + if imdb: + imdb_id.append(imdb) + if not tvdb_id and tmdb_id and library.is_show: for tmdb in tmdb_id: tvdb = self.tmdb_to_tvdb(tmdb) @@ -298,22 +298,29 @@ class Convert: if not tvdb_id: raise Failed(f"Unable to convert TMDb ID: {util.compile_list(tmdb_id)} to TVDb ID") + if not imdb_id and tvdb_id: + for tvdb in tvdb_id: + imdb = self.tvdb_to_imdb(tvdb) + if imdb: + imdb_id.append(imdb) - def update_cache(cache_ids, id_type, guid_type): + def update_cache(cache_ids, id_type, imdb_in, guid_type): if self.config.Cache: cache_ids = util.compile_list(cache_ids) - logger.info(util.adjust_space(f" Cache | {'^' if expired else '+'} | {item.guid:<46} | {id_type} ID: {cache_ids:<6} | {item.title}")) - self.config.Cache.update_guid_map(guid_type, item.guid, cache_ids, expired) + imdb_in = util.compile_list(imdb_in) if imdb_in else None + ids = f"{item.guid:<46} | {id_type} ID: {cache_ids:<7} | IMDb ID: {str(imdb_in):<10}" + logger.info(util.adjust_space(f" Cache | {'^' if expired else '+'} | {ids} | {item.title}")) + self.config.Cache.update_guid_map(item.guid, cache_ids, imdb_in, expired, guid_type) if tmdb_id and library.is_movie: - update_cache(tmdb_id, "TMDb", "movie") - return "movie", tmdb_id + update_cache(tmdb_id, "TMDb", imdb_id, "movie") + return "movie", tmdb_id, imdb_id elif tvdb_id and library.is_show: - update_cache(tvdb_id, "TVDb", "show") - return "show", tvdb_id + update_cache(tvdb_id, "TVDb", imdb_id, "show") + return "show", tvdb_id, imdb_id elif anidb_id and tmdb_id and library.is_show: - update_cache(tmdb_id, "TMDb", "show_movie") - return "movie", tmdb_id + update_cache(tmdb_id, "TMDb", imdb_id, "show_movie") + return "movie", tmdb_id, imdb_id else: logger.debug(f"TMDb: {tmdb_id}, IMDb: {imdb_id}, TVDb: {tvdb_id}") raise Failed(f"No ID to convert") @@ -322,4 +329,4 @@ class Convert: except BadRequest: util.print_stacktrace() logger.info(util.adjust_space(f"Mapping Error | {item.guid:<46} | Bad Request for {item.title}")) - return None, None + return None, None, None diff --git a/modules/icheckmovies.py b/modules/icheckmovies.py index 3d183021..ebccc9bc 100644 --- a/modules/icheckmovies.py +++ b/modules/icheckmovies.py @@ -16,7 +16,7 @@ class ICheckMovies: def _parse_list(self, list_url, language): imdb_urls = self._request(list_url, language, "//a[@class='optionIcon optionIMDB external']/@href") - return [t[t.find("/tt") + 1:-1] for t in imdb_urls] + return [(t[t.find("/tt") + 1:-1], "imdb") for t in imdb_urls] def get_list_description(self, list_url, language): descriptions = self._request(list_url, language, "//div[@class='span-19 last']/p/em/text()") @@ -34,21 +34,9 @@ class ICheckMovies: raise Failed(f"ICheckMovies Error: {list_url} failed to parse") return valid_lists - def get_items(self, method, data, language): - movie_ids = [] + def get_icheckmovies_ids(self, method, data, language): if method == "icheckmovies_list": logger.info(f"Processing ICheckMovies List: {data}") - imdb_ids = self._parse_list(data, language) - total_ids = len(imdb_ids) - for i, imdb_id in enumerate(imdb_ids, 1): - try: - util.print_return(f"Converting IMDb ID {i}/{total_ids}") - movie_ids.append(self.config.Convert.imdb_to_tmdb(imdb_id)) - except Failed as e: - logger.error(e) - logger.info(util.adjust_space(f"Processed {total_ids} IMDb IDs")) + return self._parse_list(data, language) else: raise Failed(f"ICheckMovies Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - return movie_ids, [] diff --git a/modules/imdb.py b/modules/imdb.py index 11205d65..e1fb9f3d 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -95,35 +95,13 @@ class IMDb: return imdb_ids raise ValueError(f"IMDb Error: No IMDb IDs Found at {imdb_url}") - def get_items(self, method, data, language, is_movie): - show_ids = [] - movie_ids = [] - fail_ids = [] - def run_convert(imdb_id): - tvdb_id = self.config.Convert.imdb_to_tvdb(imdb_id) if not is_movie else None - tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) if tvdb_id is None else None - if tmdb_id: movie_ids.append(tmdb_id) - elif tvdb_id: show_ids.append(tvdb_id) - else: - logger.error(f"Convert Error: No {'' if is_movie else 'TVDb ID or '}TMDb ID found for IMDb: {imdb_id}") - fail_ids.append(imdb_id) - + def get_imdb_ids(self, method, data, language): if method == "imdb_id": logger.info(f"Processing IMDb ID: {data}") - run_convert(data) + return [(data, "imdb")] elif method == "imdb_list": status = f"{data['limit']} Items at " if data['limit'] > 0 else '' logger.info(f"Processing IMDb List: {status}{data['url']}") - imdb_ids = self._ids_from_url(data["url"], language, data["limit"]) - total_ids = len(imdb_ids) - for i, imdb in enumerate(imdb_ids, 1): - util.print_return(f"Converting IMDb ID {i}/{total_ids}") - run_convert(imdb) - logger.info(util.adjust_space(f"Processed {total_ids} IMDb IDs")) + return [(i, "imdb") for i in self._ids_from_url(data["url"], language, data["limit"])] else: raise Failed(f"IMDb Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(fail_ids)} IMDb IDs Failed to Convert: {fail_ids}") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids diff --git a/modules/letterboxd.py b/modules/letterboxd.py index 0e9f33e7..7ad7d0c5 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -50,13 +50,13 @@ class Letterboxd: raise Failed(f"Letterboxd Error: {list_url} failed to parse") return valid_lists - def get_items(self, method, data, language): - movie_ids = [] + def get_tmdb_ids(self, method, data, language): if method == "letterboxd_list": logger.info(f"Processing Letterboxd List: {data}") items = self._parse_list(data, language) total_items = len(items) if total_items > 0: + ids = [] for i, item in enumerate(items, 1): letterboxd_id, slug = item util.print_return(f"Finding TMDb ID {i}/{total_items}") @@ -72,12 +72,10 @@ class Letterboxd: continue if self.config.Cache: self.config.Cache.update_letterboxd_map(expired, letterboxd_id, tmdb_id) - movie_ids.append(tmdb_id) + ids.append((tmdb_id, "tmdb")) logger.info(util.adjust_space(f"Processed {total_items} TMDb IDs")) + return ids else: - logger.error(f"Letterboxd Error: No List Items found in {data}") + raise Failed(f"Letterboxd Error: No List Items found in {data}") else: raise Failed(f"Letterboxd Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - return movie_ids, [] diff --git a/modules/mal.py b/modules/mal.py index e8ff7d0e..1df5c2fa 100644 --- a/modules/mal.py +++ b/modules/mal.py @@ -155,7 +155,7 @@ class MyAnimeList: url = f"{urls['user']}/{username}/animelist?{final_status}sort={sort_by}&limit={limit}" return self._parse_request(url) - def get_items(self, method, data): + def get_mal_ids(self, method, data): if method == "mal_id": logger.info(f"Processing MyAnimeList ID: {data}") mal_ids = [data] @@ -173,9 +173,6 @@ class MyAnimeList: mal_ids = self._userlist(data["username"], data["status"], data["sort_by"], data["limit"]) else: raise Failed(f"MyAnimeList Error: Method {method} not supported") - movie_ids, show_ids = self.config.Convert.myanimelist_to_ids(mal_ids) logger.debug("") logger.debug(f"{len(mal_ids)} MyAnimeList IDs Found: {mal_ids}") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids + return mal_ids diff --git a/modules/plex.py b/modules/plex.py index 89957132..d38e149d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -132,10 +132,6 @@ show_only_searches = [ "episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt", "episode_user_rating.lte", "episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt", "episode_year.lte" ] -number_attributes = [ - "plays", "episode_plays", "duration", "tmdb_vote_count", "first_episode_aired", "last_episode_aired", - "added", "episode_added", "release", "episode_air_date", "last_played", "episode_last_played" -] float_attributes = ["user_rating", "episode_user_rating", "critic_rating", "audience_rating"] boolean_attributes = [ "hdr", "unmatched", "duplicate", "unplayed", "progress", "trash", @@ -143,6 +139,7 @@ boolean_attributes = [ ] tmdb_attributes = ["actor", "director", "producer", "writer"] date_attributes = ["added", "episode_added", "release", "episode_air_date", "last_played", "episode_last_played", "first_episode_aired", "last_episode_aired"] +number_attributes = ["plays", "episode_plays", "duration", "tmdb_vote_count"] + date_attributes search_display = {"added": "Date Added", "release": "Release Date", "hdr": "HDR", "progress": "In Progress", "episode_progress": "Episode In Progress"} sorts = { None: None, @@ -299,6 +296,7 @@ class Plex: self.missing = {} self.movie_map = {} self.show_map = {} + self.imdb_map = {} self.movie_rating_key_map = {} self.show_rating_key_map = {} self.run_again = [] @@ -585,7 +583,7 @@ class Plex: raise Failed(f"Collection Error: No valid Plex Collections in {collections}") return valid_collections - def get_items(self, method, data): + def get_rating_keys(self, method, data): media_type = "Movie" if self.is_movie else "Show" items = [] if method == "plex_all": @@ -634,7 +632,10 @@ class Plex: else: raise Failed(f"Plex Error: Method {method} not supported") if len(items) > 0: - return [item.ratingKey for item in items] + ids = [item.ratingKey for item in items] + logger.debug("") + logger.debug(f"{len(ids)} Keys Found: {ids}") + return ids else: raise Failed("Plex Error: No Items found in Plex") @@ -679,7 +680,7 @@ class Plex: for i, item in enumerate(items, 1): util.print_return(f"Processing: {i}/{len(items)} {item.title}") if item.ratingKey not in self.movie_rating_key_map and item.ratingKey not in self.show_rating_key_map: - id_type, main_id = self.config.Convert.get_id(item, self) + id_type, main_id, imdb_id = self.config.Convert.get_id(item, self) if main_id: if id_type == "movie": self.movie_rating_key_map[item.ratingKey] = main_id[0] @@ -687,6 +688,8 @@ class Plex: elif id_type == "show": self.show_rating_key_map[item.ratingKey] = main_id[0] util.add_dict_list(main_id, item.ratingKey, self.show_map) + if imdb_id: + util.add_dict_list(imdb_id, item.ratingKey, self.imdb_map) logger.info("") logger.info(util.adjust_space(f"Processed {len(items)} {'Movies' if self.is_movie else 'Shows'}")) return items diff --git a/modules/stevenlu.py b/modules/stevenlu.py index 7712dcce..4356c7ae 100644 --- a/modules/stevenlu.py +++ b/modules/stevenlu.py @@ -10,21 +10,9 @@ class StevenLu: def __init__(self, config): self.config = config - def get_items(self, method): - movie_ids = [] - fail_ids = [] + def get_stevenlu_ids(self, method): if method == "stevenlu_popular": logger.info(f"Processing StevenLu Popular Movies") - for i in self.config.get_json(base_url): - tmdb_id = self.config.Convert.imdb_to_tmdb(i["imdb_id"]) - if tmdb_id: - movie_ids.append(tmdb_id) - else: - logger.error(f"Convert Error: No TMDb ID found for IMDb: {i['imdb_id']}") - fail_ids.append(i["imdb_id"]) + return [(i["imdb_id"], "imdb") for i in self.config.get_json(base_url)] else: raise Failed(f"StevenLu Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(fail_ids)} IMDb IDs Failed to Convert: {fail_ids}") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - return movie_ids, [] diff --git a/modules/tautulli.py b/modules/tautulli.py index c00e6335..a953c8a0 100644 --- a/modules/tautulli.py +++ b/modules/tautulli.py @@ -20,7 +20,7 @@ class Tautulli: if response["response"]["result"] != "success": raise Failed(f"Tautulli Error: {response['response']['message']}") - def get_items(self, library, params): + def get_rating_keys(self, library, params): query_size = int(params["list_size"]) + int(params["list_buffer"]) logger.info(f"Processing Tautulli Most {params['list_type'].capitalize()}: {params['list_size']} {'Movies' if library.is_movie else 'Shows'}") response = self._request(f"{self.url}/api/v2?apikey={self.apikey}&cmd=get_home_stats&time_range={params['list_days']}&stats_count={query_size}") @@ -50,6 +50,8 @@ class Tautulli: logger.error(f"Plex Error: Item {item} not found") continue count += 1 + logger.debug("") + logger.debug(f"{len(rating_keys)} Keys Found: {rating_keys}") return rating_keys def _section_id(self, library_name): diff --git a/modules/tmdb.py b/modules/tmdb.py index fd2fbb84..9b56e222 100644 --- a/modules/tmdb.py +++ b/modules/tmdb.py @@ -80,11 +80,24 @@ class TMDb: raise Failed(f"TMDb Error: TMDb {'Movie' if is_movie else 'Show'} ID: {tmdb_id} not found") @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) - def convert_to(self, external_id, external_source, is_movie): - search_results = self.Movie.external(external_id=external_id, external_source=external_source) - search = search_results["movie_results" if is_movie else "tv_results"] - if len(search) == 1: return int(search[0]["id"]) - else: raise Failed(f"TMDb Error: No TMDb ID found for {external_source.upper().replace('B_', 'b ')} {external_id}") + def convert_to(self, external_id, external_source): + return self.Movie.external(external_id=external_id, external_source=external_source) + + def convert_tvdb_to(self, tvdb_id): + search = self.convert_to(tvdb_id, "tvdb_id") + if len(search["tv_results"]) == 1: + return int(search["tv_results"][0]["id"]) + else: + raise Failed(f"TMDb Error: No TMDb ID found for TVDb ID {tvdb_id}") + + def convert_imdb_to(self, imdb_id): + search = self.convert_to(imdb_id, "imdb_id") + if len(search["movie_results"]) > 0: + return int(search["movie_results"][0]["id"]), "movie" + elif len(search["tv_results"]) > 0: + return int(search["tv_results"][0]["id"]), "show" + else: + raise Failed(f"TMDb Error: No TMDb ID found for IMDb ID {imdb_id}") def get_movie_show_or_collection(self, tmdb_id, is_movie): if is_movie: @@ -140,35 +153,27 @@ class TMDb: except TMDbException as e: raise Failed(f"TMDb Error: No List found for TMDb ID {tmdb_id}: {e}") def _credits(self, tmdb_id, actor=False, crew=False, director=False, producer=False, writer=False): - movie_ids = [] - show_ids = [] + ids = [] actor_credits = self._person_credits(tmdb_id) if actor: for credit in actor_credits.cast: if credit.media_type == "movie": - movie_ids.append(credit.id) + ids.append((credit.id, "tmdb")) elif credit.media_type == "tv": - try: - show_ids.append(self.config.Convert.tmdb_to_tvdb(credit.id, fail=True)) - except Failed as e: - logger.warning(e) + ids.append((credit.id, "tmdb_show")) for credit in actor_credits.crew: if crew or \ (director and credit.department == "Directing") or \ (producer and credit.department == "Production") or \ (writer and credit.department == "Writing"): if credit.media_type == "movie": - movie_ids.append(credit.id) + ids.append((credit.id, "tmdb")) elif credit.media_type == "tv": - try: - show_ids.append(self.config.Convert.tmdb_to_tvdb(credit.id, fail=True)) - except Failed as e: - logger.warning(e) - return movie_ids, show_ids + ids.append((credit.id, "tmdb_show")) + return ids def _pagenation(self, method, amount, is_movie): ids = [] - count = 0 for x in range(int(amount / 20) + 1): if method == "tmdb_popular": tmdb_items = self.Movie.popular(x + 1) if is_movie else self.TV.popular(x + 1) elif method == "tmdb_top_rated": tmdb_items = self.Movie.top_rated(x + 1) if is_movie else self.TV.top_rated(x + 1) @@ -178,18 +183,15 @@ class TMDb: else: raise Failed(f"TMDb Error: {method} method not supported") for tmdb_item in tmdb_items: try: - ids.append(tmdb_item.id if is_movie else self.config.Convert.tmdb_to_tvdb(tmdb_item.id, fail=True)) - count += 1 + ids.append((tmdb_item.id, "tmdb" if is_movie else "tmdb_show")) except Failed as e: logger.error(e) - pass - if count == amount: break - if count == amount: break + if len(ids) == amount: break + if len(ids) == amount: break return ids def _discover(self, attrs, amount, is_movie): ids = [] - count = 0 for date_attr in discover_dates: if date_attr in attrs: attrs[date_attr] = util.validate_date(attrs[date_attr], f"tmdb_discover attribute {date_attr}", return_as="%Y-%m-%d") @@ -202,13 +204,11 @@ class TMDb: tmdb_items = self.Discover.discover_movies(attrs) if is_movie else self.Discover.discover_tv_shows(attrs) for tmdb_item in tmdb_items: try: - ids.append(tmdb_item.id if is_movie else self.config.Convert.tmdb_to_tvdb(tmdb_item.id, fail=True)) - count += 1 + ids.append((tmdb_item.id, "tmdb" if is_movie else "tmdb_show")) except Failed as e: logger.error(e) - pass - if count == amount: break - if count == amount: break + if len(ids) == amount: break + if len(ids) == amount: break return ids, amount def validate_tmdb_ids(self, tmdb_ids, tmdb_method): @@ -231,11 +231,10 @@ class TMDb: elif tmdb_type == "List": self.get_list(tmdb_id) return tmdb_id - def get_items(self, method, data, is_movie): + def get_tmdb_ids(self, method, data, is_movie): pretty = method.replace("_", " ").title().replace("Tmdb", "TMDb") media_type = "Movie" if is_movie else "Show" - movie_ids = [] - show_ids = [] + ids = [] if method in ["tmdb_discover", "tmdb_company", "tmdb_keyword"] or (method == "tmdb_network" and not is_movie): attrs = None tmdb_id = "" @@ -255,8 +254,7 @@ class TMDb: else: attrs = data.copy() limit = int(attrs.pop("limit")) - if is_movie: movie_ids, amount = self._discover(attrs, limit, is_movie) - else: show_ids, amount = self._discover(attrs, limit, is_movie) + ids, amount = self._discover(attrs, limit, is_movie) if method in ["tmdb_company", "tmdb_network", "tmdb_keyword"]: logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({amount} {media_type}{'' if amount == 1 else 's'})") elif method == "tmdb_discover": @@ -264,8 +262,7 @@ class TMDb: for attr, value in attrs.items(): logger.info(f" {attr}: {value}") elif method in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]: - if is_movie: movie_ids = self._pagenation(method, data, is_movie) - else: show_ids = self._pagenation(method, data, is_movie) + ids = self._pagenation(method, data, is_movie) logger.info(f"Processing {pretty}: {data} {media_type}{'' if data == 1 else 's'}") else: tmdb_id = int(data) @@ -274,34 +271,31 @@ class TMDb: tmdb_name = tmdb_list.name for tmdb_item in tmdb_list.items: if tmdb_item.media_type == "movie": - movie_ids.append(tmdb_item.id) + ids.append((tmdb_item.id, "tmdb")) elif tmdb_item.media_type == "tv": - try: show_ids.append(self.config.Convert.tmdb_to_tvdb(tmdb_item.id, fail=True)) - except Failed: pass + try: + ids.append((tmdb_item.id, "tmdb_show")) + except Failed: + pass elif method == "tmdb_movie": tmdb_name = str(self.get_movie(tmdb_id).title) - movie_ids.append(tmdb_id) + ids.append((tmdb_id, "tmdb")) elif method == "tmdb_collection": tmdb_items = self.get_collection(tmdb_id) tmdb_name = str(tmdb_items.name) for tmdb_item in tmdb_items.parts: - movie_ids.append(tmdb_item["id"]) + ids.append((tmdb_item["id"], "tmdb")) elif method == "tmdb_show": tmdb_name = str(self.get_show(tmdb_id).name) - show_ids.append(self.config.Convert.tmdb_to_tvdb(tmdb_id, fail=True)) + ids.append((tmdb_id, "tmdb_show")) else: tmdb_name = str(self.get_person(tmdb_id).name) - if method == "tmdb_actor": movie_ids, show_ids = self._credits(tmdb_id, actor=True) - elif method == "tmdb_director": movie_ids, show_ids = self._credits(tmdb_id, director=True) - elif method == "tmdb_producer": movie_ids, show_ids = self._credits(tmdb_id, producer=True) - elif method == "tmdb_writer": movie_ids, show_ids = self._credits(tmdb_id, writer=True) - elif method == "tmdb_crew": movie_ids, show_ids = self._credits(tmdb_id, crew=True) + if method == "tmdb_actor": ids = self._credits(tmdb_id, actor=True) + elif method == "tmdb_director": ids = self._credits(tmdb_id, director=True) + elif method == "tmdb_producer": ids = self._credits(tmdb_id, producer=True) + elif method == "tmdb_writer": ids = self._credits(tmdb_id, writer=True) + elif method == "tmdb_crew": ids = self._credits(tmdb_id, crew=True) else: raise Failed(f"TMDb Error: Method {method} not supported") - if len(movie_ids) > 0: - logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(movie_ids)} Movie{'' if len(movie_ids) == 1 else 's'})") - if not is_movie and len(show_ids) > 0: - logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(show_ids)} Show{'' if len(show_ids) == 1 else 's'})") - logger.debug("") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids + if len(ids) > 0: + logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(ids)} Item{'' if len(ids) == 1 else 's'})") + return ids diff --git a/modules/trakt.py b/modules/trakt.py index 6c8391d3..f1886281 100644 --- a/modules/trakt.py +++ b/modules/trakt.py @@ -142,39 +142,59 @@ class Trakt: except Failed: raise Failed(f"Trakt Error: List {data} not found") - def _parse(self, items, top=True, is_movie=True): + def _parse(self, items, top=True, item_type=None): ids = [] for item in items: - data = item["movie" if is_movie else "show"] if top else item - if data["ids"]["tmdb" if is_movie else "tvdb"]: - ids.append(data["ids"]["tmdb" if is_movie else "tvdb"]) + if top: + if item_type: + data = item[item_type] + elif item["type"] in ["movie", "show"]: + data = item[item["type"]] + else: + continue else: - logger.error(f"Trakt Error: No {'TMDb' if is_movie else 'TVDb'} ID found for {data['title']} ({data['year']})") - return (ids, []) if is_movie else ([], ids) + data = item + if item_type: + id_type = "TMDb" if item_type == "movie" else "TVDb" + else: + id_type = "TMDb" if item["type"] == "movie" else "TVDb" + if data["ids"][id_type.lower()]: + ids.append((data["ids"][id_type.lower()], id_type.lower())) + else: + logger.error(f"Trakt Error: No {id_type} ID found for {data['title']} ({data['year']})") + return ids - def _user_list(self, list_type, data, is_movie): - path = f"{requests.utils.urlparse(data).path}/items" if list_type == "list" else f"/users/{data}/{list_type}" + def _user_list(self, data): try: - items = self._request(f"{path}/{'movies' if is_movie else 'shows'}") + items = self._request(f"{requests.utils.urlparse(data).path}/items") except Failed: - raise Failed(f"Trakt Error: {'List' if list_type == 'list' else 'User'} {data} not found") + raise Failed(f"Trakt Error: List {data} not found") if len(items) == 0: - if list_type == "list": - raise Failed(f"Trakt Error: List {data} is empty") - else: - raise Failed(f"Trakt Error: {data}'s {list_type.capitalize()} is empty") - return self._parse(items, is_movie=is_movie) + raise Failed(f"Trakt Error: List {data} is empty") + return self._parse(items) + + def _user_items(self, list_type, data, is_movie): + try: + items = self._request(f"/users/{data}/{list_type}/{'movies' if is_movie else 'shows'}") + except Failed: + raise Failed(f"Trakt Error: User {data} not found") + if len(items) == 0: + raise Failed(f"Trakt Error: {data}'s {list_type.capitalize()} is empty") + return self._parse(items, item_type="movie" if is_movie else "show") def _pagenation(self, pagenation, amount, is_movie): items = self._request(f"/{'movies' if is_movie else 'shows'}/{pagenation}?limit={amount}") - return self._parse(items, top=pagenation != "popular", is_movie=is_movie) + return self._parse(items, top=pagenation != "popular", item_type="movie" if is_movie else "show") def validate_trakt(self, trakt_lists, is_movie, trakt_type="list"): values = util.get_list(trakt_lists, split=False) trakt_values = [] for value in values: try: - self._user_list(trakt_type, value, is_movie) + if trakt_type == "list": + self._user_list(value) + else: + self._user_items(trakt_type, value, is_movie) trakt_values.append(value) except Failed as e: logger.error(e) @@ -187,21 +207,17 @@ class Trakt: raise Failed(f"Trakt Error: No valid Trakt Lists in {values}") return trakt_values - def get_items(self, method, data, is_movie): + def get_trakt_ids(self, method, data, is_movie): pretty = method.replace("_", " ").title() media_type = "Movie" if is_movie else "Show" if method in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]: logger.info(f"Processing {pretty}: {data} {media_type}{'' if data == 1 else 's'}") - movie_ids, show_ids = self._pagenation(method[6:], data, is_movie) + return self._pagenation(method[6:], data, is_movie) elif method in ["trakt_collection", "trakt_watchlist"]: logger.info(f"Processing {pretty} {media_type}s for {data}") - movie_ids, show_ids = self._user_list(method[6:], data, is_movie) + return self._user_items(method[6:], data, is_movie) elif method == "trakt_list": logger.info(f"Processing {pretty}: {data}") - movie_ids, show_ids = self._user_list(method[6:], data, is_movie) + return self._user_list(data) else: raise Failed(f"Trakt Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids diff --git a/modules/tvdb.py b/modules/tvdb.py index 7ee1a918..d2f2843a 100644 --- a/modules/tvdb.py +++ b/modules/tvdb.py @@ -54,6 +54,7 @@ class TVDbObj: self.summary = results[0] if len(results) > 0 and len(results[0]) > 0 else None tmdb_id = None + imdb_id = None if self.is_movie: results = response.xpath("//*[text()='TheMovieDB.com']/@href") if len(results) > 0: @@ -61,16 +62,16 @@ class TVDbObj: tmdb_id = util.regex_first_int(results[0], "TMDb ID") except Failed: pass - if tmdb_id is None: - results = response.xpath("//*[text()='IMDB']/@href") - if len(results) > 0: - try: - tmdb_id = self.config.Convert.imdb_to_tmdb(util.get_id_from_imdb_url(results[0]), fail=True) - except Failed: - pass - if tmdb_id is None: - raise Failed(f"TVDB Error: No TMDb ID found for {self.title}") + results = response.xpath("//*[text()='IMDB']/@href") + if len(results) > 0: + try: + imdb_id = util.get_id_from_imdb_url(results[0]) + except Failed: + pass + if tmdb_id is None and imdb_id is None: + raise Failed(f"TVDB Error: No TMDb ID or IMDb ID found for {self.title}") self.tmdb_id = tmdb_id + self.imdb_id = imdb_id class TVDb: def __init__(self, config): @@ -99,8 +100,7 @@ class TVDb: return description[0] if len(description) > 0 and len(description[0]) > 0 else "" def _ids_from_url(self, tvdb_url, language): - show_ids = [] - movie_ids = [] + ids = [] tvdb_url = tvdb_url.strip() if tvdb_url.startswith((urls["list"], urls["alt_list"])): try: @@ -111,23 +111,23 @@ class TVDb: item_url = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/@href")[0] if item_url.startswith("/series/"): try: - show_ids.append(self.get_series(language, f"{base_url}{item_url}").id) + ids.append((self.get_series(language, f"{base_url}{item_url}").id, "tvdb")) except Failed as e: logger.error(f"{e} for series {title}") elif item_url.startswith("/movies/"): try: - tmdb_id = self.get_movie(language, f"{base_url}{item_url}").tmdb_id - if tmdb_id: - movie_ids.append(tmdb_id) - else: - raise Failed(f"TVDb Error: TMDb ID not found from TVDb URL: {tvdb_url}") + movie = self.get_movie(language, f"{base_url}{item_url}") + if movie.tmdb_id: + ids.append((movie.tmdb_id, "tmdb")) + elif movie.imdb_id: + ids.append((movie.imdb_id, "imdb")) except Failed as e: - logger.error(f"{e} for series {title}") + logger.error(e) else: logger.error(f"TVDb Error: Skipping Movie: {title}") time.sleep(2) - if len(show_ids) > 0 or len(movie_ids) > 0: - return movie_ids, show_ids + if len(ids) > 0: + return ids raise Failed(f"TVDb Error: No TVDb IDs found at {tvdb_url}") except requests.exceptions.MissingSchema: util.print_stacktrace() @@ -135,21 +135,19 @@ class TVDb: else: raise Failed(f"TVDb Error: {tvdb_url} must begin with {urls['list']}") - def get_items(self, method, data, language): - show_ids = [] - movie_ids = [] + def get_tvdb_ids(self, method, data, language): if method == "tvdb_show": logger.info(f"Processing TVDb Show: {data}") - show_ids.append(self.get_series(language, data).id) + return [(self.get_series(language, data).id, "tvdb")] elif method == "tvdb_movie": logger.info(f"Processing TVDb Movie: {data}") - movie_ids.append(self.get_movie(language, data).tmdb_id) + movie = self.get_movie(language, data) + if movie.tmdb_id: + return [(movie.tmdb_id, "tmdb")] + elif movie.imdb_id: + return [(movie.imdb_id, "imdb")] elif method == "tvdb_list": logger.info(f"Processing TVDb List: {data}") - movie_ids, show_ids = self._ids_from_url(data, language) + return self._ids_from_url(data, language) else: raise Failed(f"TVDb Error: Method {method} not supported") - logger.debug("") - logger.debug(f"{len(movie_ids)} TMDb IDs Found: {movie_ids}") - logger.debug(f"{len(show_ids)} TVDb IDs Found: {show_ids}") - return movie_ids, show_ids diff --git a/modules/util.py b/modules/util.py index 274967ea..d5dc6d7e 100644 --- a/modules/util.py +++ b/modules/util.py @@ -246,39 +246,46 @@ def is_locked(filepath): file_object.close() return locked -def date_filter(current, modifier, data, final, current_time): - if current is None: - return False +def is_date_filter(value, modifier, data, final, current_time): + if value is None: + return True if modifier in ["", ".not"]: threshold_date = current_time - timedelta(days=data) - if (modifier == "" and (current is None or current < threshold_date)) \ - or (modifier == ".not" and current and current >= threshold_date): - return False + if (modifier == "" and (value is None or value < threshold_date)) \ + or (modifier == ".not" and value and value >= threshold_date): + return True elif modifier in [".before", ".after"]: filter_date = validate_date(data, final) - if (modifier == ".before" and current >= filter_date) or (modifier == ".after" and current <= filter_date): - return False + if (modifier == ".before" and value >= filter_date) or (modifier == ".after" and value <= filter_date): + return True elif modifier == ".regex": - jailbreak = False + jailbreak = True for check_data in data: - if re.compile(check_data).match(current.strftime("%m/%d/%Y")): + if re.compile(check_data).match(value.strftime("%m/%d/%Y")): jailbreak = True break if not jailbreak: - return False - return True + return True + return False -def number_filter(current, modifier, data): - return current is None or (modifier == ".gt" and current <= data) \ - or (modifier == ".gte" and current < data) \ - or (modifier == ".lt" and current >= data) \ - or (modifier == ".lte" and current > data) +def is_number_filter(value, modifier, data): + return value is None or (modifier == ".gt" and value <= data) \ + or (modifier == ".gte" and value < data) \ + or (modifier == ".lt" and value >= data) \ + or (modifier == ".lte" and value > data) -def string_filter(current, modifier, data): - return (modifier in ["", ".not"] and data.lower() in current.lower()) \ - or (modifier == ".begins" and current.lower().startswith(data.lower())) \ - or (modifier == ".ends" and current.lower().endswith(data.lower())) \ - or (modifier == ".regex" and re.compile(data).match(current)) +def is_string_filter(values, modifier, data): + jailbreak = False + for value in values: + for check_value in data: + if (modifier in ["", ".not"] and check_value.lower() in value.lower()) \ + or (modifier == ".begins" and value.lower().startswith(check_value.lower())) \ + or (modifier == ".ends" and value.lower().endswith(check_value.lower())) \ + or (modifier == ".regex" and re.compile(check_value).match(value)): + jailbreak = True + break + if jailbreak: break + return (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"]) def parse(attribute, data, datatype=None, methods=None, parent=None, default=None, options=None, translation=None, minimum=1, maximum=None): display = f"{parent + ' ' if parent else ''}{attribute} attribute" diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 8bd11038..50cd055e 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -265,12 +265,14 @@ def mass_metadata(config, library, items): tvdb_id = None imdb_id = None if config.Cache: - t_id, guid_media_type, _ = config.Cache.query_guid_map(item.guid) + t_id, i_id, guid_media_type, _ = config.Cache.query_guid_map(item.guid) if t_id: if "movie" in guid_media_type: tmdb_id = t_id[0] else: tvdb_id = t_id[0] + if i_id: + imdb_id = i_id[0] if not tmdb_id and not tvdb_id: tmdb_id = library.get_tmdb_from_map(item) if not tmdb_id and not tvdb_id and library.is_show: @@ -469,7 +471,7 @@ def run_collection(config, library, metadata, requested_collections): for filter_key, filter_value in builder.filters: logger.info(f"Collection Filter {filter_key}: {filter_value}") - builder.collect_rating_keys() + builder.find_rating_keys() if len(builder.rating_keys) > 0 and builder.build_collection: logger.info("")