You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
672 lines
46 KiB
672 lines
46 KiB
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["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["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
|