From 78dce17cb900f65d869bcc2a4a11da81de59a7b0 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 6 Dec 2021 02:52:08 -0500 Subject: [PATCH] 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 cd8977b1..19835e92 100644 --- a/modules/config.py +++ b/modules/config.py @@ -125,7 +125,7 @@ class Config: 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 @@ class Config: 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 @@ class Config: 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 @@ class Config: 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 @@ class Config: "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 f5197c1c..6da3f6ff 100644 --- a/modules/library.py +++ b/modules/library.py @@ -38,17 +38,19 @@ class Library(ABC): 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 @@ class Library(ABC): 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 fb3f874a..84ea484b 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -15,8 +15,6 @@ class Metadata: 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 @@ class Metadata: 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 7b9be25b..3137437a 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -260,6 +260,9 @@ class Plex(Library): 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 fdc687fb..01825f3b 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -6,6 +6,7 @@ try: 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 = ""