Plex-Meta-Manager/modules/config.py
2021-02-20 01:41:40 -05:00

693 lines
48 KiB
Python

import glob, logging, os, re, requests
from modules import util
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.tmdb import TMDbAPI
from modules.trakt import TraktAPI
from modules.tvdb import TVDbAPI
from modules.util import Failed
from plexapi.exceptions import BadRequest
from ruamel import yaml
logger = logging.getLogger("Plex Meta Manager")
class Config:
def __init__(self, default_dir, config_path=None):
logger.info("Locating config...")
if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path)
elif config_path and not os.path.exists(config_path): raise Failed("Config Error: config not found at {}".format(os.path.abspath(config_path)))
elif os.path.exists(os.path.join(default_dir, "config.yml")): self.config_path = os.path.abspath(os.path.join(default_dir, "config.yml"))
else: raise Failed("Config Error: config not found at {}".format(os.path.abspath(default_dir)))
logger.info("Using {} as config".format(self.config_path))
yaml.YAML().allow_duplicate_keys = True
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):
message = ""
endline = ""
data = data if parent is None else data[parent]
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)
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)
if parent not in new_config: new_config = {parent: {attribute: default}}
elif not new_config[parent]: new_config[parent] = {attribute: default}
elif attribute not in new_config[parent]: new_config[parent][attribute] = default
else: endLine = ""
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)
elif var_type == "bool":
if isinstance(data[attribute], bool): return data[attribute]
else: message = "Config Error: {} 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)
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)
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])
if default is not None or default_is_none:
message = message + " using {} as default".format(default)
message = message + endline
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)
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
if self.general["cache"]:
util.seperator()
self.Cache = Cache(self.config_path, self.general["cache_expiration"])
else:
self.Cache = None
util.seperator()
self.TMDb = None
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)
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"))
else:
raise Failed("Config Error: tmdb attribute not found")
util.seperator()
self.Trakt = None
if "trakt" in self.data:
logger.info("Connecting to Trakt...")
self.trakt = {}
try:
self.trakt["client_id"] = check_for_attribute(self.data, "client_id", parent="trakt", throw=True)
self.trakt["client_secret"] = check_for_attribute(self.data, "client_secret", parent="trakt", throw=True)
self.trakt["config_path"] = self.config_path
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.info("Trakt Connection {}".format("Failed" if self.Trakt is None else "Successful"))
else:
logger.warning("trakt attribute not found")
util.seperator()
self.MyAnimeList = None
self.MyAnimeListIDList = MyAnimeListIDList()
if "mal" in self.data:
logger.info("Connecting to My Anime List...")
self.mal = {}
try:
self.mal["client_id"] = check_for_attribute(self.data, "client_id", parent="mal", throw=True)
self.mal["client_secret"] = check_for_attribute(self.data, "client_secret", parent="mal", throw=True)
self.mal["config_path"] = self.config_path
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.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful"))
else:
logger.warning("mal attribute not found")
self.TVDb = TVDbAPI(Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt)
self.IMDb = IMDbAPI(Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt, TVDb=self.TVDb) if self.TMDb or self.Trakt else None
self.AniDB = AniDBAPI(Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt)
util.seperator()
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["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["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["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.libraries = []
libs = check_for_attribute(self.data, "libraries", throw=True)
for lib in libs:
util.seperator()
params = {}
if "library_name" in libs[lib] and libs[lib]["library_name"]:
params["name"] = str(libs[lib]["library_name"])
logger.info("Connecting to {} ({}) Library...".format(params["name"], lib))
else:
params["name"] = str(lib)
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["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")
except Failed as e:
logger.error("Config Error: Skipping {} Library {}".format(str(lib), e))
continue
params["asset_directory"] = None
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"]))
else:
logger.warning("Config Warning: Assets will not be used asset_directory library attribute is blank")
elif self.general["plex"]["asset_directory"]:
params["asset_directory"] = self.general["plex"]["asset_directory"]
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
try:
self.libraries.append(PlexAPI(params))
logger.info("{} Library Connection Successful".format(params["name"]))
except Failed as e:
logger.error(e)
logger.info("{} Library Connection Failed".format(params["name"]))
continue
util.seperator()
if len(self.libraries) > 0:
logger.info("{} Plex Library Connection{} Successful".format(len(self.libraries), "s" if len(self.libraries) > 1 else ""))
else:
raise Failed("Plex Error: No Plex libraries were found")
util.seperator()
def update_libraries(self, test, requested_collections):
for library in self.libraries:
logger.info("")
util.seperator("{} Library".format(library.name))
try: library.update_metadata(self.TMDb, test)
except Failed as e: logger.error(e)
logger.info("")
util.seperator("{} Library {}Collections".format(library.name, "Test " if test else ""))
collections = {c: library.collections[c] for c in util.get_list(requested_collections) if c in library.collections} if requested_collections else library.collections
if collections:
logger.info("")
util.seperator("Mapping {} Library".format(library.name))
logger.info("")
movie_map, show_map = self.map_guids(library)
for c in collections:
if test and ("test" not in collections[c] or collections[c]["test"] is not True):
continue
try:
logger.info("")
util.seperator("{} Collection".format(c))
logger.info("")
map = {}
try:
builder = CollectionBuilder(self, library, c, collections[c])
except Exception as e:
util.print_stacktrace()
logger.error(e)
continue
try:
collection_obj = library.get_collection(c)
collection_name = collection_obj.title
except Failed as e:
collection_obj = None
collection_name = c
if builder.schedule is not None:
print_multiline(builder.schedule, info=True)
logger.info("")
if builder.sync:
logger.info("Sync Mode: sync")
if collection_obj:
for item in collection_obj.items():
map[item.ratingKey] = item
else:
logger.info("Sync Mode: append")
for i, f in enumerate(builder.filters):
if i == 0:
logger.info("")
logger.info("Collection Filter {}: {}".format(f[0], f[1]))
builder.run_methods(collection_obj, collection_name, map, movie_map, show_map)
try:
plex_collection = library.get_collection(collection_name)
except Failed as e:
logger.debug(e)
continue
builder.update_details(plex_collection)
except Exception as e:
util.print_stacktrace()
logger.error("Unknown Error: {}".format(e))
if library.show_unmanaged is True and not test and not requested_collections:
logger.info("")
util.seperator("Unmanaged Collections in {} Library".format(library.name))
logger.info("")
unmanaged_count = 0
collections_in_plex = [str(pcol) for pcol in collections]
for col in library.get_all_collections():
if col.title not in collections_in_plex:
logger.info(col.title)
unmanaged_count += 1
logger.info("{} Unmanaged Collections".format(unmanaged_count))
else:
logger.info("")
logger.error("No collection to update")
def map_guids(self, library):
movie_map = {}
show_map = {}
length = 0
count = 0
logger.info("Mapping {} Library: {}".format("Movie" if library.is_movie else "Show", library.name))
items = library.Plex.all()
for i, item in enumerate(items, 1):
length = util.print_return(length, "Processing: {}/{} {}".format(i, len(items), item.title))
try:
id_type, main_id = self.get_id(item, library, length)
except BadRequest:
util.print_stacktrace()
util.print_end(length, "{} {:<46} | {} for {}".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, error_message, item.title))
continue
if isinstance(main_id, list):
if id_type == "movie":
for m in main_id: movie_map[m] = item.ratingKey
elif id_type == "show":
for m in main_id: show_map[m] = item.ratingKey
else:
if id_type == "movie": movie_map[main_id] = item.ratingKey
elif id_type == "show": show_map[main_id] = item.ratingKey
util.print_end(length, "Processed {} {}".format(len(items), "Movies" if library.is_movie else "Shows"))
return movie_map, show_map
def get_id(self, item, library, length):
expired = None
tmdb_id = None
imdb_id = None
tvdb_id = None
anidb_id = None
mal_id = None
error_message = None
if self.Cache:
if library.is_movie: tmdb_id, expired = self.Cache.get_tmdb_id("movie", plex_guid=item.guid)
else: tvdb_id, expired = self.Cache.get_tvdb_id("show", plex_guid=item.guid)
if not tvdb_id and library.is_show:
tmdb_id, expired = self.Cache.get_tmdb_id("show", plex_guid=item.guid)
anidb_id, expired = self.Cache.get_anidb_id("show", plex_guid=item.guid)
if expired or (not tmdb_id and library.is_movie) or (not tvdb_id and not tmdb_id and library.is_show):
guid = requests.utils.urlparse(item.guid)
item_type = guid.scheme.split(".")[-1]
check_id = guid.netloc
if item_type == "plex" and library.is_movie:
for guid_tag in item.guids:
url_parsed = requests.utils.urlparse(guid_tag.id)
if url_parsed.scheme == "tmdb": tmdb_id = int(url_parsed.netloc)
elif url_parsed.scheme == "imdb": imdb_id = url_parsed.netloc
elif item_type == "imdb": imdb_id = check_id
elif item_type == "thetvdb": tvdb_id = int(check_id)
elif item_type == "themoviedb": tmdb_id = int(check_id)
elif item_type == "hama":
if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1))
elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1)
else: error_message = "Hama Agent ID: {} not supported".format(check_id)
elif item_type == "myanimelist": mal_id = check_id
elif item_type == "local": error_message = "No match in Plex"
else: error_message = "Agent {} not supported".format(item_type)
if not error_message:
if anidb_id and not tvdb_id:
try: tvdb_id = self.AniDB.convert_anidb_to_tvdb(anidb_id)
except Failed: pass
if anidb_id and not imdb_id:
try: imdb_id = self.AniDB.convert_anidb_to_imdb(anidb_id)
except Failed: pass
if mal_id:
try:
ids = self.MyAnimeListIDList.find_mal_ids(mal_id)
if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: tvdb_id = int(ids["thetvdb_id"])
elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: tmdb_id = int(ids["themoviedb_id"])
else: raise Failed("MyAnimeList Error: MyAnimeList ID: {} has no other IDs associated with it".format(mal_id))
except Failed:
pass
if mal_id and not tvdb_id:
try: tvdb_id = self.MyAnimeListIDList.convert_mal_to_tvdb(mal_id)
except Failed: pass
if mal_id and not tmdb_id:
try: tmdb_id = self.MyAnimeListIDList.convert_mal_to_tmdb(mal_id)
except Failed: pass
if not tmdb_id and imdb_id and isinstance(imdb_id, list) and self.TMDb:
tmdb_id = []
new_imdb_id = []
for imdb in imdb_id:
try:
temp_tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb)
tmdb_id.append(temp_tmdb_id)
new_imdb_id.append(imdb)
except Failed:
continue
imdb_id = new_imdb_id
if not tmdb_id and imdb_id and self.TMDb:
try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id)
except Failed: pass
if not tmdb_id and imdb_id and self.Trakt:
try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb_id)
except Failed: pass
if not tmdb_id and tvdb_id and self.TMDb:
try: tmdb_id = self.TMDb.convert_tvdb_to_tmdb(tvdb_id)
except Failed: pass
if not tmdb_id and tvdb_id and self.Trakt:
try: tmdb_id = self.Trakt.convert_tvdb_to_tmdb(tvdb_id)
except Failed: pass
if not imdb_id and tmdb_id and self.TMDb:
try: imdb_id = self.TMDb.convert_tmdb_to_imdb(tmdb_id)
except Failed: pass
if not imdb_id and tmdb_id and self.Trakt:
try: imdb_id = self.Trakt.convert_tmdb_to_imdb(tmdb_id)
except Failed: pass
if not imdb_id and tvdb_id and self.Trakt:
try: imdb_id = self.Trakt.convert_tmdb_to_imdb(tmdb_id)
except Failed: pass
if not tvdb_id and tmdb_id and self.TMDb and library.is_show:
try: tvdb_id = self.TMDb.convert_tmdb_to_tvdb(tmdb_id)
except Failed: pass
if not tvdb_id and tmdb_id and self.Trakt and library.is_show:
try: tvdb_id = self.Trakt.convert_tmdb_to_tvdb(tmdb_id)
except Failed: pass
if not tvdb_id and imdb_id and self.Trakt and library.is_show:
try: tvdb_id = self.Trakt.convert_imdb_to_tvdb(imdb_id)
except Failed: pass
if (not tmdb_id and library.is_movie) or (not tvdb_id and not ((anidb_id or mal_id) and tmdb_id) and library.is_show):
service_name = "TMDb ID" if library.is_movie else "TVDb ID"
if self.TMDb and self.Trakt: api_name = "TMDb or Trakt"
elif self.TMDb: api_name = "TMDb"
elif self.Trakt: api_name = "Trakt"
else: api_name = None
if tmdb_id and imdb_id: id_name = "TMDb ID: {} or IMDb ID: {}".format(tmdb_id, imdb_id)
elif imdb_id and tvdb_id: id_name = "IMDb ID: {} or TVDb ID: {}".format(imdb_id, tvdb_id)
elif tmdb_id: id_name = "TMDb ID: {}".format(tmdb_id)
elif imdb_id: id_name = "IMDb ID: {}".format(imdb_id)
elif tvdb_id: id_name = "TVDb ID: {}".format(tvdb_id)
else: id_name = None
if anidb_id and not tmdb_id and not tvdb_id: error_message = "Unable to convert AniDb ID: {} to TMDb ID or TVDb ID".format(anidb_id)
elif mal_id and not tmdb_id and not tvdb_id: error_message = "Unable to convert MyAnimeList ID: {} to TMDb ID or TVDb ID".format(mal_id)
elif id_name and api_name: error_message = "Unable to convert {} to {} using {}".format(id_name, service_name, api_name)
elif id_name: error_message = "Configure TMDb or Trakt to covert {} to {}".format(id_name, service_name)
else: error_message = "No ID to convert to {}".format(service_name)
if self.Cache and (tmdb_id and library.is_movie) or ((tvdb_id or ((anidb_id or mal_id) and tmdb_id)) and library.is_show):
if isinstance(tmdb_id, list):
for i in range(len(tmdb_id)):
util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id[i] if tmdb_id[i] else "None", imdb_id[i] if imdb_id[i] else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title))
self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id[i], imdb_id[i], tvdb_id, anidb_id, mal_id, expired)
else:
util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id if tmdb_id else "None", imdb_id if imdb_id else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title))
self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired)
if tmdb_id and library.is_movie: return "movie", tmdb_id
elif tvdb_id and library.is_show: return "show", tvdb_id
elif (anidb_id or mal_id) and tmdb_id: return "movie", tmdb_id
else:
util.print_end(length, "{} {:<46} | {} for {}".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, error_message, item.title))
return None, None