From 03e77ab2acdd057788bf04f352fc431967a00e16 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 1 Dec 2021 00:38:20 -0500 Subject: [PATCH 01/28] update webhook start and end payloads --- .github/workflows/latest.yml | 2 -- modules/config.py | 4 ++-- modules/webhooks.py | 7 ++++--- plex_meta_manager.py | 6 ++++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index c458a4318..1ca8f5955 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -3,8 +3,6 @@ name: Docker Latest Release on: push: branches: [ master ] - pull_request: - branches: [ master ] jobs: diff --git a/modules/config.py b/modules/config.py index 987c25339..b855f9e5e 100644 --- a/modules/config.py +++ b/modules/config.py @@ -44,7 +44,7 @@ def __init__(self, default_dir, attrs): self.default_dir = default_dir self.test_mode = attrs["test"] if "test" in attrs else False self.trace_mode = attrs["trace"] if "trace" in attrs else False - self.run_start_time = attrs["time"] + self.start_time = attrs["time_obj"] self.run_hour = datetime.strptime(attrs["time"], "%H:%M").hour self.requested_collections = util.get_list(attrs["collections"]) if "collections" in attrs else None self.requested_libraries = util.get_list(attrs["libraries"]) if "libraries" in attrs else None @@ -231,7 +231,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No self.Webhooks = Webhooks(self, self.webhooks, notifiarr=self.NotifiarrFactory) try: - self.Webhooks.start_time_hooks(self.run_start_time) + self.Webhooks.start_time_hooks(self.start_time) except Failed as e: util.print_stacktrace() logger.error(f"Webhooks Error: {e}") diff --git a/modules/webhooks.py b/modules/webhooks.py index d7889bb2f..3a67e7c07 100644 --- a/modules/webhooks.py +++ b/modules/webhooks.py @@ -43,12 +43,13 @@ def _request(self, webhooks, json): def start_time_hooks(self, start_time): if self.run_start_webhooks: - self._request(self.run_start_webhooks, {"start_time": start_time}) + self._request(self.run_start_webhooks, {"start_time": start_time.strftime("%Y-%m-%d %H:%M:%S")}) - def end_time_hooks(self, start_time, run_time, stats): + def end_time_hooks(self, start_time, end_time, run_time, stats): if self.run_end_webhooks: self._request(self.run_end_webhooks, { - "start_time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"), + "start_time": start_time.strftime("%Y-%m-%d %H:%M:%S"), + "end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"), "run_time": run_time, "collections_created": stats["created"], "collections_modified": stats["modified"], diff --git a/plex_meta_manager.py b/plex_meta_manager.py index f6e73550a..fdc687fbc 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -135,6 +135,7 @@ def start(attrs): start_time = datetime.now() if "time" not in attrs: attrs["time"] = start_time.strftime("%H:%M") + attrs["time_obj"] = start_time util.separator(f"Starting {start_type}Run") config = None global stats @@ -152,10 +153,11 @@ def start(attrs): util.print_stacktrace() util.print_multiline(e, critical=True) logger.info("") - run_time = str(datetime.now() - start_time).split('.')[0] + end_time = datetime.now() + run_time = str(end_time - start_time).split('.')[0] if config: try: - config.Webhooks.end_time_hooks(start_time, run_time, stats) + config.Webhooks.end_time_hooks(start_time, end_time, run_time, stats) except Failed as e: util.print_stacktrace() logger.error(f"Webhooks Error: {e}") From 4b40ca2a687b27701ae3bf6b055a4b8b20e1c8b0 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 1 Dec 2021 08:17:28 -0500 Subject: [PATCH 02/28] fix arr error and add custom sort to flixpatrol builders --- modules/builder.py | 1 + modules/flixpatrol.py | 1 - modules/radarr.py | 11 ++++++++++- modules/sonarr.py | 11 ++++++++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 10cd8bb03..ddc754cb6 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -151,6 +151,7 @@ "tvdb_list", "imdb_list", "stevenlu_popular", "anidb_popular", "trakt_list", "trakt_trending", "trakt_popular", "trakt_boxoffice", "trakt_collected_daily", "trakt_collected_weekly", "trakt_collected_monthly", "trakt_collected_yearly", "trakt_collected_all", + "flixpatrol_url", "flixpatrol_demographics", "flixpatrol_popular", "flixpatrol_top", "trakt_recommended_daily", "trakt_recommended_weekly", "trakt_recommended_monthly", "trakt_recommended_yearly", "trakt_recommended_all", "trakt_watched_daily", "trakt_watched_weekly", "trakt_watched_monthly", "trakt_watched_yearly", "trakt_watched_all", "tautulli_popular", "tautulli_watched", "letterboxd_list", "icheckmovies_list", diff --git a/modules/flixpatrol.py b/modules/flixpatrol.py index 47ab95ac8..b30f6cfbf 100644 --- a/modules/flixpatrol.py +++ b/modules/flixpatrol.py @@ -1,5 +1,4 @@ import logging -from datetime import datetime, timedelta from modules import util from modules.util import Failed diff --git a/modules/radarr.py b/modules/radarr.py index 0d072c195..396b34ddc 100644 --- a/modules/radarr.py +++ b/modules/radarr.py @@ -55,6 +55,7 @@ def add_tmdb(self, tmdb_ids, **options): added = [] exists = [] + skipped = [] invalid = [] movies = [] for i, item in enumerate(tmdb_ids, 1): @@ -64,7 +65,7 @@ def add_tmdb(self, tmdb_ids, **options): if self.config.Cache: _id = self.config.Cache.query_radarr_adds(tmdb_id, self.library.original_mapping_name) if _id: - exists.append(item) + skipped.append(item) continue try: movie = self.api.get_movie(tmdb_id=tmdb_id) @@ -98,6 +99,14 @@ def add_tmdb(self, tmdb_ids, **options): self.config.Cache.update_radarr_adds(movie.tmdbId, self.library.original_mapping_name) logger.info(f"{len(exists)} Movie{'s' if len(exists) > 1 else ''} already existing in Radarr") + if len(skipped) > 0: + logger.info("") + for movie in skipped: + logger.info(f"Skipped: In Cache | {movie}") + if self.config.Cache: + self.config.Cache.update_radarr_adds(movie[0] if isinstance(movie, tuple) else movie, self.library.original_mapping_name) + logger.info(f"{len(skipped)} Movie{'s' if len(skipped) > 1 else ''} already existing in Radarr") + if len(invalid) > 0: logger.info("") for tmdb_id in invalid: diff --git a/modules/sonarr.py b/modules/sonarr.py index c0753e265..4303b103b 100644 --- a/modules/sonarr.py +++ b/modules/sonarr.py @@ -81,6 +81,7 @@ def add_tvdb(self, tvdb_ids, **options): added = [] exists = [] + skipped = [] invalid = [] shows = [] for i, item in enumerate(tvdb_ids, 1): @@ -90,7 +91,7 @@ def add_tvdb(self, tvdb_ids, **options): if self.config.Cache: _id = self.config.Cache.query_sonarr_adds(tvdb_id, self.library.original_mapping_name) if _id: - exists.append(item) + skipped.append(item) continue try: show = self.api.get_series(tvdb_id=tvdb_id) @@ -124,6 +125,14 @@ def add_tvdb(self, tvdb_ids, **options): self.config.Cache.update_sonarr_adds(series.tvdbId, self.library.original_mapping_name) logger.info(f"{len(exists)} Series already existing in Sonarr") + if len(skipped) > 0: + logger.info("") + for series in skipped: + logger.info(f"Skipped: In Cache | {series}") + if self.config.Cache: + self.config.Cache.update_sonarr_adds(series[0] if isinstance(series, tuple) else series, self.library.original_mapping_name) + logger.info(f"{len(skipped)} Movie{'s' if len(skipped) > 1 else ''} already existing in Sonarr") + if len(invalid) > 0: for tvdb_id in invalid: logger.info("") From bdba74dcaf1234fc2ad65dcb3b694472b94af124 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 1 Dec 2021 10:17:19 -0500 Subject: [PATCH 03/28] fixed anilist default year --- modules/builder.py | 7 ++++--- modules/convert.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index ddc754cb6..50171f6b5 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -825,6 +825,7 @@ def _anilist(self, method_name, method_data): elif self.current_time.month in [3, 4, 5]: current_season = "spring" elif self.current_time.month in [6, 7, 8]: current_season = "summer" else: current_season = "fall" + default_year = self.current_year + 1 if self.current_time.month == 12 else self.current_year for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"): new_dictionary = {} for search_method, search_data in dict_data.items(): @@ -834,10 +835,10 @@ def _anilist(self, method_name, method_data): elif search_attr == "season": new_dictionary[search_attr] = util.parse(search_attr, search_data, parent=method_name, default=current_season, options=util.seasons) if "year" not in dict_methods: - logger.warning(f"Collection Warning: {method_name} year attribute not found using this year: {self.current_year} by default") - new_dictionary["year"] = self.current_year + logger.warning(f"Collection Warning: {method_name} year attribute not found using this year: {default_year} by default") + new_dictionary["year"] = default_year elif search_attr == "year": - new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="int", parent=method_name, default=self.current_year, minimum=1917, maximum=self.current_year + 1) + new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="int", parent=method_name, default=default_year, minimum=1917, maximum=default_year + 1) elif search_data is None: raise Failed(f"Collection Error: {method_name} {search_final} attribute is blank") elif search_attr == "adult": diff --git a/modules/convert.py b/modules/convert.py index de8b53f6a..93af8c31b 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -304,8 +304,8 @@ def update_cache(cache_ids, id_type, imdb_in, guid_type): logger.debug(f"TMDb: {tmdb_id}, IMDb: {imdb_id}, TVDb: {tvdb_id}") raise Failed(f"No ID to convert") except Failed as e: - logger.info(util.adjust_space(f"Mapping Error | {item.guid:<46} | {e} for {item.title}")) + logger.info(util.adjust_space(f'Mapping Error | {item.guid:<46} | {e} for "{item.title}"')) except BadRequest: util.print_stacktrace() - logger.info(util.adjust_space(f"Mapping Error | {item.guid:<46} | Bad Request for {item.title}")) + logger.info(util.adjust_space(f'Mapping Error | {item.guid:<46} | Bad Request for "{item.title}"')) return None, None, None From c542d3026c19ca26b18d1b488d8a951ce83306b0 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 1 Dec 2021 11:59:49 -0500 Subject: [PATCH 04/28] added only_filter_missing collection detail --- modules/builder.py | 5 +++-- modules/config.py | 2 ++ modules/library.py | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 50171f6b5..ae814a46b 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -80,7 +80,7 @@ background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"] boolean_details = [ "visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", - "missing_only_released", "delete_below_minimum" + "missing_only_released", "only_filter_missing", "delete_below_minimum" ] string_details = ["sort_title", "content_rating", "name_mapping"] ignored_details = [ @@ -178,6 +178,7 @@ def __init__(self, config, library, metadata, name, no_missing, data): "show_missing": self.library.show_missing, "save_missing": self.library.save_missing, "missing_only_released": self.library.missing_only_released, + "only_filter_missing": self.library.only_filter_missing, "create_asset_folders": self.library.create_asset_folders, "delete_below_minimum": self.library.delete_below_minimum, "collection_creation_webhooks": self.library.collection_creation_webhooks, @@ -1641,7 +1642,7 @@ def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False): return True def check_filters(self, current, display): - if self.filters or self.tmdb_filters: + if (self.filters or self.tmdb_filters) and not self.details["only_filter_missing"]: util.print_return(f"Filtering {display} {current.title}") if self.tmdb_filters: if current.ratingKey not in self.library.movie_rating_key_map and current.ratingKey not in self.library.show_rating_key_map: diff --git a/modules/config.py b/modules/config.py index b855f9e5e..3fa2cd5e9 100644 --- a/modules/config.py +++ b/modules/config.py @@ -193,6 +193,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "show_missing_assets": check_for_attribute(self.data, "show_missing_assets", parent="settings", var_type="bool", default=True), "save_missing": check_for_attribute(self.data, "save_missing", parent="settings", var_type="bool", default=True), "missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False), + "only_filter_missing": check_for_attribute(self.data, "only_filter_missing", parent="settings", var_type="bool", default=False), "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), @@ -408,6 +409,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["show_missing_assets"] = check_for_attribute(lib, "show_missing_assets", parent="settings", var_type="bool", default=self.general["show_missing_assets"], do_print=False, save=False) params["save_missing"] = check_for_attribute(lib, "save_missing", parent="settings", var_type="bool", default=self.general["save_missing"], do_print=False, save=False) params["missing_only_released"] = check_for_attribute(lib, "missing_only_released", parent="settings", var_type="bool", default=self.general["missing_only_released"], do_print=False, save=False) + params["only_filter_missing"] = check_for_attribute(lib, "only_filter_missing", parent="settings", var_type="bool", default=self.general["only_filter_missing"], do_print=False, save=False) params["create_asset_folders"] = check_for_attribute(lib, "create_asset_folders", parent="settings", var_type="bool", default=self.general["create_asset_folders"], do_print=False, save=False) params["collection_minimum"] = check_for_attribute(lib, "collection_minimum", parent="settings", var_type="int", default=self.general["collection_minimum"], do_print=False, save=False) params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False) diff --git a/modules/library.py b/modules/library.py index 4f3854a58..fede7691c 100644 --- a/modules/library.py +++ b/modules/library.py @@ -47,6 +47,7 @@ def __init__(self, config, params): self.show_missing_assets = params["show_missing_assets"] self.save_missing = params["save_missing"] self.missing_only_released = params["missing_only_released"] + self.only_filter_missing = params["only_filter_missing"] self.create_asset_folders = params["create_asset_folders"] self.assets_for_all = params["assets_for_all"] self.delete_unmanaged_collections = params["delete_unmanaged_collections"] From 7424cae26665f6a8fd5e1ebe629dcec2da4a1f7b Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 1 Dec 2021 12:03:57 -0500 Subject: [PATCH 05/28] update template --- config/config.yml.template | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/config.yml.template b/config/config.yml.template index 8e85950d4..23559fa19 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -18,15 +18,16 @@ settings: # Can be individually specified cache_expiration: 60 asset_directory: config/assets asset_folders: true + create_asset_folders: false sync_mode: append show_unmanaged: true show_filtered: false show_missing: true + show_missing_assets: true save_missing: true run_again_delay: 2 - released_missing_only: false - create_asset_folders: false missing_only_released: false + only_filter_missing: false collection_minimum: 1 delete_below_minimum: true tvdb_language: eng From 987fa2b3f0a08633b81ee266737707f9df29b840 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 2 Dec 2021 01:39:46 -0500 Subject: [PATCH 06/28] check for 'a' in anidb ids --- modules/convert.py | 3 ++- modules/trakt.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/convert.py b/modules/convert.py index 93af8c31b..9ba69deb3 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -228,7 +228,8 @@ def get_id(self, item, library): if check_id.startswith("tvdb"): tvdb_id.append(int(re.search("-(.*)", check_id).group(1))) elif check_id.startswith("anidb"): - anidb_id = int(re.search("-(.*)", check_id).group(1)) + anidb_str = str(re.search("-(.*)", check_id).group(1)) + anidb_id = int(anidb_str[1:] if anidb_str[0] == "a" else anidb_str) library.anidb_map[anidb_id] = item.ratingKey else: raise Failed(f"Hama Agent ID: {check_id} not supported") diff --git a/modules/trakt.py b/modules/trakt.py index a0b517d1e..d268ec117 100644 --- a/modules/trakt.py +++ b/modules/trakt.py @@ -202,6 +202,8 @@ 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: + if isinstance(value, dict): + raise Failed("Trakt Error: List cannot be a dictionary") try: if trakt_type == "list": self._user_list(value) From c09571e510017b010d3adb508cafeb05b705bc62 Mon Sep 17 00:00:00 2001 From: Chaz Larson Date: Thu, 2 Dec 2021 10:05:51 -0600 Subject: [PATCH 07/28] spelling, extra words --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 074d2c583..facee0654 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The script works with most Metadata agents including the new Plex Movie Agent, N ## IBRACORP Video Walkthrough -[IBRACORP](https://ibracorp.io/) made a video walkthough for installing Plex Meta Manager on Unraid. While you might not be using Unraid the video goes over many key accepts of Plex Meta Manager and can be a great place to start learning how to use the script. +[IBRACORP](https://ibracorp.io/) made a video walkthough for installing Plex Meta Manager on Unraid. While you might not be using Unraid the video goes over many key aspects of Plex Meta Manager and can be a great place to start learning how to use the script. [![Plex Meta Manager](https://img.youtube.com/vi/dF69MNoot3w/0.jpg)](https://www.youtube.com/watch?v=dF69MNoot3w "Plex Meta Manager") @@ -33,6 +33,6 @@ The script works with most Metadata agents including the new Plex Movie Agent, N * Before posting on GitHub about an enhancement, error, or configuration question please visit the [Plex Meta Manager Discord Server](https://discord.gg/TsdpsFYqqm). * 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 post in the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions). -* To see user submitted Metadata configuration files, and you to even add your own, go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs). -* Pull Request are welcome but please submit them to the develop branch. +* To see user submitted Metadata configuration files, and even add your own, go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs). +* Pull Requests are welcome but please submit them to the develop branch. * If you wish to contribute to the Wiki please fork and send a pull request on the [Plex Meta Manager Wiki Repository](https://github.com/meisnate12/Plex-Meta-Manager-Wiki). From fc2c43db7d51b14305e8e958030044b0ca6b878d Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 2 Dec 2021 23:23:22 -0500 Subject: [PATCH 08/28] allow syncing with empty lists --- modules/builder.py | 11 ++++++----- modules/meta.py | 2 +- modules/plex.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index ae814a46b..7060dd81e 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -93,6 +93,7 @@ poster_details + background_details + summary_details + string_details item_bool_details = ["item_assets", "revert_overlay", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh"] item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay"] + item_bool_details + list(plex.item_advance_keys.keys()) +none_details = ["label.sync", "item_label.sync"] radarr_details = ["radarr_add", "radarr_add_existing", "radarr_folder", "radarr_monitor", "radarr_search", "radarr_availability", "radarr_quality", "radarr_tag"] sonarr_details = [ "sonarr_add", "sonarr_add_existing", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series", @@ -146,7 +147,7 @@ smart_invalid = ["collection_order", "collection_level"] smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label"] + radarr_details + sonarr_details custom_sort_builders = [ - "tmdb_list", "tmdb_popular", "tmdb_now_playing", "tmdb_top_rated", + "plex_search", "tmdb_list", "tmdb_popular", "tmdb_now_playing", "tmdb_top_rated", "tmdb_trending_daily", "tmdb_trending_weekly", "tmdb_discover", "tvdb_list", "imdb_list", "stevenlu_popular", "anidb_popular", "trakt_list", "trakt_trending", "trakt_popular", "trakt_boxoffice", @@ -550,7 +551,7 @@ def cant_interact(attr1, attr2, fail=False): logger.debug(f"Value: {method_data}") try: if method_data is None and method_name in all_builders + plex.searches: raise Failed(f"Collection Error: {method_final} attribute is blank") - elif method_data is None: logger.warning(f"Collection Warning: {method_final} attribute is blank") + elif method_data is None and method_name not in none_details: logger.warning(f"Collection Warning: {method_final} attribute is blank") elif not self.config.Trakt and "trakt" in method_name: raise Failed(f"Collection Error: {method_final} requires Trakt to be configured") elif not self.library.Radarr and "radarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Radarr to be configured") elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured") @@ -714,9 +715,9 @@ def _details(self, method_name, method_data, method_final, methods): if "label.remove" in methods and "label.sync" in methods: raise Failed("Collection Error: Cannot use label.remove and label.sync together") if method_final == "label" and "label_sync_mode" in methods and self.data[methods["label_sync_mode"]] == "sync": - self.details["label.sync"] = util.get_list(method_data) + self.details["label.sync"] = util.get_list(method_data) if method_data else [] else: - self.details[method_final] = util.get_list(method_data) + self.details[method_final] = util.get_list(method_data) if method_data else [] elif method_name in notification_details: self.details[method_name] = util.parse(method_name, method_data, datatype="list") elif method_name in boolean_details: @@ -731,7 +732,7 @@ def _item_details(self, method_name, method_data, method_mod, method_final, meth raise Failed(f"Collection Error: Cannot use item_label and item_label.sync together") if "item_label.remove" in methods and "item_label.sync" in methods: raise Failed(f"Collection Error: Cannot use item_label.remove and item_label.sync together") - self.item_details[method_final] = util.get_list(method_data) + self.item_details[method_final] = util.get_list(method_data) if method_data else [] elif method_name in ["item_radarr_tag", "item_sonarr_tag"]: if method_name in methods and f"{method_name}.sync" in methods: raise Failed(f"Collection Error: Cannot use {method_name} and {method_name}.sync together") diff --git a/modules/meta.py b/modules/meta.py index e83f6e249..fb3f874ac 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -139,7 +139,7 @@ def edit_tags(attr, obj, group, alias, extra=None, movie_library=False): if extra: add_tags.extend(extra) remove_tags = util.get_list(group[alias[f"{attr}.remove"]]) if f"{attr}.remove" in alias else None - sync_tags = util.get_list(group[alias[f"{attr}.sync"]]) if f"{attr}.sync" in alias else None + sync_tags = util.get_list(group[alias[f"{attr}.sync"]] if group[alias[f"{attr}.sync"]] else []) if f"{attr}.sync" in alias else None return self.library.edit_tags(attr, obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags) return False diff --git a/modules/plex.py b/modules/plex.py index b067bd1aa..235fa31b2 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -602,7 +602,7 @@ def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None): except BadRequest: _item_tags = [] _add = [f"{t[:1].upper()}{t[1:]}" for t in _add_tags + _sync_tags if t.lower() not in _item_tags] - _remove = [t for t in _item_tags if (_sync_tags and t not in _sync_tags) or t in _remove_tags] + _remove = [t for t in _item_tags if (sync_tags is not None and t not in _sync_tags) or t in _remove_tags] if _add: self.query_data(getattr(obj, f"add{attr.capitalize()}"), _add) display += f"+{', +'.join(_add)}" From 019bbc5e38b4e84faa1cade43fd7fdfed30ea14f Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 3 Dec 2021 17:05:24 -0500 Subject: [PATCH 09/28] fix asset error --- modules/config.py | 12 +++++++++++- modules/library.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/config.py b/modules/config.py index 3fa2cd5e9..cd8977b1a 100644 --- a/modules/config.py +++ b/modules/config.py @@ -142,7 +142,17 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No else: message = f"Path {os.path.abspath(data[attribute])} does not exist" elif var_type == "list": return util.get_list(data[attribute], split=False) elif var_type == "list_path": - temp_list = [p for p in util.get_list(data[attribute], split=False) if os.path.exists(os.path.abspath(p))] + temp_list = [] + warning_message = "" + for p in util.get_list(data[attribute], split=False): + if os.path.exists(os.path.abspath(p)): + temp_list.append(p) + else: + if len(warning_message) > 0: + warning_message += "\n" + warning_message += f"Config Warning: Path does not exist: {os.path.abspath(p)}" + if do_print: + util.print_multiline(f"Config Warning: {warning_message}") if len(temp_list) > 0: return temp_list else: message = "No Paths exist" elif var_type == "lower_list": return util.get_list(data[attribute], lower=True) diff --git a/modules/library.py b/modules/library.py index fede7691c..f5197c1ce 100644 --- a/modules/library.py +++ b/modules/library.py @@ -34,7 +34,7 @@ def __init__(self, config, params): self.name = params["name"] self.original_mapping_name = params["mapping_name"] self.metadata_path = params["metadata_path"] - self.asset_directory = params["asset_directory"] + self.asset_directory = params["asset_directory"] if params["asset_directory"] else [] self.default_dir = params["default_dir"] self.mapping_name, output = util.validate_filename(self.original_mapping_name) self.image_table_name = self.config.Cache.get_image_table_name(self.original_mapping_name) if self.config.Cache else None From e8890b085b1920e616fd3c156181f6a1519a6f16 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 3 Dec 2021 17:26:38 -0500 Subject: [PATCH 10/28] fix collection_level display error --- modules/builder.py | 2 +- modules/notifiarr.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 7060dd81e..bb2d5ec20 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -561,7 +561,7 @@ def cant_interact(attr1, attr2, fail=False): elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries") elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries") elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries") - elif self.parts_collection and method_name not in parts_collection_valid: raise Failed(f"Collection Error: {method_final} attribute does not work with Collection Level: {self.details['collection_level'].capitalize()}") + elif self.parts_collection and method_name not in parts_collection_valid: raise Failed(f"Collection Error: {method_final} attribute does not work with Collection Level: {self.collection_level.capitalize()}") elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections") elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection") elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter") diff --git a/modules/notifiarr.py b/modules/notifiarr.py index f3f72d2f8..3cd67b7e5 100644 --- a/modules/notifiarr.py +++ b/modules/notifiarr.py @@ -25,6 +25,7 @@ def __init__(self, config, params): def get_url(self, path): url = f"{dev_url if self.develop else base_url}{'notification/test' if self.test else f'{path}{self.apikey}'}" - logger.debug(url.replace(self.apikey, "APIKEY")) + if self.config.trace_mode: + logger.debug(url.replace(self.apikey, "APIKEY")) params = {"event": "pmm" if self.test else "collections"} return url, params From e3b77b15e140bd794599dd9756f01e6bad2aa0c1 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 02:51:06 -0500 Subject: [PATCH 11/28] fix sync blank labels --- modules/builder.py | 2 +- modules/plex.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index bb2d5ec20..1f7f4e974 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -551,7 +551,7 @@ def cant_interact(attr1, attr2, fail=False): logger.debug(f"Value: {method_data}") try: if method_data is None and method_name in all_builders + plex.searches: raise Failed(f"Collection Error: {method_final} attribute is blank") - elif method_data is None and method_name not in none_details: logger.warning(f"Collection Warning: {method_final} attribute is blank") + elif method_data is None and method_final not in none_details: logger.warning(f"Collection Warning: {method_final} attribute is blank") elif not self.config.Trakt and "trakt" in method_name: raise Failed(f"Collection Error: {method_final} requires Trakt to be configured") elif not self.library.Radarr and "radarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Radarr to be configured") elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured") diff --git a/modules/plex.py b/modules/plex.py index 235fa31b2..7b9be25b2 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -592,7 +592,7 @@ def edit_item(self, item, name, item_type, edits, advanced=False): def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None): display = "" key = builder.filter_translation[attr] if attr in builder.filter_translation else attr - if add_tags or remove_tags or sync_tags: + if add_tags or remove_tags or sync_tags is not None: _add_tags = add_tags if add_tags else [] _remove_tags = [t.lower() for t in remove_tags] if remove_tags else [] _sync_tags = [t.lower() for t in sync_tags] if sync_tags else [] From 78dce17cb900f65d869bcc2a4a11da81de59a7b0 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 02:52:08 -0500 Subject: [PATCH 12/28] Added tmdb_collection library operation --- modules/config.py | 16 ++++++++++-- modules/library.py | 11 ++++---- modules/meta.py | 57 +++++++++++++++++++++------------------- modules/plex.py | 3 +++ plex_meta_manager.py | 62 ++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 114 insertions(+), 35 deletions(-) diff --git a/modules/config.py b/modules/config.py index cd8977b1a..19835e92b 100644 --- a/modules/config.py +++ b/modules/config.py @@ -125,7 +125,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No else: endline = "" yaml.round_trip_dump(loaded_config, open(self.config_path, "w"), indent=None, block_seq_indent=2) elif data[attribute] is None: - if default_is_none and var_type == "list": return [] + if default_is_none and var_type in ["list", "int_list"]: return [] elif default_is_none: return None else: message = f"{text} is blank" elif var_type == "url": @@ -141,6 +141,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No if os.path.exists(os.path.abspath(data[attribute])): return data[attribute] else: message = f"Path {os.path.abspath(data[attribute])} does not exist" elif var_type == "list": return util.get_list(data[attribute], split=False) + elif var_type == "int_list": return util.get_list(data[attribute], int_list=True) elif var_type == "list_path": temp_list = [] warning_message = "" @@ -437,6 +438,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["split_duplicates"] = check_for_attribute(lib, "split_duplicates", var_type="bool", default=False, save=False, do_print=False) params["radarr_add_all"] = check_for_attribute(lib, "radarr_add_all", var_type="bool", default=False, save=False, do_print=False) params["sonarr_add_all"] = check_for_attribute(lib, "sonarr_add_all", var_type="bool", default=False, save=False, do_print=False) + params["tmdb_collections"] = None if lib and "operations" in lib and lib["operations"]: if isinstance(lib["operations"], dict): @@ -460,6 +462,17 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["radarr_add_all"] = check_for_attribute(lib["operations"], "radarr_add_all", var_type="bool", default=False, save=False) if "sonarr_add_all" in lib["operations"]: params["sonarr_add_all"] = check_for_attribute(lib["operations"], "sonarr_add_all", var_type="bool", default=False, save=False) + if "tmdb_collections" in lib["operations"]: + params["tmdb_collections"] = {"exclude_ids": [], "remove_collection": False, "template": {"tmdb_collection_details": "<>"}} + if lib["operations"]["tmdb_collections"] and isinstance(lib["operations"]["tmdb_collections"], dict): + params["tmdb_collections"]["exclude_ids"] = check_for_attribute(lib["operations"]["tmdb_collections"], "exclude_ids", var_type="int_list", default_is_none=True, save=False) + params["tmdb_collections"]["remove_collection"] = check_for_attribute(lib["operations"]["tmdb_collections"], "remove_collection", var_type="bool", default=False, save=False) + if "template" in lib["operations"]["tmdb_collections"] and lib["operations"]["tmdb_collections"]["template"] and isinstance(lib["operations"]["tmdb_collections"]["template"], dict): + params["tmdb_collections"]["template"] = lib["operations"]["tmdb_collections"]["template"] + else: + logger.warning("Config Warning: Using default template for tmdb_collections") + else: + logger.error("Config Error: tmdb_collections blank using default settings") else: logger.error("Config Error: operations must be a dictionary") @@ -512,7 +525,6 @@ def check_dict(attr, name): "optimize": check_for_attribute(lib, "optimize", parent="plex", var_type="bool", default=self.general["plex"]["optimize"], save=False) } library = Plex(self, params) - logger.info("") logger.info(f"{display_name} Library Connection Successful") except Failed as e: self.errors.append(e) diff --git a/modules/library.py b/modules/library.py index f5197c1ce..6da3f6ff1 100644 --- a/modules/library.py +++ b/modules/library.py @@ -38,17 +38,19 @@ def __init__(self, config, params): self.default_dir = params["default_dir"] self.mapping_name, output = util.validate_filename(self.original_mapping_name) self.image_table_name = self.config.Cache.get_image_table_name(self.original_mapping_name) if self.config.Cache else None - self.missing_path = os.path.join(self.default_dir, f"{self.original_mapping_name}_missing.yml") + self.missing_path = os.path.join(self.default_dir, f"{self.mapping_name}_missing.yml") self.asset_folders = params["asset_folders"] + self.create_asset_folders = params["create_asset_folders"] self.sync_mode = params["sync_mode"] + self.collection_minimum = params["collection_minimum"] + self.delete_below_minimum = params["delete_below_minimum"] + self.missing_only_released = params["missing_only_released"] self.show_unmanaged = params["show_unmanaged"] self.show_filtered = params["show_filtered"] self.show_missing = params["show_missing"] self.show_missing_assets = params["show_missing_assets"] self.save_missing = params["save_missing"] - self.missing_only_released = params["missing_only_released"] self.only_filter_missing = params["only_filter_missing"] - self.create_asset_folders = params["create_asset_folders"] self.assets_for_all = params["assets_for_all"] self.delete_unmanaged_collections = params["delete_unmanaged_collections"] self.delete_collections_with_less = params["delete_collections_with_less"] @@ -56,10 +58,9 @@ def __init__(self, config, params): self.mass_audience_rating_update = params["mass_audience_rating_update"] self.mass_critic_rating_update = params["mass_critic_rating_update"] self.mass_trakt_rating_update = params["mass_trakt_rating_update"] + self.tmdb_collections = params["tmdb_collections"] self.radarr_add_all = params["radarr_add_all"] self.sonarr_add_all = params["sonarr_add_all"] - self.collection_minimum = params["collection_minimum"] - self.delete_below_minimum = params["delete_below_minimum"] self.error_webhooks = params["error_webhooks"] self.collection_creation_webhooks = params["collection_creation_webhooks"] self.collection_addition_webhooks = params["collection_addition_webhooks"] diff --git a/modules/meta.py b/modules/meta.py index fb3f874ac..84ea484b3 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -15,8 +15,6 @@ def __init__(self, config, library, file_type, path): self.library = library self.type = file_type self.path = path - logger.info("") - logger.info(f"Loading Metadata {file_type}: {path}") def get_dict(attribute, attr_data, check_list=None): if check_list is None: check_list = [] @@ -35,30 +33,37 @@ def get_dict(attribute, attr_data, check_list=None): else: logger.warning(f"Config Warning: {attribute} attribute is blank") return None - try: - if file_type in ["URL", "Git"]: - content_path = path if file_type == "URL" else f"{github_base}{path}.yml" - response = self.config.get(content_path) - if response.status_code >= 400: - raise Failed(f"URL Error: No file found at {content_path}") - content = response.content - elif os.path.exists(os.path.abspath(path)): - content = open(path, encoding="utf-8") - else: - raise Failed(f"File Error: File does not exist {path}") - data, ind, bsi = yaml.util.load_yaml_guess_indent(content) - self.metadata = get_dict("metadata", data, library.metadatas) - self.templates = get_dict("templates", data) - self.collections = get_dict("collections", data, library.collections) - - if self.metadata is None and self.collections is None: - raise Failed("YAML Error: metadata or collections attribute is required") - logger.info(f"Metadata File Loaded Successfully") - except yaml.scanner.ScannerError as ye: - raise Failed(f"YAML Error: {util.tab_new_lines(ye)}") - except Exception as e: - util.print_stacktrace() - raise Failed(f"YAML Error: {e}") + if file_type == "Data": + self.metadata = None + self.collections = get_dict("collections", path, library.collections) + self.templates = get_dict("templates", path) + else: + try: + logger.info("") + logger.info(f"Loading Metadata {file_type}: {path}") + if file_type in ["URL", "Git"]: + content_path = path if file_type == "URL" else f"{github_base}{path}.yml" + response = self.config.get(content_path) + if response.status_code >= 400: + raise Failed(f"URL Error: No file found at {content_path}") + content = response.content + elif os.path.exists(os.path.abspath(path)): + content = open(path, encoding="utf-8") + else: + raise Failed(f"File Error: File does not exist {path}") + data, ind, bsi = yaml.util.load_yaml_guess_indent(content) + self.metadata = get_dict("metadata", data, library.metadatas) + self.templates = get_dict("templates", data) + self.collections = get_dict("collections", data, library.collections) + + if self.metadata is None and self.collections is None: + raise Failed("YAML Error: metadata or collections attribute is required") + logger.info(f"Metadata File Loaded Successfully") + except yaml.scanner.ScannerError as ye: + raise Failed(f"YAML Error: {util.tab_new_lines(ye)}") + except Exception as e: + util.print_stacktrace() + raise Failed(f"YAML Error: {e}") def get_collections(self, requested_collections): if requested_collections: diff --git a/modules/plex.py b/modules/plex.py index 7b9be25b2..3137437a5 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -260,6 +260,9 @@ def __init__(self, config, params): self.is_other = self.agent == "com.plexapp.agents.none" if self.is_other: self.type = "Video" + if self.tmdb_collections and self.is_show: + self.tmdb_collections = None + logger.error("Config Error: tmdb_collections only work with Movie Libraries.") def get_all_collections(self): return self.search(libtype="collection") diff --git a/plex_meta_manager.py b/plex_meta_manager.py index fdc687fbc..01825f3bd 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -6,6 +6,7 @@ from modules import util from modules.builder import CollectionBuilder from modules.config import Config + from modules.meta import Metadata from modules.util import Failed, NotScheduled except ModuleNotFoundError: print("Requirements Error: Requirements are not installed") @@ -180,6 +181,42 @@ def update_libraries(config): plexapi.server.TIMEOUT = library.timeout logger.info("") util.separator(f"{library.name} Library") + + logger.debug("") + logger.debug(f"Mapping Name: {library.original_mapping_name}") + logger.debug(f"Folder Name: {library.mapping_name}") + logger.debug(f"Missing Path: {library.missing_path}") + for ad in library.asset_directory: + logger.debug(f"Asset Directory: {ad}") + logger.debug(f"Asset Folders: {library.asset_folders}") + logger.debug(f"Create Asset Folders: {library.create_asset_folders}") + logger.debug(f"Sync Mode: {library.sync_mode}") + logger.debug(f"Collection Minimum: {library.collection_minimum}") + logger.debug(f"Delete Below Minimum: {library.delete_below_minimum}") + logger.debug(f"Missing Only Released: {library.missing_only_released}") + logger.debug(f"Only Filter Missing: {library.only_filter_missing}") + logger.debug(f"Show Unmanaged: {library.show_unmanaged}") + logger.debug(f"Show Filtered: {library.show_filtered}") + logger.debug(f"Show Missing: {library.show_missing}") + logger.debug(f"Show Missing Assets: {library.show_missing_assets}") + logger.debug(f"Save Missing: {library.save_missing}") + logger.debug(f"Assets For All: {library.assets_for_all}") + logger.debug(f"Delete Collections With Less: {library.delete_collections_with_less}") + logger.debug(f"Delete Unmanaged Collections: {library.delete_unmanaged_collections}") + logger.debug(f"Mass Genre Update: {library.mass_genre_update}") + logger.debug(f"Mass Audience Rating Update: {library.mass_audience_rating_update}") + logger.debug(f"Mass Critic Rating Update: {library.mass_critic_rating_update}") + logger.debug(f"Mass Trakt Rating Update: {library.mass_trakt_rating_update}") + logger.debug(f"Split Duplicates: {library.split_duplicates}") + logger.debug(f"Split Duplicates: {library.split_duplicates}") + logger.debug(f"Radarr Add All: {library.radarr_add_all}") + logger.debug(f"Sonarr Add All: {library.sonarr_add_all}") + logger.debug(f"TMDb Collections: {library.tmdb_collections}") + logger.debug(f"Clean Bundles: {library.clean_bundles}") + logger.debug(f"Empty Trash: {library.empty_trash}") + logger.debug(f"Optimize: {library.optimize}") + logger.debug(f"Timeout: {library.timeout}") + items = None if not library.is_other: logger.info("") @@ -292,11 +329,13 @@ def library_operations(config, library, items=None): logger.info(util.adjust_space(f"{item.title[:25]:<25} | Splitting")) if library.assets_for_all or library.mass_genre_update or library.mass_audience_rating_update or \ - library.mass_critic_rating_update or library.mass_trakt_rating_update or library.radarr_add_all or library.sonarr_add_all: + library.mass_critic_rating_update or library.mass_trakt_rating_update or library.tmdb_collections or \ + library.radarr_add_all or library.sonarr_add_all: if items is None: items = library.get_all() radarr_adds = [] sonarr_adds = [] + tmdb_collections = {} trakt_ratings = config.Trakt.user_ratings(library.is_movie) if library.mass_trakt_rating_update else [] for i, item in enumerate(items, 1): @@ -346,7 +385,7 @@ def library_operations(config, library, items=None): sonarr_adds.append((tvdb_id, f"{path.replace(library.Sonarr.plex_path, library.Sonarr.sonarr_path)}/")) tmdb_item = None - if library.mass_genre_update == "tmdb" or library.mass_audience_rating_update == "tmdb" or library.mass_critic_rating_update == "tmdb": + if library.tmdb_collections or library.mass_genre_update == "tmdb" or library.mass_audience_rating_update == "tmdb" or library.mass_critic_rating_update == "tmdb": if tvdb_id and not tmdb_id: tmdb_id = config.Convert.tvdb_to_tmdb(tvdb_id) if tmdb_id: @@ -388,6 +427,9 @@ def library_operations(config, library, items=None): if not tmdb_item and not omdb_item and not tvdb_item: continue + if library.tmdb_collections and tmdb_item and tmdb_item.belongs_to_collection: + tmdb_collections[tmdb_item.belongs_to_collection.id] = tmdb_item.belongs_to_collection.name + if library.mass_genre_update: try: if tmdb_item and library.mass_genre_update == "tmdb": @@ -446,6 +488,22 @@ def library_operations(config, library, items=None): except Failed as e: logger.error(e) + if tmdb_collections: + logger.info("") + util.separator(f"Starting TMDb Collections") + logger.info("") + metadata = Metadata(config, library, "Data", { + "collections": { + _n.replace(" Collection", "") if library.tmdb_collections["remove_collection"] else _n: + {"template": {"name": "TMDb Collection", "collection_id": _i}} + for _i, _n in tmdb_collections.items() if int(_i) not in library.tmdb_collections["exclude_ids"] + }, + "templates": { + "TMDb Collection": library.tmdb_collections["template"] + } + }) + run_collection(config, library, metadata, metadata.get_collections(None)) + if library.delete_collections_with_less is not None or library.delete_unmanaged_collections: logger.info("") suffix = "" From d3eb57902ba98c517c4a4793d1feab7265cac040 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 04:16:43 -0500 Subject: [PATCH 13/28] check paths when adding to radarr/sonarr --- modules/radarr.py | 48 +++++++++++++++++++++++++++++++++------------- modules/sonarr.py | 49 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/modules/radarr.py b/modules/radarr.py index 396b34ddc..38e8f0821 100644 --- a/modules/radarr.py +++ b/modules/radarr.py @@ -53,11 +53,20 @@ def add_tmdb(self, tmdb_ids, **options): tags = options["tag"] if "tag" in options else self.tag search = options["search"] if "search" in options else self.search + arr_paths = {} + arr_ids = {} + for movie in self.api.all_movies(): + if movie.path: + arr_paths[movie.path] = movie.tmdbId + arr_ids[movie.tmdbId] = movie + added = [] exists = [] skipped = [] invalid = [] movies = [] + path_lookup = {} + mismatched = {} for i, item in enumerate(tmdb_ids, 1): path = item[1] if isinstance(item, tuple) else None tmdb_id = item[0] if isinstance(item, tuple) else item @@ -68,8 +77,18 @@ def add_tmdb(self, tmdb_ids, **options): skipped.append(item) continue try: + if tmdb_id in arr_ids: + exists.append(arr_ids[tmdb_id]) + continue + if path in arr_paths: + mismatched[path] = tmdb_id + continue movie = self.api.get_movie(tmdb_id=tmdb_id) - movies.append((movie, path) if path else movie) + if path: + movies.append((movie, path)) + path_lookup[path] = tmdb_id + else: + movies.append(movie) except ArrException: invalid.append(item) if len(movies) == 100 or len(tmdb_ids) == i: @@ -91,26 +110,29 @@ def add_tmdb(self, tmdb_ids, **options): self.config.Cache.update_radarr_adds(movie.tmdbId, self.library.original_mapping_name) logger.info(f"{len(added)} Movie{'s' if len(added) > 1 else ''} added to Radarr") - if len(exists) > 0: + if len(exists) > 0 or len(skipped) > 0: logger.info("") - for movie in exists: - logger.info(f"Already in Radarr | {movie.tmdbId:<6} | {movie.title}") - if self.config.Cache: - self.config.Cache.update_radarr_adds(movie.tmdbId, self.library.original_mapping_name) - logger.info(f"{len(exists)} Movie{'s' if len(exists) > 1 else ''} already existing in Radarr") + if len(exists) > 0: + for movie in exists: + logger.info(f"Already in Radarr | {movie.tmdbId:<6} | {movie.title}") + if self.config.Cache: + self.config.Cache.update_radarr_adds(movie.tmdbId, self.library.original_mapping_name) + if len(skipped) > 0: + for movie in skipped: + logger.info(f"Skipped: In Cache | {movie}") + logger.info(f"{len(exists) + len(skipped)} Movie{'s' if len(skipped) > 1 else ''} already exist in Radarr") - if len(skipped) > 0: + if len(mismatched) > 0: logger.info("") - for movie in skipped: - logger.info(f"Skipped: In Cache | {movie}") - if self.config.Cache: - self.config.Cache.update_radarr_adds(movie[0] if isinstance(movie, tuple) else movie, self.library.original_mapping_name) - logger.info(f"{len(skipped)} Movie{'s' if len(skipped) > 1 else ''} already existing in Radarr") + for path, tmdb_id in mismatched.items(): + logger.info(f"Plex TMDb ID: {tmdb_id:<7} | Radarr TMDb ID: {arr_paths[path]:<7} | Path: {path}") + logger.info(f"{len(mismatched)} Movie{'s' if len(mismatched) > 1 else ''} with mismatched TMDb IDs") if len(invalid) > 0: logger.info("") for tmdb_id in invalid: logger.info(f"Invalid TMDb ID | {tmdb_id}") + logger.info(f"{len(invalid)} Movie{'s' if len(invalid) > 1 else ''} with Invalid IDs") return len(added) diff --git a/modules/sonarr.py b/modules/sonarr.py index 4303b103b..dc13eceed 100644 --- a/modules/sonarr.py +++ b/modules/sonarr.py @@ -79,11 +79,20 @@ def add_tvdb(self, tvdb_ids, **options): search = options["search"] if "search" in options else self.search cutoff_search = options["cutoff_search"] if "cutoff_search" in options else self.cutoff_search + arr_paths = {} + arr_ids = {} + for series in self.api.all_series(): + if series.path: + arr_paths[series.path] = series.tvdbId + arr_paths[series.tvdbId] = series + added = [] exists = [] skipped = [] invalid = [] shows = [] + path_lookup = {} + mismatched = {} for i, item in enumerate(tvdb_ids, 1): path = item[1] if isinstance(item, tuple) else None tvdb_id = item[0] if isinstance(item, tuple) else item @@ -94,8 +103,18 @@ def add_tvdb(self, tvdb_ids, **options): skipped.append(item) continue try: + if tvdb_id in arr_ids: + exists.append(arr_ids[tvdb_id]) + continue + if path in arr_paths: + mismatched[path] = tvdb_id + continue show = self.api.get_series(tvdb_id=tvdb_id) - shows.append((show, path) if path else show) + if path: + shows.append((show, path)) + path_lookup[path] = tvdb_id + else: + shows.append(show) except ArrException: invalid.append(item) if len(shows) == 100 or len(tvdb_ids) == i: @@ -117,26 +136,30 @@ def add_tvdb(self, tvdb_ids, **options): self.config.Cache.update_sonarr_adds(series.tvdbId, self.library.original_mapping_name) logger.info(f"{len(added)} Series added to Sonarr") - if len(exists) > 0: + if len(exists) > 0 or len(skipped) > 0: logger.info("") - for series in exists: - logger.info(f"Already in Sonarr | {series.tvdbId:<6} | {series.title}") - if self.config.Cache: - self.config.Cache.update_sonarr_adds(series.tvdbId, self.library.original_mapping_name) - logger.info(f"{len(exists)} Series already existing in Sonarr") + if len(exists) > 0: + for series in exists: + logger.info(f"Already in Sonarr | {series.tvdbId:<6} | {series.title}") + if self.config.Cache: + self.config.Cache.update_sonarr_adds(series.tvdbId, self.library.original_mapping_name) + if len(skipped) > 0: + logger.info("") + for series in skipped: + logger.info(f"Skipped: In Cache | {series}") + logger.info(f"{len(exists) + len(skipped)} Series already exist in Sonarr") - if len(skipped) > 0: + if len(mismatched) > 0: logger.info("") - for series in skipped: - logger.info(f"Skipped: In Cache | {series}") - if self.config.Cache: - self.config.Cache.update_sonarr_adds(series[0] if isinstance(series, tuple) else series, self.library.original_mapping_name) - logger.info(f"{len(skipped)} Movie{'s' if len(skipped) > 1 else ''} already existing in Sonarr") + for path, tmdb_id in mismatched.items(): + logger.info(f"Plex TVDb ID: {tmdb_id:<7} | Sonarr TVDb ID: {arr_paths[path]:<7} | Path: {path}") + logger.info(f"{len(mismatched)} Series with mismatched TVDb IDs") if len(invalid) > 0: for tvdb_id in invalid: logger.info("") logger.info(f"Invalid TVDb ID | {tvdb_id}") + logger.info(f"{len(invalid)} Series with Invalid IDs") return len(added) From 23c5af19e2239413367ef9503cede5c08436ec30 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 17:30:38 -0500 Subject: [PATCH 14/28] adds range schedule and delete_not_scheduled --- modules/builder.py | 34 ++++++++++++++++++++++++++++++++-- modules/config.py | 2 ++ modules/library.py | 1 + modules/util.py | 5 ++++- plex_meta_manager.py | 34 ++++++++++++++++++++++++++-------- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 1f7f4e974..7159dcf8e 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -182,6 +182,7 @@ def __init__(self, config, library, metadata, name, no_missing, data): "only_filter_missing": self.library.only_filter_missing, "create_asset_folders": self.library.create_asset_folders, "delete_below_minimum": self.library.delete_below_minimum, + "delete_not_scheduled": self.library.delete_not_scheduled, "collection_creation_webhooks": self.library.collection_creation_webhooks, "collection_addition_webhooks": self.library.collection_addition_webhooks, "collection_removal_webhooks": self.library.collection_removal_webhooks, @@ -337,6 +338,12 @@ def scan_text(og_txt, var, var_value): except Failed: continue + if "delete_not_scheduled" in methods: + logger.debug("") + logger.debug("Validating Method: delete_not_scheduled") + logger.debug(f"Value: {data[methods['delete_not_scheduled']]}") + self.details["delete_not_scheduled"] = util.parse("delete_not_scheduled", self.data, datatype="bool", methods=methods, default=False) + if "schedule" in methods: logger.debug("") logger.debug("Validating Method: schedule") @@ -352,7 +359,7 @@ def scan_text(og_txt, var, var_value): run_time = str(schedule).lower() if run_time.startswith(("day", "daily")): skip_collection = False - elif run_time.startswith(("hour", "week", "month", "year")): + elif run_time.startswith(("hour", "week", "month", "year", "range")): match = re.search("\\(([^)]+)\\)", run_time) if not match: logger.error(f"Collection Error: failed to parse schedule: {schedule}") @@ -396,12 +403,35 @@ def scan_text(og_txt, var, var_value): self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" if self.current_time.month == month and (self.current_time.day == day or (self.current_time.day == last_day.day and day > last_day.day)): skip_collection = False + elif run_time.startswith("range"): + match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])-(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) + if not match: + logger.error(f"Collection Error: range schedule attribute {schedule} invalid must be in the MM/DD-MM/DD format i.e. range(12/01-12/25)") + continue + month_start = int(match.group(1)) + day_start = int(match.group(2)) + month_end = int(match.group(3)) + day_end = int(match.group(4)) + check = datetime.strptime(f"{self.current_time.month}/{self.current_time.day}", "%m/%d") + start = datetime.strptime(f"{month_start}/{day_start}", "%m/%d") + end = datetime.strptime(f"{month_end}/{day_end}", "%m/%d") + self.schedule += f"\nScheduled between {util.pretty_months[month_start]} {util.make_ordinal(day_start)} and {util.pretty_months[month_end]} {util.make_ordinal(day_end)}" + if start <= check <= end if start < end else check <= end or check >= start: + skip_collection = False else: logger.error(f"Collection Error: schedule attribute {schedule} invalid") if len(self.schedule) == 0: skip_collection = False if skip_collection: - raise NotScheduled(f"{self.schedule}\n\nCollection {self.name} not scheduled to run") + suffix = "" + if self.details["delete_not_scheduled"]: + try: + self.obj = self.library.get_collection(self.name) + self.delete_collection() + suffix = f" and was deleted" + except Failed: + suffix = f" and could not be found to delete" + raise NotScheduled(f"{self.schedule}\n\nCollection {self.name} not scheduled to run{suffix}") self.collectionless = "plex_collectionless" in methods diff --git a/modules/config.py b/modules/config.py index 19835e92b..11dbe5cad 100644 --- a/modules/config.py +++ b/modules/config.py @@ -208,6 +208,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), + "delete_not_scheduled": check_for_attribute(self.data, "delete_not_scheduled", parent="settings", var_type="bool", default=False), "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default") } self.webhooks = { @@ -424,6 +425,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["create_asset_folders"] = check_for_attribute(lib, "create_asset_folders", parent="settings", var_type="bool", default=self.general["create_asset_folders"], do_print=False, save=False) params["collection_minimum"] = check_for_attribute(lib, "collection_minimum", parent="settings", var_type="int", default=self.general["collection_minimum"], do_print=False, save=False) params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False) + params["delete_not_scheduled"] = check_for_attribute(lib, "delete_not_scheduled", parent="settings", var_type="bool", default=self.general["delete_not_scheduled"], do_print=False, save=False) params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", parent="settings", var_type="bool", default=False, do_print=False, save=False) params["delete_collections_with_less"] = check_for_attribute(lib, "delete_collections_with_less", parent="settings", var_type="int", default_is_none=True, do_print=False, save=False) params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True) diff --git a/modules/library.py b/modules/library.py index 6da3f6ff1..1e75984a6 100644 --- a/modules/library.py +++ b/modules/library.py @@ -44,6 +44,7 @@ def __init__(self, config, params): self.sync_mode = params["sync_mode"] self.collection_minimum = params["collection_minimum"] self.delete_below_minimum = params["delete_below_minimum"] + self.delete_not_scheduled = params["delete_not_scheduled"] self.missing_only_released = params["missing_only_released"] self.show_unmanaged = params["show_unmanaged"] self.show_filtered = params["show_filtered"] diff --git a/modules/util.py b/modules/util.py index fe2ee0c8b..301b3756d 100644 --- a/modules/util.py +++ b/modules/util.py @@ -200,7 +200,10 @@ def separator(text=None, space=True, border=True, debug=False): if text: text_list = text.split("\n") for t in text_list: - logger.info(f"|{sep}{centered(t, sep=sep)}{sep}|") + if debug: + logger.debug(f"|{sep}{centered(t, sep=sep)}{sep}|") + else: + logger.info(f"|{sep}{centered(t, sep=sep)}{sep}|") if border and debug: logger.debug(border_text) elif border: diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 01825f3bd..4bc5e0055 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -51,21 +51,21 @@ def get_arg(env_str, default, arg_bool=False, arg_int=False): else: return default -test = get_arg("PMM_TEST", args.test, arg_bool=True) -debug = get_arg("PMM_DEBUG", args.debug, arg_bool=True) -trace = get_arg("PMM_TRACE", args.trace, arg_bool=True) +config_file = get_arg("PMM_CONFIG", args.config) +times = get_arg("PMM_TIME", args.times) run = get_arg("PMM_RUN", args.run, arg_bool=True) -no_countdown = get_arg("PMM_NO_COUNTDOWN", args.no_countdown, arg_bool=True) -no_missing = get_arg("PMM_NO_MISSING", args.no_missing, arg_bool=True) -library_only = get_arg("PMM_LIBRARIES_ONLY", args.library_only, arg_bool=True) +test = get_arg("PMM_TEST", args.test, arg_bool=True) collection_only = get_arg("PMM_COLLECTIONS_ONLY", args.collection_only, arg_bool=True) +library_only = get_arg("PMM_LIBRARIES_ONLY", args.library_only, arg_bool=True) collections = get_arg("PMM_COLLECTIONS", args.collections) libraries = get_arg("PMM_LIBRARIES", args.libraries) resume = get_arg("PMM_RESUME", args.resume) -times = get_arg("PMM_TIME", args.times) +no_countdown = get_arg("PMM_NO_COUNTDOWN", args.no_countdown, arg_bool=True) +no_missing = get_arg("PMM_NO_MISSING", args.no_missing, arg_bool=True) divider = get_arg("PMM_DIVIDER", args.divider) screen_width = get_arg("PMM_WIDTH", args.width, arg_int=True) -config_file = get_arg("PMM_CONFIG", args.config) +debug = get_arg("PMM_DEBUG", args.debug, arg_bool=True) +trace = get_arg("PMM_TRACE", args.trace, arg_bool=True) stats = {} util.separating_character = divider[0] @@ -137,6 +137,23 @@ def start(attrs): if "time" not in attrs: attrs["time"] = start_time.strftime("%H:%M") attrs["time_obj"] = start_time + util.separator(debug=True) + logger.debug(f"--config (PMM_CONFIG): {config_file}") + logger.debug(f"--time (PMM_TIME): {times}") + logger.debug(f"--run (PMM_RUN): {run}") + logger.debug(f"--run-tests (PMM_TEST): {test}") + logger.debug(f"--collections-only (PMM_COLLECTIONS_ONLY): {collection_only}") + logger.debug(f"--libraries-only (PMM_LIBRARIES_ONLY): {library_only}") + logger.debug(f"--run-collections (PMM_COLLECTIONS): {collections}") + logger.debug(f"--run-libraries (PMM_LIBRARIES): {libraries}") + logger.debug(f"--resume (PMM_RESUME): {resume}") + logger.debug(f"--no-countdown (PMM_NO_COUNTDOWN): {no_countdown}") + logger.debug(f"--no-missing (PMM_NO_MISSING): {no_missing}") + logger.debug(f"--divider (PMM_DIVIDER): {divider}") + logger.debug(f"--width (PMM_WIDTH): {screen_width}") + logger.debug(f"--debug (PMM_DEBUG): {debug}") + logger.debug(f"--trace (PMM_TRACE): {trace}") + logger.debug("") util.separator(f"Starting {start_type}Run") config = None global stats @@ -193,6 +210,7 @@ def update_libraries(config): logger.debug(f"Sync Mode: {library.sync_mode}") logger.debug(f"Collection Minimum: {library.collection_minimum}") logger.debug(f"Delete Below Minimum: {library.delete_below_minimum}") + logger.debug(f"Delete Not Scheduled: {library.delete_not_scheduled}") logger.debug(f"Missing Only Released: {library.missing_only_released}") logger.debug(f"Only Filter Missing: {library.only_filter_missing}") logger.debug(f"Show Unmanaged: {library.show_unmanaged}") From aebe78799f6d5c5fce1485013c5a24ab4881a726 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 21:10:36 -0500 Subject: [PATCH 15/28] check arr paths for new IDs --- modules/radarr.py | 13 +++++++++++++ modules/sonarr.py | 14 +++++++++++++- requirements.txt | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/radarr.py b/modules/radarr.py index 38e8f0821..2269ad9c5 100644 --- a/modules/radarr.py +++ b/modules/radarr.py @@ -19,6 +19,7 @@ def __init__(self, config, library, params): try: self.api = RadarrAPI(self.url, self.token, session=self.config.session) self.api.respect_list_exclusions_when_adding() + self.api._validate_add_options(params["root_folder_path"], params["quality_profile"]) except ArrException as e: raise Failed(e) self.add = params["add"] @@ -67,6 +68,7 @@ def add_tmdb(self, tmdb_ids, **options): movies = [] path_lookup = {} mismatched = {} + path_in_use = {} for i, item in enumerate(tmdb_ids, 1): path = item[1] if isinstance(item, tuple) else None tmdb_id = item[0] if isinstance(item, tuple) else item @@ -84,6 +86,9 @@ def add_tmdb(self, tmdb_ids, **options): mismatched[path] = tmdb_id continue movie = self.api.get_movie(tmdb_id=tmdb_id) + if f"{folder}/{movie.folder}" in arr_paths: + path_in_use[f"{folder}/{movie.folder}"] = tmdb_id + continue if path: movies.append((movie, path)) path_lookup[path] = tmdb_id @@ -124,10 +129,18 @@ def add_tmdb(self, tmdb_ids, **options): if len(mismatched) > 0: logger.info("") + logger.info("Items in Plex that have already been added to Radarr but under a different TMDb ID then in Plex") for path, tmdb_id in mismatched.items(): logger.info(f"Plex TMDb ID: {tmdb_id:<7} | Radarr TMDb ID: {arr_paths[path]:<7} | Path: {path}") logger.info(f"{len(mismatched)} Movie{'s' if len(mismatched) > 1 else ''} with mismatched TMDb IDs") + if len(path_in_use) > 0: + logger.info("") + logger.info("TMDb IDs that cannot be added to Radarr because the path they will use is already in use by a different TMDb ID") + for path, tmdb_id in path_in_use.items(): + logger.info(f"TMDb ID: {tmdb_id:<7} | Radarr TMDb ID: {arr_paths[path]:<7} | Path: {path}") + logger.info(f"{len(path_in_use)} Movie{'s' if len(path_in_use) > 1 else ''} with paths already in use by other TMDb IDs") + if len(invalid) > 0: logger.info("") for tmdb_id in invalid: diff --git a/modules/sonarr.py b/modules/sonarr.py index dc13eceed..0eefe4ce6 100644 --- a/modules/sonarr.py +++ b/modules/sonarr.py @@ -37,6 +37,7 @@ def __init__(self, config, library, params): try: self.api = SonarrAPI(self.url, self.token, session=self.config.session) self.api.respect_list_exclusions_when_adding() + self.api._validate_add_options(params["root_folder_path"], params["quality_profile"], params["language_profile"]) except ArrException as e: raise Failed(e) self.add = params["add"] @@ -93,6 +94,7 @@ def add_tvdb(self, tvdb_ids, **options): shows = [] path_lookup = {} mismatched = {} + path_in_use = {} for i, item in enumerate(tvdb_ids, 1): path = item[1] if isinstance(item, tuple) else None tvdb_id = item[0] if isinstance(item, tuple) else item @@ -110,6 +112,9 @@ def add_tvdb(self, tvdb_ids, **options): mismatched[path] = tvdb_id continue show = self.api.get_series(tvdb_id=tvdb_id) + if f"{folder}/{show.folder}" in arr_paths: + path_in_use[f"{folder}/{show.folder}"] = tvdb_id + continue if path: shows.append((show, path)) path_lookup[path] = tvdb_id @@ -144,17 +149,24 @@ def add_tvdb(self, tvdb_ids, **options): if self.config.Cache: self.config.Cache.update_sonarr_adds(series.tvdbId, self.library.original_mapping_name) if len(skipped) > 0: - logger.info("") for series in skipped: logger.info(f"Skipped: In Cache | {series}") logger.info(f"{len(exists) + len(skipped)} Series already exist in Sonarr") if len(mismatched) > 0: logger.info("") + logger.info("Items in Plex that have already been added to Sonarr but under a different TVDb ID then in Plex") for path, tmdb_id in mismatched.items(): logger.info(f"Plex TVDb ID: {tmdb_id:<7} | Sonarr TVDb ID: {arr_paths[path]:<7} | Path: {path}") logger.info(f"{len(mismatched)} Series with mismatched TVDb IDs") + if len(path_in_use) > 0: + logger.info("") + logger.info("TVDb IDs that cannot be added to Sonarr because the path they will use is already in use by a different TVDb ID") + for path, tvdb_id in path_in_use.items(): + logger.info(f"TVDb ID: {tvdb_id:<7} | Sonarr TVDb ID: {arr_paths[path]:<7} | Path: {path}") + logger.info(f"{len(path_in_use)} Series with paths already in use by other TVDb IDs") + if len(invalid) > 0: for tvdb_id in invalid: logger.info("") diff --git a/requirements.txt b/requirements.txt index 61a6d685d..2cb08b6f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ PlexAPI==4.8.0 tmdbv3api==1.7.6 -arrapi==1.2.7 +arrapi==1.2.8 lxml==4.6.4 requests==2.26.0 ruamel.yaml==0.17.17 From f8e51c361c1054e081bf6f5376dfd36b6eee1f9f Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 01:29:41 -0500 Subject: [PATCH 16/28] consolidate collection webhooks --- modules/builder.py | 24 ++++++++---------------- modules/config.py | 29 ++++++++++++++++++++++------- modules/library.py | 4 +--- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 7159dcf8e..be3a8ae0d 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -87,8 +87,7 @@ "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" ] -notification_details = ["collection_creation_webhooks", "collection_addition_webhooks", "collection_removal_webhooks"] -details = ["collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details + notification_details +details = ["collection_changes_webhooks", "collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ poster_details + background_details + summary_details + string_details item_bool_details = ["item_assets", "revert_overlay", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh"] @@ -183,9 +182,7 @@ def __init__(self, config, library, metadata, name, no_missing, data): "create_asset_folders": self.library.create_asset_folders, "delete_below_minimum": self.library.delete_below_minimum, "delete_not_scheduled": self.library.delete_not_scheduled, - "collection_creation_webhooks": self.library.collection_creation_webhooks, - "collection_addition_webhooks": self.library.collection_addition_webhooks, - "collection_removal_webhooks": self.library.collection_removal_webhooks, + "collection_changes_webhooks": self.library.collection_changes_webhooks } self.item_details = {} self.radarr_details = {} @@ -748,7 +745,7 @@ def _details(self, method_name, method_data, method_final, methods): self.details["label.sync"] = util.get_list(method_data) if method_data else [] else: self.details[method_final] = util.get_list(method_data) if method_data else [] - elif method_name in notification_details: + elif method_name == "collection_changes_webhooks": self.details[method_name] = util.parse(method_name, method_data, datatype="list") elif method_name in boolean_details: default = self.details[method_name] if method_name in self.details else None @@ -1596,7 +1593,7 @@ def add_to_collection(self): else: self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection) amount_added += 1 - if self.details["collection_addition_webhooks"]: + if self.details["collection_changes_webhooks"]: if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map: add_id = self.library.movie_rating_key_map[current.ratingKey] elif self.library.is_show and current.ratingKey in self.library.show_rating_key_map: @@ -1620,7 +1617,7 @@ def sync_collection(self): self.library.reload(item) logger.info(f"{self.name} Collection | - | {self.item_title(item)}") self.library.alter_collection(item, self.name, smart_label_collection=self.smart_label_collection, add=False) - if self.details["collection_removal_webhooks"]: + if self.details["collection_changes_webhooks"]: if self.library.is_movie and item.ratingKey in self.library.movie_rating_key_map: remove_id = self.library.movie_rating_key_map[item.ratingKey] elif self.library.is_show and item.ratingKey in self.library.show_rating_key_map: @@ -2147,17 +2144,12 @@ def sort_collection(self): previous = key def send_notifications(self): - if self.obj and ( - (self.details["collection_creation_webhooks"] and self.created) or - (self.details["collection_addition_webhooks"] and len(self.notification_additions) > 0) or - (self.details["collection_removal_webhooks"] and len(self.notification_removals) > 0) - ): + if self.obj and self.details["collection_changes_webhooks"] and \ + (self.created or len(self.notification_additions) > 0 or len(self.notification_removals) > 0): self.obj.reload() try: self.library.Webhooks.collection_hooks( - self.details["collection_creation_webhooks"] + - self.details["collection_addition_webhooks"] + - self.details["collection_removal_webhooks"], + self.details["collection_changes_webhooks"], self.obj, created=self.created, additions=self.notification_additions, diff --git a/modules/config.py b/modules/config.py index 11dbe5cad..b4f1dd741 100644 --- a/modules/config.py +++ b/modules/config.py @@ -84,9 +84,28 @@ def replace_attr(all_data, attr, par): replace_attr(new_config["libraries"][library], "show_filtered", "plex") replace_attr(new_config["libraries"][library], "show_missing", "plex") replace_attr(new_config["libraries"][library], "save_missing", "plex") + if new_config["libraries"][library] and "webhooks" in new_config["libraries"][library] and "collection_changes" not in new_config["libraries"][library]["webhooks"]: + changes = [] + def hooks(attr): + if attr in new_config["libraries"][library]["webhooks"]: + changes.extend([w for w in util.get_list(new_config["libraries"][library]["webhooks"].pop(attr), split=False) if w not in changes]) + hooks("collection_creation") + hooks("collection_addition") + hooks("collection_removal") + new_config["libraries"][library]["webhooks"]["collection_changes"] = changes if changes else None if "libraries" in new_config: new_config["libraries"] = new_config.pop("libraries") if "settings" in new_config: new_config["settings"] = new_config.pop("settings") - if "webhooks" in new_config: new_config["webhooks"] = new_config.pop("webhooks") + if "webhooks" in new_config: + temp = new_config.pop("webhooks") + changes = [] + def hooks(attr): + if attr in temp: + changes.extend([w for w in util.get_list(temp.pop(attr), split=False) if w not in changes]) + hooks("collection_creation") + hooks("collection_addition") + hooks("collection_removal") + temp["collection_changes"] = changes if changes else None + new_config["webhooks"] = temp if "plex" in new_config: new_config["plex"] = new_config.pop("plex") if "tmdb" in new_config: new_config["tmdb"] = new_config.pop("tmdb") if "tautulli" in new_config: new_config["tautulli"] = new_config.pop("tautulli") @@ -215,9 +234,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "error": check_for_attribute(self.data, "error", parent="webhooks", var_type="list", default_is_none=True), "run_start": check_for_attribute(self.data, "run_start", parent="webhooks", var_type="list", default_is_none=True), "run_end": check_for_attribute(self.data, "run_end", parent="webhooks", var_type="list", default_is_none=True), - "collection_creation": check_for_attribute(self.data, "collection_creation", parent="webhooks", var_type="list", default_is_none=True), - "collection_addition": check_for_attribute(self.data, "collection_addition", parent="webhooks", var_type="list", default_is_none=True), - "collection_removal": check_for_attribute(self.data, "collection_removal", parent="webhooks", var_type="list", default_is_none=True), + "collection_changes": check_for_attribute(self.data, "collection_changes", parent="webhooks", var_type="list", default_is_none=True) } if self.general["cache"]: util.separator() @@ -429,9 +446,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", parent="settings", var_type="bool", default=False, do_print=False, save=False) params["delete_collections_with_less"] = check_for_attribute(lib, "delete_collections_with_less", parent="settings", var_type="int", default_is_none=True, do_print=False, save=False) params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True) - params["collection_creation_webhooks"] = check_for_attribute(lib, "collection_creation", parent="webhooks", var_type="list", default=self.webhooks["collection_creation"], do_print=False, save=False, default_is_none=True) - params["collection_addition_webhooks"] = check_for_attribute(lib, "collection_addition", parent="webhooks", var_type="list", default=self.webhooks["collection_addition"], do_print=False, save=False, default_is_none=True) - params["collection_removal_webhooks"] = check_for_attribute(lib, "collection_removal", parent="webhooks", var_type="list", default=self.webhooks["collection_removal"], do_print=False, save=False, default_is_none=True) + params["collection_changes_webhooks"] = check_for_attribute(lib, "collection_creation", parent="webhooks", var_type="list", default=self.webhooks["collection_changes"], do_print=False, save=False, default_is_none=True) params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", parent="settings", var_type="bool", default=self.general["assets_for_all"], do_print=False, save=False) params["mass_genre_update"] = check_for_attribute(lib, "mass_genre_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=False) params["mass_audience_rating_update"] = check_for_attribute(lib, "mass_audience_rating_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=False) diff --git a/modules/library.py b/modules/library.py index 1e75984a6..ed44ef220 100644 --- a/modules/library.py +++ b/modules/library.py @@ -63,9 +63,7 @@ def __init__(self, config, params): self.radarr_add_all = params["radarr_add_all"] self.sonarr_add_all = params["sonarr_add_all"] self.error_webhooks = params["error_webhooks"] - self.collection_creation_webhooks = params["collection_creation_webhooks"] - self.collection_addition_webhooks = params["collection_addition_webhooks"] - self.collection_removal_webhooks = params["collection_removal_webhooks"] + self.collection_changes_webhooks = params["collection_changes_webhooks"] self.split_duplicates = params["split_duplicates"] # TODO: Here or just in Plex? self.clean_bundles = params["plex"]["clean_bundles"] # TODO: Here or just in Plex? self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex? From d38f9df168558fe8478696174107b6fc0ae026fd Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 02:10:07 -0500 Subject: [PATCH 17/28] #232 genre_mapper library operation --- modules/config.py | 9 +++++++++ modules/library.py | 3 ++- plex_meta_manager.py | 16 +++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/config.py b/modules/config.py index b4f1dd741..f7188c7a3 100644 --- a/modules/config.py +++ b/modules/config.py @@ -456,6 +456,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["radarr_add_all"] = check_for_attribute(lib, "radarr_add_all", var_type="bool", default=False, save=False, do_print=False) params["sonarr_add_all"] = check_for_attribute(lib, "sonarr_add_all", var_type="bool", default=False, save=False, do_print=False) params["tmdb_collections"] = None + params["genre_mapper"] = None if lib and "operations" in lib and lib["operations"]: if isinstance(lib["operations"], dict): @@ -490,6 +491,14 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No logger.warning("Config Warning: Using default template for tmdb_collections") else: logger.error("Config Error: tmdb_collections blank using default settings") + if "genre_mapper" in lib["operations"]: + if lib["operations"]["genre_mapper"] and isinstance(lib["operations"]["genre_mapper"], dict): + params["genre_mapper"] = {} + for new_genre, old_genres in lib["operations"]["genre_mapper"].items(): + for old_genre in util.get_list(old_genres, split=False): + params["genre_mapper"][old_genre] = new_genre + else: + logger.error("Config Error: genre_mapper is blank") else: logger.error("Config Error: operations must be a dictionary") diff --git a/modules/library.py b/modules/library.py index ed44ef220..e5f5b537b 100644 --- a/modules/library.py +++ b/modules/library.py @@ -59,9 +59,10 @@ def __init__(self, config, params): self.mass_audience_rating_update = params["mass_audience_rating_update"] self.mass_critic_rating_update = params["mass_critic_rating_update"] self.mass_trakt_rating_update = params["mass_trakt_rating_update"] - self.tmdb_collections = params["tmdb_collections"] self.radarr_add_all = params["radarr_add_all"] self.sonarr_add_all = params["sonarr_add_all"] + self.tmdb_collections = params["tmdb_collections"] + self.genre_mapper = params["genre_mapper"] self.error_webhooks = params["error_webhooks"] self.collection_changes_webhooks = params["collection_changes_webhooks"] self.split_duplicates = params["split_duplicates"] # TODO: Here or just in Plex? diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 4bc5e0055..a1a155db9 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -230,6 +230,7 @@ def update_libraries(config): logger.debug(f"Radarr Add All: {library.radarr_add_all}") logger.debug(f"Sonarr Add All: {library.sonarr_add_all}") logger.debug(f"TMDb Collections: {library.tmdb_collections}") + logger.debug(f"Genre Mapper: {library.genre_mapper}") logger.debug(f"Clean Bundles: {library.clean_bundles}") logger.debug(f"Empty Trash: {library.empty_trash}") logger.debug(f"Optimize: {library.optimize}") @@ -442,9 +443,6 @@ def library_operations(config, library, items=None): else: logger.info(util.adjust_space(f"{item.title[:25]:<25} | No TVDb ID for Guid: {item.guid}")) - if not tmdb_item and not omdb_item and not tvdb_item: - continue - if library.tmdb_collections and tmdb_item and tmdb_item.belongs_to_collection: tmdb_collections[tmdb_item.belongs_to_collection.id] = tmdb_item.belongs_to_collection.name @@ -493,6 +491,18 @@ def library_operations(config, library, items=None): logger.info(util.adjust_space(f"{item.title[:25]:<25} | Critic Rating | {new_rating}")) except Failed: pass + if library.genre_mapper: + try: + adds = [] + deletes = [] + library.reload(item) + for genre in item.genres: + if genre.tag in library.genre_mapper: + deletes.append(genre.tag) + adds.append(library.genre_mapper[genre.tag]) + library.edit_tags("genre", item, add_tags=adds, remove_tags=deletes) + except Failed: + pass if library.Radarr and library.radarr_add_all: try: From dc32dc708fd22ddf76b867e58807f6e05e92a0c9 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 02:52:09 -0500 Subject: [PATCH 18/28] #358 added move_collection_prefix to templates --- modules/builder.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index be3a8ae0d..2eac53ab9 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -269,17 +269,21 @@ def __init__(self, config, library, metadata, name, no_missing, data): optional = [] if "optional" in template: if template["optional"]: - if isinstance(template["optional"], list): - for op in template["optional"]: - if op not in default: - optional.append(op) - else: - logger.warning(f"Template Warning: variable {op} cannot be optional if it has a default") - else: - optional.append(str(template["optional"])) + for op in util.get_list(template["optional"]): + if op not in default: + optional.append(str(op)) + else: + logger.warning(f"Template Warning: variable {op} cannot be optional if it has a default") else: raise Failed("Collection Error: template sub-attribute optional is blank") + if "move_collection_prefix" in template: + if template["move_collection_prefix"]: + for op in util.get_list(template["move_collection_prefix"]): + variables["collection_name"] = variables["collection_name"].replace(f"{str(op).strip()} ", "") + f", {str(op).strip()}" + else: + raise Failed("Collection Error: template sub-attribute move_collection_prefix is blank") + def check_data(_data): if isinstance(_data, dict): final_data = {} @@ -325,7 +329,7 @@ def scan_text(og_txt, var, var_value): return final_data for method_name, attr_data in template.items(): - if method_name not in self.data and method_name not in ["default", "optional"]: + if method_name not in self.data and method_name not in ["default", "optional", "move_collection_prefix"]: if attr_data is None: logger.error(f"Template Error: template attribute {method_name} is blank") continue From 8602b7971aa813dfa60504eb403c670a5d3afba4 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 11:23:20 -0500 Subject: [PATCH 19/28] version --- VERSION | 2 +- config/config.yml.template | 5 ++--- modules/builder.py | 21 ++++++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index da38e07b3..8046d61b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.13.1 \ No newline at end of file +1.13.1-develop127 \ No newline at end of file diff --git a/config/config.yml.template b/config/config.yml.template index 23559fa19..b3230d05d 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -30,14 +30,13 @@ settings: # Can be individually specified only_filter_missing: false collection_minimum: 1 delete_below_minimum: true + delete_not_scheduled: false tvdb_language: eng webhooks: # Can be individually specified per library as well error: run_start: run_end: - collection_creation: - collection_addition: - collection_removal: + collection_changes: plex: # Can be individually specified per library as well; REQUIRED for the script to run url: http://192.168.1.12:32400 token: #################### diff --git a/modules/builder.py b/modules/builder.py index 2eac53ab9..10ed940e6 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -84,7 +84,7 @@ ] string_details = ["sort_title", "content_rating", "name_mapping"] ignored_details = [ - "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", + "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" ] details = ["collection_changes_webhooks", "collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details @@ -395,15 +395,18 @@ def scan_text(og_txt, var, var_value): except ValueError: logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer between 1 and 31") elif run_time.startswith("year"): - match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) - if not match: + try: + if "/" in param: + opt = param.split("/") + month = int(opt[0]) + day = int(opt[1]) + self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" + if self.current_time.month == month and (self.current_time.day == day or (self.current_time.day == last_day.day and day > last_day.day)): + skip_collection = False + else: + raise ValueError + except ValueError: logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)") - continue - month = int(match.group(1)) - day = int(match.group(2)) - self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" - if self.current_time.month == month and (self.current_time.day == day or (self.current_time.day == last_day.day and day > last_day.day)): - skip_collection = False elif run_time.startswith("range"): match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])-(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) if not match: From 35b9896650aaf53967db701d70bde812749f8e10 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 13:48:31 -0500 Subject: [PATCH 20/28] Library Operations Printout --- plex_meta_manager.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index a1a155db9..21cf262f2 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -226,7 +226,6 @@ def update_libraries(config): logger.debug(f"Mass Critic Rating Update: {library.mass_critic_rating_update}") logger.debug(f"Mass Trakt Rating Update: {library.mass_trakt_rating_update}") logger.debug(f"Split Duplicates: {library.split_duplicates}") - logger.debug(f"Split Duplicates: {library.split_duplicates}") logger.debug(f"Radarr Add All: {library.radarr_add_all}") logger.debug(f"Sonarr Add All: {library.sonarr_add_all}") logger.debug(f"TMDb Collections: {library.tmdb_collections}") @@ -340,6 +339,22 @@ def library_operations(config, library, items=None): logger.info("") util.separator(f"{library.name} Library Operations") logger.info("") + logger.debug(f"Assets For All: {library.assets_for_all}") + logger.debug(f"Delete Collections With Less: {library.delete_collections_with_less}") + logger.debug(f"Delete Unmanaged Collections: {library.delete_unmanaged_collections}") + logger.debug(f"Mass Genre Update: {library.mass_genre_update}") + logger.debug(f"Mass Audience Rating Update: {library.mass_audience_rating_update}") + logger.debug(f"Mass Critic Rating Update: {library.mass_critic_rating_update}") + logger.debug(f"Mass Trakt Rating Update: {library.mass_trakt_rating_update}") + logger.debug(f"Split Duplicates: {library.split_duplicates}") + logger.debug(f"Radarr Add All: {library.radarr_add_all}") + logger.debug(f"Sonarr Add All: {library.sonarr_add_all}") + logger.debug(f"TMDb Collections: {library.tmdb_collections}") + logger.debug(f"Genre Mapper: {library.genre_mapper}") + tmdb_operation = library.assets_for_all or library.mass_genre_update or library.mass_audience_rating_update \ + or library.mass_critic_rating_update or library.mass_trakt_rating_update \ + or library.tmdb_collections or library.radarr_add_all or library.sonarr_add_all + logger.debug(f"TMDb Operation: {tmdb_operation}") if library.split_duplicates: items = library.search(**{"duplicate": True}) @@ -347,9 +362,7 @@ def library_operations(config, library, items=None): item.split() logger.info(util.adjust_space(f"{item.title[:25]:<25} | Splitting")) - if library.assets_for_all or library.mass_genre_update or library.mass_audience_rating_update or \ - library.mass_critic_rating_update or library.mass_trakt_rating_update or library.tmdb_collections or \ - library.radarr_add_all or library.sonarr_add_all: + if tmdb_operation: if items is None: items = library.get_all() radarr_adds = [] From e81e07b0036315a30abc498501f3003f52ce1c63 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 14:21:07 -0500 Subject: [PATCH 21/28] update tmdb_collections --- modules/builder.py | 2 +- modules/config.py | 8 ++++++-- modules/library.py | 9 ++++++--- plex_meta_manager.py | 12 +++++------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 10ed940e6..9617e4d1d 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -37,7 +37,7 @@ "producers": "producer", "writers": "writer", "years": "year", "show_year": "year", "show_years": "year", - "show_title": "title", + "show_title": "title", "filter": "filters", "seasonyear": "year", "isadult": "adult", "startdate": "start", "enddate": "end", "averagescore": "score", "minimum_tag_percentage": "min_tag_percent", "minimumtagrank": "min_tag_percent", "minimum_tag_rank": "min_tag_percent", "anilist_tag": "anilist_search", "anilist_genre": "anilist_search", "anilist_season": "anilist_search", diff --git a/modules/config.py b/modules/config.py index f7188c7a3..3952d4ad7 100644 --- a/modules/config.py +++ b/modules/config.py @@ -481,16 +481,20 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No if "sonarr_add_all" in lib["operations"]: params["sonarr_add_all"] = check_for_attribute(lib["operations"], "sonarr_add_all", var_type="bool", default=False, save=False) if "tmdb_collections" in lib["operations"]: - params["tmdb_collections"] = {"exclude_ids": [], "remove_collection": False, "template": {"tmdb_collection_details": "<>"}} + params["tmdb_collections"] = {"exclude_ids": [], "remove_suffix": None, "template": {"tmdb_collection_details": "<>"}} if lib["operations"]["tmdb_collections"] and isinstance(lib["operations"]["tmdb_collections"], dict): params["tmdb_collections"]["exclude_ids"] = check_for_attribute(lib["operations"]["tmdb_collections"], "exclude_ids", var_type="int_list", default_is_none=True, save=False) - params["tmdb_collections"]["remove_collection"] = check_for_attribute(lib["operations"]["tmdb_collections"], "remove_collection", var_type="bool", default=False, save=False) + params["tmdb_collections"]["remove_suffix"] = check_for_attribute(lib["operations"]["tmdb_collections"], "remove_suffix", default_is_none=True, save=False) if "template" in lib["operations"]["tmdb_collections"] and lib["operations"]["tmdb_collections"]["template"] and isinstance(lib["operations"]["tmdb_collections"]["template"], dict): params["tmdb_collections"]["template"] = lib["operations"]["tmdb_collections"]["template"] else: logger.warning("Config Warning: Using default template for tmdb_collections") else: logger.error("Config Error: tmdb_collections blank using default settings") + if params["tmdb_collections"]["exclude_ids"] is None: + params["tmdb_collections"]["exclude_ids"] = [] + if params["tmdb_collections"]["remove_suffix"]: + params["tmdb_collections"]["remove_suffix"] = params["tmdb_collections"]["remove_suffix"].strip() if "genre_mapper" in lib["operations"]: if lib["operations"]["genre_mapper"] and isinstance(lib["operations"]["genre_mapper"], dict): params["genre_mapper"] = {} diff --git a/modules/library.py b/modules/library.py index e5f5b537b..fb55edcd7 100644 --- a/modules/library.py +++ b/modules/library.py @@ -69,7 +69,10 @@ def __init__(self, config, params): self.clean_bundles = params["plex"]["clean_bundles"] # TODO: Here or just in Plex? self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex? self.optimize = params["plex"]["optimize"] # TODO: Here or just in Plex? - + self.library_operation = self.assets_for_all or self.delete_unmanaged_collections or self.delete_collections_with_less \ + or self.mass_genre_update or self.mass_audience_rating_update or self.mass_critic_rating_update \ + or self.mass_trakt_rating_update or self.radarr_add_all or self.sonarr_add_all \ + or self.tmdb_collections or self.genre_mapper metadata = [] for file_type, metadata_file in self.metadata_path: if file_type == "Folder": @@ -94,9 +97,9 @@ def __init__(self, config, params): except Failed as e: util.print_multiline(e, error=True) - if len(self.metadata_files) == 0: + if len(self.metadata_files) == 0 and not self.library_operation: logger.info("") - raise Failed("Metadata File Error: No valid metadata files found") + raise Failed("Config Error: No valid metadata files or library operations found") if self.asset_directory: logger.info("") diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 21cf262f2..f79c0f3cc 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -235,12 +235,11 @@ def update_libraries(config): logger.debug(f"Optimize: {library.optimize}") logger.debug(f"Timeout: {library.timeout}") - items = None if not library.is_other: logger.info("") util.separator(f"Mapping {library.name} Library", space=False, border=False) logger.info("") - items = library.map_guids() + library.map_guids() for metadata in library.metadata_files: logger.info("") util.separator(f"Running Metadata File\n{metadata.path}") @@ -272,7 +271,7 @@ def update_libraries(config): builder.sort_collection() if not config.test_mode and not collection_only: - library_operations(config, library, items=items) + library_operations(config, library) logger.removeHandler(library_handler) except Exception as e: @@ -335,7 +334,7 @@ def update_libraries(config): if library.optimize: library.query(library.PlexServer.library.optimize) -def library_operations(config, library, items=None): +def library_operations(config, library): logger.info("") util.separator(f"{library.name} Library Operations") logger.info("") @@ -363,8 +362,7 @@ def library_operations(config, library, items=None): logger.info(util.adjust_space(f"{item.title[:25]:<25} | Splitting")) if tmdb_operation: - if items is None: - items = library.get_all() + items = library.get_all() radarr_adds = [] sonarr_adds = [] tmdb_collections = {} @@ -535,7 +533,7 @@ def library_operations(config, library, items=None): logger.info("") metadata = Metadata(config, library, "Data", { "collections": { - _n.replace(" Collection", "") if library.tmdb_collections["remove_collection"] else _n: + _n.replace(library.tmdb_collections["remove_suffix"], "").strip() if library.tmdb_collections["remove_suffix"] else _n: {"template": {"name": "TMDb Collection", "collection_id": _i}} for _i, _n in tmdb_collections.items() if int(_i) not in library.tmdb_collections["exclude_ids"] }, From e8341bf3d3fc0756c3f1b8c78a2a3a9ec70a6e33 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 22:31:20 -0500 Subject: [PATCH 22/28] #486 added server_preroll collection detail --- modules/builder.py | 5 ++++- modules/config.py | 16 ++++++++-------- modules/plex.py | 4 ++++ plex_meta_manager.py | 13 +++++++++---- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 9617e4d1d..67c4c6020 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -87,7 +87,7 @@ "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" ] -details = ["collection_changes_webhooks", "collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details +details = ["server_preroll", "collection_changes_webhooks", "collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ poster_details + background_details + summary_details + string_details item_bool_details = ["item_assets", "revert_overlay", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh"] @@ -205,6 +205,7 @@ def __init__(self, config, library, metadata, name, no_missing, data): self.summaries = {} self.schedule = "" self.minimum = self.library.collection_minimum + self.server_preroll = None self.current_time = datetime.now() self.current_year = self.current_time.year self.exists = False @@ -743,6 +744,8 @@ def _details(self, method_name, method_data, method_final, methods): raise Failed(f"Collection Error: {method_data} collection_mode invalid\n\tdefault (Library default)\n\thide (Hide Collection)\n\thide_items (Hide Items in this Collection)\n\tshow_items (Show this Collection and its Items)") elif method_name == "collection_minimum": self.minimum = util.parse(method_name, method_data, datatype="int", minimum=1) + elif method_name == "server_preroll": + self.server_preroll = util.parse(method_name, method_data) elif method_name == "label": if "label" in methods and "label.sync" in methods: raise Failed("Collection Error: Cannot use label and label.sync together") diff --git a/modules/config.py b/modules/config.py index 3952d4ad7..bb3c7be9c 100644 --- a/modules/config.py +++ b/modules/config.py @@ -214,21 +214,21 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "cache_expiration": check_for_attribute(self.data, "cache_expiration", parent="settings", var_type="int", default=60), "asset_directory": check_for_attribute(self.data, "asset_directory", parent="settings", var_type="list_path", default=[os.path.join(default_dir, "assets")], default_is_none=True), "asset_folders": check_for_attribute(self.data, "asset_folders", parent="settings", var_type="bool", default=True), - "assets_for_all": check_for_attribute(self.data, "assets_for_all", parent="settings", var_type="bool", default=False, save=False, do_print=False), + "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), "sync_mode": check_for_attribute(self.data, "sync_mode", parent="settings", default="append", test_list=sync_modes), + "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), + "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), + "delete_not_scheduled": check_for_attribute(self.data, "delete_not_scheduled", parent="settings", var_type="bool", default=False), "run_again_delay": check_for_attribute(self.data, "run_again_delay", parent="settings", var_type="int", default=0), + "missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False), + "only_filter_missing": check_for_attribute(self.data, "only_filter_missing", parent="settings", var_type="bool", default=False), "show_unmanaged": check_for_attribute(self.data, "show_unmanaged", parent="settings", var_type="bool", default=True), "show_filtered": check_for_attribute(self.data, "show_filtered", parent="settings", var_type="bool", default=False), "show_missing": check_for_attribute(self.data, "show_missing", parent="settings", var_type="bool", default=True), "show_missing_assets": check_for_attribute(self.data, "show_missing_assets", parent="settings", var_type="bool", default=True), "save_missing": check_for_attribute(self.data, "save_missing", parent="settings", var_type="bool", default=True), - "missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False), - "only_filter_missing": check_for_attribute(self.data, "only_filter_missing", parent="settings", var_type="bool", default=False), - "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), - "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), - "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), - "delete_not_scheduled": check_for_attribute(self.data, "delete_not_scheduled", parent="settings", var_type="bool", default=False), - "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default") + "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default"), + "assets_for_all": check_for_attribute(self.data, "assets_for_all", parent="settings", var_type="bool", default=False, save=False, do_print=False) } self.webhooks = { "error": check_for_attribute(self.data, "error", parent="webhooks", var_type="list", default_is_none=True), diff --git a/modules/plex.py b/modules/plex.py index 3137437a5..c71945518 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -264,6 +264,10 @@ def __init__(self, config, params): self.tmdb_collections = None logger.error("Config Error: tmdb_collections only work with Movie Libraries.") + def set_server_preroll(self, preroll): + self.PlexServer.settings.get('cinemaTrailersPrerollID').set(preroll) + self.PlexServer.settings.save() + def get_all_collections(self): return self.search(libtype="collection") diff --git a/plex_meta_manager.py b/plex_meta_manager.py index f79c0f3cc..7b06e9426 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -640,7 +640,7 @@ def run_collection(config, library, metadata, requested_collections): builder = CollectionBuilder(config, library, metadata, mapping_name, no_missing, collection_attrs) logger.info("") - util.separator(f"Building {mapping_name} Collection", space=False, border=False) + util.separator(f"Running {mapping_name} Collection", space=False, border=False) if len(builder.schedule) > 0: util.print_multiline(builder.schedule, info=True) @@ -651,7 +651,7 @@ def run_collection(config, library, metadata, requested_collections): items_added = 0 items_removed = 0 - if not builder.smart_url: + if not builder.smart_url and builder.builders: logger.info("") logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}") @@ -693,7 +693,7 @@ def run_collection(config, library, metadata, requested_collections): stats["sonarr"] += sonarr_add run_item_details = True - if builder.build_collection: + if builder.build_collection and builder.builders: try: builder.load_collection() if builder.created: @@ -711,9 +711,14 @@ def run_collection(config, library, metadata, requested_collections): library.run_sort.append(builder) # builder.sort_collection() + if builder.server_preroll is not None: + library.set_server_preroll(builder.server_preroll) + logger.info("") + logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}") + builder.send_notifications() - if builder.item_details and run_item_details: + if builder.item_details and run_item_details and builder.builders: try: builder.load_collection_items() except Failed: From 27edb7627c34bbb781ce68b644dd20727374a283 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 7 Dec 2021 23:00:17 -0500 Subject: [PATCH 23/28] #379 Added ignore_ids and ignore_imdb_ids --- modules/builder.py | 58 ++++++++++++++++++++++++++-------------------- modules/util.py | 7 ++++++ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 67c4c6020..b007769ff 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -87,7 +87,8 @@ "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" ] -details = ["server_preroll", "collection_changes_webhooks", "collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details +details = ["ignore_ids", "ignore_imdb_ids", "server_preroll", "collection_changes_webhooks", "collection_mode", "collection_order", + "collection_level", "collection_minimum", "label"] + boolean_details + string_details collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ poster_details + background_details + summary_details + string_details item_bool_details = ["item_assets", "revert_overlay", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh"] @@ -746,6 +747,10 @@ def _details(self, method_name, method_data, method_final, methods): self.minimum = util.parse(method_name, method_data, datatype="int", minimum=1) elif method_name == "server_preroll": self.server_preroll = util.parse(method_name, method_data) + elif method_name == "ignore_ids": + self.ignore_ids = util.parse(method_name, method_data, datatype="intlist") + elif method_name == "ignore_imdb_ids": + self.ignore_imdb_ids = util.parse(method_name, method_data, datatype="intlist") elif method_name == "label": if "label" in methods and "label.sync" in methods: raise Failed("Collection Error: Cannot use label and label.sync together") @@ -1236,10 +1241,11 @@ def find_rating_keys(self): if id_type == "ratingKey": rating_keys.append(input_id) elif id_type == "tmdb" and not self.parts_collection: - if input_id in self.library.movie_map: - rating_keys.extend(self.library.movie_map[input_id]) - elif input_id not in self.missing_movies: - self.missing_movies.append(input_id) + if input_id not in self.ignore_ids: + if input_id in self.library.movie_map: + rating_keys.extend(self.library.movie_map[input_id]) + elif input_id not in self.missing_movies: + self.missing_movies.append(input_id) elif id_type in ["tvdb", "tmdb_show"] and not self.parts_collection: if id_type == "tmdb_show": try: @@ -1247,27 +1253,29 @@ def find_rating_keys(self): except Failed as e: logger.error(e) continue - if input_id in self.library.show_map: - rating_keys.extend(self.library.show_map[input_id]) - elif input_id not in self.missing_shows: - self.missing_shows.append(input_id) + if input_id not in self.ignore_ids: + if input_id in self.library.show_map: + rating_keys.extend(self.library.show_map[input_id]) + elif input_id not in self.missing_shows: + self.missing_shows.append(input_id) elif id_type == "imdb" and not self.parts_collection: - if input_id in self.library.imdb_map: - rating_keys.extend(self.library.imdb_map[input_id]) - else: - if self.do_missing: - try: - tmdb_id, tmdb_type = self.config.Convert.imdb_to_tmdb(input_id, fail=True) - 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, fail=True) - if tvdb_id not in self.missing_shows: - self.missing_shows.append(tvdb_id) - except Failed as e: - logger.error(e) - continue + if input_id not in self.ignore_imdb_ids: + if input_id in self.library.imdb_map: + rating_keys.extend(self.library.imdb_map[input_id]) + else: + if self.do_missing: + try: + tmdb_id, tmdb_type = self.config.Convert.imdb_to_tmdb(input_id, fail=True) + 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, fail=True) + if tvdb_id not in self.missing_shows: + self.missing_shows.append(tvdb_id) + except Failed as e: + logger.error(e) + continue elif id_type == "tvdb_season" and self.collection_level == "season": show_id, season_num = input_id.split("_") show_id = int(show_id) diff --git a/modules/util.py b/modules/util.py index 301b3756d..2e01c5af1 100644 --- a/modules/util.py +++ b/modules/util.py @@ -333,6 +333,13 @@ def parse(attribute, data, datatype=None, methods=None, parent=None, default=Non if value: return [v for v in value if v] if isinstance(value, list) else [str(value)] return [] + elif datatype == "intlist": + if value: + try: + return [int(v) for v in value if v] if isinstance(value, list) else [int(value)] + except ValueError: + pass + return [] elif datatype == "dictlist": final_list = [] for dict_data in get_list(value): From dc223b25967a95af7e9309632a67f0755128b2c9 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 8 Dec 2021 00:17:35 -0500 Subject: [PATCH 24/28] #379 ignore_ids and ignore_imdb_ids are now settings attributes --- modules/builder.py | 6 ++++-- modules/config.py | 9 +++++++-- modules/library.py | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index b007769ff..c04275e82 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -206,6 +206,8 @@ def __init__(self, config, library, metadata, name, no_missing, data): self.summaries = {} self.schedule = "" self.minimum = self.library.collection_minimum + self.ignore_ids = [i for i in self.library.ignore_ids] + self.ignore_imdb_ids = [i for i in self.library.ignore_imdb_ids] self.server_preroll = None self.current_time = datetime.now() self.current_year = self.current_time.year @@ -748,9 +750,9 @@ def _details(self, method_name, method_data, method_final, methods): elif method_name == "server_preroll": self.server_preroll = util.parse(method_name, method_data) elif method_name == "ignore_ids": - self.ignore_ids = util.parse(method_name, method_data, datatype="intlist") + self.ignore_ids.extend(util.parse(method_name, method_data, datatype="intlist")) elif method_name == "ignore_imdb_ids": - self.ignore_imdb_ids = util.parse(method_name, method_data, datatype="intlist") + self.ignore_imdb_ids.extend(util.parse(method_name, method_data, datatype="list")) elif method_name == "label": if "label" in methods and "label.sync" in methods: raise Failed("Collection Error: Cannot use label and label.sync together") diff --git a/modules/config.py b/modules/config.py index bb3c7be9c..e577f4a16 100644 --- a/modules/config.py +++ b/modules/config.py @@ -143,6 +143,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No elif attribute not in loaded_config[parent]: loaded_config[parent][attribute] = default else: endline = "" yaml.round_trip_dump(loaded_config, open(self.config_path, "w"), indent=None, block_seq_indent=2) + if default_is_none and var_type in ["list", "int_list"]: return [] elif data[attribute] is None: if default_is_none and var_type in ["list", "int_list"]: return [] elif default_is_none: return None @@ -228,6 +229,8 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "show_missing_assets": check_for_attribute(self.data, "show_missing_assets", parent="settings", var_type="bool", default=True), "save_missing": check_for_attribute(self.data, "save_missing", parent="settings", var_type="bool", default=True), "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default"), + "ignore_ids": check_for_attribute(self.data, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True), + "ignore_imdb_ids": check_for_attribute(self.data, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True), "assets_for_all": check_for_attribute(self.data, "assets_for_all", parent="settings", var_type="bool", default=False, save=False, do_print=False) } self.webhooks = { @@ -445,6 +448,10 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["delete_not_scheduled"] = check_for_attribute(lib, "delete_not_scheduled", parent="settings", var_type="bool", default=self.general["delete_not_scheduled"], do_print=False, save=False) params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", parent="settings", var_type="bool", default=False, do_print=False, save=False) params["delete_collections_with_less"] = check_for_attribute(lib, "delete_collections_with_less", parent="settings", var_type="int", default_is_none=True, do_print=False, save=False) + params["ignore_ids"] = check_for_attribute(lib, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True, do_print=False, save=False) + params["ignore_ids"].extend([i for i in self.general["ignore_ids"] if i not in params["ignore_ids"]]) + params["ignore_imdb_ids"] = check_for_attribute(lib, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True, do_print=False, save=False) + params["ignore_imdb_ids"].extend([i for i in self.general["ignore_imdb_ids"] if i not in params["ignore_imdb_ids"]]) params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True) params["collection_changes_webhooks"] = check_for_attribute(lib, "collection_creation", parent="webhooks", var_type="list", default=self.webhooks["collection_changes"], do_print=False, save=False, default_is_none=True) params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", parent="settings", var_type="bool", default=self.general["assets_for_all"], do_print=False, save=False) @@ -491,8 +498,6 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No logger.warning("Config Warning: Using default template for tmdb_collections") else: logger.error("Config Error: tmdb_collections blank using default settings") - if params["tmdb_collections"]["exclude_ids"] is None: - params["tmdb_collections"]["exclude_ids"] = [] if params["tmdb_collections"]["remove_suffix"]: params["tmdb_collections"]["remove_suffix"] = params["tmdb_collections"]["remove_suffix"].strip() if "genre_mapper" in lib["operations"]: diff --git a/modules/library.py b/modules/library.py index fb55edcd7..2c8ed99a4 100644 --- a/modules/library.py +++ b/modules/library.py @@ -52,6 +52,8 @@ def __init__(self, config, params): self.show_missing_assets = params["show_missing_assets"] self.save_missing = params["save_missing"] self.only_filter_missing = params["only_filter_missing"] + self.ignore_ids = params["ignore_ids"] + self.ignore_imdb_ids = params["ignore_imdb_ids"] self.assets_for_all = params["assets_for_all"] self.delete_unmanaged_collections = params["delete_unmanaged_collections"] self.delete_collections_with_less = params["delete_collections_with_less"] From 5af5a64ba7fd684f7f2edfbbc42a5a940f67a495 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 8 Dec 2021 01:23:23 -0500 Subject: [PATCH 25/28] #295 adds show_missing_season_assets as global/library attribute --- modules/config.py | 2 ++ modules/library.py | 1 + modules/plex.py | 9 ++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index e577f4a16..6ada542e8 100644 --- a/modules/config.py +++ b/modules/config.py @@ -216,6 +216,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No "asset_directory": check_for_attribute(self.data, "asset_directory", parent="settings", var_type="list_path", default=[os.path.join(default_dir, "assets")], default_is_none=True), "asset_folders": check_for_attribute(self.data, "asset_folders", parent="settings", var_type="bool", default=True), "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), + "show_missing_season_assets": check_for_attribute(self.data, "show_missing_season_assets", parent="settings", var_type="bool", default=False), "sync_mode": check_for_attribute(self.data, "sync_mode", parent="settings", default="append", test_list=sync_modes), "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), @@ -443,6 +444,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No params["missing_only_released"] = check_for_attribute(lib, "missing_only_released", parent="settings", var_type="bool", default=self.general["missing_only_released"], do_print=False, save=False) params["only_filter_missing"] = check_for_attribute(lib, "only_filter_missing", parent="settings", var_type="bool", default=self.general["only_filter_missing"], do_print=False, save=False) params["create_asset_folders"] = check_for_attribute(lib, "create_asset_folders", parent="settings", var_type="bool", default=self.general["create_asset_folders"], do_print=False, save=False) + params["show_missing_season_assets"] = check_for_attribute(lib, "show_missing_season_assets", parent="settings", var_type="bool", default=self.general["show_missing_season_assets"], do_print=False, save=False) params["collection_minimum"] = check_for_attribute(lib, "collection_minimum", parent="settings", var_type="int", default=self.general["collection_minimum"], do_print=False, save=False) params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False) params["delete_not_scheduled"] = check_for_attribute(lib, "delete_not_scheduled", parent="settings", var_type="bool", default=self.general["delete_not_scheduled"], do_print=False, save=False) diff --git a/modules/library.py b/modules/library.py index 2c8ed99a4..1b7e66989 100644 --- a/modules/library.py +++ b/modules/library.py @@ -41,6 +41,7 @@ def __init__(self, config, params): self.missing_path = os.path.join(self.default_dir, f"{self.mapping_name}_missing.yml") self.asset_folders = params["asset_folders"] self.create_asset_folders = params["create_asset_folders"] + self.show_missing_season_assets = params["show_missing_season_assets"] self.sync_mode = params["sync_mode"] self.collection_minimum = params["collection_minimum"] self.delete_below_minimum = params["delete_below_minimum"] diff --git a/modules/plex.py b/modules/plex.py index c71945518..139fa7d4d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -651,6 +651,8 @@ def update_item_from_assets(self, item, overlay=None, create=False): if poster or background: self.upload_images(item, poster=poster, background=background, overlay=overlay) if self.is_show: + missing_assets = "" + found_season = False for season in self.query(item.seasons): season_name = f"Season{'0' if season.seasonNumber < 10 else ''}{season.seasonNumber}" if item_dir: @@ -659,11 +661,14 @@ def update_item_from_assets(self, item, overlay=None, create=False): else: season_poster_filter = os.path.join(ad, f"{name}_{season_name}.*") season_background_filter = os.path.join(ad, f"{name}_{season_name}_background.*") - matches = util.glob_filter(season_poster_filter) season_poster = None season_background = None + matches = util.glob_filter(season_poster_filter) if len(matches) > 0: season_poster = ImageData("asset_directory", os.path.abspath(matches[0]), prefix=f"{item.title} Season {season.seasonNumber}'s ", is_url=False) + found_season = True + elif season.seasonNumber > 0: + missing_assets += f"\nMissing Season {season.seasonNumber} Poster" matches = util.glob_filter(season_background_filter) if len(matches) > 0: season_background = ImageData("asset_directory", os.path.abspath(matches[0]), prefix=f"{item.title} Season {season.seasonNumber}'s ", is_poster=False, is_url=False) @@ -678,6 +683,8 @@ def update_item_from_assets(self, item, overlay=None, create=False): if len(matches) > 0: episode_poster = ImageData("asset_directory", os.path.abspath(matches[0]), prefix=f"{item.title} {episode.seasonEpisode.upper()}'s ", is_url=False) self.upload_images(episode, poster=episode_poster) + if self.show_missing_season_assets and found_season and missing_assets: + util.print_multiline(f"Missing Season Posters for {item.title}{missing_assets}", info=True) if not poster and overlay: self.upload_images(item, overlay=overlay) if create and self.asset_folders and not found_folder: From 8ab777780c7895eda17180f235a1962ee8c43b1a Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 8 Dec 2021 01:55:46 -0500 Subject: [PATCH 26/28] update actions --- .github/workflows/develop.yml | 6 ++++++ .github/workflows/latest.yml | 6 ++++++ .github/workflows/version.yml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 049c1eae2..799001233 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -24,6 +24,11 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 @@ -34,5 +39,6 @@ jobs: with: context: ./ file: ./Dockerfile + platforms: linux/amd64,linux/arm64,linux/ppc64le push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:develop diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index 1ca8f5955..d6c36a678 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -20,6 +20,11 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 @@ -30,5 +35,6 @@ jobs: with: context: ./ file: ./Dockerfile + platforms: linux/amd64,linux/arm64,linux/ppc64le push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:latest diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index fdfa7384b..5f1673769 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -21,6 +21,11 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 @@ -35,5 +40,6 @@ jobs: with: context: ./ file: ./Dockerfile + platforms: linux/amd64,linux/arm64,linux/ppc64le push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:${{ steps.get_version.outputs.VERSION }} From e44b7efcfca6bb62c42ea66ed12dd6213a3105d7 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 8 Dec 2021 02:28:11 -0500 Subject: [PATCH 27/28] update actions --- .github/workflows/develop.yml | 2 +- .github/workflows/latest.yml | 2 +- .github/workflows/version.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 799001233..6da75fa22 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -39,6 +39,6 @@ jobs: with: context: ./ file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/ppc64le + platforms: linux/amd64,linux/arm64 push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:develop diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index d6c36a678..8a644b21b 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -35,6 +35,6 @@ jobs: with: context: ./ file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/ppc64le + platforms: linux/amd64,linux/arm64 push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:latest diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 5f1673769..4b39b6da3 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -40,6 +40,6 @@ jobs: with: context: ./ file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/ppc64le + platforms: linux/amd64,linux/arm64 push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:${{ steps.get_version.outputs.VERSION }} From a242fe2ab8b95966d0060b08d6912aaf989b2577 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 8 Dec 2021 07:55:04 -0500 Subject: [PATCH 28/28] version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8046d61b9..f0df1f7d5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.13.1-develop127 \ No newline at end of file +1.13.2 \ No newline at end of file