Plex-Meta-Manager/modules/config.py

589 lines
42 KiB
Python
Raw Normal View History

2021-02-24 06:42:58 +00:00
import logging, os, re, requests
2021-01-20 21:37:59 +00:00
from modules import util
from modules.anidb import AniDBAPI
2021-02-20 05:41:45 +00:00
from modules.builder import CollectionBuilder
2021-01-20 21:37:59 +00:00
from modules.cache import Cache
from modules.imdb import IMDbAPI
from modules.mal import MyAnimeListAPI
from modules.mal import MyAnimeListIDList
2021-02-21 08:13:07 +00:00
from modules.plex import PlexAPI
from modules.radarr import RadarrAPI
from modules.sonarr import SonarrAPI
from modules.tautulli import TautulliAPI
2021-01-20 21:37:59 +00:00
from modules.tmdb import TMDbAPI
2021-02-24 06:42:58 +00:00
from modules.trakttv import TraktAPI
2021-01-20 21:37:59 +00:00
from modules.tvdb import TVDbAPI
from modules.util import Failed
2021-02-15 21:49:56 +00:00
from plexapi.exceptions import BadRequest
2021-01-20 21:37:59 +00:00
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)
2021-02-24 06:44:06 +00:00
elif config_path and not os.path.exists(config_path): raise Failed(f"Config Error: config not found at {os.path.abspath(config_path)}")
2021-01-20 21:37:59 +00:00
elif os.path.exists(os.path.join(default_dir, "config.yml")): self.config_path = os.path.abspath(os.path.join(default_dir, "config.yml"))
2021-02-24 06:44:06 +00:00
else: raise Failed(f"Config Error: config not found at {os.path.abspath(default_dir)}")
logger.info(f"Using {self.config_path} as config")
2021-01-20 21:37:59 +00:00
yaml.YAML().allow_duplicate_keys = True
2021-02-21 20:19:44 +00:00
try:
new_config, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path))
def replace_attr(all_data, attr, par):
if "settings" not in all_data:
all_data["settings"] = {}
if par in all_data and all_data[par] and attr in all_data[par] and attr not in all_data["settings"]:
all_data["settings"][attr] = all_data[par][attr]
del all_data[par][attr]
if "libraries" not in new_config:
new_config["libraries"] = {}
if "settings" not in new_config:
new_config["settings"] = {}
if "tmdb" not in new_config:
new_config["tmdb"] = {}
replace_attr(new_config, "cache", "cache")
replace_attr(new_config, "cache_expiration", "cache")
2021-02-21 20:21:18 +00:00
if "config" in new_config:
del new_config["cache"]
2021-02-21 20:19:44 +00:00
replace_attr(new_config, "asset_directory", "plex")
replace_attr(new_config, "sync_mode", "plex")
replace_attr(new_config, "show_unmanaged", "plex")
replace_attr(new_config, "show_filtered", "plex")
replace_attr(new_config, "show_missing", "plex")
replace_attr(new_config, "save_missing", "plex")
if new_config["libraries"]:
for library in new_config["libraries"]:
if "plex" in new_config["libraries"][library]:
replace_attr(new_config["libraries"][library], "asset_directory", "plex")
replace_attr(new_config["libraries"][library], "sync_mode", "plex")
replace_attr(new_config["libraries"][library], "show_unmanaged", "plex")
replace_attr(new_config["libraries"][library], "show_filtered", "plex")
replace_attr(new_config["libraries"][library], "show_missing", "plex")
replace_attr(new_config["libraries"][library], "save_missing", "plex")
2021-02-22 01:55:54 +00:00
if "libraries" in new_config: new_config["libraries"] = new_config.pop("libraries")
if "settings" in new_config: new_config["settings"] = new_config.pop("settings")
if "plex" in new_config: new_config["plex"] = new_config.pop("plex")
if "tmdb" in new_config: new_config["tmdb"] = new_config.pop("tmdb")
if "tautulli" in new_config: new_config["tautulli"] = new_config.pop("tautulli")
if "radarr" in new_config: new_config["radarr"] = new_config.pop("radarr")
if "sonarr" in new_config: new_config["sonarr"] = new_config.pop("sonarr")
if "trakt" in new_config: new_config["trakt"] = new_config.pop("trakt")
if "mal" in new_config: new_config["mal"] = new_config.pop("mal")
2021-02-21 20:19:44 +00:00
yaml.round_trip_dump(new_config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
self.data = new_config
except yaml.scanner.ScannerError as e:
2021-02-24 06:44:06 +00:00
raise Failed(f"YAML Error: {util.tab_new_lines(e)}")
2021-01-20 21:37:59 +00:00
2021-02-21 08:13:07 +00:00
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):
2021-01-20 21:37:59 +00:00
endline = ""
2021-02-21 08:13:07 +00:00
if parent is not None:
if parent in data:
data = data[parent]
else:
data = None
do_print = False
save = False
2021-02-24 06:44:06 +00:00
text = f"{attribute} attribute" if parent is None else f"{parent} sub-attribute {attribute}"
2021-01-20 21:37:59 +00:00
if data is None or attribute not in data:
2021-02-24 06:44:06 +00:00
message = f"{text} not found"
2021-01-20 21:37:59 +00:00
if parent and save is True:
2021-02-24 06:42:58 +00:00
loaded_config, ind_in, bsi_in = yaml.util.load_yaml_guess_indent(open(self.config_path))
2021-02-24 06:44:06 +00:00
endline = f"\n{parent} sub-attribute {attribute} added to config"
2021-02-24 06:42:58 +00:00
if parent not in loaded_config or not loaded_config[parent]: loaded_config[parent] = {attribute: default}
elif attribute not in loaded_config[parent]: loaded_config[parent][attribute] = default
else: endline = ""
yaml.round_trip_dump(loaded_config, open(self.config_path, "w"), indent=ind_in, block_seq_indent=bsi_in)
elif not data[attribute] and data[attribute] is not False:
2021-02-20 05:41:45 +00:00
if default_is_none is True: return None
2021-02-24 06:44:06 +00:00
else: message = f"{text} is blank"
2021-01-20 21:37:59 +00:00
elif var_type == "bool":
if isinstance(data[attribute], bool): return data[attribute]
2021-02-24 06:44:06 +00:00
else: message = f"{text} must be either true or false"
2021-01-20 21:37:59 +00:00
elif var_type == "int":
if isinstance(data[attribute], int) and data[attribute] > 0: return data[attribute]
2021-02-24 06:44:06 +00:00
else: message = f"{text} must an integer > 0"
2021-01-20 21:37:59 +00:00
elif var_type == "path":
if os.path.exists(os.path.abspath(data[attribute])): return data[attribute]
2021-02-24 06:44:06 +00:00
else: message = f"Path {os.path.abspath(data[attribute])} does not exist"
2021-02-21 08:13:07 +00:00
elif var_type == "list": return util.get_list(data[attribute])
2021-02-24 06:42:58 +00:00
elif var_type == "list_path":
2021-02-21 20:19:44 +00:00
temp_list = [path for path in util.get_list(data[attribute], split=True) if os.path.exists(os.path.abspath(path))]
2021-02-21 08:13:07 +00:00
if len(temp_list) > 0: return temp_list
else: message = "No Paths exist"
2021-02-24 06:42:58 +00:00
elif var_type == "lower_list": return util.get_list(data[attribute], lower=True)
2021-01-20 21:37:59 +00:00
elif test_list is None or data[attribute] in test_list: return data[attribute]
2021-02-24 06:44:06 +00:00
else: message = f"{text}: {data[attribute]} is an invalid input"
2021-02-21 08:13:07 +00:00
if var_type == "path" and default and os.path.exists(os.path.abspath(default)):
return default
elif var_type == "path" and default:
default = None
2021-02-23 15:01:14 +00:00
if attribute in data and data[attribute]:
2021-02-24 06:44:06 +00:00
message = f"neither {data[attribute]} or the default path {default} could be found"
2021-02-23 15:01:14 +00:00
else:
2021-02-24 06:44:06 +00:00
message = f"no {text} found and the default path {default} could be found"
2021-01-20 21:37:59 +00:00
if default is not None or default_is_none:
2021-02-24 06:44:06 +00:00
message = message + f" using {default} as default"
2021-01-20 21:37:59 +00:00
message = message + endline
2021-02-21 08:13:07 +00:00
if req_default and default is None:
2021-02-24 06:44:06 +00:00
raise Failed(f"Config Error: {attribute} attribute must be set under {parent} globally or under this specific Library")
2021-01-20 21:37:59 +00:00
if (default is None and not default_is_none) or throw:
if len(options) > 0:
message = message + "\n" + options
2021-02-24 06:44:06 +00:00
raise Failed(f"Config Error: {message}")
2021-01-20 21:37:59 +00:00
if do_print:
2021-02-24 06:44:06 +00:00
util.print_multiline(f"Config Warning: {message}")
2021-01-20 21:37:59 +00:00
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 = {}
2021-02-21 17:01:10 +00:00
self.general["cache"] = check_for_attribute(self.data, "cache", parent="settings", 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="settings", var_type="int", default=60)
2021-01-20 21:37:59 +00:00
if self.general["cache"]:
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
self.Cache = Cache(self.config_path, self.general["cache_expiration"])
else:
self.Cache = None
2021-02-24 06:42:58 +00:00
self.general["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="settings", var_type="list_path", default=[os.path.join(default_dir, "assets")])
2021-02-21 17:01:10 +00:00
self.general["sync_mode"] = check_for_attribute(self.data, "sync_mode", parent="settings", default="append", test_list=["append", "sync"], options=" append (Only Add Items to the Collection)\n sync (Add & Remove Items from the Collection)")
self.general["show_unmanaged"] = check_for_attribute(self.data, "show_unmanaged", parent="settings", var_type="bool", default=True)
self.general["show_filtered"] = check_for_attribute(self.data, "show_filtered", parent="settings", var_type="bool", default=False)
self.general["show_missing"] = check_for_attribute(self.data, "show_missing", parent="settings", var_type="bool", default=True)
self.general["save_missing"] = check_for_attribute(self.data, "save_missing", parent="settings", var_type="bool", default=True)
2021-01-20 21:37:59 +00:00
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
self.TMDb = None
if "tmdb" in self.data:
logger.info("Connecting to TMDb...")
self.tmdb = {}
2021-02-21 08:13:07 +00:00
try: self.tmdb["apikey"] = check_for_attribute(self.data, "apikey", parent="tmdb", throw=True)
2021-02-21 17:01:10 +00:00
except Failed as e: raise Failed(e)
2021-01-20 21:37:59 +00:00
self.tmdb["language"] = check_for_attribute(self.data, "language", parent="tmdb", default="en")
self.TMDb = TMDbAPI(self.tmdb)
2021-02-24 06:44:06 +00:00
logger.info(f"TMDb Connection {'Failed' if self.TMDb is None else 'Successful'}")
2021-01-20 21:37:59 +00:00
else:
raise Failed("Config Error: tmdb attribute not found")
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
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:
2021-02-21 17:01:10 +00:00
logger.error(e)
2021-02-24 06:44:06 +00:00
logger.info(f"Trakt Connection {'Failed' if self.Trakt is None else 'Successful'}")
2021-01-20 21:37:59 +00:00
else:
logger.warning("trakt attribute not found")
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
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:
2021-02-21 17:01:10 +00:00
logger.error(e)
2021-02-24 06:44:06 +00:00
logger.info(f"My Anime List Connection {'Failed' if self.MyAnimeList is None else 'Successful'}")
2021-01-20 21:37:59 +00:00
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)
2021-02-24 06:42:58 +00:00
util.separator()
2021-02-20 05:41:45 +00:00
logger.info("Connecting to Plex Libraries...")
2021-01-20 21:37:59 +00:00
self.general["plex"] = {}
2021-02-21 08:13:07 +00:00
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)
2021-02-22 01:56:31 +00:00
self.general["plex"]["timeout"] = check_for_attribute(self.data, "timeout", parent="plex", var_type="int", default=60)
2021-01-20 21:37:59 +00:00
self.general["radarr"] = {}
2021-02-21 08:13:07 +00:00
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)
2021-02-24 06:42:58 +00:00
self.general["radarr"]["tag"] = check_for_attribute(self.data, "tag", parent="radarr", var_type="lower_list", default_is_none=True)
2021-01-20 21:37:59 +00:00
self.general["sonarr"] = {}
2021-02-21 08:13:07 +00:00
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)
2021-02-24 06:42:58 +00:00
self.general["sonarr"]["tag"] = check_for_attribute(self.data, "tag", parent="sonarr", var_type="lower_list", default_is_none=True)
2021-01-20 21:37:59 +00:00
self.general["tautulli"] = {}
2021-02-21 08:13:07 +00:00
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)
2021-01-20 21:37:59 +00:00
self.libraries = []
2021-02-21 08:13:07 +00:00
try: libs = check_for_attribute(self.data, "libraries", throw=True)
2021-02-21 17:01:10 +00:00
except Failed as e: raise Failed(e)
2021-01-20 21:37:59 +00:00
for lib in libs:
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
params = {}
if "library_name" in libs[lib] and libs[lib]["library_name"]:
params["name"] = str(libs[lib]["library_name"])
2021-02-24 06:44:06 +00:00
logger.info(f"Connecting to {params['name']} ({lib}) Library...")
2021-01-20 21:37:59 +00:00
else:
params["name"] = str(lib)
2021-02-24 06:44:06 +00:00
logger.info(f"Connecting to {params['name']} Library...")
2021-02-21 17:01:10 +00:00
2021-02-24 06:42:58 +00:00
params["asset_directory"] = check_for_attribute(libs[lib], "asset_directory", parent="settings", var_type="list_path", default=self.general["asset_directory"], default_is_none=True, save=False)
2021-02-21 17:01:10 +00:00
if params["asset_directory"] is None:
logger.warning("Config Warning: Assets will not be used asset_directory attribute must be set under config or under this specific Library")
2021-02-21 17:13:43 +00:00
params["sync_mode"] = check_for_attribute(libs[lib], "sync_mode", parent="settings", test_list=["append", "sync"], options=" append (Only Add Items to the Collection)\n sync (Add & Remove Items from the Collection)", default=self.general["sync_mode"], save=False)
params["show_unmanaged"] = check_for_attribute(libs[lib], "show_unmanaged", parent="settings", var_type="bool", default=self.general["show_unmanaged"], save=False)
params["show_filtered"] = check_for_attribute(libs[lib], "show_filtered", parent="settings", var_type="bool", default=self.general["show_filtered"], save=False)
params["show_missing"] = check_for_attribute(libs[lib], "show_missing", parent="settings", var_type="bool", default=self.general["show_missing"], save=False)
params["save_missing"] = check_for_attribute(libs[lib], "save_missing", parent="settings", var_type="bool", default=self.general["save_missing"], save=False)
2021-02-21 17:01:10 +00:00
2021-01-20 21:37:59 +00:00
try:
2021-02-24 06:44:06 +00:00
params["metadata_path"] = check_for_attribute(libs[lib], "metadata_path", var_type="path", default=os.path.join(default_dir, f"{lib}.yml"), throw=True)
2021-02-21 08:13:07 +00:00
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)
2021-01-20 21:37:59 +00:00
params["plex"] = {}
2021-02-21 08:13:07 +00:00
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)
2021-02-22 01:56:31 +00:00
params["plex"]["timeout"] = check_for_attribute(libs[lib], "timeout", parent="plex", var_type="int", default=self.general["plex"]["timeout"], save=False)
2021-02-21 17:01:10 +00:00
library = PlexAPI(params, self.TMDb, self.TVDb)
2021-02-24 06:44:06 +00:00
logger.info(f"{params['name']} Library Connection Successful")
2021-01-20 21:37:59 +00:00
except Failed as e:
2021-02-21 17:01:10 +00:00
util.print_multiline(e)
2021-02-24 06:44:06 +00:00
logger.info(f"{params['name']} Library Connection Failed")
2021-01-20 21:37:59 +00:00
continue
2021-02-21 08:13:07 +00:00
if self.general["radarr"]["url"] or "radarr" in libs[lib]:
2021-02-24 06:44:06 +00:00
logger.info(f"Connecting to {params['name']} library's Radarr...")
2021-02-21 08:13:07 +00:00
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)
2021-02-24 06:42:58 +00:00
radarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="radarr", var_type="lower_list", default=self.general["radarr"]["tag"], default_is_none=True, save=False)
2021-02-21 17:01:10 +00:00
library.add_Radarr(RadarrAPI(self.TMDb, radarr_params))
2021-02-21 08:13:07 +00:00
except Failed as e:
2021-02-21 17:01:10 +00:00
util.print_multiline(e)
2021-02-24 06:44:06 +00:00
logger.info(f"{params['name']} library's Radarr Connection {'Failed' if library.Radarr is None else 'Successful'}")
2021-02-21 08:13:07 +00:00
if self.general["sonarr"]["url"] or "sonarr" in libs[lib]:
2021-02-24 06:44:06 +00:00
logger.info(f"Connecting to {params['name']} library's Sonarr...")
2021-02-21 08:13:07 +00:00
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)
2021-02-24 06:42:58 +00:00
sonarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="sonarr", var_type="lower_list", default=self.general["sonarr"]["tag"], default_is_none=True, save=False)
2021-02-21 17:01:10 +00:00
library.add_Sonarr(SonarrAPI(self.TVDb, sonarr_params, library.Plex.language))
2021-02-21 08:13:07 +00:00
except Failed as e:
2021-02-21 17:01:10 +00:00
util.print_multiline(e)
2021-02-24 06:44:06 +00:00
logger.info(f"{params['name']} library's Sonarr Connection {'Failed' if library.Sonarr is None else 'Successful'}")
2021-02-21 08:13:07 +00:00
if self.general["tautulli"]["url"] or "tautulli" in libs[lib]:
2021-02-24 06:44:06 +00:00
logger.info(f"Connecting to {params['name']} library's Tautulli...")
2021-02-21 08:13:07 +00:00
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)
2021-02-21 17:01:10 +00:00
library.add_Tautulli(TautulliAPI(tautulli_params))
2021-02-21 08:13:07 +00:00
except Failed as e:
2021-02-21 17:01:10 +00:00
util.print_multiline(e)
2021-02-24 06:44:06 +00:00
logger.info(f"{params['name']} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}")
2021-01-20 21:37:59 +00:00
2021-02-21 17:01:10 +00:00
self.libraries.append(library)
2021-01-20 21:37:59 +00:00
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
if len(self.libraries) > 0:
2021-02-24 06:44:06 +00:00
logger.info(f"{len(self.libraries)} Plex Library Connection{'s' if len(self.libraries) > 1 else ''} Successful")
2021-01-20 21:37:59 +00:00
else:
raise Failed("Plex Error: No Plex libraries were found")
2021-02-24 06:42:58 +00:00
util.separator()
2021-01-20 21:37:59 +00:00
2021-02-15 23:09:24 +00:00
def update_libraries(self, test, requested_collections):
2021-01-20 21:37:59 +00:00
for library in self.libraries:
2021-02-22 01:56:31 +00:00
os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout)
2021-01-20 21:37:59 +00:00
logger.info("")
2021-02-24 06:44:06 +00:00
util.separator(f"{library.name} Library")
2021-02-15 06:51:15 +00:00
try: library.update_metadata(self.TMDb, test)
2021-01-20 21:37:59 +00:00
except Failed as e: logger.error(e)
logger.info("")
2021-02-24 06:44:06 +00:00
util.separator(f"{library.name} Library {'Test ' if test else ''}Collections")
2021-02-17 16:01:24 +00:00
collections = {c: library.collections[c] for c in util.get_list(requested_collections) if c in library.collections} if requested_collections else library.collections
2021-02-17 06:10:50 +00:00
if collections:
2021-01-20 21:37:59 +00:00
logger.info("")
2021-02-24 06:44:06 +00:00
util.separator(f"Mapping {library.name} Library")
2021-01-20 21:37:59 +00:00
logger.info("")
movie_map, show_map = self.map_guids(library)
2021-02-17 06:10:50 +00:00
for c in collections:
2021-02-15 06:51:15 +00:00
if test and ("test" not in collections[c] or collections[c]["test"] is not True):
no_template_test = True
if "template" in collections[c] and collections[c]["template"]:
for data_template in util.get_list(collections[c]["template"], split=False):
if "name" in data_template \
2021-02-24 06:42:58 +00:00
and data_template["name"] \
and library.templates \
and data_template["name"] in library.templates \
and library.templates[data_template["name"]] \
and "test" in library.templates[data_template["name"]] \
and library.templates[data_template["name"]]["test"] is True:
no_template_test = False
if no_template_test:
continue
2021-01-20 21:37:59 +00:00
try:
logger.info("")
2021-02-24 06:44:06 +00:00
util.separator(f"{c} Collection")
2021-01-20 21:37:59 +00:00
logger.info("")
2021-01-26 21:47:46 +00:00
2021-02-24 06:42:58 +00:00
rating_key_map = {}
2021-02-20 05:41:45 +00:00
try:
builder = CollectionBuilder(self, library, c, collections[c])
except Exception as e:
util.print_stacktrace()
logger.error(e)
2021-01-20 21:37:59 +00:00
continue
try:
collection_obj = library.get_collection(c)
collection_name = collection_obj.title
2021-02-24 06:42:58 +00:00
except Failed:
2021-01-20 21:37:59 +00:00
collection_obj = None
collection_name = c
2021-02-20 05:41:45 +00:00
if builder.schedule is not None:
2021-02-24 06:42:58 +00:00
util.print_multiline(builder.schedule, info=True)
2021-02-20 05:41:45 +00:00
logger.info("")
if builder.sync:
2021-01-20 21:37:59 +00:00
logger.info("Sync Mode: sync")
if collection_obj:
2021-01-26 06:25:05 +00:00
for item in collection_obj.items():
2021-02-24 06:42:58 +00:00
rating_key_map[item.ratingKey] = item
2021-01-20 21:37:59 +00:00
else:
logger.info("Sync Mode: append")
2021-02-20 05:41:45 +00:00
for i, f in enumerate(builder.filters):
2021-01-20 21:37:59 +00:00
if i == 0:
logger.info("")
2021-02-24 06:44:06 +00:00
logger.info(f"Collection Filter {f[0]}: {f[1]}")
2021-01-20 21:37:59 +00:00
2021-02-24 06:42:58 +00:00
builder.run_methods(collection_obj, collection_name, rating_key_map, movie_map, show_map)
2021-01-20 21:37:59 +00:00
try:
plex_collection = library.get_collection(collection_name)
except Failed as e:
logger.debug(e)
continue
2021-02-20 05:41:45 +00:00
builder.update_details(plex_collection)
2021-01-20 21:37:59 +00:00
except Exception as e:
util.print_stacktrace()
2021-02-24 06:44:06 +00:00
logger.error(f"Unknown Error: {e}")
2021-02-15 23:09:24 +00:00
if library.show_unmanaged is True and not test and not requested_collections:
logger.info("")
2021-02-24 06:44:06 +00:00
util.separator(f"Unmanaged Collections in {library.name} Library")
logger.info("")
unmanaged_count = 0
2021-02-24 06:42:58 +00:00
collections_in_plex = [str(plex_col) for plex_col in collections]
for col in library.get_all_collections():
2021-02-24 06:42:58 +00:00
if col.title not in collections_in_plex:
logger.info(col.title)
unmanaged_count += 1
logger.info("{} Unmanaged Collections".format(unmanaged_count))
2021-01-20 21:37:59 +00:00
else:
2021-02-16 04:54:47 +00:00
logger.info("")
2021-01-20 21:37:59 +00:00
logger.error("No collection to update")
def map_guids(self, library):
movie_map = {}
show_map = {}
length = 0
2021-02-24 06:44:06 +00:00
logger.info(f"Mapping {'Movie' if library.is_movie else 'Show'} Library: {library.name}")
2021-01-20 21:37:59 +00:00
items = library.Plex.all()
for i, item in enumerate(items, 1):
2021-02-24 06:44:06 +00:00
length = util.print_return(length, f"Processing: {i}/{len(items)} {item.title}")
2021-02-15 21:34:14 +00:00
try:
id_type, main_id = self.get_id(item, library, length)
except BadRequest:
util.print_stacktrace()
2021-02-24 06:44:06 +00:00
util.print_end(length, f"{'Cache | ! |' if self.Cache else 'Mapping Error:'} | {item.guid} for {item.title} not found")
2021-02-15 21:34:14 +00:00
continue
2021-02-12 14:23:07 +00:00
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
2021-02-24 06:44:06 +00:00
util.print_end(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}")
2021-01-20 21:37:59 +00:00
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):
2021-01-21 21:42:31 +00:00
guid = requests.utils.urlparse(item.guid)
2021-01-20 21:37:59 +00:00
item_type = guid.scheme.split(".")[-1]
check_id = guid.netloc
2021-01-25 22:53:24 +00:00
if item_type == "plex" and library.is_movie:
for guid_tag in item.guids:
url_parsed = requests.utils.urlparse(guid_tag.id)
2021-02-16 15:28:11 +00:00
if url_parsed.scheme == "tmdb": tmdb_id = int(url_parsed.netloc)
2021-01-25 22:53:24 +00:00
elif url_parsed.scheme == "imdb": imdb_id = url_parsed.netloc
2021-01-20 21:37:59 +00:00
elif item_type == "imdb": imdb_id = check_id
2021-02-16 15:28:11 +00:00
elif item_type == "thetvdb": tvdb_id = int(check_id)
elif item_type == "themoviedb": tmdb_id = int(check_id)
2021-01-20 21:37:59 +00:00
elif item_type == "hama":
2021-02-16 15:28:11 +00:00
if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1))
2021-01-20 21:37:59 +00:00
elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1)
2021-02-24 06:44:06 +00:00
else: error_message = f"Hama Agent ID: {check_id} not supported"
2021-01-20 21:37:59 +00:00
elif item_type == "myanimelist": mal_id = check_id
elif item_type == "local": error_message = "No match in Plex"
2021-02-24 06:44:06 +00:00
else: error_message = f"Agent {item_type} not supported"
2021-01-20 21:37:59 +00:00
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"])
2021-02-24 06:44:06 +00:00
else: raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} has no other IDs associated with it")
2021-01-20 21:37:59 +00:00
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
2021-02-12 14:23:07 +00:00
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
2021-01-20 21:37:59 +00:00
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
2021-02-24 06:44:06 +00:00
if tmdb_id and imdb_id: id_name = f"TMDb ID: {tmdb_id} or IMDb ID: {imdb_id}"
elif imdb_id and tvdb_id: id_name = f"IMDb ID: {imdb_id} or TVDb ID: {tvdb_id}"
2021-01-20 21:37:59 +00:00
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):
2021-02-12 14:23:07 +00:00
if isinstance(tmdb_id, list):
for i in range(len(tmdb_id)):
2021-02-24 06:44:06 +00:00
util.print_end(length, f"Cache | {'^' if expired is True else '+'} | {item.guid:<46} | {tmdb_id[i] if tmdb_id[i] else 'None':<6} | {imdb_id[i] if imdb_id[i] else 'None':<10} | {tvdb_id if tvdb_id else 'None':<6} | {anidb_id if anidb_id else 'None':<5} | {mal_id if mal_id else 'None':<5} | {item.title}")
2021-02-12 14:23:07 +00:00
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)
2021-01-20 21:37:59 +00:00
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