diff --git a/config/config.yml.template b/config/config.yml.template index 18504972..fb8f36da 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -15,6 +15,8 @@ plex: # Can be individually specified pe asset_directory: config/assets show_unmanaged: true show_filtered: false + show_missing: true + save_missing: true radarr: # Can be individually specified per library as well url: http://192.168.1.12:7878 token: ################################ diff --git a/modules/builder.py b/modules/builder.py index 9bbc2b66..c5adedd0 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -708,51 +708,27 @@ class CollectionBuilder: if "name_mapping" in self.details: if self.details["name_mapping"]: name_mapping = self.details["name_mapping"] else: logger.error("Collection Error: name_mapping attribute is blank") - path = os.path.join(self.library.asset_directory, "{}".format(name_mapping), "poster.*") - matches = glob.glob(path) - if len(matches) > 0: - for match in matches: - self.posters.append(("file", os.path.abspath(match), "asset_directory")) - elif len(self.posters) == 0 and "poster" not in self.details: - logger.warning("poster not found at: {}".format(os.path.abspath(path))) - path = os.path.join(self.library.asset_directory, "{}".format(name_mapping), "background.*") - matches = glob.glob(path) - if len(matches) > 0: - for match in matches: - self.backgrounds.append(("file", os.path.abspath(match), "asset_directory")) - elif len(self.backgrounds) == 0 and "background" not in self.details: - logger.warning("background not found at: {}".format(os.path.abspath(path))) - - poster = util.choose_from_list(self.posters, "poster", list_type="tuple") - if not poster and "poster" in self.details: poster = self.details["poster"] - if poster: - if poster[0] == "url": collection.uploadPoster(url=poster[1]) - else: collection.uploadPoster(filepath=poster[1]) - logger.info("Detail: {} updated poster to [{}] {}".format(poster[2], poster[0], poster[1])) - - background = util.choose_from_list(self.backgrounds, "background", list_type="tuple") - if not background and "background" in self.details: background = self.details["background"] - if background: - if background[0] == "url": collection.uploadArt(url=background[1]) - else: collection.uploadArt(filepath=background[1]) - logger.info("Detail: {} updated background to [{}] {}".format(background[2], background[0], background[1])) - - if self.library.asset_directory: - path = os.path.join(self.library.asset_directory, "{}".format(name_mapping)) - if os.path.isdir(path): + for ad in self.library.asset_directory: + path = os.path.join(ad, "{}".format(name_mapping)) + if not os.path.isdir(path): + continue + matches = glob.glob(os.path.join(ad, "{}".format(name_mapping), "poster.*")) + if len(matches) > 0: + for match in matches: + self.posters.append(("file", os.path.abspath(match), "asset_directory")) + matches = glob.glob(os.path.join(ad, "{}".format(name_mapping), "background.*")) + if len(matches) > 0: + for match in matches: + self.backgrounds.append(("file", os.path.abspath(match), "asset_directory")) dirs = [folder for folder in os.listdir(path) if os.path.isdir(os.path.join(path, folder))] if len(dirs) > 0: for item in collection.items(): folder = os.path.basename(os.path.dirname(item.locations[0])) if folder in dirs: - files = [file for file in os.listdir(os.path.join(path, folder)) if os.path.isfile(os.path.join(path, folder, file))] - poster_path = None - background_path = None - for file in files: - if poster_path is None and file.startswith("poster."): - poster_path = os.path.join(path, folder, file) - if background_path is None and file.startswith("background."): - background_path = os.path.join(path, folder, file) + matches = glob.glob(os.path.join(path, folder, "poster.*")) + poster_path = os.path.abspath(matches[0]) if len(matches) > 0 else None + matches = glob.glob(os.path.join(path, folder, "background.*")) + background_path = os.path.abspath(matches[0]) if len(matches) > 0 else None if poster_path: item.uploadPoster(filepath=poster_path) logger.info("Detail: asset_directory updated {}'s poster to [file] {}".format(item.title, poster_path)) @@ -763,3 +739,17 @@ class CollectionBuilder: logger.warning("No Files Found: {}".format(os.path.join(path, folder))) else: logger.warning("No Folder: {}".format(os.path.join(path, folder))) + + poster = util.choose_from_list(self.posters, "poster", list_type="tuple") + if not poster and "poster" in self.details: poster = self.details["poster"] + if poster: + if poster[0] == "url": collection.uploadPoster(url=poster[1]) + else: collection.uploadPoster(filepath=poster[1]) + logger.info("Detail: {} updated collection poster to [{}] {}".format(poster[2], poster[0], poster[1])) + + background = util.choose_from_list(self.backgrounds, "background", list_type="tuple") + if not background and "background" in self.details: background = self.details["background"] + if background: + if background[0] == "url": collection.uploadArt(url=background[1]) + else: collection.uploadArt(filepath=background[1]) + logger.info("Detail: {} updated collection background to [{}] {}".format(background[2], background[0], background[1])) diff --git a/modules/config.py b/modules/config.py index e3e04504..8544f737 100644 --- a/modules/config.py +++ b/modules/config.py @@ -4,9 +4,12 @@ from modules.anidb import AniDBAPI from modules.builder import CollectionBuilder from modules.cache import Cache from modules.imdb import IMDbAPI -from modules.plex import PlexAPI from modules.mal import MyAnimeListAPI from modules.mal import MyAnimeListIDList +from modules.plex import PlexAPI +from modules.radarr import RadarrAPI +from modules.sonarr import SonarrAPI +from modules.tautulli import TautulliAPI from modules.tmdb import TMDbAPI from modules.trakt import TraktAPI from modules.tvdb import TVDbAPI @@ -29,13 +32,19 @@ class Config: try: self.data, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path)) except yaml.scanner.ScannerError as e: raise Failed("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) - def check_for_attribute(data, attribute, parent=None, test_list=None, options="", default=None, do_print=True, default_is_none=False, var_type="str", throw=False, save=True): + def check_for_attribute(data, attribute, parent=None, test_list=None, options="", default=None, do_print=True, default_is_none=False, req_default=False, var_type="str", throw=False, save=True): message = "" endline = "" - data = data if parent is None else data[parent] + if parent is not None: + if parent in data: + data = data[parent] + else: + data = None + do_print = False + save = False text = "{} attribute".format(attribute) if parent is None else "{} sub-attribute {}".format(parent, attribute) if data is None or attribute not in data: - message = "Config Error: {} not found".format(text) + message = "{} not found".format(text) if parent and save is True: new_config, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path)) endline = "\n| {} sub-attribute {} added to config".format(parent, attribute) @@ -46,40 +55,47 @@ class Config: yaml.round_trip_dump(new_config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi) elif not data[attribute] and data[attribute] != False: if default_is_none is True: return None - else: message = "Config Error: {} is blank".format(text) + else: message = "{} is blank".format(text) elif var_type == "bool": if isinstance(data[attribute], bool): return data[attribute] - else: message = "Config Error: {} must be either true or false".format(text) + else: message = "{} must be either true or false".format(text) elif var_type == "int": if isinstance(data[attribute], int) and data[attribute] > 0: return data[attribute] - else: message = "Config Error: {} must an integer > 0".format(text) + else: message = "{} must an integer > 0".format(text) elif var_type == "path": if os.path.exists(os.path.abspath(data[attribute])): return data[attribute] - else: message = "Config Error: {} could not be found".format(text) - if default and os.path.exists(os.path.abspath(default)): - return default - elif default: - default = None - default_is_none = True - message = "Config Error: neither {} or the default path {} could be found".format(data[attribute], default) + else: message = "Path {} does not exist".format(os.path.abspath(data[attribute])) + elif var_type == "list": return util.get_list(data[attribute]) + elif var_type == "listpath": + temp_list = [path for path in util.get_list(data[attribute]) if os.path.exists(os.path.abspath(path))] + if len(temp_list) > 0: return temp_list + else: message = "No Paths exist" + elif var_type == "lowerlist": return util.get_list(data[attribute], lower=True) elif test_list is None or data[attribute] in test_list: return data[attribute] - else: message = "Config Error: {}: {} is an invalid input".format(text, data[attribute]) + else: message = "{}: {} is an invalid input".format(text, data[attribute]) + if var_type == "path" and default and os.path.exists(os.path.abspath(default)): + return default + elif var_type == "path" and default: + default = None + message = "neither {} or the default path {} could be found".format(data[attribute], default) if default is not None or default_is_none: message = message + " using {} as default".format(default) message = message + endline + if req_default and default is None: + raise Failed("{} attribute must be set under {} globally or under this specific Library".format(attribute, parent)) if (default is None and not default_is_none) or throw: if len(options) > 0: message = message + "\n" + options raise Failed(message) if do_print: - util.print_multiline(message) + util.print_multiline("Config Warning: {}".format(message)) if attribute in data and data[attribute] and test_list is not None and data[attribute] not in test_list: util.print_multiline(options) return default self.general = {} - self.general["cache"] = check_for_attribute(self.data, "cache", parent="cache", options="| \ttrue (Create a cache to store ids)\n| \tfalse (Do not create a cache to store ids)", var_type="bool", default=True) if "cache" in self.data else True - self.general["cache_expiration"] = check_for_attribute(self.data, "cache_expiration", parent="cache", var_type="int", default=60) if "cache" in self.data else 60 + self.general["cache"] = check_for_attribute(self.data, "cache", parent="cache", options=" true (Create a cache to store ids)\n false (Do not create a cache to store ids)", var_type="bool", default=True) + self.general["cache_expiration"] = check_for_attribute(self.data, "cache_expiration", parent="cache", var_type="int", default=60) if self.general["cache"]: util.seperator() self.Cache = Cache(self.config_path, self.general["cache_expiration"]) @@ -92,7 +108,8 @@ class Config: if "tmdb" in self.data: logger.info("Connecting to TMDb...") self.tmdb = {} - self.tmdb["apikey"] = check_for_attribute(self.data, "apikey", parent="tmdb", throw=True) + try: self.tmdb["apikey"] = check_for_attribute(self.data, "apikey", parent="tmdb", throw=True) + except Failed as e: raise Failed("Config Error: {}".format(e)) self.tmdb["language"] = check_for_attribute(self.data, "language", parent="tmdb", default="en") self.TMDb = TMDbAPI(self.tmdb) logger.info("TMDb Connection {}".format("Failed" if self.TMDb is None else "Successful")) @@ -112,7 +129,7 @@ class Config: authorization = self.data["trakt"]["authorization"] if "authorization" in self.data["trakt"] and self.data["trakt"]["authorization"] else None self.Trakt = TraktAPI(self.trakt, authorization) except Failed as e: - logger.error(e) + logger.error("Config Error: {}".format(e)) logger.info("Trakt Connection {}".format("Failed" if self.Trakt is None else "Successful")) else: logger.warning("trakt attribute not found") @@ -131,7 +148,7 @@ class Config: authorization = self.data["mal"]["authorization"] if "authorization" in self.data["mal"] and self.data["mal"]["authorization"] else None self.MyAnimeList = MyAnimeListAPI(self.mal, self.MyAnimeListIDList, authorization) except Failed as e: - logger.error(e) + logger.error("Config Error: {}".format(e)) logger.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful")) else: logger.warning("mal attribute not found") @@ -145,42 +162,42 @@ class Config: logger.info("Connecting to Plex Libraries...") self.general["plex"] = {} - self.general["plex"]["url"] = check_for_attribute(self.data, "url", parent="plex", default_is_none=True) if "plex" in self.data else None - self.general["plex"]["token"] = check_for_attribute(self.data, "token", parent="plex", default_is_none=True) if "plex" in self.data else None - self.general["plex"]["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="plex", var_type="path", default=os.path.join(default_dir, "assets")) if "plex" in self.data else os.path.join(default_dir, "assets") - self.general["plex"]["sync_mode"] = check_for_attribute(self.data, "sync_mode", parent="plex", default="append", test_list=["append", "sync"], options="| \tappend (Only Add Items to the Collection)\n| \tsync (Add & Remove Items from the Collection)") if "plex" in self.data else "append" - self.general["plex"]["show_unmanaged"] = check_for_attribute(self.data, "show_unmanaged", parent="plex", var_type="bool", default=True) if "plex" in self.data else True - self.general["plex"]["show_filtered"] = check_for_attribute(self.data, "show_filtered", parent="plex", var_type="bool", default=False) if "plex" in self.data else False - self.general["plex"]["show_missing"] = check_for_attribute(self.data, "show_missing", parent="plex", var_type="bool", default=True) if "plex" in self.data else True - self.general["plex"]["save_missing"] = check_for_attribute(self.data, "save_missing", parent="plex", var_type="bool", default=True) if "plex" in self.data else True + self.general["plex"]["url"] = check_for_attribute(self.data, "url", parent="plex", default_is_none=True) + self.general["plex"]["token"] = check_for_attribute(self.data, "token", parent="plex", default_is_none=True) + self.general["plex"]["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="plex", var_type="listpath", default=os.path.join(default_dir, "assets")) + self.general["plex"]["sync_mode"] = check_for_attribute(self.data, "sync_mode", parent="plex", default="append", test_list=["append", "sync"], options=" append (Only Add Items to the Collection)\n sync (Add & Remove Items from the Collection)") + self.general["plex"]["show_unmanaged"] = check_for_attribute(self.data, "show_unmanaged", parent="plex", var_type="bool", default=True) + self.general["plex"]["show_filtered"] = check_for_attribute(self.data, "show_filtered", parent="plex", var_type="bool", default=False) + self.general["plex"]["show_missing"] = check_for_attribute(self.data, "show_missing", parent="plex", var_type="bool", default=True) + self.general["plex"]["save_missing"] = check_for_attribute(self.data, "save_missing", parent="plex", var_type="bool", default=True) self.general["radarr"] = {} - self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", default_is_none=True) if "radarr" in self.data else None - self.general["radarr"]["version"] = check_for_attribute(self.data, "version", parent="radarr", test_list=["v2", "v3"], options="| \tv2 (For Radarr 0.2)\n| \tv3 (For Radarr 3.0)", default="v2") if "radarr" in self.data else "v2" - self.general["radarr"]["token"] = check_for_attribute(self.data, "token", parent="radarr", default_is_none=True) if "radarr" in self.data else None - self.general["radarr"]["quality_profile"] = check_for_attribute(self.data, "quality_profile", parent="radarr", default_is_none=True) if "radarr" in self.data else None - self.general["radarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="radarr", default_is_none=True) if "radarr" in self.data else None - self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False) if "radarr" in self.data else False - self.general["radarr"]["search"] = check_for_attribute(self.data, "search", parent="radarr", var_type="bool", default=False) if "radarr" in self.data else False - self.general["radarr"]["tag"] = util.get_list(check_for_attribute(self.data, "tag", parent="radarr", default_is_none=True), lower=True) if "radarr" in self.data else None + self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", default_is_none=True) + self.general["radarr"]["version"] = check_for_attribute(self.data, "version", parent="radarr", test_list=["v2", "v3"], options=" v2 (For Radarr 0.2)\n v3 (For Radarr 3.0)", default="v2") + self.general["radarr"]["token"] = check_for_attribute(self.data, "token", parent="radarr", default_is_none=True) + self.general["radarr"]["quality_profile"] = check_for_attribute(self.data, "quality_profile", parent="radarr", default_is_none=True) + self.general["radarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="radarr", default_is_none=True) + self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False) + self.general["radarr"]["search"] = check_for_attribute(self.data, "search", parent="radarr", var_type="bool", default=False) + self.general["radarr"]["tag"] = check_for_attribute(self.data, "tag", parent="radarr", var_type="lowerlist", default_is_none=True) self.general["sonarr"] = {} - self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", default_is_none=True) if "sonarr" in self.data else None - self.general["sonarr"]["token"] = check_for_attribute(self.data, "token", parent="sonarr", default_is_none=True) if "sonarr" in self.data else None - self.general["sonarr"]["version"] = check_for_attribute(self.data, "version", parent="sonarr", test_list=["v2", "v3"], options="| \tv2 (For Sonarr 0.2)\n| \tv3 (For Sonarr 3.0)", default="v2") if "sonarr" in self.data else "v2" - self.general["sonarr"]["quality_profile"] = check_for_attribute(self.data, "quality_profile", parent="sonarr", default_is_none=True) if "sonarr" in self.data else None - self.general["sonarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="sonarr", default_is_none=True) if "sonarr" in self.data else None - self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False) if "sonarr" in self.data else False - self.general["sonarr"]["search"] = check_for_attribute(self.data, "search", parent="sonarr", var_type="bool", default=False) if "sonarr" in self.data else False - self.general["sonarr"]["tag"] = util.get_list(check_for_attribute(self.data, "tag", parent="sonarr", default_is_none=True), lower=True) if "sonarr" in self.data else None + self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", default_is_none=True) + self.general["sonarr"]["token"] = check_for_attribute(self.data, "token", parent="sonarr", default_is_none=True) + self.general["sonarr"]["version"] = check_for_attribute(self.data, "version", parent="sonarr", test_list=["v2", "v3"], options=" v2 (For Sonarr 0.2)\n v3 (For Sonarr 3.0)", default="v2") + self.general["sonarr"]["quality_profile"] = check_for_attribute(self.data, "quality_profile", parent="sonarr", default_is_none=True) + self.general["sonarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="sonarr", default_is_none=True) + self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False) + self.general["sonarr"]["search"] = check_for_attribute(self.data, "search", parent="sonarr", var_type="bool", default=False) + self.general["sonarr"]["tag"] = check_for_attribute(self.data, "tag", parent="sonarr", var_type="lowerlist", default_is_none=True) self.general["tautulli"] = {} - self.general["tautulli"]["url"] = check_for_attribute(self.data, "url", parent="tautulli", default_is_none=True) if "tautulli" in self.data else None - self.general["tautulli"]["apikey"] = check_for_attribute(self.data, "apikey", parent="tautulli", default_is_none=True) if "tautulli" in self.data else None - + self.general["tautulli"]["url"] = check_for_attribute(self.data, "url", parent="tautulli", default_is_none=True) + self.general["tautulli"]["apikey"] = check_for_attribute(self.data, "apikey", parent="tautulli", default_is_none=True) self.libraries = [] - libs = check_for_attribute(self.data, "libraries", throw=True) + try: libs = check_for_attribute(self.data, "libraries", throw=True) + except Failed as e: raise Failed("Config Error: {}".format(e)) for lib in libs: util.seperator() params = {} @@ -192,46 +209,22 @@ class Config: logger.info("Connecting to {} Library...".format(params["name"])) default_lib = os.path.join(default_dir, "{}.yml".format(lib)) try: - if "metadata_path" in libs[lib]: - if libs[lib]["metadata_path"]: - if os.path.exists(libs[lib]["metadata_path"]): params["metadata_path"] = libs[lib]["metadata_path"] - else: raise Failed("metadata_path not found at {}".format(libs[lib]["metadata_path"])) - else: raise Failed("metadata_path attribute is blank") - else: - if os.path.exists(default_lib): params["metadata_path"] = os.path.abspath(default_lib) - else: raise Failed("default metadata_path not found at {}".format(os.path.abspath(os.path.join(default_dir, "{}.yml".format(params["name"]))))) - - if "library_type" in libs[lib]: - if libs[lib]["library_type"]: - if libs[lib]["library_type"] in ["movie", "show"]: params["library_type"] = libs[lib]["library_type"] - else: raise Failed("library_type attribute must be either 'movie' or 'show'") - else: raise Failed("library_type attribute is blank") - else: raise Failed("library_type attribute is required") - + params["metadata_path"] = check_for_attribute(libs[lib], "metadata_path", var_type="path", default=os.path.join(default_dir, "{}.yml".format(lib)), throw=True) + params["library_type"] = check_for_attribute(libs[lib], "library_type", test_list=["movie", "show"], options=" movie (For Movie Libraries)\n show (For Show Libraries)", throw=True) params["plex"] = {} - if "plex" in libs[lib] and libs[lib]["plex"] and "url" in libs[lib]["plex"]: - if libs[lib]["plex"]["url"]: params["plex"]["url"] = libs[lib]["plex"]["url"] - else: raise Failed("url library attribute is blank") - elif self.general["plex"]["url"]: params["plex"]["url"] = self.general["plex"]["url"] - else: raise Failed("url attribute must be set under plex or under this specific Library") - - if "plex" in libs[lib] and libs[lib]["plex"] and "token" in libs[lib]["plex"]: - if libs[lib]["plex"]["token"]: params["plex"]["token"] = libs[lib]["plex"]["token"] - else: raise Failed("token library attribute is blank") - elif self.general["plex"]["token"]: params["plex"]["token"] = self.general["plex"]["token"] - else: raise Failed("token attribute must be set under plex or under this specific Library") + params["plex"]["url"] = check_for_attribute(libs[lib], "url", parent="plex", default=self.general["plex"]["url"], req_default=True, save=False) + params["plex"]["token"] = check_for_attribute(libs[lib], "token", parent="plex", default=self.general["plex"]["token"], req_default=True, save=False) except Failed as e: - logger.error("Config Error: Skipping {} Library {}".format(str(lib), e)) + util.print_multiline("Config Error: Skipping {} Library {}".format(str(lib), e)) continue - params["asset_directory"] = None + params["asset_directory"] = check_for_attribute(libs[lib], "asset_directory", parent="plex", var_type="listpath", default=os.path.join(default_dir, "assets")) if "plex" in libs[lib] and "asset_directory" in libs[lib]["plex"]: if libs[lib]["plex"]["asset_directory"]: - if os.path.exists(libs[lib]["plex"]["asset_directory"]): - params["asset_directory"] = libs[lib]["plex"]["asset_directory"] - else: - logger.warning("Config Warning: Assets will not be used asset_directory not found at {}".format(libs[lib]["plex"]["asset_directory"])) + temp_list = [path for path in util.get_list(libs[lib]["plex"]["asset_directory"]) if os.path.exists(os.path.abspath(path))] + if len(temp_list) > 0: params["asset_directory"] = temp_list + else: logger.warning("Config Warning: No Paths exist") else: logger.warning("Config Warning: Assets will not be used asset_directory library attribute is blank") elif self.general["plex"]["asset_directory"]: @@ -239,203 +232,62 @@ class Config: else: logger.warning("Config Warning: Assets will not be used asset_directory attribute must be set under config or under this specific Library") - params["sync_mode"] = self.general["plex"]["sync_mode"] - if "plex" in libs[lib] and "sync_mode" in libs[lib]["plex"]: - if libs[lib]["plex"]["sync_mode"]: - if libs[lib]["plex"]["sync_mode"] in ["append", "sync"]: - params["sync_mode"] = libs[lib]["plex"]["sync_mode"] - else: - logger.warning("Config Warning: sync_mode attribute must be either 'append' or 'sync' using general value: {}".format(self.general["plex"]["sync_mode"])) - else: - logger.warning("Config Warning: sync_mode attribute is blank using general value: {}".format(self.general["plex"]["sync_mode"])) - - params["show_unmanaged"] = self.general["plex"]["show_unmanaged"] - if "plex" in libs[lib] and "show_unmanaged" in libs[lib]["plex"]: - if libs[lib]["plex"]["show_unmanaged"]: - if isinstance(libs[lib]["plex"]["show_unmanaged"], bool): - params["plex"]["show_unmanaged"] = libs[lib]["plex"]["show_unmanaged"] - else: - logger.warning("Config Warning: plex sub-attribute show_unmanaged must be either true or false using general value: {}".format(self.general["plex"]["show_unmanaged"])) - else: - logger.warning("Config Warning: plex sub-attribute show_unmanaged is blank using general value: {}".format(self.general["plex"]["show_unmanaged"])) - - params["show_filtered"] = self.general["plex"]["show_filtered"] - if "plex" in libs[lib] and "show_filtered" in libs[lib]["plex"]: - if libs[lib]["plex"]["show_filtered"]: - if isinstance(libs[lib]["plex"]["show_filtered"], bool): - params["plex"]["show_filtered"] = libs[lib]["plex"]["show_filtered"] - else: - logger.warning("Config Warning: plex sub-attribute show_filtered must be either true or false using general value: {}".format(self.general["plex"]["show_filtered"])) - else: - logger.warning("Config Warning: plex sub-attribute show_filtered is blank using general value: {}".format(self.general["plex"]["show_filtered"])) - - params["show_missing"] = self.general["plex"]["show_missing"] - if "plex" in libs[lib] and "show_missing" in libs[lib]["plex"]: - if libs[lib]["plex"]["show_missing"]: - if isinstance(libs[lib]["plex"]["show_missing"], bool): - params["plex"]["show_missing"] = libs[lib]["plex"]["show_missing"] - else: - logger.warning("Config Warning: plex sub-attribute show_missing must be either true or false using general value: {}".format(self.general["plex"]["show_missing"])) - else: - logger.warning("Config Warning: plex sub-attribute show_missing is blank using general value: {}".format(self.general["plex"]["show_missing"])) - - params["save_missing"] = self.general["plex"]["save_missing"] - if "plex" in libs[lib] and "save_missing" in libs[lib]["plex"]: - if libs[lib]["plex"]["save_missing"]: - if isinstance(libs[lib]["plex"]["save_missing"], bool): - params["plex"]["save_missing"] = libs[lib]["plex"]["save_missing"] - else: - logger.warning("Config Warning: plex sub-attribute save_missing must be either true or false using general value: {}".format(self.general["plex"]["save_missing"])) - else: - logger.warning("Config Warning: plex sub-attribute save_missing is blank using general value: {}".format(self.general["plex"]["save_missing"])) - - params["tmdb"] = self.TMDb - params["tvdb"] = self.TVDb - - params["radarr"] = self.general["radarr"].copy() - if "radarr" in libs[lib] and libs[lib]["radarr"]: - if "url" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["url"]: - params["radarr"]["url"] = libs[lib]["radarr"]["url"] - else: - logger.warning("Config Warning: radarr sub-attribute url is blank using general value: {}".format(self.general["radarr"]["url"])) - - if "token" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["token"]: - params["radarr"]["token"] = libs[lib]["radarr"]["token"] - else: - logger.warning("Config Warning: radarr sub-attribute token is blank using general value: {}".format(self.general["radarr"]["token"])) - - if "version" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["version"]: - if libs[lib]["radarr"]["version"] in ["v2", "v3"]: - params["radarr"]["version"] = libs[lib]["radarr"]["version"] - else: - logger.warning("Config Warning: radarr sub-attribute version must be either 'v2' or 'v3' using general value: {}".format(self.general["radarr"]["version"])) - else: - logger.warning("Config Warning: radarr sub-attribute version is blank using general value: {}".format(self.general["radarr"]["version"])) - - if "quality_profile" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["quality_profile"]: - params["radarr"]["quality_profile"] = libs[lib]["radarr"]["quality_profile"] - else: - logger.warning("Config Warning: radarr sub-attribute quality_profile is blank using general value: {}".format(self.general["radarr"]["quality_profile"])) - - if "root_folder_path" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["root_folder_path"]: - params["radarr"]["root_folder_path"] = libs[lib]["radarr"]["root_folder_path"] - else: - logger.warning("Config Warning: radarr sub-attribute root_folder_path is blank using general value: {}".format(self.general["radarr"]["root_folder_path"])) - - if "add" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["add"]: - if isinstance(libs[lib]["radarr"]["add"], bool): - params["radarr"]["add"] = libs[lib]["radarr"]["add"] - else: - logger.warning("Config Warning: radarr sub-attribute add must be either true or false using general value: {}".format(self.general["radarr"]["add"])) - else: - logger.warning("Config Warning: radarr sub-attribute add is blank using general value: {}".format(self.general["radarr"]["add"])) - - if "search" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["search"]: - if isinstance(libs[lib]["radarr"]["search"], bool): - params["radarr"]["search"] = libs[lib]["radarr"]["search"] - else: - logger.warning("Config Warning: radarr sub-attribute search must be either true or false using general value: {}".format(self.general["radarr"]["search"])) - else: - logger.warning("Config Warning: radarr sub-attribute search is blank using general value: {}".format(self.general["radarr"]["search"])) - - if "tag" in libs[lib]["radarr"]: - if libs[lib]["radarr"]["tag"]: - params["radarr"]["tag"] = util.get_list(libs[lib]["radarr"]["tag"], lower=True) - else: - logger.warning("Config Warning: radarr sub-attribute tag is blank using general value: {}".format(self.general["radarr"]["tag"])) - - if not params["radarr"]["url"] or not params["radarr"]["token"] or not params["radarr"]["quality_profile"] or not params["radarr"]["root_folder_path"]: - params["radarr"] = None - - params["sonarr"] = self.general["sonarr"].copy() - if "sonarr" in libs[lib] and libs[lib]["sonarr"]: - if "url" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["url"]: - params["sonarr"]["url"] = libs[lib]["sonarr"]["url"] - else: - logger.warning("Config Warning: sonarr sub-attribute url is blank using general value: {}".format(self.general["sonarr"]["url"])) - - if "token" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["token"]: - params["sonarr"]["token"] = libs[lib]["sonarr"]["token"] - else: - logger.warning("Config Warning: sonarr sub-attribute token is blank using general value: {}".format(self.general["sonarr"]["token"])) - - if "version" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["version"]: - if libs[lib]["sonarr"]["version"] in ["v2", "v3"]: - params["sonarr"]["version"] = libs[lib]["sonarr"]["version"] - else: - logger.warning("Config Warning: sonarr sub-attribute version must be either 'v2' or 'v3' using general value: {}".format(self.general["sonarr"]["version"])) - else: - logger.warning("Config Warning: sonarr sub-attribute version is blank using general value: {}".format(self.general["sonarr"]["version"])) - - if "quality_profile" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["quality_profile"]: - params["sonarr"]["quality_profile"] = libs[lib]["sonarr"]["quality_profile"] - else: - logger.warning("Config Warning: sonarr sub-attribute quality_profile is blank using general value: {}".format(self.general["sonarr"]["quality_profile"])) - - if "root_folder_path" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["root_folder_path"]: - params["sonarr"]["root_folder_path"] = libs[lib]["sonarr"]["root_folder_path"] - else: - logger.warning("Config Warning: sonarr sub-attribute root_folder_path is blank using general value: {}".format(self.general["sonarr"]["root_folder_path"])) - - if "add" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["add"]: - if isinstance(libs[lib]["sonarr"]["add"], bool): - params["sonarr"]["add"] = libs[lib]["sonarr"]["add"] - else: - logger.warning("Config Warning: sonarr sub-attribute add must be either true or false using general value: {}".format(self.general["sonarr"]["add"])) - else: - logger.warning("Config Warning: sonarr sub-attribute add is blank using general value: {}".format(self.general["sonarr"]["add"])) - - if "search" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["search"]: - if isinstance(libs[lib]["sonarr"]["search"], bool): - params["sonarr"]["search"] = libs[lib]["sonarr"]["search"] - else: - logger.warning("Config Warning: sonarr sub-attribute search must be either true or false using general value: {}".format(self.general["sonarr"]["search"])) - else: - logger.warning("Config Warning: sonarr sub-attribute search is blank using general value: {}".format(self.general["sonarr"]["search"])) - - if "tag" in libs[lib]["sonarr"]: - if libs[lib]["sonarr"]["tag"]: - params["sonarr"]["tag"] = util.get_list(libs[lib]["sonarr"]["tag"], lower=True) - else: - logger.warning("Config Warning: sonarr sub-attribute tag is blank using general value: {}".format(self.general["sonarr"]["tag"])) - - if not params["sonarr"]["url"] or not params["sonarr"]["token"] or not params["sonarr"]["quality_profile"] or not params["sonarr"]["root_folder_path"] or params["library_type"] == "movie": - params["sonarr"] = None - - - params["tautulli"] = self.general["tautulli"].copy() - if "tautulli" in libs[lib] and libs[lib]["tautulli"]: - if "url" in libs[lib]["tautulli"]: - if libs[lib]["tautulli"]["url"]: - params["tautulli"]["url"] = libs[lib]["tautulli"]["url"] - else: - logger.warning("Config Warning: tautulli sub-attribute url is blank using general value: {}".format(self.general["tautulli"]["url"])) - - if "apikey" in libs[lib]["tautulli"]: - if libs[lib]["tautulli"]["apikey"]: - params["tautulli"]["apikey"] = libs[lib]["tautulli"]["apikey"] - else: - logger.warning("Config Warning: tautulli sub-attribute apikey is blank using general value: {}".format(self.general["tautulli"]["apikey"])) - - if not params["tautulli"]["url"] or not params["tautulli"]["apikey"] : - params["tautulli"] = None + params["sync_mode"] = check_for_attribute(libs[lib], "sync_mode", parent="plex", test_list=["append", "sync"], options=" append (Only Add Items to the Collection)\n sync (Add & Remove Items from the Collection)", default=self.general["plex"]["sync_mode"], save=False) + params["show_unmanaged"] = check_for_attribute(libs[lib], "show_unmanaged", parent="plex", var_type="bool", default=self.general["plex"]["show_unmanaged"], save=False) + params["show_filtered"] = check_for_attribute(libs[lib], "show_filtered", parent="plex", var_type="bool", default=self.general["plex"]["show_filtered"], save=False) + params["show_missing"] = check_for_attribute(libs[lib], "show_missing", parent="plex", var_type="bool", default=self.general["plex"]["show_missing"], save=False) + params["save_missing"] = check_for_attribute(libs[lib], "save_missing", parent="plex", var_type="bool", default=self.general["plex"]["save_missing"], save=False) + + if self.general["radarr"]["url"] or "radarr" in libs[lib]: + logger.info("Connecting to {} library's Radarr...".format(params["name"])) + radarr_params = {} + try: + radarr_params["url"] = check_for_attribute(libs[lib], "url", parent="radarr", default=self.general["radarr"]["url"], req_default=True, save=False) + radarr_params["token"] = check_for_attribute(libs[lib], "token", parent="radarr", default=self.general["radarr"]["token"], req_default=True, save=False) + radarr_params["version"] = check_for_attribute(libs[lib], "version", parent="radarr", test_list=["v2", "v3"], options=" v2 (For Radarr 0.2)\n v3 (For Radarr 3.0)", default=self.general["radarr"]["version"], save=False) + radarr_params["quality_profile"] = check_for_attribute(libs[lib], "quality_profile", parent="radarr", default=self.general["radarr"]["quality_profile"], req_default=True, save=False) + radarr_params["root_folder_path"] = check_for_attribute(libs[lib], "root_folder_path", parent="radarr", default=self.general["radarr"]["root_folder_path"], req_default=True, save=False) + radarr_params["add"] = check_for_attribute(libs[lib], "add", parent="radarr", var_type="bool", default=self.general["radarr"]["add"], save=False) + radarr_params["search"] = check_for_attribute(libs[lib], "search", parent="radarr", var_type="bool", default=self.general["radarr"]["search"], save=False) + radarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="radarr", var_type="lowerlist", default=self.general["radarr"]["tag"], default_is_none=True, save=False) + Radarr = RadarrAPI(self.TMDb, radarr_params) + except Failed as e: + util.print_multiline("Config Error: {}".format(e)) + Radarr = None + logger.info("{} library's Radarr Connection {}".format(params["name"], "Failed" if Radarr is None else "Successful")) + + if self.general["sonarr"]["url"] or "sonarr" in libs[lib]: + logger.info("Connecting to {} library's Sonarr...".format(params["name"])) + sonarr_params = {} + try: + sonarr_params["url"] = check_for_attribute(libs[lib], "url", parent="sonarr", default=self.general["sonarr"]["url"], req_default=True, save=False) + sonarr_params["token"] = check_for_attribute(libs[lib], "token", parent="sonarr", default=self.general["sonarr"]["token"], req_default=True, save=False) + sonarr_params["version"] = check_for_attribute(libs[lib], "version", parent="sonarr", test_list=["v2", "v3"], options=" v2 (For Sonarr 0.2)\n v3 (For Sonarr 3.0)", default=self.general["sonarr"]["version"], save=False) + sonarr_params["quality_profile"] = check_for_attribute(libs[lib], "quality_profile", parent="sonarr", default=self.general["sonarr"]["quality_profile"], req_default=True, save=False) + sonarr_params["root_folder_path"] = check_for_attribute(libs[lib], "root_folder_path", parent="sonarr", default=self.general["sonarr"]["root_folder_path"], req_default=True, save=False) + sonarr_params["add"] = check_for_attribute(libs[lib], "add", parent="sonarr", var_type="bool", default=self.general["sonarr"]["add"], save=False) + sonarr_params["search"] = check_for_attribute(libs[lib], "search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["search"], save=False) + sonarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="sonarr", var_type="lowerlist", default=self.general["sonarr"]["tag"], default_is_none=True, save=False) + Sonarr = SonarrAPI(self.TVDb, sonarr_params, self.tmdb["language"]) + except Failed as e: + util.print_multiline("Config Error: {}".format(e)) + Sonarr = None + logger.info("{} library's Sonarr Connection {}".format(params["name"], "Failed" if Sonarr is None else "Successful")) + + if self.general["tautulli"]["url"] or "tautulli" in libs[lib]: + logger.info("Connecting to {} library's Tautulli...".format(params["name"])) + tautulli_params = {} + try: + tautulli_params["url"] = check_for_attribute(libs[lib], "url", parent="tautulli", default=self.general["tautulli"]["url"], req_default=True, save=False) + tautulli_params["apikey"] = check_for_attribute(libs[lib], "apikey", parent="tautulli", default=self.general["tautulli"]["apikey"], req_default=True, save=False) + Tautulli = TautulliAPI(tautulli_params) + except Failed as e: + util.print_multiline("Config Error: {}".format(e)) + Tautulli = None + logger.info("{} library's Tautulli Connection {}".format(params["name"], "Failed" if Tautulli is None else "Successful")) try: - self.libraries.append(PlexAPI(params)) + self.libraries.append(PlexAPI(params, self.TMDb, self.TVDb, Radarr, Sonarr, Tautulli)) logger.info("{} Library Connection Successful".format(params["name"])) except Failed as e: logger.error(e) diff --git a/modules/plex.py b/modules/plex.py index 3a62e603..ad0f08e0 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -1,9 +1,6 @@ import datetime, logging, os, requests from lxml import html from modules import util -from modules.radarr import RadarrAPI -from modules.sonarr import SonarrAPI -from modules.tautulli import TautulliAPI from modules.util import Failed from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.library import Collections, MovieSection, ShowSection @@ -15,7 +12,7 @@ from ruamel import yaml logger = logging.getLogger("Plex Meta Manager") class PlexAPI: - def __init__(self, params): + def __init__(self, params, TMDb, TVDb, Radarr, Sonarr, Tautulli): try: self.PlexServer = PlexServer(params["plex"]["url"], params["plex"]["token"], timeout=600) except Unauthorized: raise Failed("Plex Error: Plex token is invalid") except ValueError as e: raise Failed("Plex Error: {}".format(e)) @@ -45,33 +42,15 @@ class PlexAPI: raise Failed("YAML Error: metadata attributes or collections attribute required") if params["asset_directory"]: - logger.info("Using Asset Directory: {}".format(params["asset_directory"])) - - self.Radarr = None - if params["tmdb"] and params["radarr"]: - logger.info("Connecting to {} library's Radarr...".format(params["name"])) - try: self.Radarr = RadarrAPI(params["tmdb"], params["radarr"]) - except Failed as e: logger.error(e) - logger.info("{} library's Radarr Connection {}".format(params["name"], "Failed" if self.Radarr is None else "Successful")) - - self.Sonarr = None - if params["tvdb"] and params["sonarr"]: - logger.info("Connecting to {} library's Sonarr...".format(params["name"])) - try: self.Sonarr = SonarrAPI(params["tvdb"], params["sonarr"], self.Plex.language) - except Failed as e: logger.error(e) - logger.info("{} library's Sonarr Connection {}".format(params["name"], "Failed" if self.Sonarr is None else "Successful")) - - self.Tautulli = None - if params["tautulli"]: - logger.info("Connecting to {} library's Tautulli...".format(params["name"])) - try: self.Tautulli = TautulliAPI(params["tautulli"]) - except Failed as e: logger.error(e) - logger.info("{} library's Tautulli Connection {}".format(params["name"], "Failed" if self.Tautulli is None else "Successful")) - - self.TMDb = params["tmdb"] - self.TVDb = params["tvdb"] + for ad in params["asset_directory"]: + logger.info("Using Asset Directory: {}".format(ad)) + + self.TMDb = TMDb + self.TVDb = TVDb + self.Radarr = Radarr + self.Sonarr = Sonarr + self.Tautulli = Tautulli self.name = params["name"] - self.missing_path = os.path.join(os.path.dirname(os.path.abspath(params["metadata_path"])), "{}_missing.yml".format(os.path.splitext(os.path.basename(params["metadata_path"]))[0])) self.metadata_path = params["metadata_path"] self.asset_directory = params["asset_directory"] @@ -81,9 +60,6 @@ class PlexAPI: self.show_missing = params["show_missing"] self.save_missing = params["save_missing"] self.plex = params["plex"] - self.radarr = params["radarr"] - self.sonarr = params["sonarr"] - self.tautulli = params["tautulli"] self.missing = {} @retry(stop_max_attempt_number=6, wait_fixed=10000) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 5e98498d..c613a997 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -4,11 +4,12 @@ from modules.config import Config parser = argparse.ArgumentParser() parser.add_argument("--mytests", dest="tests", help=argparse.SUPPRESS, action="store_true", default=False) +parser.add_argument("--debug", dest="debug", help=argparse.SUPPRESS, action="store_true", default=False) parser.add_argument("-c", "--config", dest="config", help="Run with desired *.yml file", type=str) parser.add_argument("-t", "--time", dest="time", help="Time to update each day use format HH:MM (Default: 03:00)", default="03:00", type=str) parser.add_argument("-r", "--run", dest="run", help="Run without the scheduler", action="store_true", default=False) -parser.add_argument("-rt", "--test", "--tests", "--run-test", "--run-tests", dest="test", help="Run only tests without the scheduler", action="store_true", default=False) -parser.add_argument("-cl", "--collections", dest="collections", help="Process only specified collections (comma-separated list)", type=str, default="") +parser.add_argument("-rt", "--test", "--tests", "--run-test", "--run-tests", dest="test", help="Run in debug mode with only collections that have test: true", action="store_true", default=False) +parser.add_argument("-cl", "--collection", "--collections", dest="collections", help="Process only specified collections (comma-separated list)", type=str, default="") parser.add_argument("-d", "--divider", dest="divider", help="Character that divides the sections (Default: '=')", default="=", type=str) parser.add_argument("-w", "--width", dest="width", help="Screen Width (Default: 100)", default=100, type=int) args = parser.parse_args() @@ -43,7 +44,7 @@ file_handler.setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(lev cmd_handler = logging.StreamHandler() cmd_handler.setFormatter(logging.Formatter("| %(message)-100s |")) -cmd_handler.setLevel(logging.DEBUG if args.tests or args.test else logging.INFO) +cmd_handler.setLevel(logging.DEBUG if args.tests or args.test or args.debug else logging.INFO) logger.addHandler(cmd_handler) logger.addHandler(file_handler)