mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-22 04:23:08 +00:00
clean up
This commit is contained in:
parent
1a3b224244
commit
e5c43e0fa7
16 changed files with 180 additions and 175 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,6 +7,7 @@ __pycache__/
|
|||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.idea
|
||||
.Python
|
||||
/modules/test.py
|
||||
logs/
|
||||
|
|
|
@ -25,7 +25,7 @@ class AniDBAPI:
|
|||
def convert_anidb(self, input_id, from_id, to_id):
|
||||
ids = self.id_list.xpath("//anime[contains(@{}, '{}')]/@{}".format(from_id, input_id, to_id))
|
||||
if len(ids) > 0:
|
||||
if from_id == "tvdbid": return [int(id) for id in ids]
|
||||
if from_id == "tvdbid": return [int(i) for i in ids]
|
||||
if len(ids[0]) > 0:
|
||||
try: return ids[0].split(",") if to_id == "imdbid" else int(ids[0])
|
||||
except ValueError: raise Failed("AniDB Error: No {} ID found for {} ID: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id))
|
||||
|
@ -80,7 +80,7 @@ class AniDBAPI:
|
|||
movie_ids = []
|
||||
for anidb_id in anime_ids:
|
||||
try:
|
||||
tmdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id), language)
|
||||
tmdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id))
|
||||
if tmdb_id: movie_ids.append(tmdb_id)
|
||||
else: raise Failed
|
||||
except Failed:
|
||||
|
@ -92,15 +92,15 @@ class AniDBAPI:
|
|||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id, language):
|
||||
def convert_from_imdb(self, imdb_id):
|
||||
output_tmdb_ids = []
|
||||
if not isinstance(imdb_id, list):
|
||||
imdb_id = [imdb_id]
|
||||
|
||||
for imdb in imdb_id:
|
||||
expired = False
|
||||
if self.Cache:
|
||||
tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb)
|
||||
expired = False
|
||||
if not tmdb_id:
|
||||
tmdb_id, expired = self.Cache.get_tmdb_from_imdb(imdb)
|
||||
if expired:
|
||||
|
|
|
@ -61,17 +61,14 @@ class CollectionBuilder:
|
|||
else:
|
||||
raise Failed("Collection Error: template sub-attribute default is blank")
|
||||
|
||||
|
||||
|
||||
for m in template:
|
||||
if m not in self.data and m != "default":
|
||||
if template[m]:
|
||||
attr = None
|
||||
def replace_txt(txt):
|
||||
txt = str(txt)
|
||||
for tm in data_template:
|
||||
if tm != "name" and "<<{}>>".format(tm) in txt:
|
||||
txt = txt.replace("<<{}>>".format(tm), str(data_template[tm]))
|
||||
for template_method in data_template:
|
||||
if template_method != "name" and "<<{}>>".format(template_method) in txt:
|
||||
txt = txt.replace("<<{}>>".format(template_method), str(data_template[template_method]))
|
||||
if "<<collection_name>>" in txt:
|
||||
txt = txt.replace("<<collection_name>>", str(self.name))
|
||||
for dm in default:
|
||||
|
@ -134,7 +131,7 @@ class CollectionBuilder:
|
|||
if weekday == current_time.weekday():
|
||||
skip_collection = False
|
||||
else:
|
||||
logger.error("Collection Error: weekly schedule attribute {} invalid must be a day of the weeek i.e. weekly(Monday)".format(schedule))
|
||||
logger.error("Collection Error: weekly schedule attribute {} invalid must be a day of the week i.e. weekly(Monday)".format(schedule))
|
||||
elif run_time.startswith("month"):
|
||||
try:
|
||||
if 1 <= int(param) <= 31:
|
||||
|
@ -162,7 +159,7 @@ class CollectionBuilder:
|
|||
if self.schedule is None:
|
||||
skip_collection = False
|
||||
if skip_collection:
|
||||
raise Failed("Skipping Collection {}".format(c))
|
||||
raise Failed("Skipping Collection {}".format(self.name))
|
||||
|
||||
logger.info("Scanning {} Collection".format(self.name))
|
||||
|
||||
|
@ -293,7 +290,6 @@ class CollectionBuilder:
|
|||
elif method_name == "imdb_list":
|
||||
new_list = []
|
||||
for imdb_list in util.get_list(data[m], split=False):
|
||||
new_dictionary = {}
|
||||
if isinstance(imdb_list, dict):
|
||||
if "url" in imdb_list and imdb_list["url"]: imdb_url = imdb_list["url"]
|
||||
else: raise Failed("Collection Error: imdb_list attribute url is required")
|
||||
|
@ -305,25 +301,25 @@ class CollectionBuilder:
|
|||
self.methods.append((method_name, new_list))
|
||||
elif method_name in util.dictionary_lists:
|
||||
if isinstance(data[m], dict):
|
||||
def get_int(parent, method, data, default, min=1, max=None):
|
||||
if method not in data: logger.warning("Collection Warning: {} {} attribute not found using {} as default".format(parent, method, default))
|
||||
elif not data[method]: logger.warning("Collection Warning: {} {} attribute is blank using {} as default".format(parent, method, default))
|
||||
elif isinstance(data[method], int) and data[method] >= min:
|
||||
if max is None or data[method] <= max: return data[method]
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer <= {} using {} as default".format(parent, method, data[method], max, default))
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer >= {} using {} as default".format(parent, method, data[method], min, default))
|
||||
return default
|
||||
def get_int(parent, method, data_in, default_in, minimum=1, maximum=None):
|
||||
if method not in data_in: logger.warning("Collection Warning: {} {} attribute not found using {} as default".format(parent, method, default))
|
||||
elif not data_in[method]: logger.warning("Collection Warning: {} {} attribute is blank using {} as default".format(parent, method, default))
|
||||
elif isinstance(data_in[method], int) and data_in[method] >= minimum:
|
||||
if maximum is None or data_in[method] <= maximum: return data_in[method]
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer <= {} using {} as default".format(parent, method, data_in[method], maximum, default))
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer >= {} using {} as default".format(parent, method, data_in[method], minimum, default))
|
||||
return default_in
|
||||
if method_name == "filters":
|
||||
for f in data[m]:
|
||||
if f in util.method_alias or (f.endswith(".not") and f[:-4] in util.method_alias):
|
||||
filter = (util.method_alias[f[:-4]] + f[-4:]) if f.endswith(".not") else util.method_alias[f]
|
||||
logger.warning("Collection Warning: {} filter will run as {}".format(f, filter))
|
||||
filter_method = (util.method_alias[f[:-4]] + f[-4:]) if f.endswith(".not") else util.method_alias[f]
|
||||
logger.warning("Collection Warning: {} filter will run as {}".format(f, filter_method))
|
||||
else:
|
||||
filter = f
|
||||
if filter in util.movie_only_filters and self.library.is_show: raise Failed("Collection Error: {} filter only works for movie libraries".format(filter))
|
||||
elif data[m][f] is None: raise Failed("Collection Error: {} filter is blank".format(filter))
|
||||
elif filter in util.all_filters: self.filters.append((filter, data[m][f]))
|
||||
else: raise Failed("Collection Error: {} filter not supported".format(filter))
|
||||
filter_method = f
|
||||
if filter_method in util.movie_only_filters and self.library.is_show: raise Failed("Collection Error: {} filter only works for movie libraries".format(filter_method))
|
||||
elif data[m][f] is None: raise Failed("Collection Error: {} filter is blank".format(filter_method))
|
||||
elif filter_method in util.all_filters: self.filters.append((filter_method, data[m][f]))
|
||||
else: raise Failed("Collection Error: {} filter not supported".format(filter_method))
|
||||
elif method_name == "plex_collectionless":
|
||||
new_dictionary = {}
|
||||
prefix_list = []
|
||||
|
@ -375,12 +371,12 @@ class CollectionBuilder:
|
|||
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(attr_data)):
|
||||
new_dictionary[attr] = str(attr_data)
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ([a-z]{2})-([A-Z]{2}) e.g. en-US".format(m, attr, attr_data))
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US".format(m, attr, attr_data))
|
||||
elif attr == "region":
|
||||
if re.compile("^[A-Z]{2}$").match(str(attr_data)):
|
||||
new_dictionary[attr] = str(attr_data)
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ^[A-Z]{2}$ e.g. US".format(m, attr, attr_data))
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ^[A-Z]{{2}}$ e.g. US".format(m, attr, attr_data))
|
||||
elif attr == "sort_by":
|
||||
if (self.library.is_movie and attr_data in util.discover_movie_sort) or (self.library.is_show and attr_data in util.discover_tv_sort):
|
||||
new_dictionary[attr] = attr_data
|
||||
|
@ -409,7 +405,7 @@ class CollectionBuilder:
|
|||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern MM/DD/YYYY e.g. 12/25/2020".format(m, attr, attr_data))
|
||||
elif attr in ["primary_release_year", "year", "first_air_date_year"]:
|
||||
if isinstance(attr_data, int) and 1800 < attr_data and attr_data < 2200:
|
||||
if isinstance(attr_data, int) and 1800 < attr_data < 2200:
|
||||
new_dictionary[attr] = attr_data
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: must be a valid year e.g. 1990".format(m, attr))
|
||||
|
@ -463,8 +459,8 @@ class CollectionBuilder:
|
|||
elif data[m]["season"] not in util.pretty_seasons: logger.warning("Collection Warning: mal_season season attribute {} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {} as default".format(data[m]["season"], new_dictionary["season"]))
|
||||
else: new_dictionary["season"] = data[m]["season"]
|
||||
|
||||
new_dictionary["year"] = get_int(method_name, "year", data[m], current_time.year, min=1917, max=current_time.year + 1)
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, max=500)
|
||||
new_dictionary["year"] = get_int(method_name, "year", data[m], current_time.year, minimum=1917, maximum=current_time.year + 1)
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, maximum=500)
|
||||
self.methods.append((method_name, [new_dictionary]))
|
||||
elif method_name == "mal_userlist":
|
||||
new_dictionary = {"status": "all", "sort_by": "list_score"}
|
||||
|
@ -482,7 +478,7 @@ class CollectionBuilder:
|
|||
elif data[m]["sort_by"] not in util.mal_userlist_sort: logger.warning("Collection Warning: mal_season sort_by attribute {} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default".format(data[m]["sort_by"]))
|
||||
else: new_dictionary["sort_by"] = util.mal_userlist_sort[data[m]["sort_by"]]
|
||||
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, max=1000)
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, maximum=1000)
|
||||
self.methods.append((method_name, [new_dictionary]))
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute is not a dictionary: {}".format(m, data[m]))
|
||||
|
@ -523,7 +519,7 @@ class CollectionBuilder:
|
|||
if self.library.Sonarr:
|
||||
self.do_arr = self.details["add_to_arr"] if "add_to_arr" in self.details else self.library.Sonarr.add
|
||||
|
||||
def run_methods(self, collection_obj, collection_name, map, movie_map, show_map):
|
||||
def run_methods(self, collection_obj, collection_name, rating_key_map, movie_map, show_map):
|
||||
items_found = 0
|
||||
for method, values in self.methods:
|
||||
logger.debug("")
|
||||
|
@ -559,7 +555,6 @@ class CollectionBuilder:
|
|||
items_found += len(items)
|
||||
elif method == "plex_search":
|
||||
search_terms = {}
|
||||
output = ""
|
||||
for i, attr_pair in enumerate(value):
|
||||
search_list = attr_pair[1]
|
||||
final_method = attr_pair[0][:-4] + "!" if attr_pair[0][-4:] == ".not" else attr_pair[0]
|
||||
|
@ -611,7 +606,7 @@ class CollectionBuilder:
|
|||
elif "trakt" in method: items_found += check_map(self.config.Trakt.get_items(method, value, self.library.is_movie))
|
||||
else: logger.error("Collection Error: {} method not supported".format(method))
|
||||
|
||||
if len(items) > 0: map = self.library.add_to_collection(collection_obj if collection_obj else collection_name, items, self.filters, self.details["show_filtered"], map, movie_map, show_map)
|
||||
if len(items) > 0: rating_key_map = self.library.add_to_collection(collection_obj if collection_obj else collection_name, items, self.filters, self.details["show_filtered"], rating_key_map, movie_map, show_map)
|
||||
else: logger.error("No items found to add to this collection ")
|
||||
|
||||
if len(missing_movies) > 0 or len(missing_shows) > 0:
|
||||
|
@ -662,7 +657,7 @@ class CollectionBuilder:
|
|||
if self.sync and items_found > 0:
|
||||
logger.info("")
|
||||
count_removed = 0
|
||||
for ratingKey, item in map.items():
|
||||
for ratingKey, item in rating_key_map.items():
|
||||
if item is not None:
|
||||
logger.info("{} Collection | - | {}".format(collection_name, item.title))
|
||||
item.removeCollection(collection_name)
|
||||
|
@ -695,10 +690,10 @@ class CollectionBuilder:
|
|||
item_labels = [label.tag for label in collection.labels]
|
||||
labels = util.get_list(self.details["label"])
|
||||
if "label_sync_mode" in self.details and self.details["label_sync_mode"] == "sync":
|
||||
for label in (l for l in item_labels if l not in labels):
|
||||
for label in (la for la in item_labels if la not in labels):
|
||||
collection.removeLabel(label)
|
||||
logger.info("Detail: Label {} removed".format(label))
|
||||
for label in (l for l in labels if l not in item_labels):
|
||||
for label in (la for la in labels if la not in item_labels):
|
||||
collection.addLabel(label)
|
||||
logger.info("Detail: Label {} added".format(label))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import glob, logging, os, re, requests
|
||||
import logging, os, re, requests
|
||||
from modules import util
|
||||
from modules.anidb import AniDBAPI
|
||||
from modules.builder import CollectionBuilder
|
||||
|
@ -11,7 +11,7 @@ from modules.radarr import RadarrAPI
|
|||
from modules.sonarr import SonarrAPI
|
||||
from modules.tautulli import TautulliAPI
|
||||
from modules.tmdb import TMDbAPI
|
||||
from modules.trakt import TraktAPI
|
||||
from modules.trakttv import TraktAPI
|
||||
from modules.tvdb import TVDbAPI
|
||||
from modules.util import Failed
|
||||
from plexapi.exceptions import BadRequest
|
||||
|
@ -77,7 +77,6 @@ class Config:
|
|||
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, req_default=False, var_type="str", throw=False, save=True):
|
||||
message = ""
|
||||
endline = ""
|
||||
if parent is not None:
|
||||
if parent in data:
|
||||
|
@ -90,14 +89,13 @@ class Config:
|
|||
if data is None or attribute not in data:
|
||||
message = "{} not found".format(text)
|
||||
if parent and save is True:
|
||||
new_config, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path))
|
||||
loaded_config, ind_in, bsi_in = 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 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:
|
||||
if default_is_none is True: return None
|
||||
else: message = "{} is blank".format(text)
|
||||
elif var_type == "bool":
|
||||
|
@ -110,11 +108,11 @@ class Config:
|
|||
if os.path.exists(os.path.abspath(data[attribute])): return data[attribute]
|
||||
else: message = "Path {} does not exist".format(os.path.abspath(data[attribute]))
|
||||
elif var_type == "list": return util.get_list(data[attribute])
|
||||
elif var_type == "listpath":
|
||||
elif var_type == "list_path":
|
||||
temp_list = [path for path in util.get_list(data[attribute], split=True) if os.path.exists(os.path.abspath(path))]
|
||||
if len(temp_list) > 0: return temp_list
|
||||
else: message = "No Paths exist"
|
||||
elif var_type == "lowerlist": return util.get_list(data[attribute], lower=True)
|
||||
elif var_type == "lower_list": return util.get_list(data[attribute], lower=True)
|
||||
elif test_list is None or data[attribute] in test_list: return data[attribute]
|
||||
else: message = "{}: {} is an invalid input".format(text, data[attribute])
|
||||
if var_type == "path" and default and os.path.exists(os.path.abspath(default)):
|
||||
|
@ -144,18 +142,18 @@ class Config:
|
|||
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)
|
||||
if self.general["cache"]:
|
||||
util.seperator()
|
||||
util.separator()
|
||||
self.Cache = Cache(self.config_path, self.general["cache_expiration"])
|
||||
else:
|
||||
self.Cache = None
|
||||
self.general["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="settings", var_type="listpath", default=[os.path.join(default_dir, "assets")])
|
||||
self.general["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="settings", var_type="list_path", default=[os.path.join(default_dir, "assets")])
|
||||
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)
|
||||
|
||||
util.seperator()
|
||||
util.separator()
|
||||
|
||||
self.TMDb = None
|
||||
if "tmdb" in self.data:
|
||||
|
@ -169,7 +167,7 @@ class Config:
|
|||
else:
|
||||
raise Failed("Config Error: tmdb attribute not found")
|
||||
|
||||
util.seperator()
|
||||
util.separator()
|
||||
|
||||
self.Trakt = None
|
||||
if "trakt" in self.data:
|
||||
|
@ -187,7 +185,7 @@ class Config:
|
|||
else:
|
||||
logger.warning("trakt attribute not found")
|
||||
|
||||
util.seperator()
|
||||
util.separator()
|
||||
|
||||
self.MyAnimeList = None
|
||||
self.MyAnimeListIDList = MyAnimeListIDList()
|
||||
|
@ -210,7 +208,7 @@ class Config:
|
|||
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()
|
||||
util.separator()
|
||||
|
||||
logger.info("Connecting to Plex Libraries...")
|
||||
|
||||
|
@ -227,7 +225,7 @@ class Config:
|
|||
self.general["radarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="radarr", default_is_none=True)
|
||||
self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False)
|
||||
self.general["radarr"]["search"] = check_for_attribute(self.data, "search", parent="radarr", var_type="bool", default=False)
|
||||
self.general["radarr"]["tag"] = check_for_attribute(self.data, "tag", parent="radarr", var_type="lowerlist", default_is_none=True)
|
||||
self.general["radarr"]["tag"] = check_for_attribute(self.data, "tag", parent="radarr", var_type="lower_list", default_is_none=True)
|
||||
|
||||
self.general["sonarr"] = {}
|
||||
self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", default_is_none=True)
|
||||
|
@ -237,7 +235,7 @@ class Config:
|
|||
self.general["sonarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="sonarr", default_is_none=True)
|
||||
self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False)
|
||||
self.general["sonarr"]["search"] = check_for_attribute(self.data, "search", parent="sonarr", var_type="bool", default=False)
|
||||
self.general["sonarr"]["tag"] = check_for_attribute(self.data, "tag", parent="sonarr", var_type="lowerlist", default_is_none=True)
|
||||
self.general["sonarr"]["tag"] = check_for_attribute(self.data, "tag", parent="sonarr", var_type="lower_list", default_is_none=True)
|
||||
|
||||
self.general["tautulli"] = {}
|
||||
self.general["tautulli"]["url"] = check_for_attribute(self.data, "url", parent="tautulli", default_is_none=True)
|
||||
|
@ -247,7 +245,7 @@ class Config:
|
|||
try: libs = check_for_attribute(self.data, "libraries", throw=True)
|
||||
except Failed as e: raise Failed(e)
|
||||
for lib in libs:
|
||||
util.seperator()
|
||||
util.separator()
|
||||
params = {}
|
||||
if "library_name" in libs[lib] and libs[lib]["library_name"]:
|
||||
params["name"] = str(libs[lib]["library_name"])
|
||||
|
@ -255,9 +253,8 @@ class Config:
|
|||
else:
|
||||
params["name"] = str(lib)
|
||||
logger.info("Connecting to {} Library...".format(params["name"]))
|
||||
default_lib = os.path.join(default_dir, "{}.yml".format(lib))
|
||||
|
||||
params["asset_directory"] = check_for_attribute(libs[lib], "asset_directory", parent="settings", var_type="listpath", default=self.general["asset_directory"], default_is_none=True, save=False)
|
||||
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)
|
||||
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")
|
||||
|
||||
|
@ -292,7 +289,7 @@ class Config:
|
|||
radarr_params["root_folder_path"] = check_for_attribute(libs[lib], "root_folder_path", parent="radarr", default=self.general["radarr"]["root_folder_path"], req_default=True, save=False)
|
||||
radarr_params["add"] = check_for_attribute(libs[lib], "add", parent="radarr", var_type="bool", default=self.general["radarr"]["add"], save=False)
|
||||
radarr_params["search"] = check_for_attribute(libs[lib], "search", parent="radarr", var_type="bool", default=self.general["radarr"]["search"], save=False)
|
||||
radarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="radarr", var_type="lowerlist", default=self.general["radarr"]["tag"], default_is_none=True, save=False)
|
||||
radarr_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)
|
||||
library.add_Radarr(RadarrAPI(self.TMDb, radarr_params))
|
||||
except Failed as e:
|
||||
util.print_multiline(e)
|
||||
|
@ -309,7 +306,7 @@ class Config:
|
|||
sonarr_params["root_folder_path"] = check_for_attribute(libs[lib], "root_folder_path", parent="sonarr", default=self.general["sonarr"]["root_folder_path"], req_default=True, save=False)
|
||||
sonarr_params["add"] = check_for_attribute(libs[lib], "add", parent="sonarr", var_type="bool", default=self.general["sonarr"]["add"], save=False)
|
||||
sonarr_params["search"] = check_for_attribute(libs[lib], "search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["search"], save=False)
|
||||
sonarr_params["tag"] = check_for_attribute(libs[lib], "search", parent="sonarr", var_type="lowerlist", default=self.general["sonarr"]["tag"], default_is_none=True, save=False)
|
||||
sonarr_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)
|
||||
library.add_Sonarr(SonarrAPI(self.TVDb, sonarr_params, library.Plex.language))
|
||||
except Failed as e:
|
||||
util.print_multiline(e)
|
||||
|
@ -328,28 +325,28 @@ class Config:
|
|||
|
||||
self.libraries.append(library)
|
||||
|
||||
util.seperator()
|
||||
util.separator()
|
||||
|
||||
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()
|
||||
util.separator()
|
||||
|
||||
def update_libraries(self, test, requested_collections):
|
||||
for library in self.libraries:
|
||||
os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout)
|
||||
logger.info("")
|
||||
util.seperator("{} Library".format(library.name))
|
||||
util.separator("{} 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 ""))
|
||||
util.separator("{} 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))
|
||||
util.separator("Mapping {} Library".format(library.name))
|
||||
logger.info("")
|
||||
movie_map, show_map = self.map_guids(library)
|
||||
for c in collections:
|
||||
|
@ -358,21 +355,21 @@ class Config:
|
|||
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 \
|
||||
and data_template["name"] \
|
||||
and library.templates \
|
||||
and data_template["name"] in self.library.templates \
|
||||
and self.library.templates[data_template["name"]] \
|
||||
and "test" in self.library.templates[data_template["name"]] \
|
||||
and self.library.templates[data_template["name"]]["test"] == True:
|
||||
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
|
||||
try:
|
||||
logger.info("")
|
||||
util.seperator("{} Collection".format(c))
|
||||
util.separator("{} Collection".format(c))
|
||||
logger.info("")
|
||||
|
||||
map = {}
|
||||
rating_key_map = {}
|
||||
try:
|
||||
builder = CollectionBuilder(self, library, c, collections[c])
|
||||
except Exception as e:
|
||||
|
@ -383,19 +380,19 @@ class Config:
|
|||
try:
|
||||
collection_obj = library.get_collection(c)
|
||||
collection_name = collection_obj.title
|
||||
except Failed as e:
|
||||
except Failed:
|
||||
collection_obj = None
|
||||
collection_name = c
|
||||
|
||||
if builder.schedule is not None:
|
||||
print_multiline(builder.schedule, info=True)
|
||||
util.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
|
||||
rating_key_map[item.ratingKey] = item
|
||||
else:
|
||||
logger.info("Sync Mode: append")
|
||||
|
||||
|
@ -404,7 +401,7 @@ class Config:
|
|||
logger.info("")
|
||||
logger.info("Collection Filter {}: {}".format(f[0], f[1]))
|
||||
|
||||
builder.run_methods(collection_obj, collection_name, map, movie_map, show_map)
|
||||
builder.run_methods(collection_obj, collection_name, rating_key_map, movie_map, show_map)
|
||||
|
||||
try:
|
||||
plex_collection = library.get_collection(collection_name)
|
||||
|
@ -419,14 +416,14 @@ class Config:
|
|||
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))
|
||||
util.separator("Unmanaged Collections in {} Library".format(library.name))
|
||||
logger.info("")
|
||||
unmanaged_count = 0
|
||||
collections_in_plex = [str(pcol) for pcol in collections]
|
||||
collections_in_plex = [str(plex_col) for plex_col in collections]
|
||||
for col in library.get_all_collections():
|
||||
if col.title not in collections_in_plex:
|
||||
logger.info(col.title)
|
||||
unmanaged_count += 1
|
||||
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("")
|
||||
|
@ -436,7 +433,6 @@ class Config:
|
|||
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):
|
||||
|
@ -445,7 +441,7 @@ class Config:
|
|||
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))
|
||||
util.print_end(length, "{} | {} for {} not found".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, item.title))
|
||||
continue
|
||||
if isinstance(main_id, list):
|
||||
if id_type == "movie":
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import logging, math, re, requests, time
|
||||
import logging, math, re, requests
|
||||
from lxml import html
|
||||
from modules import util
|
||||
from modules.util import Failed
|
||||
|
@ -33,8 +33,8 @@ class IMDbAPI:
|
|||
except IndexError: raise Failed("IMDb Error: Failed to parse URL: {}".format(imdb_url))
|
||||
try: total = int(re.findall("(\\d+) title", results)[0])
|
||||
except IndexError: raise Failed("IMDb Error: No Results at URL: {}".format(imdb_url))
|
||||
if "&start=" in current_url: current_url = re.sub("&start=\d+", "", current_url)
|
||||
if "&count=" in current_url: current_url = re.sub("&count=\d+", "", current_url)
|
||||
if "&start=" in current_url: current_url = re.sub("&start=\\d+", "", current_url)
|
||||
if "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url)
|
||||
if limit < 1 or total < limit: limit = total
|
||||
remainder = limit % 250
|
||||
if remainder == 0: remainder = 250
|
||||
|
@ -66,7 +66,7 @@ class IMDbAPI:
|
|||
if tvdb_id: show_ids.append(tvdb_id)
|
||||
elif method == "imdb_list":
|
||||
if status_message:
|
||||
logger.info("Processing {}: {}".format(pretty,"{} Items at {}".format(data["limit"], data["url"]) if data["limit"] > 0 else data["url"]))
|
||||
logger.info("Processing {}: {}".format(pretty, "{} Items at {}".format(data["limit"], data["url"]) if data["limit"] > 0 else data["url"]))
|
||||
imdb_ids = self.get_imdb_ids_from_url(data["url"], language, data["limit"])
|
||||
total_ids = len(imdb_ids)
|
||||
length = 0
|
||||
|
@ -86,6 +86,8 @@ class IMDbAPI:
|
|||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id, language):
|
||||
update_tmdb = False
|
||||
update_tvdb = False
|
||||
if self.Cache:
|
||||
tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb_id)
|
||||
update_tmdb = False
|
||||
|
@ -121,7 +123,7 @@ class IMDbAPI:
|
|||
try:
|
||||
if tvdb_id and not from_cache: self.TVDb.get_series(language, tvdb_id=tvdb_id)
|
||||
except Failed: tvdb_id = None
|
||||
if not tmdb_id and not tvdb_id : raise Failed("IMDb Error: No TMDb ID or TVDb ID found for IMDb: {}".format(imdb_id))
|
||||
if not tmdb_id and not tvdb_id: raise Failed("IMDb Error: No TMDb ID or TVDb ID found for IMDb: {}".format(imdb_id))
|
||||
if self.Cache:
|
||||
if tmdb_id and update_tmdb is not False:
|
||||
self.Cache.update_imdb("movie", update_tmdb, imdb_id, tmdb_id)
|
||||
|
|
|
@ -27,7 +27,7 @@ class MyAnimeListIDList:
|
|||
raise Failed("MyAnimeList Error: MyAnimeList ID: {} not found".format(mal_id))
|
||||
|
||||
class MyAnimeListAPI:
|
||||
def __init__(self, params, MyAnimeListIDList, authorization=None):
|
||||
def __init__(self, params, MyAnimeListIDList_in, authorization=None):
|
||||
self.urls = {
|
||||
"oauth_token": "https://myanimelist.net/v1/oauth2/token",
|
||||
"oauth_authorize": "https://myanimelist.net/v1/oauth2/authorize",
|
||||
|
@ -40,7 +40,7 @@ class MyAnimeListAPI:
|
|||
self.client_secret = params["client_secret"]
|
||||
self.config_path = params["config_path"]
|
||||
self.authorization = authorization
|
||||
self.MyAnimeListIDList = MyAnimeListIDList
|
||||
self.MyAnimeListIDList = MyAnimeListIDList_in
|
||||
if not self.save_authorization(self.authorization):
|
||||
if not self.refresh_authorization():
|
||||
self.get_authorization()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import datetime, logging, os, requests
|
||||
from lxml import html
|
||||
import logging, os, re, requests
|
||||
from datetime import datetime, timedelta
|
||||
from modules import util
|
||||
from modules.util import Failed
|
||||
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
|
||||
|
@ -16,7 +16,7 @@ class PlexAPI:
|
|||
try: self.PlexServer = PlexServer(params["plex"]["url"], params["plex"]["token"], timeout=params["plex"]["timeout"])
|
||||
except Unauthorized: raise Failed("Plex Error: Plex token is invalid")
|
||||
except ValueError as e: raise Failed("Plex Error: {}".format(e))
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
except requests.exceptions.ConnectionError:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Plex Error: Plex url is invalid")
|
||||
self.is_movie = params["library_type"] == "movie"
|
||||
|
@ -30,7 +30,7 @@ class PlexAPI:
|
|||
if attribute in self.data:
|
||||
if self.data[attribute]:
|
||||
if isinstance(self.data[attribute], dict): return self.data[attribute]
|
||||
else: logger.waring("Config Warning: {} must be a dictionary".format(attribute))
|
||||
else: logger.warning("Config Warning: {} must be a dictionary".format(attribute))
|
||||
else: logger.warning("Config Warning: {} attribute is blank".format(attribute))
|
||||
return None
|
||||
|
||||
|
@ -101,7 +101,7 @@ class PlexAPI:
|
|||
try: valid_collections.append(self.get_collection(collection))
|
||||
except Failed as e: logger.error(e)
|
||||
if len(valid_collections) == 0:
|
||||
raise Failed("Collection Error: No valid Plex Collections in {}".format(collections[c][m]))
|
||||
raise Failed("Collection Error: No valid Plex Collections in {}".format(collections))
|
||||
return valid_collections
|
||||
|
||||
def add_missing(self, collection, items, is_movie):
|
||||
|
@ -119,7 +119,7 @@ class PlexAPI:
|
|||
except yaml.scanner.ScannerError as e:
|
||||
logger.error("YAML Error: {}".format(str(e).replace("\n", "\n|\t ")))
|
||||
|
||||
def add_to_collection(self, collection, items, filters, show_filtered, map, movie_map, show_map):
|
||||
def add_to_collection(self, collection, items, filters, show_filtered, rating_key_map, movie_map, show_map):
|
||||
name = collection.title if isinstance(collection, Collections) else collection
|
||||
collection_items = collection.items() if isinstance(collection, Collections) else []
|
||||
total = len(items)
|
||||
|
@ -145,7 +145,6 @@ class PlexAPI:
|
|||
break
|
||||
elif method == "original_language":
|
||||
terms = util.get_list(f[1], lower=True)
|
||||
tmdb_id = None
|
||||
movie = None
|
||||
for key, value in movie_map.items():
|
||||
if current.ratingKey == value:
|
||||
|
@ -174,6 +173,7 @@ class PlexAPI:
|
|||
break
|
||||
else:
|
||||
terms = util.get_list(f[1])
|
||||
attrs = []
|
||||
if method in ["video_resolution", "audio_language", "subtitle_language"]:
|
||||
for media in current.media:
|
||||
if method == "video_resolution": attrs = [media.videoResolution]
|
||||
|
@ -189,20 +189,20 @@ class PlexAPI:
|
|||
length = util.print_return(length, "Filtering {}/{} {}".format((" " * (max_length - len(str(i)))) + str(i), total, current.title))
|
||||
if match:
|
||||
util.print_end(length, "{} Collection | {} | {}".format(name, "=" if current in collection_items else "+", current.title))
|
||||
if current in collection_items: map[current.ratingKey] = None
|
||||
if current in collection_items: rating_key_map[current.ratingKey] = None
|
||||
else: current.addCollection(name)
|
||||
elif show_filtered is True:
|
||||
logger.info("{} Collection | X | {}".format(name, current.title))
|
||||
media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "")
|
||||
util.print_end(length, "{} {} Processed".format(total, media_type))
|
||||
return map
|
||||
return rating_key_map
|
||||
|
||||
def search_item(self, data, year=None):
|
||||
return util.choose_from_list(self.search(data, year=year), "movie" if self.is_movie else "show", str(data), exact=True)
|
||||
|
||||
def update_metadata(self, TMDb, test):
|
||||
logger.info("")
|
||||
util.seperator("{} Library Metadata".format(self.name))
|
||||
util.separator("{} Library Metadata".format(self.name))
|
||||
logger.info("")
|
||||
if not self.metadata:
|
||||
raise Failed("No metadata to edit")
|
||||
|
@ -210,11 +210,11 @@ class PlexAPI:
|
|||
if test and ("test" not in self.metadata[m] or self.metadata[m]["test"] is not True):
|
||||
continue
|
||||
logger.info("")
|
||||
util.seperator()
|
||||
util.separator()
|
||||
logger.info("")
|
||||
year = None
|
||||
if "year" in self.metadata[m]:
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.now()
|
||||
if self.metadata[m]["year"] is None: logger.error("Metadata Error: year attribute is blank")
|
||||
elif not isinstance(self.metadata[m]["year"], int): logger.error("Metadata Error: year attribute must be an integer")
|
||||
elif self.metadata[m]["year"] not in range(1800, now.year + 2): logger.error("Metadata Error: year attribute must be between 1800-{}".format(now.year + 1))
|
||||
|
@ -253,7 +253,6 @@ class PlexAPI:
|
|||
except Failed as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
originally_available = tmdb_item.first_air_date if tmdb_item else None
|
||||
rating = tmdb_item.vote_average if tmdb_item else None
|
||||
original_title = tmdb_item.original_name if tmdb_item and tmdb_item.original_name != tmdb_item.name else None
|
||||
|
@ -325,10 +324,10 @@ class PlexAPI:
|
|||
if self.metadata[m]["label_sync_mode"] is None: logger.error("Metadata Error: label_sync_mode attribute is blank defaulting to append")
|
||||
elif self.metadata[m]["label_sync_mode"] not in ["append", "sync"]: logger.error("Metadata Error: label_sync_mode attribute must be either 'append' or 'sync' defaulting to append")
|
||||
elif self.metadata[m]["label_sync_mode"] == "sync":
|
||||
for label in (l for l in item_labels if l not in labels):
|
||||
for label in (la for la in item_labels if la not in labels):
|
||||
item.removeLabel(label)
|
||||
logger.info("Detail: Label {} removed".format(label))
|
||||
for label in (l for l in labels if l not in item_labels):
|
||||
for label in (la for la in labels if la not in item_labels):
|
||||
item.addLabel(label)
|
||||
logger.info("Detail: Label {} added".format(label))
|
||||
else:
|
||||
|
@ -381,7 +380,7 @@ class PlexAPI:
|
|||
if self.metadata[m]["episodes"]:
|
||||
for episode_str in self.metadata[m]["episodes"]:
|
||||
logger.info("")
|
||||
match = re.search("[Ss]{1}\d+[Ee]{1}\d+", episode_str)
|
||||
match = re.search("[Ss]\\d+[Ee]\\d+", episode_str)
|
||||
if match:
|
||||
output = match.group(0)[1:].split("E" if "E" in m.group(0) else "e")
|
||||
episode_id = int(output[0])
|
||||
|
@ -421,6 +420,6 @@ class PlexAPI:
|
|||
else:
|
||||
logger.info("Season: {} Episode: {} Details Update Not Needed".format(season_id, episode_id))
|
||||
else:
|
||||
logger.error("Metadata Error: episode {} invlaid must have S##E## format".format(episode_str))
|
||||
logger.error("Metadata Error: episode {} invalid must have S##E## format".format(episode_str))
|
||||
else:
|
||||
logger.error("Metadata Error: episodes attribute is blank")
|
||||
|
|
|
@ -11,7 +11,7 @@ class RadarrAPI:
|
|||
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/")
|
||||
try:
|
||||
result = requests.get("{}system/status".format(self.base_url), params=self.url_params).json()
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Radarr Error: Could not connect to Radarr at {}".format(params["url"]))
|
||||
if "error" in result and result["error"] == "Unauthorized":
|
||||
|
@ -94,7 +94,7 @@ class RadarrAPI:
|
|||
else:
|
||||
try:
|
||||
logger.error("Radarr Error: ({}) {}: ({}) {}".format(tmdb_id, movie.title, response.status_code, response.json()[0]["errorMessage"]))
|
||||
except KeyError as e:
|
||||
except KeyError:
|
||||
logger.debug(url_json)
|
||||
logger.error("Radarr Error: {}".format(response.json()))
|
||||
logger.info("{} Movie{} added to Radarr".format(add_count, "s" if add_count > 1 else ""))
|
||||
|
|
|
@ -11,7 +11,7 @@ class SonarrAPI:
|
|||
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/")
|
||||
try:
|
||||
result = requests.get("{}system/status".format(self.base_url), params=self.url_params).json()
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Sonarr Error: Could not connect to Sonarr at {}".format(params["url"]))
|
||||
if "error" in result and result["error"] == "Unauthorized":
|
||||
|
@ -72,7 +72,7 @@ class SonarrAPI:
|
|||
"language": self.language,
|
||||
"monitored": True,
|
||||
"rootFolderPath": self.root_folder_path,
|
||||
"seasons" : [],
|
||||
"seasons": [],
|
||||
"images": [{"covertype": "poster", "url": show.poster_path}],
|
||||
"addOptions": {"searchForMissingEpisodes": self.search}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class SonarrAPI:
|
|||
else:
|
||||
try:
|
||||
logger.error("Sonarr Error: ({}) {}: ({}) {}".format(tvdb_id, show.title, response.status_code, response.json()[0]["errorMessage"]))
|
||||
except KeyError as e:
|
||||
except KeyError:
|
||||
logger.debug(url_json)
|
||||
logger.error("Sonarr Error: {}".format(response.json()))
|
||||
logger.info("{} Show{} added to Sonarr".format(add_count, "s" if add_count > 1 else ""))
|
||||
|
|
|
@ -9,7 +9,7 @@ class TautulliAPI:
|
|||
def __init__(self, params):
|
||||
try:
|
||||
response = requests.get("{}/api/v2?apikey={}&cmd=get_library_names".format(params["url"], params["apikey"])).json()
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Tautulli Error: Invalid url")
|
||||
if response["response"]["result"] != "success":
|
||||
|
|
|
@ -9,7 +9,7 @@ def run_tests(default_dir):
|
|||
try:
|
||||
config = Config(default_dir)
|
||||
logger.info("")
|
||||
util.seperator("Mapping Tests")
|
||||
util.separator("Mapping Tests")
|
||||
for library in config.libraries:
|
||||
config.map_guids(library)
|
||||
anidb_tests(config)
|
||||
|
@ -19,13 +19,13 @@ def run_tests(default_dir):
|
|||
tmdb_tests(config)
|
||||
trakt_tests(config)
|
||||
tvdb_tests(config)
|
||||
util.seperator("Finished All Plex Meta Manager Tests")
|
||||
util.separator("Finished All Plex Meta Manager Tests")
|
||||
except KeyboardInterrupt:
|
||||
util.seperator("Canceled Plex Meta Manager Tests")
|
||||
util.separator("Canceled Plex Meta Manager Tests")
|
||||
|
||||
def anidb_tests(config):
|
||||
if config.AniDB:
|
||||
util.seperator("AniDB Tests")
|
||||
util.separator("AniDB Tests")
|
||||
|
||||
try:
|
||||
config.AniDB.convert_anidb_to_tvdb(69)
|
||||
|
@ -84,11 +84,11 @@ def anidb_tests(config):
|
|||
logger.error("Failure | Validate AniDB List: {}".format(e))
|
||||
|
||||
else:
|
||||
util.seperator("AniDB Not Configured")
|
||||
util.separator("AniDB Not Configured")
|
||||
|
||||
def imdb_tests(config):
|
||||
if config.IMDb:
|
||||
util.seperator("IMDb Tests")
|
||||
util.separator("IMDb Tests")
|
||||
|
||||
tmdb_ids, tvdb_ids = config.IMDb.get_items("imdb_list", {"url": "https://www.imdb.com/search/title/?groups=top_1000", "limit": 0}, "en", status_message=False)
|
||||
if len(tmdb_ids) == 1000: logger.info("Success | IMDb URL get TMDb IDs")
|
||||
|
@ -103,11 +103,11 @@ def imdb_tests(config):
|
|||
else: logger.error("Failure | IMDb ID get TMDb IDs: {} Should be 1".format(len(tmdb_ids)))
|
||||
|
||||
else:
|
||||
util.seperator("IMDb Not Configured")
|
||||
util.separator("IMDb Not Configured")
|
||||
|
||||
def mal_tests(config):
|
||||
if config.MyAnimeListIDList:
|
||||
util.seperator("MyAnimeListXML Tests")
|
||||
util.separator("MyAnimeListXML Tests")
|
||||
|
||||
try:
|
||||
config.MyAnimeListIDList.convert_mal_to_tvdb(21)
|
||||
|
@ -145,10 +145,10 @@ def mal_tests(config):
|
|||
logger.error("Failure | Find MyAnimeList ID: {}".format(e))
|
||||
|
||||
else:
|
||||
util.seperator("MyAnimeListXML Not Configured")
|
||||
util.separator("MyAnimeListXML Not Configured")
|
||||
|
||||
if config.MyAnimeList:
|
||||
util.seperator("MyAnimeList Tests")
|
||||
util.separator("MyAnimeList Tests")
|
||||
|
||||
mal_list_tests = [
|
||||
("mal_all", 10),
|
||||
|
@ -173,11 +173,11 @@ def mal_tests(config):
|
|||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Anime using {}: {}".format(util.pretty_names[mal_list_test[0]], e))
|
||||
else:
|
||||
util.seperator("MyAnimeList Not Configured")
|
||||
util.separator("MyAnimeList Not Configured")
|
||||
|
||||
def tautulli_tests(config):
|
||||
if config.libraries[0].Tautulli:
|
||||
util.seperator("Tautulli Tests")
|
||||
util.separator("Tautulli Tests")
|
||||
|
||||
try:
|
||||
config.libraries[0].Tautulli.get_section_id(config.libraries[0].name)
|
||||
|
@ -200,11 +200,11 @@ def tautulli_tests(config):
|
|||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Top: {}".format(e))
|
||||
else:
|
||||
util.seperator("Tautulli Not Configured")
|
||||
util.separator("Tautulli Not Configured")
|
||||
|
||||
def tmdb_tests(config):
|
||||
if config.TMDb:
|
||||
util.seperator("TMDb Tests")
|
||||
util.separator("TMDb Tests")
|
||||
|
||||
try:
|
||||
config.TMDb.convert_imdb_to_tmdb("tt0076759")
|
||||
|
@ -284,11 +284,11 @@ def tmdb_tests(config):
|
|||
util.print_stacktrace()
|
||||
logger.error("Failure | Get {} using {}: {}".format("Movies" if tmdb_list_test[2] else "Shows", util.pretty_names[tmdb_list_test[0]], e))
|
||||
else:
|
||||
util.seperator("TMDb Not Configured")
|
||||
util.separator("TMDb Not Configured")
|
||||
|
||||
def trakt_tests(config):
|
||||
if config.Trakt:
|
||||
util.seperator("Trakt Tests")
|
||||
util.separator("Trakt Tests")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_imdb_to_tmdb("tt0076759")
|
||||
|
@ -369,11 +369,11 @@ def trakt_tests(config):
|
|||
util.print_stacktrace()
|
||||
logger.error("Failure | Get {} using {}: {}".format("Movies" if trakt_list_test[2] else "Shows", util.pretty_names[trakt_list_test[0]], e))
|
||||
else:
|
||||
util.seperator("Trakt Not Configured")
|
||||
util.separator("Trakt Not Configured")
|
||||
|
||||
def tvdb_tests(config):
|
||||
if config.TVDb:
|
||||
util.seperator("TVDb Tests")
|
||||
util.separator("TVDb Tests")
|
||||
|
||||
tmdb_ids, tvdb_ids = config.TVDb.get_items("tvdb_list", "https://www.thetvdb.com/lists/arrowverse", "en", status_message=False)
|
||||
if len(tvdb_ids) == 10 and len(tmdb_ids) == 0: logger.info("Success | TVDb URL get TVDb IDs and TMDb IDs")
|
||||
|
@ -412,4 +412,4 @@ def tvdb_tests(config):
|
|||
logger.error("Failure | TVDb ID get TVDb Movie ID: {}".format(e))
|
||||
|
||||
else:
|
||||
util.seperator("TVDb Not Configured")
|
||||
util.separator("TVDb Not Configured")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import logging, os, tmdbv3api
|
||||
import logging, tmdbv3api
|
||||
from modules import util
|
||||
from modules.util import Failed
|
||||
from retrying import retry
|
||||
|
@ -30,9 +30,13 @@ class TMDbAPI:
|
|||
|
||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
|
||||
def convert_from_tmdb(self, tmdb_id, convert_to, is_movie):
|
||||
try: return self.Movie.external_ids(tmdb_id)[convert_to] if is_movie else self.TV.external_ids(tmdb_id)[convert_to]
|
||||
except TMDbException: raise Failed("TMDb Error: No {} found for TMDb ID {}".format(convert_to.upper().replace("B_", "b "), tmdb_id))
|
||||
|
||||
try:
|
||||
id_to_return = self.Movie.external_ids(tmdb_id)[convert_to] if is_movie else self.TV.external_ids(tmdb_id)[convert_to]
|
||||
if not id_to_return or (convert_to == "tvdb_id" and id_to_return == 0):
|
||||
raise Failed("TMDb Error: No {} found for TMDb ID {}".format(convert_to.upper().replace("B_", "b "), tmdb_id))
|
||||
return id_to_return
|
||||
except TMDbException:
|
||||
raise Failed("TMDb Error: {} TMDb ID: {} not found".format("Movie" if is_movie else "Show", tmdb_id))
|
||||
|
||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
|
||||
def convert_to_tmdb(self, external_id, external_source, is_movie):
|
||||
|
@ -105,6 +109,7 @@ class TMDbAPI:
|
|||
elif method == "tmdb_now_playing" and is_movie: tmdb_items = self.Movie.now_playing(x + 1)
|
||||
elif method == "tmdb_trending_daily": tmdb_items = self.Trending.movie_day(x + 1) if is_movie else self.Trending.tv_day(x + 1)
|
||||
elif method == "tmdb_trending_weekly": tmdb_items = self.Trending.movie_week(x + 1) if is_movie else self.Trending.tv_week(x + 1)
|
||||
else: raise Failed("TMDb Error: {} method not supported".format(method))
|
||||
for tmdb_item in tmdb_items:
|
||||
try:
|
||||
ids.append(tmdb_item.id if is_movie else self.convert_tmdb_to_tvdb(tmdb_item.id))
|
||||
|
@ -143,6 +148,9 @@ class TMDbAPI:
|
|||
movie_ids = []
|
||||
show_ids = []
|
||||
if method in ["tmdb_discover", "tmdb_company", "tmdb_keyword"] or (method == "tmdb_network" and not is_movie):
|
||||
attrs = None
|
||||
tmdb_id = ""
|
||||
tmdb_name = ""
|
||||
if method in ["tmdb_company", "tmdb_network", "tmdb_keyword"]:
|
||||
tmdb_id = int(data)
|
||||
if method == "tmdb_company":
|
||||
|
@ -163,7 +171,7 @@ class TMDbAPI:
|
|||
if status_message:
|
||||
if method in ["tmdb_company", "tmdb_network", "tmdb_keyword"]:
|
||||
logger.info("Processing {}: ({}) {} ({} {}{})".format(pretty, tmdb_id, tmdb_name, amount, media_type, "" if amount == 1 else "s"))
|
||||
else:
|
||||
elif method == "tmdb_discover":
|
||||
logger.info("Processing {}: {} {}{}".format(pretty, amount, media_type, "" if amount == 1 else "s"))
|
||||
for attr, value in attrs.items():
|
||||
logger.info(" {}: {}".format(attr, value))
|
||||
|
|
|
@ -119,7 +119,7 @@ class TraktAPI:
|
|||
except Failed as e:
|
||||
logger.error(e)
|
||||
if len(trakt_values) == 0:
|
||||
raise Failed("Trakt Error: No valid Trakt Lists in {}".format(value))
|
||||
raise Failed("Trakt Error: No valid Trakt Lists in {}".format(values))
|
||||
return trakt_values
|
||||
|
||||
def validate_trakt_watchlist(self, values, is_movie):
|
||||
|
@ -131,7 +131,7 @@ class TraktAPI:
|
|||
except Failed as e:
|
||||
logger.error(e)
|
||||
if len(trakt_values) == 0:
|
||||
raise Failed("Trakt Error: No valid Trakt Watchlists in {}".format(value))
|
||||
raise Failed("Trakt Error: No valid Trakt Watchlists in {}".format(values))
|
||||
return trakt_values
|
||||
|
||||
def get_items(self, method, data, is_movie, status_message=True):
|
|
@ -1,4 +1,4 @@
|
|||
import logging, math, re, requests, time
|
||||
import logging, requests
|
||||
from lxml import html
|
||||
from modules import util
|
||||
from modules.util import Failed
|
||||
|
@ -20,6 +20,10 @@ class TVDbObj:
|
|||
results = response.xpath("//*[text()='TheTVDB.com {} ID']/parent::node()/span/text()".format(self.media_type))
|
||||
if len(results) > 0:
|
||||
self.id = int(results[0])
|
||||
elif tvdb_url.startswith(TVDb.movie_id_url):
|
||||
raise Failed("TVDb Error: Could not find a TVDb Movie using TVDb Movie ID: {}".format(tvdb_url[len(TVDb.series_id_url):]))
|
||||
elif tvdb_url.startswith(TVDb.series_id_url):
|
||||
raise Failed("TVDb Error: Could not find a TVDb Series using TVDb Series ID: {}".format(tvdb_url[len(TVDb.series_id_url):]))
|
||||
else:
|
||||
raise Failed("TVDb Error: Could not find a TVDb {} ID at the URL {}".format(self.media_type, tvdb_url))
|
||||
|
||||
|
@ -41,7 +45,7 @@ class TVDbObj:
|
|||
if not tmdb_id:
|
||||
results = response.xpath("//*[text()='IMDB']/@href")
|
||||
if len(results) > 0:
|
||||
try: tmdb_id = TVDb.convert_from_imdb(util.get_id_from_imdb_url(results[0]), language)
|
||||
try: tmdb_id = TVDb.convert_from_imdb(util.get_id_from_imdb_url(results[0]))
|
||||
except Failed as e: logger.error(e)
|
||||
self.tmdb_id = tmdb_id
|
||||
self.tvdb_url = tvdb_url
|
||||
|
@ -67,7 +71,7 @@ class TVDbAPI:
|
|||
|
||||
def get_series(self, language, tvdb_url=None, tvdb_id=None):
|
||||
if not tvdb_url and not tvdb_id:
|
||||
raise Failed("TVDB Error: getget_seriesmove requires either tvdb_url or tvdb_id")
|
||||
raise Failed("TVDB Error: get_series requires either tvdb_url or tvdb_id")
|
||||
elif not tvdb_url and tvdb_id:
|
||||
tvdb_url = "{}{}".format(self.series_id_url, tvdb_id)
|
||||
return TVDbObj(tvdb_url, language, False, self)
|
||||
|
@ -104,7 +108,7 @@ class TVDbAPI:
|
|||
if len(show_ids) > 0 or len(movie_ids) > 0:
|
||||
return movie_ids, show_ids
|
||||
raise Failed("TVDb Error: No TVDb IDs found at {}".format(tvdb_url))
|
||||
except requests.exceptions.MissingSchema as e:
|
||||
except requests.exceptions.MissingSchema:
|
||||
util.print_stacktrace()
|
||||
raise Failed("TVDb Error: URL Lookup Failed for {}".format(tvdb_url))
|
||||
else:
|
||||
|
@ -137,10 +141,10 @@ class TVDbAPI:
|
|||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id, language):
|
||||
def convert_from_imdb(self, imdb_id):
|
||||
update = False
|
||||
if self.Cache:
|
||||
tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb_id)
|
||||
update = False
|
||||
if not tmdb_id:
|
||||
tmdb_id, update = self.Cache.get_tmdb_from_imdb(imdb_id)
|
||||
if update:
|
||||
|
|
|
@ -18,7 +18,7 @@ class Failed(Exception):
|
|||
def retry_if_not_failed(exception):
|
||||
return not isinstance(exception, Failed)
|
||||
|
||||
seperating_character = "="
|
||||
separating_character = "="
|
||||
screen_width = 100
|
||||
|
||||
method_alias = {
|
||||
|
@ -524,7 +524,7 @@ def get_year_list(data, method):
|
|||
final_years.append(i)
|
||||
else:
|
||||
year = re.search("(\\d+)", str(value)).group(1)
|
||||
if int(start) < 1800 or int(start) > current_year:
|
||||
if int(year) < 1800 or int(year) > current_year:
|
||||
logger.error("Collection Error: Skipping {} year {} must be between 1800 and {}".format(method, year, current_year))
|
||||
else:
|
||||
if len(str(year)) != len(str(value)):
|
||||
|
@ -572,14 +572,14 @@ def windows_input(prompt, timeout=5):
|
|||
start_time = time.time()
|
||||
while True:
|
||||
if msvcrt.kbhit():
|
||||
chr = msvcrt.getwche()
|
||||
if ord(chr) == 13: # enter_key
|
||||
char = msvcrt.getwche()
|
||||
if ord(char) == 13: # enter_key
|
||||
out = "".join(result)
|
||||
print("")
|
||||
logger.debug("{}: {}".format(prompt, out))
|
||||
return out
|
||||
elif ord(chr) >= 32: #space_char
|
||||
result.append(chr)
|
||||
elif ord(char) >= 32: #space_char
|
||||
result.append(char)
|
||||
if (time.time() - start_time) > timeout:
|
||||
print("")
|
||||
raise TimeoutExpired
|
||||
|
@ -631,13 +631,13 @@ def get_centered_text(text):
|
|||
side = int(space / 2)
|
||||
return "{}{}{}".format(" " * side, text, " " * side)
|
||||
|
||||
def seperator(text=None):
|
||||
def separator(text=None):
|
||||
logger.handlers[0].setFormatter(logging.Formatter("%(message)-{}s".format(screen_width - 2)))
|
||||
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s %(message)-{}s".format(screen_width - 2)))
|
||||
logger.info("|{}|".format(seperating_character * screen_width))
|
||||
logger.info("|{}|".format(separating_character * screen_width))
|
||||
if text:
|
||||
logger.info("| {} |".format(get_centered_text(text)))
|
||||
logger.info("|{}|".format(seperating_character * screen_width))
|
||||
logger.info("|{}|".format(separating_character * screen_width))
|
||||
logger.handlers[0].setFormatter(logging.Formatter("| %(message)-{}s |".format(screen_width - 2)))
|
||||
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s | %(message)-{}s |".format(screen_width - 2)))
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import argparse, logging, os, re, schedule, sys, time, traceback, datetime
|
||||
import argparse, logging, os, re, schedule, sys, time, datetime
|
||||
from modules import tests, util
|
||||
from modules.config import Config
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--mytests", dest="tests", help=argparse.SUPPRESS, action="store_true", default=False)
|
||||
parser.add_argument("--my-tests", dest="tests", help=argparse.SUPPRESS, action="store_true", default=False)
|
||||
parser.add_argument("--debug", dest="debug", help=argparse.SUPPRESS, action="store_true", default=False)
|
||||
parser.add_argument("-c", "--config", dest="config", help="Run with desired *.yml file", type=str)
|
||||
parser.add_argument("-t", "--time", dest="time", help="Time to update each day use format HH:MM (Default: 03:00)", default="03:00", type=str)
|
||||
|
@ -17,7 +17,7 @@ args = parser.parse_args()
|
|||
if not re.match("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$", args.time):
|
||||
raise util.Failed("Argument Error: time argument invalid: {} must be in the HH:MM format".format(args.time))
|
||||
|
||||
util.seperating_character = args.divider[0]
|
||||
util.separating_character = args.divider[0]
|
||||
if 90 <= args.width <= 300:
|
||||
util.screen_width = args.width
|
||||
else:
|
||||
|
@ -51,27 +51,27 @@ logger.addHandler(file_handler)
|
|||
|
||||
sys.excepthook = util.my_except_hook
|
||||
|
||||
util.seperator()
|
||||
util.separator()
|
||||
logger.info(util.get_centered_text(" "))
|
||||
logger.info(util.get_centered_text(" ____ _ __ __ _ __ __ "))
|
||||
logger.info(util.get_centered_text("| _ \| | _____ __ | \/ | ___| |_ __ _ | \/ | __ _ _ __ __ _ __ _ ___ _ __ "))
|
||||
logger.info(util.get_centered_text("| |_) | |/ _ \ \/ / | |\/| |/ _ \ __/ _` | | |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|"))
|
||||
logger.info(util.get_centered_text("| _ \\| | _____ __ | \\/ | ___| |_ __ _ | \\/ | __ _ _ __ __ _ __ _ ___ _ __ "))
|
||||
logger.info(util.get_centered_text("| |_) | |/ _ \\ \\/ / | |\\/| |/ _ \\ __/ _` | | |\\/| |/ _` | '_ \\ / _` |/ _` |/ _ \\ '__|"))
|
||||
logger.info(util.get_centered_text("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | "))
|
||||
logger.info(util.get_centered_text("|_| |_|\___/_/\_\ |_| |_|\___|\__\__,_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| "))
|
||||
logger.info(util.get_centered_text("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| "))
|
||||
logger.info(util.get_centered_text(" |___/ "))
|
||||
logger.info(util.get_centered_text(" Version: 1.2.2 "))
|
||||
util.seperator()
|
||||
util.separator()
|
||||
|
||||
if args.tests:
|
||||
tests.run_tests(default_dir)
|
||||
sys.exit(0)
|
||||
|
||||
def start(config_path, test, daily, collections):
|
||||
if daily: type = "Daily "
|
||||
elif test: type = "Test "
|
||||
elif collections: type = "Collections "
|
||||
else: type = ""
|
||||
util.seperator("Starting {}Run".format(type))
|
||||
if daily: start_type = "Daily "
|
||||
elif test: start_type = "Test "
|
||||
elif collections: start_type = "Collections "
|
||||
else: start_type = ""
|
||||
util.separator("Starting {}Run".format(start_type))
|
||||
try:
|
||||
config = Config(default_dir, config_path)
|
||||
config.update_libraries(test, collections)
|
||||
|
@ -79,7 +79,7 @@ def start(config_path, test, daily, collections):
|
|||
util.print_stacktrace()
|
||||
logger.critical(e)
|
||||
logger.info("")
|
||||
util.seperator("Finished {}Run".format(type))
|
||||
util.separator("Finished {}Run".format(start_type))
|
||||
|
||||
try:
|
||||
if args.run or args.test or args.collections:
|
||||
|
@ -101,4 +101,4 @@ try:
|
|||
length = util.print_return(length, "Current Time: {} | {} until the daily run at {}".format(current, time_str, args.time))
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
util.seperator("Exiting Plex Meta Manager")
|
||||
util.separator("Exiting Plex Meta Manager")
|
||||
|
|
Loading…
Reference in a new issue