From df2f7717792bbf18051c8d471f96047c85f57829 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 11 Feb 2021 17:35:45 -0500 Subject: [PATCH 01/13] fixed for #3 --- modules/plex.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/plex.py b/modules/plex.py index d366bc8e..8dd0a0c6 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -294,10 +294,8 @@ class PlexAPI: add_edit("originally_available", str(item.originallyAvailableAt)[:-9], self.metadata[m], key="originallyAvailableAt", value=originally_available) add_edit("rating", item.rating, self.metadata[m], value=rating) add_edit("content_rating", item.contentRating, self.metadata[m], key="contentRating") - if self.is_movie: - add_edit("original_title", item.originalTitle, self.metadata[m], key="originalTitle", value=original_title) - elif "original_title" in self.metadata[m]: - logger.error("Metadata Error: original_title does not work with shows") + originalTitle = item.originalTitle if self.is_movie else item._data.attrib.get("originalTitle") + add_edit("original_title", originalTitle, self.metadata[m], key="originalTitle", value=original_title) add_edit("studio", item.studio, self.metadata[m], value=studio) add_edit("tagline", item.tagline, self.metadata[m], value=tagline) add_edit("summary", item.summary, self.metadata[m], value=summary) From 08e6420543c708fd0b3331b5dcf1f51fc00469c4 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 11 Feb 2021 22:18:23 -0500 Subject: [PATCH 02/13] fix for #17 --- modules/config.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/config.py b/modules/config.py index 6b6d03db..ee587d26 100644 --- a/modules/config.py +++ b/modules/config.py @@ -129,7 +129,7 @@ class Config: self.MyAnimeList = MyAnimeListAPI(self.mal, self.MyAnimeListIDList, authorization) except Failed as e: logger.error(e) - logger.info("My Anime List Connection {}".format("Failed" if self.Trakt is None else "Successful")) + logger.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful")) else: logger.warning("mal attribute not found") @@ -782,13 +782,13 @@ class Config: elif current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall" new_dictionary["year"] = get_int(method_name, "year", collections[c][m], current_time.year, min=1917, max=current_time.year + 1) new_dictionary["limit"] = get_int(method_name, "limit", collections[c][m], 100, max=500) - if "sort_by" not in collections[c][m]: logger.warning("Collection Error: mal_season sort_by attribute not found using members as default") - elif not collections[c][m]["sort_by"]: logger.warning("Collection Error: mal_season sort_by attribute is blank using members as default") - elif collections[c][m]["sort_by"] not in util.mal_season_sort: logger.warning("Collection Error: mal_season sort_by attribute {} invalid must be either 'members' or 'score' using members as default".format(collections[c][m]["sort_by"])) + if "sort_by" not in collections[c][m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default") + elif not collections[c][m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default") + elif collections[c][m]["sort_by"] not in util.mal_season_sort: logger.warning("Collection Warning: mal_season sort_by attribute {} invalid must be either 'members' or 'score' using members as default".format(collections[c][m]["sort_by"])) else: new_dictionary["sort_by"] = util.mal_season_sort[collections[c][m]["sort_by"]] - if "season" not in collections[c][m]: logger.warning("Collection Error: mal_season season attribute not found using the current season: {} as default".format(new_dictionary["season"])) - elif not collections[c][m]["season"]: logger.warning("Collection Error: mal_season season attribute is blank using the current season: {} as default".format(new_dictionary["season"])) - elif collections[c][m]["season"] not in util.pretty_seasons: logger.warning("Collection Error: mal_season season attribute {} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {} as default".format(collections[c][m]["season"], new_dictionary["season"])) + if "season" not in collections[c][m]: logger.warning("Collection Warning: mal_season season attribute not found using the current season: {} as default".format(new_dictionary["season"])) + elif not collections[c][m]["season"]: logger.warning("Collection Warning: mal_season season attribute is blank using the current season: {} as default".format(new_dictionary["season"])) + elif collections[c][m]["season"] not in util.pretty_seasons: logger.warning("Collection Warning: mal_season season attribute {} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {} as default".format(collections[c][m]["season"], new_dictionary["season"])) else: new_dictionary["season"] = collections[c][m]["season"] methods.append((method_name, [new_dictionary])) elif method_name == "mal_userlist": @@ -796,13 +796,13 @@ class Config: if "username" not in collections[c][m]: raise Failed("Collection Error: mal_userlist username attribute is required") elif not collections[c][m]["username"]: raise Failed("Collection Error: mal_userlist username attribute is blank") else: new_dictionary["username"] = collections[c][m]["username"] - if "status" not in collections[c][m]: logger.warning("Collection Error: mal_season status attribute not found using all as default") - elif not collections[c][m]["status"]: logger.warning("Collection Error: mal_season status attribute is blank using all as default") - elif collections[c][m]["status"] not in util.mal_userlist_status: logger.warning("Collection Error: mal_season status attribute {} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default".format(collections[c][m]["status"])) + if "status" not in collections[c][m]: logger.warning("Collection Warning: mal_season status attribute not found using all as default") + elif not collections[c][m]["status"]: logger.warning("Collection Warning: mal_season status attribute is blank using all as default") + elif collections[c][m]["status"] not in util.mal_userlist_status: logger.warning("Collection Warning: mal_season status attribute {} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default".format(collections[c][m]["status"])) else: new_dictionary["status"] = util.mal_userlist_status[collections[c][m]["status"]] - if "sort_by" not in collections[c][m]: logger.warning("Collection Error: mal_season sort_by attribute not found using score as default") - elif not collections[c][m]["sort_by"]: logger.warning("Collection Error: mal_season sort_by attribute is blank using score as default") - elif collections[c][m]["sort_by"] not in util.mal_userlist_sort: logger.warning("Collection Error: mal_season sort_by attribute {} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default".format(collections[c][m]["sort_by"])) + if "sort_by" not in collections[c][m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default") + elif not collections[c][m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default") + elif collections[c][m]["sort_by"] not in util.mal_userlist_sort: logger.warning("Collection Warning: mal_season sort_by attribute {} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default".format(collections[c][m]["sort_by"])) else: new_dictionary["sort_by"] = util.mal_userlist_sort[collections[c][m]["sort_by"]] new_dictionary["limit"] = get_int(method_name, "limit", collections[c][m], 100, max=1000) methods.append((method_name, [new_dictionary])) From 9314417ce4be4862d918d38128fee518d2e1803e Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 09:23:07 -0500 Subject: [PATCH 03/13] anime id updates and fix for #22 --- modules/anidb.py | 62 +++++++++++++++++++++++++++-------------------- modules/config.py | 44 +++++++++++++++++++-------------- 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/modules/anidb.py b/modules/anidb.py index 318f442f..72fe6c3a 100644 --- a/modules/anidb.py +++ b/modules/anidb.py @@ -23,9 +23,12 @@ class AniDBAPI: def convert_tvdb_to_anidb(self, tvdb_id): return self.convert_anidb(tvdb_id, "tvdbid", "anidbid") def convert_imdb_to_anidb(self, imdb_id): return self.convert_anidb(imdb_id, "imdbid", "anidbid") def convert_anidb(self, input_id, from_id, to_id): - ids = self.id_list.xpath("//anime[@{}='{}']/@{}".format(from_id, input_id, to_id)) + ids = self.id_list.xpath("//anime[contains(@{}, '{}')]/@{}".format(from_id, input_id, to_id)) if len(ids) > 0: - if len(ids[0]) > 0: return ids[0] if to_id == "imdbid" else int(ids[0]) + if from_id == "tvdbid": return [int(id) for id in ids] + if len(ids[0]) > 0: + try: return ids[0].split(",") if to_id == "imdbid" else int(ids[0]) + except ValueError: raise Failed("AniDB Error: No {} ID found for {} ID: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id)) else: raise Failed("AniDB Error: No {} ID found for {} ID: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id)) else: raise Failed("AniDB Error: {} ID: {} not found".format(util.pretty_ids[from_id], input_id)) @@ -77,7 +80,7 @@ class AniDBAPI: movie_ids = [] for anidb_id in anime_ids: try: - tmdb_id, tvdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id), language) + tmdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id), language) if tmdb_id: movie_ids.append(tmdb_id) else: raise Failed except Failed: @@ -90,27 +93,34 @@ class AniDBAPI: return movie_ids, show_ids def convert_from_imdb(self, imdb_id, language): - if self.Cache: - tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb_id) - expired = False - if not tmdb_id: - tmdb_id, expired = self.Cache.get_tmdb_from_imdb(imdb_id) - if expired: - tmdb_id = None - else: - tmdb_id = None - from_cache = tmdb_id is not None + output_tmdb_ids = [] + if not isinstance(imdb_id, list): + imdb_id = [imdb_id] + + for imdb in imdb_id: + if self.Cache: + tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb) + expired = False + if not tmdb_id: + tmdb_id, expired = self.Cache.get_tmdb_from_imdb(imdb) + if expired: + tmdb_id = None + else: + tmdb_id = None + from_cache = tmdb_id is not None - if not tmdb_id and self.TMDb: - try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id) - except Failed: pass - if not tmdb_id and self.Trakt: - try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb_id) - except Failed: pass - try: - if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id) - except Failed: tmdb_id = None - if not tmdb_id: raise Failed("TVDb Error: No TMDb ID found for IMDb: {}".format(imdb_id)) - if self.Cache and tmdb_id and expired is not False: - self.Cache.update_imdb("movie", expired, imdb_id, tmdb_id) - return tmdb_id + if not tmdb_id and self.TMDb: + try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb) + except Failed: pass + if not tmdb_id and self.Trakt: + try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb) + except Failed: pass + try: + if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id) + except Failed: tmdb_id = None + if tmdb_id: output_tmdb_ids.append(tmdb_id) + if self.Cache and tmdb_id and expired is not False: + self.Cache.update_imdb("movie", expired, imdb, tmdb_id) + if len(output_tmdb_ids) == 0: raise Failed("AniDB Error: No TMDb ID found for IMDb: {}".format(imdb_id)) + elif len(output_tmdb_ids) == 1: return output_tmdb_ids[0] + else: return output_tmdb_ids diff --git a/modules/config.py b/modules/config.py index ee587d26..8f1069e2 100644 --- a/modules/config.py +++ b/modules/config.py @@ -1067,10 +1067,14 @@ class Config: for i, item in enumerate(items, 1): length = util.print_return(length, "Processing: {}/{} {}".format(i, len(items), item.title)) id_type, main_id = self.get_id(item, library, length) - if id_type == "movie": - movie_map[main_id] = item.ratingKey - elif id_type == "show": - show_map[main_id] = item.ratingKey + if isinstance(main_id, list): + if id_type == "movie": + for m in main_id: movie_map[m] = item.ratingKey + elif id_type == "show": + for m in main_id: show_map[m] = item.ratingKey + else: + if id_type == "movie": movie_map[main_id] = item.ratingKey + elif id_type == "show": show_map[main_id] = item.ratingKey util.print_end(length, "Processed {} {}".format(len(items), "Movies" if library.is_movie else "Shows")) return movie_map, show_map @@ -1130,6 +1134,17 @@ class Config: if mal_id and not tmdb_id: try: tmdb_id = self.MyAnimeListIDList.convert_mal_to_tmdb(mal_id) except Failed: pass + if not tmdb_id and imdb_id and isinstance(imdb_id, list) and self.TMDb: + tmdb_id = [] + new_imdb_id = [] + for imdb in imdb_id: + try: + temp_tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb) + tmdb_id.append(temp_tmdb_id) + new_imdb_id.append(imdb) + except Failed: + continue + imdb_id = new_imdb_id if not tmdb_id and imdb_id and self.TMDb: try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id) except Failed: pass @@ -1160,18 +1175,6 @@ class Config: if not tvdb_id and imdb_id and self.Trakt and library.is_show: try: tvdb_id = self.Trakt.convert_imdb_to_tvdb(imdb_id) except Failed: pass - if tvdb_id and not anidb_id: - try: anidb_id = self.AniDB.convert_tvdb_to_anidb(tvdb_id) - except Failed: pass - if imdb_id and not anidb_id: - try: anidb_id = self.AniDB.convert_imdb_to_anidb(imdb_id) - except Failed: pass - if tvdb_id and not mal_id: - try: mal_id = self.MyAnimeListIDList.convert_tvdb_to_mal(tvdb_id) - except Failed: pass - if tmdb_id and not mal_id: - try: mal_id = self.MyAnimeListIDList.convert_tmdb_to_mal(tmdb_id) - except Failed: pass if (not tmdb_id and library.is_movie) or (not tvdb_id and not ((anidb_id or mal_id) and tmdb_id) and library.is_show): service_name = "TMDb ID" if library.is_movie else "TVDb ID" @@ -1194,8 +1197,13 @@ class Config: elif id_name: error_message = "Configure TMDb or Trakt to covert {} to {}".format(id_name, service_name) else: error_message = "No ID to convert to {}".format(service_name) if self.Cache and (tmdb_id and library.is_movie) or ((tvdb_id or ((anidb_id or mal_id) and tmdb_id)) and library.is_show): - util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id if tmdb_id else "None", imdb_id if imdb_id else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) - self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired) + if isinstance(tmdb_id, list): + for i in range(len(tmdb_id)): + util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id[i] if tmdb_id[i] else "None", imdb_id[i] if imdb_id[i] else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) + self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id[i], imdb_id[i], tvdb_id, anidb_id, mal_id, expired) + else: + util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id if tmdb_id else "None", imdb_id if imdb_id else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) + self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired) if tmdb_id and library.is_movie: return "movie", tmdb_id elif tvdb_id and library.is_show: return "show", tvdb_id elif (anidb_id or mal_id) and tmdb_id: return "movie", tmdb_id From 3b14ad0ae85b63c7b8f4b792d8bbc107c800c91a Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 10:37:04 -0500 Subject: [PATCH 04/13] #13 filter by original language --- modules/config.py | 25 ++++++++++++++++++------- modules/plex.py | 21 ++++++++++++++++++++- modules/util.py | 3 +++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/modules/config.py b/modules/config.py index 8f1069e2..7a8452da 100644 --- a/modules/config.py +++ b/modules/config.py @@ -640,10 +640,12 @@ class Config: final_filter = filter if final_filter in util.movie_only_filters and library.is_show: logger.error("Collection Error: {} filter only works for movie libraries".format(final_filter)) + elif collections[c][m][filter] is None: + logger.error("Collection Error: {} filter is blank".format(final_filter)) elif final_filter in util.all_filters: filters.append((final_filter, collections[c][m][filter])) else: - logger.error("Collection Error: {} filter not supported".format(filter)) + logger.error("Collection Error: {} filter not supported".format(final_filter)) elif method_name == "plex_collectionless": new_dictionary = {} @@ -940,24 +942,33 @@ class Config: elif "trakt" in method: items_found += check_map(self.Trakt.get_items(method, value, library.is_movie)) else: logger.error("Collection Error: {} method not supported".format(method)) - if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, map=map) + if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, map, movie_map, show_map) else: logger.error("No items found to add to this collection ") if len(missing_movies) > 0 or len(missing_shows) > 0: logger.info("") if len(missing_movies) > 0: + if "original_language" in filters: + terms = filters["original_language"] if isinstance(filters["original_language"], list) else [lang.lower() for lang in str(filters["original_language"]).split(", ")] + not_lang = False + if "original_language.not" in filters: + terms = filters["original_language.not"] if isinstance(filters["original_language.not"], list) else [lang.lower() for lang in str(filters["original_language.not"]).split(", ")] + not_lang = True + missing_movies_with_names = [] for missing_id in missing_movies: try: - title = str(self.TMDb.get_movie(missing_id).title) - missing_movies_with_names.append((title, missing_id)) - logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) + movie = self.TMDb.get_movie(missing_id) + if (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms): + title = str(movie.title) + missing_movies_with_names.append((title, missing_id)) + logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) except Failed as e: logger.error(e) - logger.info("{} Movie{} Missing".format(len(missing_movies), "s" if len(missing_movies) > 1 else "")) + logger.info("{} Movie{} Missing".format(len(missing_movies_with_names), "s" if len(missing_movies_with_names) > 1 else "")) library.save_missing(collection_name, missing_movies_with_names, True) if do_arr and library.Radarr: - library.Radarr.add_tmdb(missing_movies) + library.Radarr.add_tmdb([missing_id for title, missing_id in missing_movies_with_names]) if len(missing_shows) > 0 and library.is_show: missing_shows_with_names = [] for missing_id in missing_shows: diff --git a/modules/plex.py b/modules/plex.py index 8dd0a0c6..75bc876d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -68,6 +68,8 @@ class PlexAPI: except Failed as e: logger.error(e) logger.info("{} library's Tautulli Connection {}".format(params["name"], "Failed" if self.Tautulli is None else "Successful")) + self.TMDb = params["tmdb"] + self.TVDb = params["tvdb"] self.name = params["name"] self.missing_path = os.path.join(os.path.dirname(os.path.abspath(params["metadata_path"])), "{}_missing.yml".format(os.path.splitext(os.path.basename(params["metadata_path"]))[0])) @@ -160,7 +162,7 @@ class PlexAPI: except yaml.scanner.ScannerError as e: logger.error("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) - def add_to_collection(self, collection, items, filters, map={}): + def add_to_collection(self, collection, items, filters, map, movie_map, show_map): name = collection.title if isinstance(collection, Collections) else collection collection_items = collection.items() if isinstance(collection, Collections) else [] total = len(items) @@ -181,6 +183,23 @@ class PlexAPI: if attr is None or attr < threshold_date: match = False break + elif method == "original_language": + terms = f[1] if isinstance(f[1], list) else [lang.lower() for lang in str(f[1]).split(", ")] + tmdb_id = None + movie = None + for key, value in movie_map.items(): + if current.ratingKey == value: + try: + movie = self.TMDb.get_movie(key) + break + except Failed: + pass + if movie is None: + logger.warning("Filter Error: No TMDb ID found for {}".format(current.title)) + continue + if (modifier == ".not" and movie.original_language in terms) or (modifier != ".not" and movie.original_language not in terms): + match = False + break elif modifier in [".gte", ".lte"]: if method == "originallyAvailableAt": threshold_date = datetime.strptime(f[1], "%m/%d/%y") diff --git a/modules/util.py b/modules/util.py index 91104620..b707b4c9 100644 --- a/modules/util.py +++ b/modules/util.py @@ -43,6 +43,7 @@ filter_alias = { "genre": "genres", "max_age": "max_age", "originally_available": "originallyAvailableAt", + "original_language": "original_language", "rating": "rating", "studio": "studio", "subtitle_language": "subtitle_language", @@ -361,6 +362,7 @@ all_filters = [ "genre", "genre.not", "max_age", "originally_available.gte", "originally_available.lte", + "original_language", "original_language.not", "rating.gte", "rating.lte", "studio", "studio.not", "subtitle_language", "subtitle_language.not", @@ -372,6 +374,7 @@ movie_only_filters = [ "audio_language", "audio_language.not", "country", "country.not", "director", "director.not", + "original_language", "original_language.not", "subtitle_language", "subtitle_language.not", "video_resolution", "video_resolution.not", "writer", "writer.not" From a222b32da40de2b7bab8fd8b229ae9aaeee5618a Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 11:58:42 -0500 Subject: [PATCH 05/13] original_language fix --- modules/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index 7a8452da..b3c8e053 100644 --- a/modules/config.py +++ b/modules/config.py @@ -948,6 +948,7 @@ class Config: if len(missing_movies) > 0 or len(missing_shows) > 0: logger.info("") if len(missing_movies) > 0: + not_lang = None if "original_language" in filters: terms = filters["original_language"] if isinstance(filters["original_language"], list) else [lang.lower() for lang in str(filters["original_language"]).split(", ")] not_lang = False @@ -959,7 +960,7 @@ class Config: for missing_id in missing_movies: try: movie = self.TMDb.get_movie(missing_id) - if (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms): + if not_lang is None or (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms): title = str(movie.title) missing_movies_with_names.append((title, missing_id)) logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) From c78358d53513ffbe8396825a928fee2e4864a55e Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 13:17:51 -0500 Subject: [PATCH 06/13] fix for original_language --- modules/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/config.py b/modules/config.py index b3c8e053..c7910b1a 100644 --- a/modules/config.py +++ b/modules/config.py @@ -949,12 +949,12 @@ class Config: logger.info("") if len(missing_movies) > 0: not_lang = None - if "original_language" in filters: - terms = filters["original_language"] if isinstance(filters["original_language"], list) else [lang.lower() for lang in str(filters["original_language"]).split(", ")] - not_lang = False - if "original_language.not" in filters: - terms = filters["original_language.not"] if isinstance(filters["original_language.not"], list) else [lang.lower() for lang in str(filters["original_language.not"]).split(", ")] - not_lang = True + terms = None + for filter_method, filter_data in filters: + if filter_method.startswith("original_language"): + terms = filter_data if isinstance(filter_data, list) else [lang.strip().lower() for lang in str(filter_data).split(",")] + not_lang = filter_method.endswith(".not") + break missing_movies_with_names = [] for missing_id in missing_movies: From 3ed6b174f597cccc19296ff95e187ec896f74e30 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 17:07:31 -0500 Subject: [PATCH 07/13] fix for #24 --- modules/tvdb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/tvdb.py b/modules/tvdb.py index 73c7a381..f483305e 100644 --- a/modules/tvdb.py +++ b/modules/tvdb.py @@ -121,11 +121,11 @@ class TVDbAPI: if status_message: logger.info("Processing {}: {}".format(pretty, data)) if method == "tvdb_show": - try: show_ids.append(self.get_series(language, tvdb_id=int(data))) - except ValueError: show_ids.append(self.get_series(language, tvdb_url=data)) + try: show_ids.append(self.get_series(language, tvdb_id=int(data)).id) + except ValueError: show_ids.append(self.get_series(language, tvdb_url=data).id) elif method == "tvdb_movie": - try: movie_ids.append(self.get_movie(language, tvdb_id=int(data))) - except ValueError: movie_ids.append(self.get_movie(language, tvdb_url=data)) + try: movie_ids.append(self.get_movie(language, tvdb_id=int(data)).id) + except ValueError: movie_ids.append(self.get_movie(language, tvdb_url=data).id) elif method == "tvdb_list": tmdb_ids, tvdb_ids = self.get_tvdb_ids_from_url(data, language) movie_ids.extend(tmdb_ids) From af2259a43cd41c7fea99d2f3a9c6d63434387b68 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 23:28:46 -0500 Subject: [PATCH 08/13] added show_filtered --- modules/config.py | 6 ++++++ modules/plex.py | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index c7910b1a..57facf2b 100644 --- a/modules/config.py +++ b/modules/config.py @@ -428,6 +428,7 @@ class Config: backgrounds_found = [] collectionless = "plex_collectionless" in collections[c] skip_collection = True + show_filtered = False if "schedule" not in collections[c]: skip_collection = False @@ -581,6 +582,9 @@ class Config: elif method_name == "add_to_arr": if isinstance(collections[c][m], bool): details[method_name] = collections[c][m] else: raise Failed("Collection Error: add_to_arr must be either true or false") + elif method_name == "show_filtered": + if isinstance(collections[c][m], bool): show_filtered = collections[c][m] + else: raise Failed("Collection Error: show_filtered must be either true or false using the default false") elif method_name in util.all_details: details[method_name] = collections[c][m] elif method_name in ["year", "year.not"]: methods.append(("plex_search", [[(method_name, util.get_year_list(collections[c][m], method_name))]])) elif method_name in ["decade", "decade.not"]: methods.append(("plex_search", [[(method_name, util.get_int_list(collections[c][m], util.remove_not(method_name)))]])) @@ -964,6 +968,8 @@ class Config: title = str(movie.title) missing_movies_with_names.append((title, missing_id)) logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) + elif show_filtered is True: + logger.info("{} Collection | X | {} (TMDb: {})".format(collection_name, title, missing_id)) except Failed as e: logger.error(e) logger.info("{} Movie{} Missing".format(len(missing_movies_with_names), "s" if len(missing_movies_with_names) > 1 else "")) diff --git a/modules/plex.py b/modules/plex.py index 75bc876d..66d2afc0 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -162,7 +162,7 @@ class PlexAPI: except yaml.scanner.ScannerError as e: logger.error("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) - def add_to_collection(self, collection, items, filters, map, movie_map, show_map): + def add_to_collection(self, collection, items, filters, show_filtered, map, movie_map, show_map): name = collection.title if isinstance(collection, Collections) else collection collection_items = collection.items() if isinstance(collection, Collections) else [] total = len(items) @@ -231,6 +231,8 @@ class PlexAPI: util.print_end(length, "{} Collection | {} | {}".format(name, "=" if current in collection_items else "+", current.title)) if current in collection_items: map[current.ratingKey] = None else: current.addCollection(name) + elif show_filtered is True: + logger.info("{} Collection | X | {}".format(name, current.title)) media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "") util.print_end(length, "{} {} Processed".format(total, media_type)) return map From ccd1eef7c65ab2e5decec54d2da03bb1d71ef540 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 12 Feb 2021 23:30:42 -0500 Subject: [PATCH 09/13] fix show_filtered --- modules/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index 57facf2b..753dba8a 100644 --- a/modules/config.py +++ b/modules/config.py @@ -946,7 +946,7 @@ class Config: elif "trakt" in method: items_found += check_map(self.Trakt.get_items(method, value, library.is_movie)) else: logger.error("Collection Error: {} method not supported".format(method)) - if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, map, movie_map, show_map) + if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, show_filtered, map, movie_map, show_map) else: logger.error("No items found to add to this collection ") if len(missing_movies) > 0 or len(missing_shows) > 0: From 83ce2711cfbf07d75a88289a5bdc6f96b19c2a15 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sat, 13 Feb 2021 00:26:40 -0500 Subject: [PATCH 10/13] finished show_filtered and show_unmanaged --- modules/config.py | 31 +++++++++++++++++++++---------- modules/plex.py | 3 ++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/config.py b/modules/config.py index 753dba8a..ce336738 100644 --- a/modules/config.py +++ b/modules/config.py @@ -142,7 +142,8 @@ class Config: self.general["plex"]["token"] = check_for_attribute(self.data, "token", parent="plex", default_is_none=True) if "plex" in self.data else None self.general["plex"]["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="plex", var_type="path", default=os.path.join(default_dir, "assets")) if "plex" in self.data else os.path.join(default_dir, "assets") self.general["plex"]["sync_mode"] = check_for_attribute(self.data, "sync_mode", parent="plex", default="append", test_list=["append", "sync"], options="| \tappend (Only Add Items to the Collection)\n| \tsync (Add & Remove Items from the Collection)") if "plex" in self.data else "append" - self.general["plex"]["show_unmanaged_collections"] = check_for_attribute(self.data, "show_unmanaged_collections", parent="plex", var_type="bool", default=True) if "plex" in self.data else True + self.general["plex"]["show_unmanaged"] = check_for_attribute(self.data, "show_unmanaged", parent="plex", var_type="bool", default=True) if "plex" in self.data else True + self.general["plex"]["show_filtered"] = check_for_attribute(self.data, "show_filtered", parent="plex", var_type="bool", default=False) if "plex" in self.data else False self.general["radarr"] = {} self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", default_is_none=True) if "radarr" in self.data else None @@ -240,15 +241,25 @@ class Config: else: logger.warning("Config Warning: sync_mode attribute is blank using general value: {}".format(self.general["plex"]["sync_mode"])) - params["show_unmanaged_collections"] = self.general["plex"]["show_unmanaged_collections"] - if "plex" in libs[lib] and "show_unmanaged_collections" in libs[lib]["plex"]: - if libs[lib]["plex"]["show_unmanaged_collections"]: - if isinstance(libs[lib]["plex"]["show_unmanaged_collections"], bool): - params["plex"]["show_unmanaged_collections"] = libs[lib]["plex"]["show_unmanaged_collections"] + params["show_unmanaged"] = self.general["plex"]["show_unmanaged"] + if "plex" in libs[lib] and "show_unmanaged" in libs[lib]["plex"]: + if libs[lib]["plex"]["show_unmanaged"]: + if isinstance(libs[lib]["plex"]["show_unmanaged"], bool): + params["plex"]["show_unmanaged"] = libs[lib]["plex"]["show_unmanaged"] else: - logger.warning("Config Warning: plex sub-attribute show_unmanaged_collections must be either true or false using general value: {}".format(self.general["plex"]["show_unmanaged_collections"])) + logger.warning("Config Warning: plex sub-attribute show_unmanaged must be either true or false using general value: {}".format(self.general["plex"]["show_unmanaged"])) else: - logger.warning("Config Warning: radarr sub-attribute add is blank using general value: {}".format(self.general["radarr"]["add"])) + logger.warning("Config Warning: plex sub-attribute show_unmanaged is blank using general value: {}".format(self.general["plex"]["show_unmanaged"])) + + params["show_filtered"] = self.general["plex"]["show_filtered"] + if "plex" in libs[lib] and "show_filtered" in libs[lib]["plex"]: + if libs[lib]["plex"]["show_filtered"]: + if isinstance(libs[lib]["plex"]["show_filtered"], bool): + params["plex"]["show_filtered"] = libs[lib]["plex"]["show_filtered"] + else: + logger.warning("Config Warning: plex sub-attribute show_filtered must be either true or false using general value: {}".format(self.general["plex"]["show_filtered"])) + else: + logger.warning("Config Warning: plex sub-attribute show_filtered is blank using general value: {}".format(self.general["plex"]["show_filtered"])) params["tmdb"] = self.TMDb params["tvdb"] = self.TVDb @@ -428,7 +439,7 @@ class Config: backgrounds_found = [] collectionless = "plex_collectionless" in collections[c] skip_collection = True - show_filtered = False + show_filtered = library.show_filtered if "schedule" not in collections[c]: skip_collection = False @@ -1061,7 +1072,7 @@ class Config: except Exception as e: util.print_stacktrace() logger.error("Unknown Error: {}".format(e)) - if library.show_unmanaged_collections is True: + if library.show_unmanaged is True: logger.info("") util.seperator("Unmanaged Collections in {} Library".format(library.name)) logger.info("") diff --git a/modules/plex.py b/modules/plex.py index 66d2afc0..99ce3c8f 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -76,7 +76,8 @@ class PlexAPI: self.metadata_path = params["metadata_path"] self.asset_directory = params["asset_directory"] self.sync_mode = params["sync_mode"] - self.show_unmanaged_collections = params["show_unmanaged_collections"] + self.show_unmanaged = params["show_unmanaged"] + self.show_filtered = params["show_filtered"] self.plex = params["plex"] self.radarr = params["radarr"] self.sonarr = params["sonarr"] From 7ae3eec54e476b5a7594eea71985a395cea24eb3 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sat, 13 Feb 2021 22:16:16 -0500 Subject: [PATCH 11/13] show_filtered fix --- modules/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index ce336738..1d14d859 100644 --- a/modules/config.py +++ b/modules/config.py @@ -975,8 +975,8 @@ class Config: for missing_id in missing_movies: try: movie = self.TMDb.get_movie(missing_id) + title = str(movie.title) if not_lang is None or (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms): - title = str(movie.title) missing_movies_with_names.append((title, missing_id)) logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) elif show_filtered is True: From 0a47273c27d319e72c28c422424fd9a609c01d1c Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sun, 14 Feb 2021 02:04:35 -0500 Subject: [PATCH 12/13] Closes #6 Add movie posters by folder --- modules/config.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/config.py b/modules/config.py index 1d14d859..dee019ba 100644 --- a/modules/config.py +++ b/modules/config.py @@ -1069,6 +1069,32 @@ class Config: if background[0] == "url": plex_collection.uploadArt(url=background[1]) else: plex_collection.uploadArt(filepath=background[1]) logger.info("Detail: {} updated background to [{}] {}".format(background[2], background[0], background[1])) + + if library.asset_directory: + path = os.path.join(library.asset_directory, "{}".format(name_mapping)) + dirs = [folder for folder in os.listdir(path) if os.path.isdir(os.path.join(path, folder))] + if len(dirs) > 0: + for item in plex_collection.items(): + folder = os.path.basename(os.path.dirname(item.locations[0])) + if folder in dirs: + files = [file for file in os.listdir(os.path.join(path, folder)) if os.path.isfile(os.path.join(path, folder, file))] + poster_path = None + background_path = None + for file in files: + if poster_path is None and file.startswith("poster."): + poster_path = os.path.join(path, folder, file) + if background_path is None and file.startswith("background."): + background_path = os.path.join(path, folder, file) + if poster_path: + item.uploadPoster(filepath=poster_path) + logger.info("Detail: asset_directory updated {}'s poster to [file] {}".format(item.title, poster_path)) + if background_path: + item.uploadArt(filepath=background_path) + logger.info("Detail: asset_directory updated {}'s background to [file] {}".format(item.title, background_path)) + if poster_path is None and background_path is None: + logger.warning("No Files Found: {}".format(os.path.join(path, folder))) + else: + logger.warning("No Folder: {}".format(os.path.join(path, folder))) except Exception as e: util.print_stacktrace() logger.error("Unknown Error: {}".format(e)) From 334ae30d98db9c28736487a60c45608f652c17a0 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sun, 14 Feb 2021 02:47:16 -0500 Subject: [PATCH 13/13] v1.1.0 --- README.md | 4 ++-- config/config.yml.template | 3 ++- plex_meta_manager.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7f824f3d..dd6930a5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Plex Meta Manager -#### Version 1.0.3 +#### Version 1.1.0 The original concept for Plex Meta Manager is [Plex Auto Collections](https://github.com/mza921/Plex-Auto-Collections), but this is rewritten from the ground up to be able to include a scheduler, metadata edits, multiple libraries, and logging. Plex Meta Manager is a Python 3 script that can be continuously run using YAML configuration files to update on a schedule the metadata of the movies, shows, and collections in your libraries as well as automatically build collections based on various methods all detailed in the wiki. Some collection examples that the script can automatically build and update daily include Plex Based Searches like actor, genre, or studio collections or Collections based on TMDb, IMDb, Trakt, TVDb, AniDB, or MyAnimeList lists and various other services. @@ -17,6 +17,6 @@ The script is designed to work with most Metadata agents including the new Plex * If you're getting an Error or have an Enhancement post in the [Issues](https://github.com/meisnate12/Plex-Meta-Manager/issues) * If you have a configuration question visit the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions) -* To see user submited Metadata configuration files and you could even add your own go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs) +* To see user submitted Metadata configuration files and you could even add your own go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs) * Pull Request are welcome * [Buy Me a Pizza](https://www.buymeacoffee.com/meisnate12) diff --git a/config/config.yml.template b/config/config.yml.template index 1e90c918..18504972 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -13,7 +13,8 @@ plex: # Can be individually specified pe token: #################### sync_mode: append asset_directory: config/assets - show_unmanaged_collections: true + show_unmanaged: true + show_filtered: false radarr: # Can be individually specified per library as well url: http://192.168.1.12:7878 token: ################################ diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 8bfff9eb..4e19b595 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -56,7 +56,7 @@ logger.info(util.get_centered_text("| |_) | |/ _ \ \/ / | |\/| |/ _ \ __/ _` | | logger.info(util.get_centered_text("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ")) logger.info(util.get_centered_text("|_| |_|\___/_/\_\ |_| |_|\___|\__\__,_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| ")) logger.info(util.get_centered_text(" |___/ ")) -logger.info(util.get_centered_text(" Version: 1.0.3 ")) +logger.info(util.get_centered_text(" Version: 1.1.0 ")) util.seperator() if args.test: