changed to f-Strings

pull/76/head
meisnate12 4 years ago
parent e5c43e0fa7
commit 4c8ccaa171

@ -23,14 +23,14 @@ class AniDBAPI:
def convert_tvdb_to_anidb(self, tvdb_id): return self.convert_anidb(tvdb_id, "tvdbid", "anidbid") def convert_tvdb_to_anidb(self, tvdb_id): return self.convert_anidb(tvdb_id, "tvdbid", "anidbid")
def convert_imdb_to_anidb(self, imdb_id): return self.convert_anidb(imdb_id, "imdbid", "anidbid") def convert_imdb_to_anidb(self, imdb_id): return self.convert_anidb(imdb_id, "imdbid", "anidbid")
def convert_anidb(self, input_id, from_id, to_id): def convert_anidb(self, input_id, from_id, to_id):
ids = self.id_list.xpath("//anime[contains(@{}, '{}')]/@{}".format(from_id, input_id, to_id)) ids = self.id_list.xpath(f"//anime[contains(@{from_id}, '{input_id}')]/@{to_id}")
if len(ids) > 0: if len(ids) > 0:
if from_id == "tvdbid": return [int(i) for i in ids] if from_id == "tvdbid": return [int(i) for i in ids]
if len(ids[0]) > 0: if len(ids[0]) > 0:
try: return ids[0].split(",") if to_id == "imdbid" else int(ids[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)) except ValueError: raise Failed(f"AniDB Error: No {util.pretty_ids[to_id]} ID found for {util.pretty_ids[from_id]} ID: {input_id}")
else: raise Failed("AniDB Error: No {} ID found for {} ID: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id)) else: raise Failed(f"AniDB Error: No {util.pretty_ids[to_id]} ID found for {util.pretty_ids[from_id]} ID: {input_id}")
else: raise Failed("AniDB Error: {} ID: {} not found".format(util.pretty_ids[from_id], input_id)) else: raise Failed(f"AniDB Error: {util.pretty_ids[from_id]} ID: {input_id} not found")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url, language): def send_request(self, url, language):
@ -41,14 +41,14 @@ class AniDBAPI:
return util.get_int_list(response.xpath("//td[@class='name anime']/a/@href"), "AniDB ID") return util.get_int_list(response.xpath("//td[@class='name anime']/a/@href"), "AniDB ID")
def validate_anidb_id(self, anidb_id, language): def validate_anidb_id(self, anidb_id, language):
response = self.send_request("{}/{}".format(self.urls["anime"], anidb_id), language) response = self.send_request(f"{self.urls['anime']}/{anidb_id}", language)
ids = response.xpath("//*[text()='a{}']/text()".format(anidb_id)) ids = response.xpath(f"//*[text()='a{anidb_id}']/text()")
if len(ids) > 0: if len(ids) > 0:
return util.regex_first_int(ids[0], "AniDB ID") return util.regex_first_int(ids[0], "AniDB ID")
raise Failed("AniDB Error: AniDB ID: {} not found".format(anidb_id)) raise Failed(f"AniDB Error: AniDB ID: {anidb_id} not found")
def get_anidb_relations(self, anidb_id, language): def get_anidb_relations(self, anidb_id, language):
response = self.send_request("{}/{}{}".format(self.urls["anime"], anidb_id, self.urls["relation"]), language) response = self.send_request(f"{self.urls['anime']}/{anidb_id}{self.urls['relation']}", language)
return util.get_int_list(response.xpath("//area/@href"), "AniDB ID") return util.get_int_list(response.xpath("//area/@href"), "AniDB ID")
def validate_anidb_list(self, anidb_list, language): def validate_anidb_list(self, anidb_list, language):
@ -60,22 +60,22 @@ class AniDBAPI:
logger.error(e) logger.error(e)
if len(anidb_values) > 0: if len(anidb_values) > 0:
return anidb_values return anidb_values
raise Failed("AniDB Error: No valid AniDB IDs in {}".format(anidb_list)) raise Failed(f"AniDB Error: No valid AniDB IDs in {anidb_list}")
def get_items(self, method, data, language, status_message=True): def get_items(self, method, data, language, status_message=True):
pretty = util.pretty_names[method] if method in util.pretty_names else method pretty = util.pretty_names[method] if method in util.pretty_names else method
if status_message: if status_message:
logger.debug("Data: {}".format(data)) logger.debug(f"Data: {data}")
anime_ids = [] anime_ids = []
if method == "anidb_popular": if method == "anidb_popular":
if status_message: if status_message:
logger.info("Processing {}: {} Anime".format(pretty, data)) logger.info(f"Processing {pretty}: {data} Anime")
anime_ids.extend(self.get_popular(language)[:data]) anime_ids.extend(self.get_popular(language)[:data])
else: else:
if status_message: logger.info("Processing {}: {}".format(pretty, data)) if status_message: logger.info(f"Processing {pretty}: {data}")
if method == "anidb_id": anime_ids.append(data) if method == "anidb_id": anime_ids.append(data)
elif method == "anidb_relation": anime_ids.extend(self.get_anidb_relations(data, language)) elif method == "anidb_relation": anime_ids.extend(self.get_anidb_relations(data, language))
else: raise Failed("AniDB Error: Method {} not supported".format(method)) else: raise Failed(f"AniDB Error: Method {method} not supported")
show_ids = [] show_ids = []
movie_ids = [] movie_ids = []
for anidb_id in anime_ids: for anidb_id in anime_ids:
@ -85,11 +85,11 @@ class AniDBAPI:
else: raise Failed else: raise Failed
except Failed: except Failed:
try: show_ids.append(self.convert_anidb_to_tvdb(anidb_id)) try: show_ids.append(self.convert_anidb_to_tvdb(anidb_id))
except Failed: logger.error("AniDB Error: No TVDb ID or IMDb ID found for AniDB ID: {}".format(anidb_id)) except Failed: logger.error(f"AniDB Error: No TVDb ID or IMDb ID found for AniDB ID: {anidb_id}")
if status_message: if status_message:
logger.debug("AniDB IDs Found: {}".format(anime_ids)) logger.debug(f"AniDB IDs Found: {anime_ids}")
logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug("TVDb IDs Found: {}".format(show_ids)) logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids return movie_ids, show_ids
def convert_from_imdb(self, imdb_id): def convert_from_imdb(self, imdb_id):
@ -121,6 +121,6 @@ class AniDBAPI:
if tmdb_id: output_tmdb_ids.append(tmdb_id) if tmdb_id: output_tmdb_ids.append(tmdb_id)
if self.Cache and tmdb_id and expired is not False: if self.Cache and tmdb_id and expired is not False:
self.Cache.update_imdb("movie", expired, imdb, tmdb_id) self.Cache.update_imdb("movie", expired, imdb, tmdb_id)
if len(output_tmdb_ids) == 0: raise Failed("AniDB Error: No TMDb ID found for IMDb: {}".format(imdb_id)) if len(output_tmdb_ids) == 0: raise Failed(f"AniDB Error: No TMDb ID found for IMDb: {imdb_id}")
elif len(output_tmdb_ids) == 1: return output_tmdb_ids[0] elif len(output_tmdb_ids) == 1: return output_tmdb_ids[0]
else: return output_tmdb_ids else: return output_tmdb_ids

@ -37,13 +37,13 @@ class CollectionBuilder:
elif not data_template["name"]: elif not data_template["name"]:
raise Failed("Collection Error: template sub-attribute name is blank") raise Failed("Collection Error: template sub-attribute name is blank")
elif data_template["name"] not in self.library.templates: elif data_template["name"] not in self.library.templates:
raise Failed("Collection Error: template {} not found".format(data_template["name"])) raise Failed(f"Collection Error: template {data_template['name']} not found")
elif not isinstance(self.library.templates[data_template["name"]], dict): elif not isinstance(self.library.templates[data_template["name"]], dict):
raise Failed("Collection Error: template {} is not a dictionary".format(data_template["name"])) raise Failed(f"Collection Error: template {data_template['name']} is not a dictionary")
else: else:
for tm in data_template: for tm in data_template:
if not data_template[tm]: if not data_template[tm]:
raise Failed("Collection Error: template sub-attribute {} is blank".format(data_template[tm])) raise Failed(f"Collection Error: template sub-attribute {data_template[tm]} is blank")
template_name = data_template["name"] template_name = data_template["name"]
template = self.library.templates[template_name] template = self.library.templates[template_name]
@ -55,7 +55,7 @@ class CollectionBuilder:
if template["default"][dv]: if template["default"][dv]:
default[dv] = template["default"][dv] default[dv] = template["default"][dv]
else: else:
raise Failed("Collection Error: template default sub-attribute {} is blank".format(dv)) raise Failed(f"Collection Error: template default sub-attribute {dv} is blank")
else: else:
raise Failed("Collection Error: template sub-attribute default is not a dictionary") raise Failed("Collection Error: template sub-attribute default is not a dictionary")
else: else:
@ -67,13 +67,13 @@ class CollectionBuilder:
def replace_txt(txt): def replace_txt(txt):
txt = str(txt) txt = str(txt)
for template_method in data_template: for template_method in data_template:
if template_method != "name" and "<<{}>>".format(template_method) in txt: if template_method != "name" and f"<<{template_method}>>" in txt:
txt = txt.replace("<<{}>>".format(template_method), str(data_template[template_method])) txt = txt.replace(f"<<{template_method}>>", str(data_template[template_method]))
if "<<collection_name>>" in txt: if "<<collection_name>>" in txt:
txt = txt.replace("<<collection_name>>", str(self.name)) txt = txt.replace("<<collection_name>>", str(self.name))
for dm in default: for dm in default:
if "<<{}>>".format(dm) in txt: if f"<<{dm}>>" in txt:
txt = txt.replace("<<{}>>".format(dm), str(default[dm])) txt = txt.replace(f"<<{dm}>>", str(default[dm]))
if txt in ["true", "True"]: return True if txt in ["true", "True"]: return True
elif txt in ["false", "False"]: return False elif txt in ["false", "False"]: return False
else: else:
@ -103,7 +103,7 @@ class CollectionBuilder:
attr = replace_txt(template[m]) attr = replace_txt(template[m])
self.data[m] = attr self.data[m] = attr
else: else:
raise Failed("Collection Error: template attribute {} is blank".format(m)) raise Failed(f"Collection Error: template attribute {m} is blank")
skip_collection = True skip_collection = True
if "schedule" not in data: if "schedule" not in data:
@ -127,48 +127,48 @@ class CollectionBuilder:
if run_time.startswith("week"): if run_time.startswith("week"):
if param.lower() in util.days_alias: if param.lower() in util.days_alias:
weekday = util.days_alias[param.lower()] weekday = util.days_alias[param.lower()]
self.schedule += "\nScheduled weekly on {}".format(util.pretty_days[weekday]) self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}"
if weekday == current_time.weekday(): if weekday == current_time.weekday():
skip_collection = False skip_collection = False
else: else:
logger.error("Collection Error: weekly schedule attribute {} invalid must be a day of the week i.e. weekly(Monday)".format(schedule)) logger.error(f"Collection Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)")
elif run_time.startswith("month"): elif run_time.startswith("month"):
try: try:
if 1 <= int(param) <= 31: if 1 <= int(param) <= 31:
self.schedule += "\nScheduled monthly on the {}".format(util.make_ordinal(param)) self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}"
if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day): if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day):
skip_collection = False skip_collection = False
else: else:
logger.error("Collection Error: monthly schedule attribute {} invalid must be between 1 and 31".format(schedule)) logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be between 1 and 31")
except ValueError: except ValueError:
logger.error("Collection Error: monthly schedule attribute {} invalid must be an integer".format(schedule)) logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer")
elif run_time.startswith("year"): elif run_time.startswith("year"):
match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param)
if match: if match:
month = int(match.group(1)) month = int(match.group(1))
day = int(match.group(2)) day = int(match.group(2))
self.schedule += "\nScheduled yearly on {} {}".format(util.pretty_months[month], util.make_ordinal(day)) self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}"
if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)): if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)):
skip_collection = False skip_collection = False
else: else:
logger.error("Collection Error: yearly schedule attribute {} invalid must be in the MM/DD format i.e. yearly(11/22)".format(schedule)) logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)")
else: else:
logger.error("Collection Error: failed to parse schedule: {}".format(schedule)) logger.error(f"Collection Error: failed to parse schedule: {schedule}")
else: else:
logger.error("Collection Error: schedule attribute {} invalid".format(schedule)) logger.error(f"Collection Error: schedule attribute {schedule} invalid")
if self.schedule is None: if self.schedule is None:
skip_collection = False skip_collection = False
if skip_collection: if skip_collection:
raise Failed("Skipping Collection {}".format(self.name)) raise Failed(f"Skipping Collection {self.name}")
logger.info("Scanning {} Collection".format(self.name)) logger.info(f"Scanning {self.name} Collection")
self.collectionless = "plex_collectionless" in data self.collectionless = "plex_collectionless" in data
self.sync = self.library.sync_mode == "sync" self.sync = self.library.sync_mode == "sync"
if "sync_mode" in data: if "sync_mode" in data:
if not data["sync_mode"]: logger.warning("Collection Warning: sync_mode attribute is blank using general: {}".format(self.library.sync_mode)) if not data["sync_mode"]: logger.warning(f"Collection Warning: sync_mode attribute is blank using general: {self.library.sync_mode}")
elif data["sync_mode"] not in ["append", "sync"]: logger.warning("Collection Warning: {} sync_mode invalid using general: {}".format(self.library.sync_mode, data["sync_mode"])) elif data["sync_mode"] not in ["append", "sync"]: logger.warning(f"Collection Warning: {self.library.sync_mode} sync_mode invalid using general: {data['sync_mode']}")
else: self.sync = data["sync_mode"] == "sync" else: self.sync = data["sync_mode"] == "sync"
if "tmdb_person" in data: if "tmdb_person" in data:
@ -180,35 +180,35 @@ class CollectionBuilder:
if "summary" not in self.details and hasattr(person, "biography") and person.biography: if "summary" not in self.details and hasattr(person, "biography") and person.biography:
self.details["summary"] = person.biography self.details["summary"] = person.biography
if "poster" not in self.details and hasattr(person, "profile_path") and person.profile_path: if "poster" not in self.details and hasattr(person, "profile_path") and person.profile_path:
self.details["poster"] = ("url", "{}{}".format(config.TMDb.image_url, person.profile_path), "tmdb_person") self.details["poster"] = ("url", f"{config.TMDb.image_url}{person.profile_path}", "tmdb_person")
if len(valid_names) > 0: self.details["tmdb_person"] = valid_names if len(valid_names) > 0: self.details["tmdb_person"] = valid_names
else: raise Failed("Collection Error: No valid TMDb Person IDs in {}".format(data["tmdb_person"])) else: raise Failed(f"Collection Error: No valid TMDb Person IDs in {data['tmdb_person']}")
else: else:
raise Failed("Collection Error: tmdb_person attribute is blank") raise Failed("Collection Error: tmdb_person attribute is blank")
for m in data: for m in data:
if "tmdb" in m and not config.TMDb: raise Failed("Collection Error: {} requires TMDb to be configured".format(m)) if "tmdb" in m and not config.TMDb: raise Failed(f"Collection Error: {m} requires TMDb to be configured")
elif "trakt" in m and not config.Trakt: raise Failed("Collection Error: {} requires Trakt todo be configured".format(m)) elif "trakt" in m and not config.Trakt: raise Failed(f"Collection Error: {m} requires Trakt todo be configured")
elif "imdb" in m and not config.IMDb: raise Failed("Collection Error: {} requires TMDb or Trakt to be configured".format(m)) elif "imdb" in m and not config.IMDb: raise Failed(f"Collection Error: {m} requires TMDb or Trakt to be configured")
elif "tautulli" in m and not self.library.Tautulli: raise Failed("Collection Error: {} requires Tautulli to be configured".format(m)) elif "tautulli" in m and not self.library.Tautulli: raise Failed(f"Collection Error: {m} requires Tautulli to be configured")
elif "mal" in m and not config.MyAnimeList: raise Failed("Collection Error: {} requires MyAnimeList to be configured".format(m)) elif "mal" in m and not config.MyAnimeList: raise Failed(f"Collection Error: {m} requires MyAnimeList to be configured")
elif data[m] is not None: elif data[m] is not None:
logger.debug("") logger.debug("")
logger.debug("Method: {}".format(m)) logger.debug(f"Method: {m}")
logger.debug("Value: {}".format(data[m])) logger.debug(f"Value: {data[m]}")
if m in util.method_alias: if m in util.method_alias:
method_name = util.method_alias[m] method_name = util.method_alias[m]
logger.warning("Collection Warning: {} attribute will run as {}".format(m, method_name)) logger.warning(f"Collection Warning: {m} attribute will run as {method_name}")
else: else:
method_name = m method_name = m
if method_name in util.show_only_lists and self.library.is_movie: if method_name in util.show_only_lists and self.library.is_movie:
raise Failed("Collection Error: {} attribute only works for show libraries".format(method_name)) raise Failed(f"Collection Error: {method_name} attribute only works for show libraries")
elif method_name in util.movie_only_lists and self.library.is_show: elif method_name in util.movie_only_lists and self.library.is_show:
raise Failed("Collection Error: {} attribute only works for movie libraries".format(method_name)) raise Failed(f"Collection Error: {method_name} attribute only works for movie libraries")
elif method_name in util.movie_only_searches and self.library.is_show: elif method_name in util.movie_only_searches and self.library.is_show:
raise Failed("Collection Error: {} plex search only works for movie libraries".format(method_name)) raise Failed(f"Collection Error: {method_name} plex search only works for movie libraries")
elif method_name not in util.collectionless_lists and self.collectionless: elif method_name not in util.collectionless_lists and self.collectionless:
raise Failed("Collection Error: {} attribute does not work for Collectionless collection".format(method_name)) raise Failed(f"Collection Error: {method_name} attribute does not work for Collectionless collection")
elif method_name == "tmdb_summary": elif method_name == "tmdb_summary":
self.details["summary"] = config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], "TMDb ID"), self.library.is_movie).overview self.details["summary"] = config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], "TMDb ID"), self.library.is_movie).overview
elif method_name == "tmdb_description": elif method_name == "tmdb_description":
@ -221,28 +221,28 @@ class CollectionBuilder:
elif data[m] == "show_items": self.details[method_name] = "showItems" elif data[m] == "show_items": self.details[method_name] = "showItems"
else: self.details[method_name] = data[m] else: self.details[method_name] = data[m]
else: else:
raise Failed("Collection Error: {} collection_mode Invalid\n| \tdefault (Library default)\n| \thide (Hide Collection)\n| \thide_items (Hide Items in this Collection)\n| \tshow_items (Show this Collection and its Items)".format(data[m])) raise Failed(f"Collection Error: {data[m]} collection_mode Invalid\n| \tdefault (Library default)\n| \thide (Hide Collection)\n| \thide_items (Hide Items in this Collection)\n| \tshow_items (Show this Collection and its Items)")
elif method_name == "collection_order": elif method_name == "collection_order":
if data[m] in ["release", "alpha"]: if data[m] in ["release", "alpha"]:
self.details[method_name] = data[m] self.details[method_name] = data[m]
else: else:
raise Failed("Collection Error: {} collection_order Invalid\n| \trelease (Order Collection by release dates)\n| \talpha (Order Collection Alphabetically)".format(data[m])) raise Failed(f"Collection Error: {data[m]} collection_order Invalid\n| \trelease (Order Collection by release dates)\n| \talpha (Order Collection Alphabetically)")
elif method_name == "url_poster": elif method_name == "url_poster":
self.posters.append(("url", data[m], method_name)) self.posters.append(("url", data[m], method_name))
elif method_name == "tmdb_poster": elif method_name == "tmdb_poster":
self.posters.append(("url", "{}{}".format(config.TMDb.image_url, config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], "TMDb ID"), self.library.is_movie).poster_path), method_name)) self.posters.append(("url", f"{config.TMDb.image_url}{config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], 'TMDb ID'), self.library.is_movie).poster_path}", method_name))
elif method_name == "tmdb_profile": elif method_name == "tmdb_profile":
self.posters.append(("url", "{}{}".format(config.TMDb.image_url, config.TMDb.get_person(util.regex_first_int(data[m], "TMDb Person ID")).profile_path), method_name)) self.posters.append(("url", f"{config.TMDb.image_url}{config.TMDb.get_person(util.regex_first_int(data[m], 'TMDb Person ID')).profile_path}", method_name))
elif method_name == "file_poster": elif method_name == "file_poster":
if os.path.exists(data[m]): self.posters.append(("file", os.path.abspath(data[m]), method_name)) if os.path.exists(data[m]): self.posters.append(("file", os.path.abspath(data[m]), method_name))
else: raise Failed("Collection Error: Poster Path Does Not Exist: {}".format(os.path.abspath(data[m]))) else: raise Failed(f"Collection Error: Poster Path Does Not Exist: {os.path.abspath(data[m])}")
elif method_name == "url_background": elif method_name == "url_background":
self.backgrounds.append(("url", data[m], method_name)) self.backgrounds.append(("url", data[m], method_name))
elif method_name == "tmdb_background": elif method_name == "tmdb_background":
self.backgrounds.append(("url", "{}{}".format(config.TMDb.image_url, config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], "TMDb ID"), self.library.is_movie).poster_path), method_name)) self.backgrounds.append(("url", f"{config.TMDb.image_url}{config.TMDb.get_movie_show_or_collection(util.regex_first_int(data[m], 'TMDb ID'), self.library.is_movie).poster_path}", method_name))
elif method_name == "file_background": elif method_name == "file_background":
if os.path.exists(data[m]): self.backgrounds.append(("file", os.path.abspath(data[m]), method_name)) if os.path.exists(data[m]): self.backgrounds.append(("file", os.path.abspath(data[m]), method_name))
else: raise Failed("Collection Error: Background Path Does Not Exist: {}".format(os.path.abspath(data[m]))) else: raise Failed(f"Collection Error: Background Path Does Not Exist: {os.path.abspath(data[m])}")
elif method_name == "label_sync_mode": elif method_name == "label_sync_mode":
if data[m] in ["append", "sync"]: self.details[method_name] = data[m] if data[m] in ["append", "sync"]: self.details[method_name] = data[m]
else: raise Failed("Collection Error: label_sync_mode attribute must be either 'append' or 'sync'") else: raise Failed("Collection Error: label_sync_mode attribute must be either 'append' or 'sync'")
@ -250,7 +250,7 @@ class CollectionBuilder:
self.details[method_name] = util.get_list(data[m]) self.details[method_name] = util.get_list(data[m])
elif method_name in util.boolean_details: elif method_name in util.boolean_details:
if isinstance(data[m], bool): self.details[method_name] = data[m] if isinstance(data[m], bool): self.details[method_name] = data[m]
else: raise Failed("Collection Error: {} attribute must be either true or false".format(method_name)) else: raise Failed(f"Collection Error: {method_name} attribute must be either true or false")
elif method_name in util.all_details: elif method_name in util.all_details:
self.details[method_name] = data[m] self.details[method_name] = data[m]
elif method_name in ["year", "year.not"]: elif method_name in ["year", "year.not"]:
@ -302,24 +302,24 @@ class CollectionBuilder:
elif method_name in util.dictionary_lists: elif method_name in util.dictionary_lists:
if isinstance(data[m], dict): if isinstance(data[m], dict):
def get_int(parent, method, data_in, default_in, minimum=1, maximum=None): 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)) if method not in data_in: logger.warning(f"Collection Warning: {parent} {method} attribute not found using {default} as default")
elif not data_in[method]: logger.warning("Collection Warning: {} {} attribute is blank using {} as default".format(parent, method, default)) elif not data_in[method]: logger.warning(f"Collection Warning: {parent} {method} attribute is blank using {default} as default")
elif isinstance(data_in[method], int) and data_in[method] >= minimum: elif isinstance(data_in[method], int) and data_in[method] >= minimum:
if maximum is None or data_in[method] <= maximum: return data_in[method] 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(f"Collection Warning: {parent} {method} attribute {data_in[method]} invalid must an integer <= {maximum} using {default} as default")
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer >= {} using {} as default".format(parent, method, data_in[method], minimum, default)) else: logger.warning(f"Collection Warning: {parent} {method} attribute {data_in[method]} invalid must an integer >= {minimum} using {default} as default")
return default_in return default_in
if method_name == "filters": if method_name == "filters":
for f in data[m]: for f in data[m]:
if f in util.method_alias or (f.endswith(".not") and f[:-4] in util.method_alias): if f in util.method_alias or (f.endswith(".not") and f[:-4] in util.method_alias):
filter_method = (util.method_alias[f[:-4]] + f[-4:]) if f.endswith(".not") else util.method_alias[f] 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)) logger.warning(f"Collection Warning: {f} filter will run as {filter_method}")
else: else:
filter_method = f 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)) if filter_method in util.movie_only_filters and self.library.is_show: raise Failed(f"Collection Error: {filter_method} filter only works for movie libraries")
elif data[m][f] is None: raise Failed("Collection Error: {} filter is blank".format(filter_method)) elif data[m][f] is None: raise Failed(f"Collection Error: {filter_method} filter is blank")
elif filter_method in util.all_filters: self.filters.append((filter_method, data[m][f])) 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)) else: raise Failed(f"Collection Error: {filter_method} filter not supported")
elif method_name == "plex_collectionless": elif method_name == "plex_collectionless":
new_dictionary = {} new_dictionary = {}
prefix_list = [] prefix_list = []
@ -343,13 +343,13 @@ class CollectionBuilder:
for s in data[m]: for s in data[m]:
if s in util.method_alias or (s.endswith(".not") and s[:-4] in util.method_alias): if s in util.method_alias or (s.endswith(".not") and s[:-4] in util.method_alias):
search = (util.method_alias[s[:-4]] + s[-4:]) if s.endswith(".not") else util.method_alias[s] search = (util.method_alias[s[:-4]] + s[-4:]) if s.endswith(".not") else util.method_alias[s]
logger.warning("Collection Warning: {} plex search attribute will run as {}".format(s, search)) logger.warning(f"Collection Warning: {s} plex search attribute will run as {search}")
else: else:
search = s search = s
if search in util.movie_only_searches and self.library.is_show: if search in util.movie_only_searches and self.library.is_show:
raise Failed("Collection Error: {} plex search attribute only works for movie libraries".format(search)) raise Failed(f"Collection Error: {search} plex search attribute only works for movie libraries")
elif util.remove_not(search) in used: elif util.remove_not(search) in used:
raise Failed("Collection Error: Only one instance of {} can be used try using it as a filter instead".format(search)) raise Failed(f"Collection Error: Only one instance of {search} can be used try using it as a filter instead")
elif search in ["year", "year.not"]: elif search in ["year", "year.not"]:
years = util.get_year_list(data[m][s], search) years = util.get_year_list(data[m][s], search)
if len(years) > 0: if len(years) > 0:
@ -359,7 +359,7 @@ class CollectionBuilder:
used.append(util.remove_not(search)) used.append(util.remove_not(search))
searches.append((search, util.get_list(data[m][s]))) searches.append((search, util.get_list(data[m][s])))
else: else:
logger.error("Collection Error: {} plex search attribute not supported".format(search)) logger.error(f"Collection Error: {search} plex search attribute not supported")
self.methods.append((method_name, [searches])) self.methods.append((method_name, [searches]))
elif method_name == "tmdb_discover": elif method_name == "tmdb_discover":
new_dictionary = {"limit": 100} new_dictionary = {"limit": 100}
@ -371,71 +371,71 @@ class CollectionBuilder:
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(attr_data)): if re.compile("([a-z]{2})-([A-Z]{2})").match(str(attr_data)):
new_dictionary[attr] = str(attr_data) new_dictionary[attr] = str(attr_data)
else: 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(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US")
elif attr == "region": elif attr == "region":
if re.compile("^[A-Z]{2}$").match(str(attr_data)): if re.compile("^[A-Z]{2}$").match(str(attr_data)):
new_dictionary[attr] = str(attr_data) new_dictionary[attr] = str(attr_data)
else: else:
raise Failed("Collection Error: {} attribute {}: {} must match pattern ^[A-Z]{{2}}$ e.g. US".format(m, attr, attr_data)) raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern ^[A-Z]{{2}}$ e.g. US")
elif attr == "sort_by": 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): 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 new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: {} is invalid".format(m, attr, attr_data)) raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} is invalid")
elif attr == "certification_country": elif attr == "certification_country":
if "certification" in data[m] or "certification.lte" in data[m] or "certification.gte" in data[m]: if "certification" in data[m] or "certification.lte" in data[m] or "certification.gte" in data[m]:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: must be used with either certification, certification.lte, or certification.gte".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr}: must be used with either certification, certification.lte, or certification.gte")
elif attr in ["certification", "certification.lte", "certification.gte"]: elif attr in ["certification", "certification.lte", "certification.gte"]:
if "certification_country" in data[m]: if "certification_country" in data[m]:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: must be used with certification_country".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr}: must be used with certification_country")
elif attr in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]: elif attr in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]:
if attr_data is True: if attr_data is True:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
elif attr in ["primary_release_date.gte", "primary_release_date.lte", "release_date.gte", "release_date.lte", "air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte"]: elif attr in ["primary_release_date.gte", "primary_release_date.lte", "release_date.gte", "release_date.lte", "air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte"]:
if re.compile("[0-1]?[0-9][/-][0-3]?[0-9][/-][1-2][890][0-9][0-9]").match(str(attr_data)): if re.compile("[0-1]?[0-9][/-][0-3]?[0-9][/-][1-2][890][0-9][0-9]").match(str(attr_data)):
the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-") the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-")
new_dictionary[attr] = "{}-{}-{}".format(the_date[2], the_date[0], the_date[1]) new_dictionary[attr] = f"{the_date[2]}-{the_date[0]}-{the_date[1]}"
elif re.compile("[1-2][890][0-9][0-9][/-][0-1]?[0-9][/-][0-3]?[0-9]").match(str(attr_data)): elif re.compile("[1-2][890][0-9][0-9][/-][0-1]?[0-9][/-][0-3]?[0-9]").match(str(attr_data)):
the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-") the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-")
new_dictionary[attr] = "{}-{}-{}".format(the_date[0], the_date[1], the_date[2]) new_dictionary[attr] = f"{the_date[0]}-{the_date[1]}-{the_date[2]}"
else: else:
raise Failed("Collection Error: {} attribute {}: {} must match pattern MM/DD/YYYY e.g. 12/25/2020".format(m, attr, attr_data)) raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern MM/DD/YYYY e.g. 12/25/2020")
elif attr in ["primary_release_year", "year", "first_air_date_year"]: elif attr in ["primary_release_year", "year", "first_air_date_year"]:
if isinstance(attr_data, int) and 1800 < attr_data < 2200: if isinstance(attr_data, int) and 1800 < attr_data < 2200:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: must be a valid year e.g. 1990".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr}: must be a valid year e.g. 1990")
elif attr in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]: elif attr in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
if (isinstance(attr_data, int) or isinstance(attr_data, float)) and 0 < attr_data: if (isinstance(attr_data, int) or isinstance(attr_data, float)) and 0 < attr_data:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: must be a valid number greater then 0".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr}: must be a valid number greater then 0")
elif attr in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]: elif attr in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {} not supported".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr} not supported")
elif attr == "limit": elif attr == "limit":
if isinstance(attr_data, int) and attr_data > 0: if isinstance(attr_data, int) and attr_data > 0:
new_dictionary[attr] = attr_data new_dictionary[attr] = attr_data
else: else:
raise Failed("Collection Error: {} attribute {}: must be a valid number greater then 0".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr}: must be a valid number greater then 0")
else: else:
raise Failed("Collection Error: {} attribute {} not supported".format(m, attr)) raise Failed(f"Collection Error: {m} attribute {attr} not supported")
else: else:
raise Failed("Collection Error: {} parameter {} is blank".format(m, attr)) raise Failed(f"Collection Error: {m} parameter {attr} is blank")
if len(new_dictionary) > 1: if len(new_dictionary) > 1:
self.methods.append((method_name, [new_dictionary])) self.methods.append((method_name, [new_dictionary]))
else: else:
raise Failed("Collection Error: {} had no valid fields".format(m)) raise Failed(f"Collection Error: {m} had no valid fields")
elif "tautulli" in method_name: elif "tautulli" in method_name:
new_dictionary = {} new_dictionary = {}
if method_name == "tautulli_popular": new_dictionary["list_type"] = "popular" if method_name == "tautulli_popular": new_dictionary["list_type"] = "popular"
elif method_name == "tautulli_watched": new_dictionary["list_type"] = "watched" elif method_name == "tautulli_watched": new_dictionary["list_type"] = "watched"
else: raise Failed("Collection Error: {} attribute not supported".format(method_name)) else: raise Failed(f"Collection Error: {method_name} attribute not supported")
new_dictionary["list_days"] = get_int(method_name, "list_days", data[m], 30) new_dictionary["list_days"] = get_int(method_name, "list_days", data[m], 30)
new_dictionary["list_size"] = get_int(method_name, "list_size", data[m], 10) new_dictionary["list_size"] = get_int(method_name, "list_size", data[m], 10)
@ -445,7 +445,7 @@ class CollectionBuilder:
new_dictionary = {"sort_by": "anime_num_list_users"} new_dictionary = {"sort_by": "anime_num_list_users"}
if "sort_by" not in data[m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default") if "sort_by" not in data[m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default")
elif not data[m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default") elif not data[m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default")
elif data[m]["sort_by"] not in util.mal_season_sort: logger.warning("Collection Warning: mal_season sort_by attribute {} invalid must be either 'members' or 'score' using members as default".format(data[m]["sort_by"])) elif data[m]["sort_by"] not in util.mal_season_sort: logger.warning(f"Collection Warning: mal_season sort_by attribute {data[m]['sort_by']} invalid must be either 'members' or 'score' using members as default")
else: new_dictionary["sort_by"] = util.mal_season_sort[data[m]["sort_by"]] else: new_dictionary["sort_by"] = util.mal_season_sort[data[m]["sort_by"]]
current_time = datetime.now() current_time = datetime.now()
@ -454,9 +454,9 @@ class CollectionBuilder:
elif current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer" elif current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
elif current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall" elif current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
if "season" not in data[m]: logger.warning("Collection Warning: mal_season season attribute not found using the current season: {} as default".format(new_dictionary["season"])) if "season" not in data[m]: logger.warning(f"Collection Warning: mal_season season attribute not found using the current season: {new_dictionary['season']} as default")
elif not data[m]["season"]: logger.warning("Collection Warning: mal_season season attribute is blank using the current season: {} as default".format(new_dictionary["season"])) elif not data[m]["season"]: logger.warning(f"Collection Warning: mal_season season attribute is blank using the current season: {new_dictionary['season']} as default")
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"])) elif data[m]["season"] not in util.pretty_seasons: logger.warning(f"Collection Warning: mal_season season attribute {data[m]['season']} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {new_dictionary['season']} as default")
else: new_dictionary["season"] = data[m]["season"] else: new_dictionary["season"] = data[m]["season"]
new_dictionary["year"] = get_int(method_name, "year", data[m], current_time.year, minimum=1917, maximum=current_time.year + 1) new_dictionary["year"] = get_int(method_name, "year", data[m], current_time.year, minimum=1917, maximum=current_time.year + 1)
@ -470,35 +470,35 @@ class CollectionBuilder:
if "status" not in data[m]: logger.warning("Collection Warning: mal_season status attribute not found using all as default") if "status" not in data[m]: logger.warning("Collection Warning: mal_season status attribute not found using all as default")
elif not data[m]["status"]: logger.warning("Collection Warning: mal_season status attribute is blank using all as default") elif not data[m]["status"]: logger.warning("Collection Warning: mal_season status attribute is blank using all as default")
elif data[m]["status"] not in util.mal_userlist_status: logger.warning("Collection Warning: mal_season status attribute {} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default".format(data[m]["status"])) elif data[m]["status"] not in util.mal_userlist_status: logger.warning(f"Collection Warning: mal_season status attribute {data[m]['status']} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default")
else: new_dictionary["status"] = util.mal_userlist_status[data[m]["status"]] else: new_dictionary["status"] = util.mal_userlist_status[data[m]["status"]]
if "sort_by" not in data[m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default") if "sort_by" not in data[m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default")
elif not data[m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default") elif not data[m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default")
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"])) elif data[m]["sort_by"] not in util.mal_userlist_sort: logger.warning(f"Collection Warning: mal_season sort_by attribute {data[m]['sort_by']} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default")
else: new_dictionary["sort_by"] = util.mal_userlist_sort[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, maximum=1000) new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, maximum=1000)
self.methods.append((method_name, [new_dictionary])) self.methods.append((method_name, [new_dictionary]))
else: else:
raise Failed("Collection Error: {} attribute is not a dictionary: {}".format(m, data[m])) raise Failed(f"Collection Error: {m} attribute is not a dictionary: {data[m]}")
elif method_name in util.count_lists: elif method_name in util.count_lists:
list_count = util.regex_first_int(data[m], "List Size", default=20) list_count = util.regex_first_int(data[m], "List Size", default=20)
if list_count < 1: if list_count < 1:
logger.warning("Collection Warning: {} must be an integer greater then 0 defaulting to 20".format(method_name)) logger.warning(f"Collection Warning: {method_name} must be an integer greater then 0 defaulting to 20")
list_count = 20 list_count = 20
self.methods.append((method_name, [list_count])) self.methods.append((method_name, [list_count]))
elif method_name in util.tmdb_lists: elif method_name in util.tmdb_lists:
values = config.TMDb.validate_tmdb_list(util.get_int_list(data[m], "TMDb {} ID".format(util.tmdb_type[method_name])), util.tmdb_type[method_name]) values = config.TMDb.validate_tmdb_list(util.get_int_list(data[m], f"TMDb {util.tmdb_type[method_name]} ID"), util.tmdb_type[method_name])
if method_name[-8:] == "_details": if method_name[-8:] == "_details":
if method_name in ["tmdb_collection_details", "tmdb_movie_details", "tmdb_show_details"]: if method_name in ["tmdb_collection_details", "tmdb_movie_details", "tmdb_show_details"]:
item = config.TMDb.get_movie_show_or_collection(values[0], self.library.is_movie) item = config.TMDb.get_movie_show_or_collection(values[0], self.library.is_movie)
if "summary" not in self.details and hasattr(item, "overview") and item.overview: if "summary" not in self.details and hasattr(item, "overview") and item.overview:
self.details["summary"] = item.overview self.details["summary"] = item.overview
if "background" not in self.details and hasattr(item, "backdrop_path") and item.backdrop_path: if "background" not in self.details and hasattr(item, "backdrop_path") and item.backdrop_path:
self.details["background"] = ("url", "{}{}".format(config.TMDb.image_url, item.backdrop_path), method_name[:-8]) self.details["background"] = ("url", f"{config.TMDb.image_url}{item.backdrop_path}", method_name[:-8])
if "poster" not in self.details and hasattr(item, "poster_path") and item.poster_path: if "poster" not in self.details and hasattr(item, "poster_path") and item.poster_path:
self.details["poster"] = ("url", "{}{}".format(config.TMDb.image_url, item.poster_path), method_name[:-8]) self.details["poster"] = ("url", f"{config.TMDb.image_url}{item.poster_path}", method_name[:-8])
else: else:
item = config.TMDb.get_list(values[0]) item = config.TMDb.get_list(values[0])
if "summary" not in self.details and hasattr(item, "description") and item.description: if "summary" not in self.details and hasattr(item, "description") and item.description:
@ -509,9 +509,9 @@ class CollectionBuilder:
elif method_name in util.all_lists: elif method_name in util.all_lists:
self.methods.append((method_name, util.get_list(data[m]))) self.methods.append((method_name, util.get_list(data[m])))
elif method_name not in util.other_attributes: elif method_name not in util.other_attributes:
raise Failed("Collection Error: {} attribute not supported".format(method_name)) raise Failed(f"Collection Error: {method_name} attribute not supported")
else: else:
raise Failed("Collection Error: {} attribute is blank".format(m)) raise Failed(f"Collection Error: {m} attribute is blank")
self.do_arr = False self.do_arr = False
if self.library.Radarr: if self.library.Radarr:
@ -523,8 +523,8 @@ class CollectionBuilder:
items_found = 0 items_found = 0
for method, values in self.methods: for method, values in self.methods:
logger.debug("") logger.debug("")
logger.debug("Method: {}".format(method)) logger.debug(f"Method: {method}")
logger.debug("Values: {}".format(values)) logger.debug(f"Values: {values}")
pretty = util.pretty_names[method] if method in util.pretty_names else method pretty = util.pretty_names[method] if method in util.pretty_names else method
for value in values: for value in values:
items = [] items = []
@ -545,9 +545,9 @@ class CollectionBuilder:
else: missing_shows.append(show_id) else: missing_shows.append(show_id)
return items_found_inside return items_found_inside
logger.info("") logger.info("")
logger.debug("Value: {}".format(value)) logger.debug(f"Value: {value}")
if method == "plex_all": if method == "plex_all":
logger.info("Processing {} {}".format(pretty, "Movies" if self.library.is_movie else "Shows")) logger.info(f"Processing {pretty} {'Movies' if self.library.is_movie else 'Shows'}")
items = self.library.Plex.all() items = self.library.Plex.all()
items_found += len(items) items_found += len(items)
elif method == "plex_collection": elif method == "plex_collection":
@ -563,8 +563,9 @@ class CollectionBuilder:
search_terms[final_method] = search_list search_terms[final_method] = search_list
ors = "" ors = ""
for o, param in enumerate(attr_pair[1]): for o, param in enumerate(attr_pair[1]):
ors += "{}{}".format(" OR " if o > 0 else "{}(".format(attr_pair[0]), param) or_des = " OR " if o > 0 else f"{attr_pair[0]}("
logger.info("\t\t AND {})".format(ors) if i > 0 else "Processing {}: {})".format(pretty, ors)) ors += f"{or_des}{param}"
logger.info(f"\t\t AND {ors})" if i > 0 else f"Processing {pretty}: {ors})")
items = self.library.Plex.search(**search_terms) items = self.library.Plex.search(**search_terms)
items_found += len(items) items_found += len(items)
elif method == "plex_collectionless": elif method == "plex_collectionless":
@ -585,7 +586,7 @@ class CollectionBuilder:
all_items = self.library.Plex.all() all_items = self.library.Plex.all()
length = 0 length = 0
for i, item in enumerate(all_items, 1): for i, item in enumerate(all_items, 1):
length = util.print_return(length, "Processing: {}/{} {}".format(i, len(all_items), item.title)) length = util.print_return(length, f"Processing: {i}/{len(all_items)} {item.title}")
add_item = True add_item = True
for collection in item.collections: for collection in item.collections:
if collection.tag.lower() in good_collections: if collection.tag.lower() in good_collections:
@ -594,7 +595,7 @@ class CollectionBuilder:
if add_item: if add_item:
items.append(item) items.append(item)
items_found += len(items) items_found += len(items)
util.print_end(length, "Processed {} {}".format(len(all_items), "Movies" if self.library.is_movie else "Shows")) util.print_end(length, f"Processed {len(all_items)} {'Movies' if self.library.is_movie else 'Shows'}")
elif "tautulli" in method: elif "tautulli" in method:
items = self.library.Tautulli.get_items(self.library, time_range=value["list_days"], stats_count=value["list_size"], list_type=value["list_type"], stats_count_buffer=value["list_buffer"]) items = self.library.Tautulli.get_items(self.library, time_range=value["list_days"], stats_count=value["list_size"], list_type=value["list_type"], stats_count_buffer=value["list_buffer"])
items_found += len(items) items_found += len(items)
@ -604,7 +605,7 @@ class CollectionBuilder:
elif "imdb" in method: items_found += check_map(self.config.IMDb.get_items(method, value, self.library.Plex.language)) elif "imdb" in method: items_found += check_map(self.config.IMDb.get_items(method, value, self.library.Plex.language))
elif "tmdb" in method: items_found += check_map(self.config.TMDb.get_items(method, value, self.library.is_movie)) elif "tmdb" in method: items_found += check_map(self.config.TMDb.get_items(method, value, self.library.is_movie))
elif "trakt" in method: items_found += check_map(self.config.Trakt.get_items(method, value, self.library.is_movie)) 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)) else: logger.error(f"Collection Error: {method} method not supported")
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) 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 ") else: logger.error("No items found to add to this collection ")
@ -628,12 +629,12 @@ class CollectionBuilder:
if not_lang is None or (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms): if not_lang is None or (not_lang is True and movie.original_language not in terms) or (not_lang is False and movie.original_language in terms):
missing_movies_with_names.append((title, missing_id)) missing_movies_with_names.append((title, missing_id))
if self.details["show_missing"] is True: if self.details["show_missing"] is True:
logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id)) logger.info(f"{collection_name} Collection | ? | {title} (TMDb: {missing_id})")
elif self.details["show_filtered"] is True: elif self.details["show_filtered"] is True:
logger.info("{} Collection | X | {} (TMDb: {})".format(collection_name, title, missing_id)) logger.info(f"{collection_name} Collection | X | {title} (TMDb: {missing_id})")
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
logger.info("{} Movie{} Missing".format(len(missing_movies_with_names), "s" if len(missing_movies_with_names) > 1 else "")) logger.info(f"{len(missing_movies_with_names)} Movie{'s' if len(missing_movies_with_names) > 1 else ''} Missing")
if self.details["save_missing"] is True: if self.details["save_missing"] is True:
self.library.add_missing(collection_name, missing_movies_with_names, True) self.library.add_missing(collection_name, missing_movies_with_names, True)
if self.do_arr and self.library.Radarr: if self.do_arr and self.library.Radarr:
@ -645,10 +646,10 @@ class CollectionBuilder:
title = str(self.config.TVDb.get_series(self.library.Plex.language, tvdb_id=missing_id).title.encode("ascii", "replace").decode()) title = str(self.config.TVDb.get_series(self.library.Plex.language, tvdb_id=missing_id).title.encode("ascii", "replace").decode())
missing_shows_with_names.append((title, missing_id)) missing_shows_with_names.append((title, missing_id))
if self.details["show_missing"] is True: if self.details["show_missing"] is True:
logger.info("{} Collection | ? | {} (TVDB: {})".format(collection_name, title, missing_id)) logger.info(f"{collection_name} Collection | ? | {title} (TVDB: {missing_id})")
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
logger.info("{} Show{} Missing".format(len(missing_shows_with_names), "s" if len(missing_shows_with_names) > 1 else "")) logger.info(f"{len(missing_shows_with_names)} Show{'s' if len(missing_shows_with_names) > 1 else ''} Missing")
if self.details["save_missing"] is True: if self.details["save_missing"] is True:
self.library.add_missing(collection_name, missing_shows_with_names, False) self.library.add_missing(collection_name, missing_shows_with_names, False)
if self.do_arr and self.library.Sonarr: if self.do_arr and self.library.Sonarr:
@ -659,10 +660,10 @@ class CollectionBuilder:
count_removed = 0 count_removed = 0
for ratingKey, item in rating_key_map.items(): for ratingKey, item in rating_key_map.items():
if item is not None: if item is not None:
logger.info("{} Collection | - | {}".format(collection_name, item.title)) logger.info(f"{collection_name} Collection | - | {item.title}")
item.removeCollection(collection_name) item.removeCollection(collection_name)
count_removed += 1 count_removed += 1
logger.info("{} {}{} Removed".format(count_removed, "Movie" if self.library.is_movie else "Show", "s" if count_removed == 1 else "")) logger.info(f"{count_removed} {'Movie' if self.library.is_movie else 'Show'}{'s' if count_removed == 1 else ''} Removed")
logger.info("") logger.info("")
def update_details(self, collection): def update_details(self, collection):
@ -692,10 +693,10 @@ class CollectionBuilder:
if "label_sync_mode" in self.details and self.details["label_sync_mode"] == "sync": if "label_sync_mode" in self.details and self.details["label_sync_mode"] == "sync":
for label in (la for la in item_labels if la not in labels): for label in (la for la in item_labels if la not in labels):
collection.removeLabel(label) collection.removeLabel(label)
logger.info("Detail: Label {} removed".format(label)) logger.info(f"Detail: Label {label} removed")
for label in (la for la in labels if la not in item_labels): for label in (la for la in labels if la not in item_labels):
collection.addLabel(label) collection.addLabel(label)
logger.info("Detail: Label {} added".format(label)) logger.info(f"Detail: Label {label} added")
if self.library.asset_directory: if self.library.asset_directory:
name_mapping = self.name name_mapping = self.name
@ -703,14 +704,14 @@ class CollectionBuilder:
if self.details["name_mapping"]: name_mapping = self.details["name_mapping"] if self.details["name_mapping"]: name_mapping = self.details["name_mapping"]
else: logger.error("Collection Error: name_mapping attribute is blank") else: logger.error("Collection Error: name_mapping attribute is blank")
for ad in self.library.asset_directory: for ad in self.library.asset_directory:
path = os.path.join(ad, "{}".format(name_mapping)) path = os.path.join(ad, f"{name_mapping}")
if not os.path.isdir(path): if not os.path.isdir(path):
continue continue
matches = glob.glob(os.path.join(ad, "{}".format(name_mapping), "poster.*")) matches = glob.glob(os.path.join(ad, f"{name_mapping}", "poster.*"))
if len(matches) > 0: if len(matches) > 0:
for match in matches: for match in matches:
self.posters.append(("file", os.path.abspath(match), "asset_directory")) self.posters.append(("file", os.path.abspath(match), "asset_directory"))
matches = glob.glob(os.path.join(ad, "{}".format(name_mapping), "background.*")) matches = glob.glob(os.path.join(ad, f"{name_mapping}", "background.*"))
if len(matches) > 0: if len(matches) > 0:
for match in matches: for match in matches:
self.backgrounds.append(("file", os.path.abspath(match), "asset_directory")) self.backgrounds.append(("file", os.path.abspath(match), "asset_directory"))
@ -725,25 +726,25 @@ class CollectionBuilder:
background_path = os.path.abspath(matches[0]) if len(matches) > 0 else None background_path = os.path.abspath(matches[0]) if len(matches) > 0 else None
if poster_path: if poster_path:
item.uploadPoster(filepath=poster_path) item.uploadPoster(filepath=poster_path)
logger.info("Detail: asset_directory updated {}'s poster to [file] {}".format(item.title, poster_path)) logger.info(f"Detail: asset_directory updated {item.title}'s poster to [file] {poster_path}")
if background_path: if background_path:
item.uploadArt(filepath=background_path) item.uploadArt(filepath=background_path)
logger.info("Detail: asset_directory updated {}'s background to [file] {}".format(item.title, background_path)) logger.info(f"Detail: asset_directory updated {item.title}'s background to [file] {background_path}")
if poster_path is None and background_path is None: if poster_path is None and background_path is None:
logger.warning("No Files Found: {}".format(os.path.join(path, folder))) logger.warning(f"No Files Found: {os.path.join(path, folder)}")
else: else:
logger.warning("No Folder: {}".format(os.path.join(path, folder))) logger.warning(f"No Folder: {os.path.join(path, folder)}")
poster = util.choose_from_list(self.posters, "poster", list_type="tuple") poster = util.choose_from_list(self.posters, "poster", list_type="tuple")
if not poster and "poster" in self.details: poster = self.details["poster"] if not poster and "poster" in self.details: poster = self.details["poster"]
if poster: if poster:
if poster[0] == "url": collection.uploadPoster(url=poster[1]) if poster[0] == "url": collection.uploadPoster(url=poster[1])
else: collection.uploadPoster(filepath=poster[1]) else: collection.uploadPoster(filepath=poster[1])
logger.info("Detail: {} updated collection poster to [{}] {}".format(poster[2], poster[0], poster[1])) logger.info(f"Detail: {poster[2]} updated collection poster to [{poster[0]}] {poster[1]}")
background = util.choose_from_list(self.backgrounds, "background", list_type="tuple") background = util.choose_from_list(self.backgrounds, "background", list_type="tuple")
if not background and "background" in self.details: background = self.details["background"] if not background and "background" in self.details: background = self.details["background"]
if background: if background:
if background[0] == "url": collection.uploadArt(url=background[1]) if background[0] == "url": collection.uploadArt(url=background[1])
else: collection.uploadArt(filepath=background[1]) else: collection.uploadArt(filepath=background[1])
logger.info("Detail: {} updated collection background to [{}] {}".format(background[2], background[0], background[1])) logger.info(f"Detail: {background[2]} updated collection background to [{background[0]}] {background[1]}")

@ -6,13 +6,13 @@ logger = logging.getLogger("Plex Meta Manager")
class Cache: class Cache:
def __init__(self, config_path, expiration): def __init__(self, config_path, expiration):
cache = "{}.cache".format(os.path.splitext(config_path)[0]) cache = f"{os.path.splitext(config_path)[0]}.cache"
with sqlite3.connect(cache) as connection: with sqlite3.connect(cache) as connection:
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
cursor.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='guids'") cursor.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='guids'")
if cursor.fetchone()[0] == 0: if cursor.fetchone()[0] == 0:
logger.info("Initializing cache database at {}".format(cache)) logger.info(f"Initializing cache database at {cache}")
cursor.execute( cursor.execute(
"""CREATE TABLE IF NOT EXISTS guids ( """CREATE TABLE IF NOT EXISTS guids (
INTEGER PRIMARY KEY, INTEGER PRIMARY KEY,
@ -34,7 +34,7 @@ class Cache:
media_type TEXT)""" media_type TEXT)"""
) )
else: else:
logger.info("Using cache database at {}".format(cache)) logger.info(f"Using cache database at {cache}")
self.expiration = expiration self.expiration = expiration
self.cache_path = cache self.cache_path = cache
@ -73,7 +73,7 @@ class Cache:
with sqlite3.connect(self.cache_path) as connection: with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
cursor.execute("SELECT * FROM guids WHERE {} = ? AND media_type = ?".format(from_id), (key, media_type)) cursor.execute(f"SELECT * FROM guids WHERE {from_id} = ? AND media_type = ?", (key, media_type))
row = cursor.fetchone() row = cursor.fetchone()
if row and row[to_id]: if row and row[to_id]:
datetime_object = datetime.strptime(row["expiration_date"], "%Y-%m-%d") datetime_object = datetime.strptime(row["expiration_date"], "%Y-%m-%d")

@ -23,10 +23,10 @@ class Config:
def __init__(self, default_dir, config_path=None): def __init__(self, default_dir, config_path=None):
logger.info("Locating config...") logger.info("Locating config...")
if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path) if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path)
elif config_path and not os.path.exists(config_path): raise Failed("Config Error: config not found at {}".format(os.path.abspath(config_path))) elif config_path and not os.path.exists(config_path): raise Failed(f"Config Error: config not found at {os.path.abspath(config_path)}")
elif os.path.exists(os.path.join(default_dir, "config.yml")): self.config_path = os.path.abspath(os.path.join(default_dir, "config.yml")) elif os.path.exists(os.path.join(default_dir, "config.yml")): self.config_path = os.path.abspath(os.path.join(default_dir, "config.yml"))
else: raise Failed("Config Error: config not found at {}".format(os.path.abspath(default_dir))) else: raise Failed(f"Config Error: config not found at {os.path.abspath(default_dir)}")
logger.info("Using {} as config".format(self.config_path)) logger.info(f"Using {self.config_path} as config")
yaml.YAML().allow_duplicate_keys = True yaml.YAML().allow_duplicate_keys = True
try: try:
@ -74,7 +74,7 @@ class Config:
yaml.round_trip_dump(new_config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi) yaml.round_trip_dump(new_config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
self.data = new_config self.data = new_config
except yaml.scanner.ScannerError as e: except yaml.scanner.ScannerError as e:
raise Failed("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) raise Failed(f"YAML Error: {util.tab_new_lines(e)}")
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): 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):
endline = "" endline = ""
@ -85,28 +85,28 @@ class Config:
data = None data = None
do_print = False do_print = False
save = False save = False
text = "{} attribute".format(attribute) if parent is None else "{} sub-attribute {}".format(parent, attribute) text = f"{attribute} attribute" if parent is None else f"{parent} sub-attribute {attribute}"
if data is None or attribute not in data: if data is None or attribute not in data:
message = "{} not found".format(text) message = f"{text} not found"
if parent and save is True: if parent and save is True:
loaded_config, ind_in, bsi_in = 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) endline = f"\n{parent} sub-attribute {attribute} added to config"
if parent not in loaded_config or not loaded_config[parent]: loaded_config[parent] = {attribute: default} 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 elif attribute not in loaded_config[parent]: loaded_config[parent][attribute] = default
else: endline = "" else: endline = ""
yaml.round_trip_dump(loaded_config, open(self.config_path, "w"), indent=ind_in, block_seq_indent=bsi_in) 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: elif not data[attribute] and data[attribute] is not False:
if default_is_none is True: return None if default_is_none is True: return None
else: message = "{} is blank".format(text) else: message = f"{text} is blank"
elif var_type == "bool": elif var_type == "bool":
if isinstance(data[attribute], bool): return data[attribute] if isinstance(data[attribute], bool): return data[attribute]
else: message = "{} must be either true or false".format(text) else: message = f"{text} must be either true or false"
elif var_type == "int": elif var_type == "int":
if isinstance(data[attribute], int) and data[attribute] > 0: return data[attribute] if isinstance(data[attribute], int) and data[attribute] > 0: return data[attribute]
else: message = "{} must an integer > 0".format(text) else: message = f"{text} must an integer > 0"
elif var_type == "path": elif var_type == "path":
if os.path.exists(os.path.abspath(data[attribute])): return data[attribute] if os.path.exists(os.path.abspath(data[attribute])): return data[attribute]
else: message = "Path {} does not exist".format(os.path.abspath(data[attribute])) else: message = f"Path {os.path.abspath(data[attribute])} does not exist"
elif var_type == "list": return util.get_list(data[attribute]) elif var_type == "list": return util.get_list(data[attribute])
elif var_type == "list_path": 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))] temp_list = [path for path in util.get_list(data[attribute], split=True) if os.path.exists(os.path.abspath(path))]
@ -114,26 +114,26 @@ class Config:
else: message = "No Paths exist" else: message = "No Paths exist"
elif var_type == "lower_list": 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] elif test_list is None or data[attribute] in test_list: return data[attribute]
else: message = "{}: {} is an invalid input".format(text, data[attribute]) else: message = f"{text}: {data[attribute]} is an invalid input"
if var_type == "path" and default and os.path.exists(os.path.abspath(default)): if var_type == "path" and default and os.path.exists(os.path.abspath(default)):
return default return default
elif var_type == "path" and default: elif var_type == "path" and default:
default = None default = None
if attribute in data and data[attribute]: if attribute in data and data[attribute]:
message = "neither {} or the default path {} could be found".format(data[attribute], default) message = f"neither {data[attribute]} or the default path {default} could be found"
else: else:
message = "no {} found and the default path {} could be found".format(text, default) message = f"no {text} found and the default path {default} could be found"
if default is not None or default_is_none: if default is not None or default_is_none:
message = message + " using {} as default".format(default) message = message + f" using {default} as default"
message = message + endline message = message + endline
if req_default and default is None: if req_default and default is None:
raise Failed("Config Error: {} attribute must be set under {} globally or under this specific Library".format(attribute, parent)) raise Failed(f"Config Error: {attribute} attribute must be set under {parent} globally or under this specific Library")
if (default is None and not default_is_none) or throw: if (default is None and not default_is_none) or throw:
if len(options) > 0: if len(options) > 0:
message = message + "\n" + options message = message + "\n" + options
raise Failed("Config Error: {}".format(message)) raise Failed(f"Config Error: {message}")
if do_print: if do_print:
util.print_multiline("Config Warning: {}".format(message)) util.print_multiline(f"Config Warning: {message}")
if attribute in data and data[attribute] and test_list is not None and data[attribute] not in test_list: if attribute in data and data[attribute] and test_list is not None and data[attribute] not in test_list:
util.print_multiline(options) util.print_multiline(options)
return default return default
@ -163,7 +163,7 @@ class Config:
except Failed as e: raise Failed(e) except Failed as e: raise Failed(e)
self.tmdb["language"] = check_for_attribute(self.data, "language", parent="tmdb", default="en") self.tmdb["language"] = check_for_attribute(self.data, "language", parent="tmdb", default="en")
self.TMDb = TMDbAPI(self.tmdb) self.TMDb = TMDbAPI(self.tmdb)
logger.info("TMDb Connection {}".format("Failed" if self.TMDb is None else "Successful")) logger.info(f"TMDb Connection {'Failed' if self.TMDb is None else 'Successful'}")
else: else:
raise Failed("Config Error: tmdb attribute not found") raise Failed("Config Error: tmdb attribute not found")
@ -181,7 +181,7 @@ class Config:
self.Trakt = TraktAPI(self.trakt, authorization) self.Trakt = TraktAPI(self.trakt, authorization)
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
logger.info("Trakt Connection {}".format("Failed" if self.Trakt is None else "Successful")) logger.info(f"Trakt Connection {'Failed' if self.Trakt is None else 'Successful'}")
else: else:
logger.warning("trakt attribute not found") logger.warning("trakt attribute not found")
@ -200,7 +200,7 @@ class Config:
self.MyAnimeList = MyAnimeListAPI(self.mal, self.MyAnimeListIDList, authorization) self.MyAnimeList = MyAnimeListAPI(self.mal, self.MyAnimeListIDList, authorization)
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
logger.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful")) logger.info(f"My Anime List Connection {'Failed' if self.MyAnimeList is None else 'Successful'}")
else: else:
logger.warning("mal attribute not found") logger.warning("mal attribute not found")
@ -249,10 +249,10 @@ class Config:
params = {} params = {}
if "library_name" in libs[lib] and libs[lib]["library_name"]: if "library_name" in libs[lib] and libs[lib]["library_name"]:
params["name"] = str(libs[lib]["library_name"]) params["name"] = str(libs[lib]["library_name"])
logger.info("Connecting to {} ({}) Library...".format(params["name"], lib)) logger.info(f"Connecting to {params['name']} ({lib}) Library...")
else: else:
params["name"] = str(lib) params["name"] = str(lib)
logger.info("Connecting to {} Library...".format(params["name"])) logger.info(f"Connecting to {params['name']} Library...")
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) 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: if params["asset_directory"] is None:
@ -265,21 +265,21 @@ class Config:
params["save_missing"] = check_for_attribute(libs[lib], "save_missing", parent="settings", var_type="bool", default=self.general["save_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)
try: try:
params["metadata_path"] = check_for_attribute(libs[lib], "metadata_path", var_type="path", default=os.path.join(default_dir, "{}.yml".format(lib)), throw=True) params["metadata_path"] = check_for_attribute(libs[lib], "metadata_path", var_type="path", default=os.path.join(default_dir, f"{lib}.yml"), throw=True)
params["library_type"] = check_for_attribute(libs[lib], "library_type", test_list=["movie", "show"], options=" movie (For Movie Libraries)\n show (For Show Libraries)", throw=True) params["library_type"] = check_for_attribute(libs[lib], "library_type", test_list=["movie", "show"], options=" movie (For Movie Libraries)\n show (For Show Libraries)", throw=True)
params["plex"] = {} params["plex"] = {}
params["plex"]["url"] = check_for_attribute(libs[lib], "url", parent="plex", default=self.general["plex"]["url"], req_default=True, save=False) 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) params["plex"]["token"] = check_for_attribute(libs[lib], "token", parent="plex", default=self.general["plex"]["token"], req_default=True, save=False)
params["plex"]["timeout"] = check_for_attribute(libs[lib], "timeout", parent="plex", var_type="int", default=self.general["plex"]["timeout"], save=False) params["plex"]["timeout"] = check_for_attribute(libs[lib], "timeout", parent="plex", var_type="int", default=self.general["plex"]["timeout"], save=False)
library = PlexAPI(params, self.TMDb, self.TVDb) library = PlexAPI(params, self.TMDb, self.TVDb)
logger.info("{} Library Connection Successful".format(params["name"])) logger.info(f"{params['name']} Library Connection Successful")
except Failed as e: except Failed as e:
util.print_multiline(e) util.print_multiline(e)
logger.info("{} Library Connection Failed".format(params["name"])) logger.info(f"{params['name']} Library Connection Failed")
continue continue
if self.general["radarr"]["url"] or "radarr" in libs[lib]: if self.general["radarr"]["url"] or "radarr" in libs[lib]:
logger.info("Connecting to {} library's Radarr...".format(params["name"])) logger.info(f"Connecting to {params['name']} library's Radarr...")
radarr_params = {} radarr_params = {}
try: try:
radarr_params["url"] = check_for_attribute(libs[lib], "url", parent="radarr", default=self.general["radarr"]["url"], req_default=True, save=False) radarr_params["url"] = check_for_attribute(libs[lib], "url", parent="radarr", default=self.general["radarr"]["url"], req_default=True, save=False)
@ -293,10 +293,10 @@ class Config:
library.add_Radarr(RadarrAPI(self.TMDb, radarr_params)) library.add_Radarr(RadarrAPI(self.TMDb, radarr_params))
except Failed as e: except Failed as e:
util.print_multiline(e) util.print_multiline(e)
logger.info("{} library's Radarr Connection {}".format(params["name"], "Failed" if library.Radarr is None else "Successful")) logger.info(f"{params['name']} library's Radarr Connection {'Failed' if library.Radarr is None else 'Successful'}")
if self.general["sonarr"]["url"] or "sonarr" in libs[lib]: if self.general["sonarr"]["url"] or "sonarr" in libs[lib]:
logger.info("Connecting to {} library's Sonarr...".format(params["name"])) logger.info(f"Connecting to {params['name']} library's Sonarr...")
sonarr_params = {} sonarr_params = {}
try: try:
sonarr_params["url"] = check_for_attribute(libs[lib], "url", parent="sonarr", default=self.general["sonarr"]["url"], req_default=True, save=False) sonarr_params["url"] = check_for_attribute(libs[lib], "url", parent="sonarr", default=self.general["sonarr"]["url"], req_default=True, save=False)
@ -310,10 +310,10 @@ class Config:
library.add_Sonarr(SonarrAPI(self.TVDb, sonarr_params, library.Plex.language)) library.add_Sonarr(SonarrAPI(self.TVDb, sonarr_params, library.Plex.language))
except Failed as e: except Failed as e:
util.print_multiline(e) util.print_multiline(e)
logger.info("{} library's Sonarr Connection {}".format(params["name"], "Failed" if library.Sonarr is None else "Successful")) logger.info(f"{params['name']} library's Sonarr Connection {'Failed' if library.Sonarr is None else 'Successful'}")
if self.general["tautulli"]["url"] or "tautulli" in libs[lib]: if self.general["tautulli"]["url"] or "tautulli" in libs[lib]:
logger.info("Connecting to {} library's Tautulli...".format(params["name"])) logger.info(f"Connecting to {params['name']} library's Tautulli...")
tautulli_params = {} tautulli_params = {}
try: try:
tautulli_params["url"] = check_for_attribute(libs[lib], "url", parent="tautulli", default=self.general["tautulli"]["url"], req_default=True, save=False) tautulli_params["url"] = check_for_attribute(libs[lib], "url", parent="tautulli", default=self.general["tautulli"]["url"], req_default=True, save=False)
@ -321,14 +321,14 @@ class Config:
library.add_Tautulli(TautulliAPI(tautulli_params)) library.add_Tautulli(TautulliAPI(tautulli_params))
except Failed as e: except Failed as e:
util.print_multiline(e) util.print_multiline(e)
logger.info("{} library's Tautulli Connection {}".format(params["name"], "Failed" if library.Tautulli is None else "Successful")) logger.info(f"{params['name']} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}")
self.libraries.append(library) self.libraries.append(library)
util.separator() util.separator()
if len(self.libraries) > 0: if len(self.libraries) > 0:
logger.info("{} Plex Library Connection{} Successful".format(len(self.libraries), "s" if len(self.libraries) > 1 else "")) logger.info(f"{len(self.libraries)} Plex Library Connection{'s' if len(self.libraries) > 1 else ''} Successful")
else: else:
raise Failed("Plex Error: No Plex libraries were found") raise Failed("Plex Error: No Plex libraries were found")
@ -338,15 +338,15 @@ class Config:
for library in self.libraries: for library in self.libraries:
os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout) os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout)
logger.info("") logger.info("")
util.separator("{} Library".format(library.name)) util.separator(f"{library.name} Library")
try: library.update_metadata(self.TMDb, test) try: library.update_metadata(self.TMDb, test)
except Failed as e: logger.error(e) except Failed as e: logger.error(e)
logger.info("") logger.info("")
util.separator("{} Library {}Collections".format(library.name, "Test " if test else "")) util.separator(f"{library.name} Library {'Test ' if test else ''}Collections")
collections = {c: library.collections[c] for c in util.get_list(requested_collections) if c in library.collections} if requested_collections else library.collections 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: if collections:
logger.info("") logger.info("")
util.separator("Mapping {} Library".format(library.name)) util.separator(f"Mapping {library.name} Library")
logger.info("") logger.info("")
movie_map, show_map = self.map_guids(library) movie_map, show_map = self.map_guids(library)
for c in collections: for c in collections:
@ -366,7 +366,7 @@ class Config:
continue continue
try: try:
logger.info("") logger.info("")
util.separator("{} Collection".format(c)) util.separator(f"{c} Collection")
logger.info("") logger.info("")
rating_key_map = {} rating_key_map = {}
@ -399,7 +399,7 @@ class Config:
for i, f in enumerate(builder.filters): for i, f in enumerate(builder.filters):
if i == 0: if i == 0:
logger.info("") logger.info("")
logger.info("Collection Filter {}: {}".format(f[0], f[1])) logger.info(f"Collection Filter {f[0]}: {f[1]}")
builder.run_methods(collection_obj, collection_name, rating_key_map, movie_map, show_map) builder.run_methods(collection_obj, collection_name, rating_key_map, movie_map, show_map)
@ -413,10 +413,10 @@ class Config:
except Exception as e: except Exception as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Unknown Error: {}".format(e)) logger.error(f"Unknown Error: {e}")
if library.show_unmanaged is True and not test and not requested_collections: if library.show_unmanaged is True and not test and not requested_collections:
logger.info("") logger.info("")
util.separator("Unmanaged Collections in {} Library".format(library.name)) util.separator(f"Unmanaged Collections in {library.name} Library")
logger.info("") logger.info("")
unmanaged_count = 0 unmanaged_count = 0
collections_in_plex = [str(plex_col) for plex_col in collections] collections_in_plex = [str(plex_col) for plex_col in collections]
@ -433,15 +433,15 @@ class Config:
movie_map = {} movie_map = {}
show_map = {} show_map = {}
length = 0 length = 0
logger.info("Mapping {} Library: {}".format("Movie" if library.is_movie else "Show", library.name)) logger.info(f"Mapping {'Movie' if library.is_movie else 'Show'} Library: {library.name}")
items = library.Plex.all() items = library.Plex.all()
for i, item in enumerate(items, 1): for i, item in enumerate(items, 1):
length = util.print_return(length, "Processing: {}/{} {}".format(i, len(items), item.title)) length = util.print_return(length, f"Processing: {i}/{len(items)} {item.title}")
try: try:
id_type, main_id = self.get_id(item, library, length) id_type, main_id = self.get_id(item, library, length)
except BadRequest: except BadRequest:
util.print_stacktrace() util.print_stacktrace()
util.print_end(length, "{} | {} for {} not found".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, item.title)) util.print_end(length, f"{'Cache | ! |' if self.Cache else 'Mapping Error:'} | {item.guid} for {item.title} not found")
continue continue
if isinstance(main_id, list): if isinstance(main_id, list):
if id_type == "movie": if id_type == "movie":
@ -451,7 +451,7 @@ class Config:
else: else:
if id_type == "movie": movie_map[main_id] = item.ratingKey if id_type == "movie": movie_map[main_id] = item.ratingKey
elif id_type == "show": show_map[main_id] = item.ratingKey elif id_type == "show": show_map[main_id] = item.ratingKey
util.print_end(length, "Processed {} {}".format(len(items), "Movies" if library.is_movie else "Shows")) util.print_end(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}")
return movie_map, show_map return movie_map, show_map
def get_id(self, item, library, length): def get_id(self, item, library, length):
@ -484,10 +484,10 @@ class Config:
elif item_type == "hama": elif item_type == "hama":
if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1)) if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1))
elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1) elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1)
else: error_message = "Hama Agent ID: {} not supported".format(check_id) else: error_message = f"Hama Agent ID: {check_id} not supported"
elif item_type == "myanimelist": mal_id = check_id elif item_type == "myanimelist": mal_id = check_id
elif item_type == "local": error_message = "No match in Plex" elif item_type == "local": error_message = "No match in Plex"
else: error_message = "Agent {} not supported".format(item_type) else: error_message = f"Agent {item_type} not supported"
if not error_message: if not error_message:
if anidb_id and not tvdb_id: if anidb_id and not tvdb_id:
@ -501,7 +501,7 @@ class Config:
ids = self.MyAnimeListIDList.find_mal_ids(mal_id) 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"]) 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"]) elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: tmdb_id = int(ids["themoviedb_id"])
else: raise Failed("MyAnimeList Error: MyAnimeList ID: {} has no other IDs associated with it".format(mal_id)) else: raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} has no other IDs associated with it")
except Failed: except Failed:
pass pass
if mal_id and not tvdb_id: if mal_id and not tvdb_id:
@ -560,8 +560,8 @@ class Config:
elif self.Trakt: api_name = "Trakt" elif self.Trakt: api_name = "Trakt"
else: api_name = None else: api_name = None
if tmdb_id and imdb_id: id_name = "TMDb ID: {} or IMDb ID: {}".format(tmdb_id, imdb_id) 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 = "IMDb ID: {} or TVDb ID: {}".format(imdb_id, tvdb_id) elif imdb_id and tvdb_id: id_name = f"IMDb ID: {imdb_id} or TVDb ID: {tvdb_id}"
elif tmdb_id: id_name = "TMDb ID: {}".format(tmdb_id) elif tmdb_id: id_name = "TMDb ID: {}".format(tmdb_id)
elif imdb_id: id_name = "IMDb ID: {}".format(imdb_id) elif imdb_id: id_name = "IMDb ID: {}".format(imdb_id)
elif tvdb_id: id_name = "TVDb ID: {}".format(tvdb_id) elif tvdb_id: id_name = "TVDb ID: {}".format(tvdb_id)
@ -575,7 +575,7 @@ class Config:
if self.Cache and (tmdb_id and library.is_movie) or ((tvdb_id or ((anidb_id or mal_id) and tmdb_id)) and library.is_show): if self.Cache and (tmdb_id and library.is_movie) or ((tvdb_id or ((anidb_id or mal_id) and tmdb_id)) and library.is_show):
if isinstance(tmdb_id, list): if isinstance(tmdb_id, list):
for i in range(len(tmdb_id)): for i in range(len(tmdb_id)):
util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id[i] if tmdb_id[i] else "None", imdb_id[i] if imdb_id[i] else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) 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}")
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) 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: 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)) 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))

@ -18,21 +18,21 @@ class IMDbAPI:
def get_imdb_ids_from_url(self, imdb_url, language, limit): def get_imdb_ids_from_url(self, imdb_url, language, limit):
imdb_url = imdb_url.strip() imdb_url = imdb_url.strip()
if not imdb_url.startswith("https://www.imdb.com/list/ls") and not imdb_url.startswith("https://www.imdb.com/search/title/?"): if not imdb_url.startswith("https://www.imdb.com/list/ls") and not imdb_url.startswith("https://www.imdb.com/search/title/?"):
raise Failed("IMDb Error: {} must begin with either:\n| https://www.imdb.com/list/ls (For Lists)\n| https://www.imdb.com/search/title/? (For Searches)".format(imdb_url)) raise Failed(f"IMDb Error: {imdb_url} must begin with either:\n| https://www.imdb.com/list/ls (For Lists)\n| https://www.imdb.com/search/title/? (For Searches)")
if imdb_url.startswith("https://www.imdb.com/list/ls"): if imdb_url.startswith("https://www.imdb.com/list/ls"):
try: list_id = re.search("(\\d+)", str(imdb_url)).group(1) try: list_id = re.search("(\\d+)", str(imdb_url)).group(1)
except AttributeError: raise Failed("IMDb Error: Failed to parse List ID from {}".format(imdb_url)) except AttributeError: raise Failed(f"IMDb Error: Failed to parse List ID from {imdb_url}")
current_url = "https://www.imdb.com/search/title/?lists=ls{}".format(list_id) current_url = f"https://www.imdb.com/search/title/?lists=ls{list_id}"
else: else:
current_url = imdb_url current_url = imdb_url
header = {"Accept-Language": language} header = {"Accept-Language": language}
length = 0 length = 0
imdb_ids = [] imdb_ids = []
try: results = self.send_request(current_url, header).xpath("//div[@class='desc']/span/text()")[0].replace(",", "") try: results = self.send_request(current_url, header).xpath("//div[@class='desc']/span/text()")[0].replace(",", "")
except IndexError: raise Failed("IMDb Error: Failed to parse URL: {}".format(imdb_url)) except IndexError: raise Failed(f"IMDb Error: Failed to parse URL: {imdb_url}")
try: total = int(re.findall("(\\d+) title", results)[0]) try: total = int(re.findall("(\\d+) title", results)[0])
except IndexError: raise Failed("IMDb Error: No Results at URL: {}".format(imdb_url)) except IndexError: raise Failed(f"IMDb Error: No Results at URL: {imdb_url}")
if "&start=" in current_url: current_url = re.sub("&start=\\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 "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url)
if limit < 1 or total < limit: limit = total if limit < 1 or total < limit: limit = total
@ -41,12 +41,12 @@ class IMDbAPI:
num_of_pages = math.ceil(int(limit) / 250) num_of_pages = math.ceil(int(limit) / 250)
for i in range(1, num_of_pages + 1): for i in range(1, num_of_pages + 1):
start_num = (i - 1) * 250 + 1 start_num = (i - 1) * 250 + 1
length = util.print_return(length, "Parsing Page {}/{} {}-{}".format(i, num_of_pages, start_num, limit if i == num_of_pages else i * 250)) length = util.print_return(length, f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * 250}")
response = self.send_request("{}&count={}&start={}".format(current_url, remainder if i == num_of_pages else 250, start_num), header) response = self.send_request(f"{current_url}&count={remainder if i == num_of_pages else 250}&start={start_num}", header)
imdb_ids.extend(response.xpath("//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst")) imdb_ids.extend(response.xpath("//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst"))
util.print_end(length) util.print_end(length)
if imdb_ids: return imdb_ids if imdb_ids: return imdb_ids
else: raise Failed("IMDb Error: No Movies Found at {}".format(imdb_url)) else: raise Failed(f"IMDb Error: No Movies Found at {imdb_url}")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url, header): def send_request(self, url, header):
@ -55,34 +55,35 @@ class IMDbAPI:
def get_items(self, method, data, language, status_message=True): def get_items(self, method, data, language, status_message=True):
pretty = util.pretty_names[method] if method in util.pretty_names else method pretty = util.pretty_names[method] if method in util.pretty_names else method
if status_message: if status_message:
logger.debug("Data: {}".format(data)) logger.debug(f"Data: {data}")
show_ids = [] show_ids = []
movie_ids = [] movie_ids = []
if method == "imdb_id": if method == "imdb_id":
if status_message: if status_message:
logger.info("Processing {}: {}".format(pretty, data)) logger.info(f"Processing {pretty}: {data}")
tmdb_id, tvdb_id = self.convert_from_imdb(data, language) tmdb_id, tvdb_id = self.convert_from_imdb(data, language)
if tmdb_id: movie_ids.append(tmdb_id) if tmdb_id: movie_ids.append(tmdb_id)
if tvdb_id: show_ids.append(tvdb_id) if tvdb_id: show_ids.append(tvdb_id)
elif method == "imdb_list": elif method == "imdb_list":
if status_message: if status_message:
logger.info("Processing {}: {}".format(pretty, "{} Items at {}".format(data["limit"], data["url"]) if data["limit"] > 0 else data["url"])) status = f"{data['limit']} Items at " if data['limit'] > 0 else ''
logger.info(f"Processing {pretty}: {status}{data['url']}")
imdb_ids = self.get_imdb_ids_from_url(data["url"], language, data["limit"]) imdb_ids = self.get_imdb_ids_from_url(data["url"], language, data["limit"])
total_ids = len(imdb_ids) total_ids = len(imdb_ids)
length = 0 length = 0
for i, imdb_id in enumerate(imdb_ids, 1): for i, imdb_id in enumerate(imdb_ids, 1):
length = util.print_return(length, "Converting IMDb ID {}/{}".format(i, total_ids)) length = util.print_return(length, f"Converting IMDb ID {i}/{total_ids}")
try: try:
tmdb_id, tvdb_id = self.convert_from_imdb(imdb_id, language) tmdb_id, tvdb_id = self.convert_from_imdb(imdb_id, language)
if tmdb_id: movie_ids.append(tmdb_id) if tmdb_id: movie_ids.append(tmdb_id)
if tvdb_id: show_ids.append(tvdb_id) if tvdb_id: show_ids.append(tvdb_id)
except Failed as e: logger.warning(e) except Failed as e: logger.warning(e)
util.print_end(length, "Processed {} IMDb IDs".format(total_ids)) util.print_end(length, f"Processed {total_ids} IMDb IDs")
else: else:
raise Failed("IMDb Error: Method {} not supported".format(method)) raise Failed(f"IMDb Error: Method {method} not supported")
if status_message: if status_message:
logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug("TVDb IDs Found: {}".format(show_ids)) logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids return movie_ids, show_ids
def convert_from_imdb(self, imdb_id, language): def convert_from_imdb(self, imdb_id, language):
@ -123,7 +124,7 @@ class IMDbAPI:
try: try:
if tvdb_id and not from_cache: self.TVDb.get_series(language, tvdb_id=tvdb_id) if tvdb_id and not from_cache: self.TVDb.get_series(language, tvdb_id=tvdb_id)
except Failed: tvdb_id = None 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(f"IMDb Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}")
if self.Cache: if self.Cache:
if tmdb_id and update_tmdb is not False: if tmdb_id and update_tmdb is not False:
self.Cache.update_imdb("movie", update_tmdb, imdb_id, tmdb_id) self.Cache.update_imdb("movie", update_tmdb, imdb_id, tmdb_id)

@ -18,13 +18,13 @@ class MyAnimeListIDList:
for attrs in self.ids: for attrs in self.ids:
if from_id in attrs and int(attrs[from_id]) == int(input_id) and to_id in attrs and int(attrs[to_id]) > 0: if from_id in attrs and int(attrs[from_id]) == int(input_id) and to_id in attrs and int(attrs[to_id]) > 0:
return int(attrs[to_id]) return int(attrs[to_id])
raise Failed("MyAnimeList Error: {} ID not found for {}: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id)) raise Failed(f"MyAnimeList Error: {util.pretty_ids[to_id]} ID not found for {util.pretty_ids[from_id]}: {input_id}")
def find_mal_ids(self, mal_id): def find_mal_ids(self, mal_id):
for mal in self.ids: for mal in self.ids:
if "mal_id" in mal and int(mal["mal_id"]) == int(mal_id): if "mal_id" in mal and int(mal["mal_id"]) == int(mal_id):
return mal return mal
raise Failed("MyAnimeList Error: MyAnimeList ID: {} not found".format(mal_id)) raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} not found")
class MyAnimeListAPI: class MyAnimeListAPI:
def __init__(self, params, MyAnimeListIDList_in, authorization=None): def __init__(self, params, MyAnimeListIDList_in, authorization=None):
@ -47,9 +47,9 @@ class MyAnimeListAPI:
def get_authorization(self): def get_authorization(self):
code_verifier = secrets.token_urlsafe(100)[:128] code_verifier = secrets.token_urlsafe(100)[:128]
url = "{}?response_type=code&client_id={}&code_challenge={}".format(self.urls["oauth_authorize"], self.client_id, code_verifier) url = f"{self.urls['oauth_authorize']}?response_type=code&client_id={self.client_id}&code_challenge={code_verifier}"
logger.info("") logger.info("")
logger.info("Navigate to: {}".format(url)) logger.info(f"Navigate to: {url}")
logger.info("") logger.info("")
logger.info("Login and click the Allow option. You will then be redirected to a localhost") logger.info("Login and click the Allow option. You will then be redirected to a localhost")
logger.info("url that most likely won't load, which is fine. Copy the URL and paste it below") logger.info("url that most likely won't load, which is fine. Copy the URL and paste it below")
@ -106,7 +106,7 @@ class MyAnimeListAPI:
"expires_in": authorization["expires_in"], "expires_in": authorization["expires_in"],
"refresh_token": authorization["refresh_token"] "refresh_token": authorization["refresh_token"]
} }
logger.info("Saving authorization information to {}".format(self.config_path)) logger.info(f"Saving authorization information to {self.config_path}")
yaml.round_trip_dump(config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi) yaml.round_trip_dump(config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
self.authorization = authorization self.authorization = authorization
return True return True
@ -119,8 +119,8 @@ class MyAnimeListAPI:
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def send_request(self, url, authorization=None): def send_request(self, url, authorization=None):
new_authorization = authorization if authorization else self.authorization new_authorization = authorization if authorization else self.authorization
response = requests.get(url, headers={"Authorization": "Bearer {}".format(new_authorization["access_token"])}).json() response = requests.get(url, headers={"Authorization": f"Bearer {new_authorization['access_token']}"}).json()
if "error" in response: raise Failed("MyAnimeList Error: {}".format(response["error"])) if "error" in response: raise Failed(f"MyAnimeList Error: {response['error']}")
else: return response else: return response
def parse_mal_ids(self, data): def parse_mal_ids(self, data):
@ -131,50 +131,51 @@ class MyAnimeListAPI:
return mal_ids return mal_ids
def get_username(self): def get_username(self):
return self.send_request("{}/@me".format(self.urls["user"]))["name"] return self.send_request(f"{self.urls['user']}/@me")["name"]
def get_ranked(self, ranking_type, limit): def get_ranked(self, ranking_type, limit):
url = "{}?ranking_type={}&limit={}".format(self.urls["ranking"], ranking_type, limit) url = f"{self.urls['ranking']}?ranking_type={ranking_type}&limit={limit}"
return self.parse_mal_ids(self.send_request(url)) return self.parse_mal_ids(self.send_request(url))
def get_season(self, season, year, sort_by, limit): def get_season(self, season, year, sort_by, limit):
url = "{}/{}/{}?sort={}&limit={}".format(self.urls["season"], year, season, sort_by, limit) url = f"{self.urls['season']}/{year}/{season}?sort={sort_by}&limit={limit}"
return self.parse_mal_ids(self.send_request(url)) return self.parse_mal_ids(self.send_request(url))
def get_suggestions(self, limit): def get_suggestions(self, limit):
url = "{}?limit={}".format(self.urls["suggestions"], limit) url = f"{self.urls['suggestions']}?limit={limit}"
return self.parse_mal_ids(self.send_request(url)) return self.parse_mal_ids(self.send_request(url))
def get_userlist(self, username, status, sort_by, limit): def get_userlist(self, username, status, sort_by, limit):
url = "{}/{}/animelist?{}sort={}&limit={}".format(self.urls["user"], username, "" if status == "all" else "status={}&".format(status), sort_by, limit) final_status = "" if status == "all" else f"status={status}&"
url = f"{self.urls['user']}/{username}/animelist?{final_status}sort={sort_by}&limit={limit}"
return self.parse_mal_ids(self.send_request(url)) return self.parse_mal_ids(self.send_request(url))
def get_items(self, method, data, status_message=True): def get_items(self, method, data, status_message=True):
if status_message: if status_message:
logger.debug("Data: {}".format(data)) logger.debug(f"Data: {data}")
pretty = util.pretty_names[method] if method in util.pretty_names else method pretty = util.pretty_names[method] if method in util.pretty_names else method
if method == "mal_id": if method == "mal_id":
mal_ids = [data] mal_ids = [data]
if status_message: if status_message:
logger.info("Processing {}: {}".format(pretty, data)) logger.info(f"Processing {pretty}: {data}")
elif method in util.mal_ranked_name: elif method in util.mal_ranked_name:
mal_ids = self.get_ranked(util.mal_ranked_name[method], data) mal_ids = self.get_ranked(util.mal_ranked_name[method], data)
if status_message: if status_message:
logger.info("Processing {}: {} Anime".format(pretty, data)) logger.info(f"Processing {pretty}: {data} Anime")
elif method == "mal_season": elif method == "mal_season":
mal_ids = self.get_season(data["season"], data["year"], data["sort_by"], data["limit"]) mal_ids = self.get_season(data["season"], data["year"], data["sort_by"], data["limit"])
if status_message: if status_message:
logger.info("Processing {}: {} Anime from {} {} sorted by {}".format(pretty, data["limit"], util.pretty_seasons[data["season"]], data["year"], util.mal_pretty[data["sort_by"]])) logger.info(f"Processing {pretty}: {data['limit']} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {util.mal_pretty[data['sort_by']]}")
elif method == "mal_suggested": elif method == "mal_suggested":
mal_ids = self.get_suggestions(data) mal_ids = self.get_suggestions(data)
if status_message: if status_message:
logger.info("Processing {}: {} Anime".format(pretty, data)) logger.info(f"Processing {pretty}: {data} Anime")
elif method == "mal_userlist": elif method == "mal_userlist":
mal_ids = self.get_userlist(data["username"], data["status"], data["sort_by"], data["limit"]) mal_ids = self.get_userlist(data["username"], data["status"], data["sort_by"], data["limit"])
if status_message: if status_message:
logger.info("Processing {}: {} Anime from {}'s {} list sorted by {}".format(pretty, data["limit"], self.get_username() if data["username"] == "@me" else data["username"], util.mal_pretty[data["status"]], util.mal_pretty[data["sort_by"]])) logger.info(f"Processing {pretty}: {data['limit']} Anime from {self.get_username() if data['username'] == '@me' else data['username']}'s {util.mal_pretty[data['status']]} list sorted by {util.mal_pretty[data['sort_by']]}")
else: else:
raise Failed("MyAnimeList Error: Method {} not supported".format(method)) raise Failed(f"MyAnimeList Error: Method {method} not supported")
show_ids = [] show_ids = []
movie_ids = [] movie_ids = []
for mal_id in mal_ids: for mal_id in mal_ids:
@ -182,12 +183,12 @@ class MyAnimeListAPI:
ids = self.MyAnimeListIDList.find_mal_ids(mal_id) ids = self.MyAnimeListIDList.find_mal_ids(mal_id)
if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: show_ids.append(int(ids["thetvdb_id"])) if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: show_ids.append(int(ids["thetvdb_id"]))
elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: movie_ids.append(int(ids["themoviedb_id"])) elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: movie_ids.append(int(ids["themoviedb_id"]))
else: raise Failed("MyAnimeList Error: MyAnimeList ID: {} has no other IDs associated with it".format(mal_id)) else: raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} has no other IDs associated with it")
except Failed as e: except Failed as e:
if status_message: if status_message:
logger.error(e) logger.error(e)
if status_message: if status_message:
logger.debug("MyAnimeList IDs Found: {}".format(mal_ids)) logger.debug(f"MyAnimeList IDs Found: {mal_ids}")
logger.debug("Shows Found: {}".format(show_ids)) logger.debug(f"Shows Found: {show_ids}")
logger.debug("Movies Found: {}".format(movie_ids)) logger.debug(f"Movies Found: {movie_ids}")
return movie_ids, show_ids return movie_ids, show_ids

@ -15,23 +15,23 @@ class PlexAPI:
def __init__(self, params, TMDb, TVDb): def __init__(self, params, TMDb, TVDb):
try: self.PlexServer = PlexServer(params["plex"]["url"], params["plex"]["token"], timeout=params["plex"]["timeout"]) 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 Unauthorized: raise Failed("Plex Error: Plex token is invalid")
except ValueError as e: raise Failed("Plex Error: {}".format(e)) except ValueError as e: raise Failed(f"Plex Error: {e}")
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
util.print_stacktrace() util.print_stacktrace()
raise Failed("Plex Error: Plex url is invalid") raise Failed("Plex Error: Plex url is invalid")
self.is_movie = params["library_type"] == "movie" self.is_movie = params["library_type"] == "movie"
self.is_show = params["library_type"] == "show" self.is_show = params["library_type"] == "show"
self.Plex = next((s for s in self.PlexServer.library.sections() if s.title == params["name"] and ((self.is_movie and isinstance(s, MovieSection)) or (self.is_show and isinstance(s, ShowSection)))), None) self.Plex = next((s for s in self.PlexServer.library.sections() if s.title == params["name"] and ((self.is_movie and isinstance(s, MovieSection)) or (self.is_show and isinstance(s, ShowSection)))), None)
if not self.Plex: raise Failed("Plex Error: Plex Library {} not found".format(params["name"])) if not self.Plex: raise Failed(f"Plex Error: Plex Library {params['name']} not found")
try: self.data, ind, bsi = yaml.util.load_yaml_guess_indent(open(params["metadata_path"], encoding="utf-8")) try: self.data, ind, bsi = yaml.util.load_yaml_guess_indent(open(params["metadata_path"], encoding="utf-8"))
except yaml.scanner.ScannerError as e: raise Failed("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) except yaml.scanner.ScannerError as e: raise Failed(f"YAML Error: {util.tab_new_lines(e)}")
def get_dict(attribute): def get_dict(attribute):
if attribute in self.data: if attribute in self.data:
if self.data[attribute]: if self.data[attribute]:
if isinstance(self.data[attribute], dict): return self.data[attribute] if isinstance(self.data[attribute], dict): return self.data[attribute]
else: logger.warning("Config Warning: {} must be a dictionary".format(attribute)) else: logger.warning(f"Config Warning: {attribute} must be a dictionary")
else: logger.warning("Config Warning: {} attribute is blank".format(attribute)) else: logger.warning(f"Config Warning: {attribute} attribute is blank")
return None return None
self.metadata = get_dict("metadata") self.metadata = get_dict("metadata")
@ -43,7 +43,7 @@ class PlexAPI:
if params["asset_directory"]: if params["asset_directory"]:
for ad in params["asset_directory"]: for ad in params["asset_directory"]:
logger.info("Using Asset Directory: {}".format(ad)) logger.info(f"Using Asset Directory: {ad}")
self.TMDb = TMDb self.TMDb = TMDb
self.TVDb = TVDb self.TVDb = TVDb
@ -51,7 +51,7 @@ class PlexAPI:
self.Sonarr = None self.Sonarr = None
self.Tautulli = None self.Tautulli = None
self.name = params["name"] self.name = params["name"]
self.missing_path = os.path.join(os.path.dirname(os.path.abspath(params["metadata_path"])), "{}_missing.yml".format(os.path.splitext(os.path.basename(params["metadata_path"]))[0])) self.missing_path = os.path.join(os.path.dirname(os.path.abspath(params["metadata_path"])), f"{os.path.splitext(os.path.basename(params['metadata_path']))[0]}_missing.yml")
self.metadata_path = params["metadata_path"] self.metadata_path = params["metadata_path"]
self.asset_directory = params["asset_directory"] self.asset_directory = params["asset_directory"]
self.sync_mode = params["sync_mode"] self.sync_mode = params["sync_mode"]
@ -93,7 +93,7 @@ class PlexAPI:
def get_collection(self, data): def get_collection(self, data):
collection = util.choose_from_list(self.search(str(data), libtype="collection"), "collection", str(data), exact=True) collection = util.choose_from_list(self.search(str(data), libtype="collection"), "collection", str(data), exact=True)
if collection: return collection if collection: return collection
else: raise Failed("Plex Error: Collection {} not found".format(data)) else: raise Failed(f"Plex Error: Collection {data} not found")
def validate_collections(self, collections): def validate_collections(self, collections):
valid_collections = [] valid_collections = []
@ -101,7 +101,7 @@ class PlexAPI:
try: valid_collections.append(self.get_collection(collection)) try: valid_collections.append(self.get_collection(collection))
except Failed as e: logger.error(e) except Failed as e: logger.error(e)
if len(valid_collections) == 0: if len(valid_collections) == 0:
raise Failed("Collection Error: No valid Plex Collections in {}".format(collections)) raise Failed(f"Collection Error: No valid Plex Collections in {collections}")
return valid_collections return valid_collections
def add_missing(self, collection, items, is_movie): def add_missing(self, collection, items, is_movie):
@ -117,7 +117,7 @@ class PlexAPI:
try: try:
yaml.round_trip_dump(self.missing, open(self.missing_path, "w")) yaml.round_trip_dump(self.missing, open(self.missing_path, "w"))
except yaml.scanner.ScannerError as e: except yaml.scanner.ScannerError as e:
logger.error("YAML Error: {}".format(str(e).replace("\n", "\n|\t "))) logger.error(f"YAML Error: {util.tab_new_lines(e)}")
def add_to_collection(self, collection, items, filters, show_filtered, rating_key_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 name = collection.title if isinstance(collection, Collections) else collection
@ -129,11 +129,11 @@ class PlexAPI:
try: try:
current = self.fetchItem(item.ratingKey if isinstance(item, (Movie, Show)) else int(item)) current = self.fetchItem(item.ratingKey if isinstance(item, (Movie, Show)) else int(item))
except (BadRequest, NotFound): except (BadRequest, NotFound):
logger.error("Plex Error: Item {} not found".format(item)) logger.error(f"Plex Error: Item {item} not found")
continue continue
match = True match = True
if filters: if filters:
length = util.print_return(length, "Filtering {}/{} {}".format((" " * (max_length - len(str(i)))) + str(i), total, current.title)) length = util.print_return(length, f"Filtering {(' ' * (max_length - len(str(i)))) + str(i)}/{total} {current.title}")
for f in filters: for f in filters:
modifier = f[0][-4:] modifier = f[0][-4:]
method = util.filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else util.filter_alias[f[0]] method = util.filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else util.filter_alias[f[0]]
@ -154,7 +154,7 @@ class PlexAPI:
except Failed: except Failed:
pass pass
if movie is None: if movie is None:
logger.warning("Filter Error: No TMDb ID found for {}".format(current.title)) logger.warning(f"Filter Error: No TMDb ID found for {current.title}")
continue continue
if (modifier == ".not" and movie.original_language in terms) or (modifier != ".not" and movie.original_language not in terms): if (modifier == ".not" and movie.original_language in terms) or (modifier != ".not" and movie.original_language not in terms):
match = False match = False
@ -186,15 +186,15 @@ class PlexAPI:
if (not list(set(terms) & set(attrs)) and modifier != ".not") or (list(set(terms) & set(attrs)) and modifier == ".not"): if (not list(set(terms) & set(attrs)) and modifier != ".not") or (list(set(terms) & set(attrs)) and modifier == ".not"):
match = False match = False
break break
length = util.print_return(length, "Filtering {}/{} {}".format((" " * (max_length - len(str(i)))) + str(i), total, current.title)) length = util.print_return(length, f"Filtering {(' ' * (max_length - len(str(i)))) + str(i)}/{total} {current.title}")
if match: if match:
util.print_end(length, "{} Collection | {} | {}".format(name, "=" if current in collection_items else "+", current.title)) util.print_end(length, f"{name} Collection | {'=' if current in collection_items else '+'} | {current.title}")
if current in collection_items: rating_key_map[current.ratingKey] = None if current in collection_items: rating_key_map[current.ratingKey] = None
else: current.addCollection(name) else: current.addCollection(name)
elif show_filtered is True: elif show_filtered is True:
logger.info("{} Collection | X | {}".format(name, current.title)) logger.info(f"{name} Collection | X | {current.title}")
media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "") media_type = f"{'Movie' if self.is_movie else 'Show'}{'s' if total > 1 else ''}"
util.print_end(length, "{} {} Processed".format(total, media_type)) util.print_end(length, f"{total} {media_type} Processed")
return rating_key_map return rating_key_map
def search_item(self, data, year=None): def search_item(self, data, year=None):
@ -202,7 +202,7 @@ class PlexAPI:
def update_metadata(self, TMDb, test): def update_metadata(self, TMDb, test):
logger.info("") logger.info("")
util.separator("{} Library Metadata".format(self.name)) util.separator(f"{self.name} Library Metadata")
logger.info("") logger.info("")
if not self.metadata: if not self.metadata:
raise Failed("No metadata to edit") raise Failed("No metadata to edit")
@ -217,7 +217,7 @@ class PlexAPI:
now = datetime.now() now = datetime.now()
if self.metadata[m]["year"] is None: logger.error("Metadata Error: year attribute is blank") 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 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)) elif self.metadata[m]["year"] not in range(1800, now.year + 2): logger.error(f"Metadata Error: year attribute must be between 1800-{now.year + 1}")
else: year = self.metadata[m]["year"] else: year = self.metadata[m]["year"]
title = m title = m
@ -228,7 +228,7 @@ class PlexAPI:
item = self.search_item(title, year=year) item = self.search_item(title, year=year)
if item is None: if item is None:
item = self.search_item("{} (SUB)".format(title), year=year) item = self.search_item(f"{title} (SUB)", year=year)
if item is None and "alt_title" in self.metadata[m]: if item is None and "alt_title" in self.metadata[m]:
if self.metadata[m]["alt_title"] is None: if self.metadata[m]["alt_title"] is None:
@ -238,11 +238,12 @@ class PlexAPI:
item = self.search_item(alt_title, year=year) item = self.search_item(alt_title, year=year)
if item is None: if item is None:
logger.error("Plex Error: Item {} not found".format(m)) logger.error(f"Plex Error: Item {m} not found")
logger.error("Skipping {}".format(m)) logger.error(f"Skipping {m}")
continue continue
logger.info("Updating {}: {}...".format("Movie" if self.is_movie else "Show", title)) item_type = "Movie" if self.is_movie else "Show"
logger.info(f"Updating {item_type}: {title}...")
tmdb_item = None tmdb_item = None
try: try:
@ -267,10 +268,10 @@ class PlexAPI:
if key is None: key = name if key is None: key = name
if value is None: value = group[name] if value is None: value = group[name]
if str(current) != str(value): if str(current) != str(value):
edits["{}.value".format(key)] = value edits[f"{key}.value"] = value
edits["{}.locked".format(key)] = 1 edits[f"{key}.locked"] = 1
else: else:
logger.error("Metadata Error: {} attribute is blank".format(name)) logger.error(f"Metadata Error: {name} attribute is blank")
add_edit("title", item.title, self.metadata[m], value=title) add_edit("title", item.title, self.metadata[m], value=title)
add_edit("sort_title", item.titleSort, self.metadata[m], key="titleSort") add_edit("sort_title", item.titleSort, self.metadata[m], key="titleSort")
add_edit("originally_available", str(item.originallyAvailableAt)[:-9], self.metadata[m], key="originallyAvailableAt", value=originally_available) add_edit("originally_available", str(item.originallyAvailableAt)[:-9], self.metadata[m], key="originallyAvailableAt", value=originally_available)
@ -283,16 +284,16 @@ class PlexAPI:
add_edit("tagline", item_tagline, self.metadata[m], value=tagline) add_edit("tagline", item_tagline, self.metadata[m], value=tagline)
add_edit("summary", item.summary, self.metadata[m], value=summary) add_edit("summary", item.summary, self.metadata[m], value=summary)
if len(edits) > 0: if len(edits) > 0:
logger.debug("Details Update: {}".format(edits)) logger.debug(f"Details Update: {edits}")
try: try:
item.edit(**edits) item.edit(**edits)
item.reload() item.reload()
logger.info("{}: {} Details Update Successful".format("Movie" if self.is_movie else "Show", m)) logger.info(f"{item_type}: {m} Details Update Successful")
except BadRequest: except BadRequest:
util.print_stacktrace() util.print_stacktrace()
logger.error("{}: {} Details Update Failed".format("Movie" if self.is_movie else "Show", m)) logger.error(f"{item_type}: {m} Details Update Failed")
else: else:
logger.info("{}: {} Details Update Not Needed".format("Movie" if self.is_movie else "Show", m)) logger.info(f"{item_type}: {m} Details Update Not Needed")
genres = [] genres = []
@ -311,10 +312,10 @@ class PlexAPI:
elif self.metadata[m]["genre_sync_mode"] == "sync": elif self.metadata[m]["genre_sync_mode"] == "sync":
for genre in (g for g in item_genres if g not in genres): for genre in (g for g in item_genres if g not in genres):
item.removeGenre(genre) item.removeGenre(genre)
logger.info("Detail: Genre {} removed".format(genre)) logger.info(f"Detail: Genre {genre} removed")
for genre in (g for g in genres if g not in item_genres): for genre in (g for g in genres if g not in item_genres):
item.addGenre(genre) item.addGenre(genre)
logger.info("Detail: Genre {} added".format(genre)) logger.info(f"Detail: Genre {genre} added")
if "label" in self.metadata[m]: if "label" in self.metadata[m]:
if self.metadata[m]["label"]: if self.metadata[m]["label"]:
@ -326,10 +327,10 @@ class PlexAPI:
elif self.metadata[m]["label_sync_mode"] == "sync": elif self.metadata[m]["label_sync_mode"] == "sync":
for label in (la for la in item_labels if la not in labels): for label in (la for la in item_labels if la not in labels):
item.removeLabel(label) item.removeLabel(label)
logger.info("Detail: Label {} removed".format(label)) logger.info(f"Detail: Label {label} removed")
for label in (la for la in labels if la not in item_labels): for label in (la for la in labels if la not in item_labels):
item.addLabel(label) item.addLabel(label)
logger.info("Detail: Label {} added".format(label)) logger.info(f"Detail: Label {label} added")
else: else:
logger.error("Metadata Error: label attribute is blank") logger.error("Metadata Error: label attribute is blank")
@ -337,10 +338,10 @@ class PlexAPI:
if self.metadata[m]["seasons"]: if self.metadata[m]["seasons"]:
for season_id in self.metadata[m]["seasons"]: for season_id in self.metadata[m]["seasons"]:
logger.info("") logger.info("")
logger.info("Updating season {} of {}...".format(season_id, m)) logger.info(f"Updating season {season_id} of {m}...")
if isinstance(season_id, int): if isinstance(season_id, int):
try: season = item.season(season_id) try: season = item.season(season_id)
except NotFound: logger.error("Metadata Error: Season: {} not found".format(season_id)) except NotFound: logger.error(f"Metadata Error: Season: {season_id} not found")
else: else:
if "title" in self.metadata[m]["seasons"][season_id] and self.metadata[m]["seasons"][season_id]["title"]: if "title" in self.metadata[m]["seasons"][season_id] and self.metadata[m]["seasons"][season_id]["title"]:
@ -351,7 +352,7 @@ class PlexAPI:
if self.metadata[m]["seasons"][season_id]["sub"] is None: if self.metadata[m]["seasons"][season_id]["sub"] is None:
logger.error("Metadata Error: sub attribute is blank") logger.error("Metadata Error: sub attribute is blank")
elif self.metadata[m]["seasons"][season_id]["sub"] is True and "(SUB)" not in title: elif self.metadata[m]["seasons"][season_id]["sub"] is True and "(SUB)" not in title:
title = "{} (SUB)".format(title) title = f"{title} (SUB)"
elif self.metadata[m]["seasons"][season_id]["sub"] is False and title.endswith(" (SUB)"): elif self.metadata[m]["seasons"][season_id]["sub"] is False and title.endswith(" (SUB)"):
title = title[:-6] title = title[:-6]
else: else:
@ -361,18 +362,18 @@ class PlexAPI:
add_edit("title", season.title, self.metadata[m]["seasons"][season_id], value=title) add_edit("title", season.title, self.metadata[m]["seasons"][season_id], value=title)
add_edit("summary", season.summary, self.metadata[m]["seasons"][season_id]) add_edit("summary", season.summary, self.metadata[m]["seasons"][season_id])
if len(edits) > 0: if len(edits) > 0:
logger.debug("Season: {} Details Update: {}".format(season_id, edits)) logger.debug(f"Season: {season_id} Details Update: {edits}")
try: try:
season.edit(**edits) season.edit(**edits)
season.reload() season.reload()
logger.info("Season: {} Details Update Successful".format(season_id)) logger.info(f"Season: {season_id} Details Update Successful")
except BadRequest: except BadRequest:
util.print_stacktrace() util.print_stacktrace()
logger.error("Season: {} Details Update Failed".format(season_id)) logger.error(f"Season: {season_id} Details Update Failed")
else: else:
logger.info("Season: {} Details Update Not Needed".format(season_id)) logger.info(f"Season: {season_id} Details Update Not Needed")
else: else:
logger.error("Metadata Error: Season: {} invalid, it must be an integer".format(season_id)) logger.error(f"Metadata Error: Season: {season_id} invalid, it must be an integer")
else: else:
logger.error("Metadata Error: seasons attribute is blank") logger.error("Metadata Error: seasons attribute is blank")
@ -385,9 +386,9 @@ class PlexAPI:
output = match.group(0)[1:].split("E" if "E" in m.group(0) else "e") output = match.group(0)[1:].split("E" if "E" in m.group(0) else "e")
episode_id = int(output[0]) episode_id = int(output[0])
season_id = int(output[1]) season_id = int(output[1])
logger.info("Updating episode S{}E{} of {}...".format(episode_id, season_id, m)) logger.info(f"Updating episode S{episode_id}E{season_id} of {m}...")
try: episode = item.episode(season=season_id, episode=episode_id) try: episode = item.episode(season=season_id, episode=episode_id)
except NotFound: logger.error("Metadata Error: episode {} of season {} not found".format(episode_id, season_id)) except NotFound: logger.error(f"Metadata Error: episode {episode_id} of season {season_id} not found")
else: else:
if "title" in self.metadata[m]["episodes"][episode_str] and self.metadata[m]["episodes"][episode_str]["title"]: if "title" in self.metadata[m]["episodes"][episode_str] and self.metadata[m]["episodes"][episode_str]["title"]:
title = self.metadata[m]["episodes"][episode_str]["title"] title = self.metadata[m]["episodes"][episode_str]["title"]
@ -397,7 +398,7 @@ class PlexAPI:
if self.metadata[m]["episodes"][episode_str]["sub"] is None: if self.metadata[m]["episodes"][episode_str]["sub"] is None:
logger.error("Metadata Error: sub attribute is blank") logger.error("Metadata Error: sub attribute is blank")
elif self.metadata[m]["episodes"][episode_str]["sub"] is True and "(SUB)" not in title: elif self.metadata[m]["episodes"][episode_str]["sub"] is True and "(SUB)" not in title:
title = "{} (SUB)".format(title) title = f"{title} (SUB)"
elif self.metadata[m]["episodes"][episode_str]["sub"] is False and title.endswith(" (SUB)"): elif self.metadata[m]["episodes"][episode_str]["sub"] is False and title.endswith(" (SUB)"):
title = title[:-6] title = title[:-6]
else: else:
@ -409,17 +410,18 @@ class PlexAPI:
add_edit("originally_available", str(episode.originallyAvailableAt)[:-9], self.metadata[m]["episodes"][episode_str], key="originallyAvailableAt") add_edit("originally_available", str(episode.originallyAvailableAt)[:-9], self.metadata[m]["episodes"][episode_str], key="originallyAvailableAt")
add_edit("summary", episode.summary, self.metadata[m]["episodes"][episode_str]) add_edit("summary", episode.summary, self.metadata[m]["episodes"][episode_str])
if len(edits) > 0: if len(edits) > 0:
logger.debug("Season: {} Episode: {} Details Update: {}".format(season_id, episode_id, edits)) logger.debug(f"Season: {season_id} Episode: {episode_id} Details Update: {edits}")
try: try:
episode.edit(**edits) episode.edit(**edits)
episode.reload() episode.reload()
logger.info("Season: {} Episode: {} Details Update Successful".format(season_id, episode_id)) logger.info(
f"Season: {season_id} Episode: {episode_id} Details Update Successful")
except BadRequest: except BadRequest:
util.print_stacktrace() util.print_stacktrace()
logger.error("Season: {} Episode: {} Details Update Failed".format(season_id, episode_id)) logger.error(f"Season: {season_id} Episode: {episode_id} Details Update Failed")
else: else:
logger.info("Season: {} Episode: {} Details Update Not Needed".format(season_id, episode_id)) logger.info(f"Season: {season_id} Episode: {episode_id} Details Update Not Needed")
else: else:
logger.error("Metadata Error: episode {} invalid must have S##E## format".format(episode_str)) logger.error(f"Metadata Error: episode {episode_str} invalid must have S##E## format")
else: else:
logger.error("Metadata Error: episodes attribute is blank") logger.error("Metadata Error: episodes attribute is blank")

@ -7,27 +7,27 @@ logger = logging.getLogger("Plex Meta Manager")
class RadarrAPI: class RadarrAPI:
def __init__(self, tmdb, params): def __init__(self, tmdb, params):
self.url_params = {"apikey": "{}".format(params["token"])} self.url_params = {"apikey": f"{params['token']}"}
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/") self.base_url = f"{params['url']}/api{'/v3' if params['version'] == 'v3' else ''}/"
try: try:
result = requests.get("{}system/status".format(self.base_url), params=self.url_params).json() result = requests.get(f"{self.base_url}system/status", params=self.url_params).json()
except Exception: except Exception:
util.print_stacktrace() util.print_stacktrace()
raise Failed("Radarr Error: Could not connect to Radarr at {}".format(params["url"])) raise Failed(f"Radarr Error: Could not connect to Radarr at {params['url']}")
if "error" in result and result["error"] == "Unauthorized": if "error" in result and result["error"] == "Unauthorized":
raise Failed("Radarr Error: Invalid API Key") raise Failed("Radarr Error: Invalid API Key")
if "version" not in result: if "version" not in result:
raise Failed("Radarr Error: Unexpected Response Check URL") raise Failed("Radarr Error: Unexpected Response Check URL")
self.quality_profile_id = None self.quality_profile_id = None
profiles = "" profiles = ""
for profile in self.send_get("{}{}".format(self.base_url, "qualityProfile" if params["version"] == "v3" else "profile")): for profile in self.send_get(f"{self.base_url}{'qualityProfile' if params['version'] == 'v3' else 'profile'}"):
if len(profiles) > 0: if len(profiles) > 0:
profiles += ", " profiles += ", "
profiles += profile["name"] profiles += profile["name"]
if profile["name"] == params["quality_profile"]: if profile["name"] == params["quality_profile"]:
self.quality_profile_id = profile["id"] self.quality_profile_id = profile["id"]
if not self.quality_profile_id: if not self.quality_profile_id:
raise Failed("Radarr Error: quality_profile: {} does not exist in radarr. Profiles available: {}".format(params["quality_profile"], profiles)) raise Failed(f"Radarr Error: quality_profile: {params['quality_profile']} does not exist in radarr. Profiles available: {profiles}")
self.tmdb = tmdb self.tmdb = tmdb
self.url = params["url"] self.url = params["url"]
self.version = params["version"] self.version = params["version"]
@ -39,7 +39,7 @@ class RadarrAPI:
def add_tmdb(self, tmdb_ids, tag=None): def add_tmdb(self, tmdb_ids, tag=None):
logger.info("") logger.info("")
logger.debug("TMDb IDs: {}".format(tmdb_ids)) logger.debug(f"TMDb IDs: {tmdb_ids}")
tag_nums = [] tag_nums = []
add_count = 0 add_count = 0
if tag is None: if tag is None:
@ -47,8 +47,8 @@ class RadarrAPI:
if tag: if tag:
tag_cache = {} tag_cache = {}
for label in tag: for label in tag:
self.send_post("{}tag".format(self.base_url), {"label": str(label)}) self.send_post(f"{self.base_url}tag", {"label": str(label)})
for t in self.send_get("{}tag".format(self.base_url)): for t in self.send_get(f"{self.base_url}tag"):
tag_cache[t["label"]] = t["id"] tag_cache[t["label"]] = t["id"]
for label in tag: for label in tag:
if label in tag_cache: if label in tag_cache:
@ -63,20 +63,20 @@ class RadarrAPI:
try: try:
year = movie.release_date.split("-")[0] year = movie.release_date.split("-")[0]
except AttributeError: except AttributeError:
logger.error("TMDb Error: No year for ({}) {}".format(tmdb_id, movie.title)) logger.error(f"TMDb Error: No year for ({tmdb_id}) {movie.title}")
continue continue
if year.isdigit() is False: if year.isdigit() is False:
logger.error("TMDb Error: No release date yet for ({}) {}".format(tmdb_id, movie.title)) logger.error(f"TMDb Error: No release date yet for ({tmdb_id}) {movie.title}")
continue continue
poster = "https://image.tmdb.org/t/p/original{}".format(movie.poster_path) poster = f"https://image.tmdb.org/t/p/original{movie.poster_path}"
titleslug = re.sub(r"([^\s\w]|_)+", "", "{} {}".format(movie.title, year)).replace(" ", "-").lower() titleslug = re.sub(r"([^\s\w]|_)+", "", f"{movie.title} {year}").replace(" ", "-").lower()
url_json = { url_json = {
"title": movie.title, "title": movie.title,
"{}".format("qualityProfileId" if self.version == "v3" else "profileId"): self.quality_profile_id, f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": self.quality_profile_id,
"year": int(year), "year": int(year),
"tmdbid": int(tmdb_id), "tmdbid": int(tmdb_id),
"titleslug": titleslug, "titleslug": titleslug,
@ -87,17 +87,17 @@ class RadarrAPI:
} }
if tag_nums: if tag_nums:
url_json["tags"] = tag_nums url_json["tags"] = tag_nums
response = self.send_post("{}movie".format(self.base_url), url_json) response = self.send_post(f"{self.base_url}movie", url_json)
if response.status_code < 400: if response.status_code < 400:
logger.info("Added to Radarr | {:<6} | {}".format(tmdb_id, movie.title)) logger.info(f"Added to Radarr | {tmdb_id:<6} | {movie.title}")
add_count += 1 add_count += 1
else: else:
try: try:
logger.error("Radarr Error: ({}) {}: ({}) {}".format(tmdb_id, movie.title, response.status_code, response.json()[0]["errorMessage"])) logger.error(f"Radarr Error: ({tmdb_id}) {movie.title}: ({response.status_code}) {response.json()[0]['errorMessage']}")
except KeyError: except KeyError:
logger.debug(url_json) logger.debug(url_json)
logger.error("Radarr Error: {}".format(response.json())) logger.error(f"Radarr Error: {response.json()}")
logger.info("{} Movie{} added to Radarr".format(add_count, "s" if add_count > 1 else "")) logger.info(f"{add_count} Movie{'s' if add_count > 1 else ''} added to Radarr")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_get(self, url): def send_get(self, url):

@ -7,27 +7,27 @@ logger = logging.getLogger("Plex Meta Manager")
class SonarrAPI: class SonarrAPI:
def __init__(self, tvdb, params, language): def __init__(self, tvdb, params, language):
self.url_params = {"apikey": "{}".format(params["token"])} self.url_params = {"apikey": f"{params['token']}"}
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/") self.base_url = f"{params['url']}/api{'/v3/' if params['version'] == 'v3' else '/'}"
try: try:
result = requests.get("{}system/status".format(self.base_url), params=self.url_params).json() result = requests.get(f"{self.base_url}system/status", params=self.url_params).json()
except Exception: except Exception:
util.print_stacktrace() util.print_stacktrace()
raise Failed("Sonarr Error: Could not connect to Sonarr at {}".format(params["url"])) raise Failed(f"Sonarr Error: Could not connect to Sonarr at {params['url']}")
if "error" in result and result["error"] == "Unauthorized": if "error" in result and result["error"] == "Unauthorized":
raise Failed("Sonarr Error: Invalid API Key") raise Failed("Sonarr Error: Invalid API Key")
if "version" not in result: if "version" not in result:
raise Failed("Sonarr Error: Unexpected Response Check URL") raise Failed("Sonarr Error: Unexpected Response Check URL")
self.quality_profile_id = None self.quality_profile_id = None
profiles = "" profiles = ""
for profile in self.send_get("{}{}".format(self.base_url, "qualityProfile" if params["version"] == "v3" else "profile")): for profile in self.send_get(f"{self.base_url}{'qualityProfile' if params['version'] == 'v3' else 'profile'}"):
if len(profiles) > 0: if len(profiles) > 0:
profiles += ", " profiles += ", "
profiles += profile["name"] profiles += profile["name"]
if profile["name"] == params["quality_profile"]: if profile["name"] == params["quality_profile"]:
self.quality_profile_id = profile["id"] self.quality_profile_id = profile["id"]
if not self.quality_profile_id: if not self.quality_profile_id:
raise Failed("Sonarr Error: quality_profile: {} does not exist in sonarr. Profiles available: {}".format(params["quality_profile"], profiles)) raise Failed(f"Sonarr Error: quality_profile: {params['quality_profile']} does not exist in sonarr. Profiles available: {profiles}")
self.tvdb = tvdb self.tvdb = tvdb
self.language = language self.language = language
self.url = params["url"] self.url = params["url"]
@ -40,7 +40,7 @@ class SonarrAPI:
def add_tvdb(self, tvdb_ids, tag=None): def add_tvdb(self, tvdb_ids, tag=None):
logger.info("") logger.info("")
logger.debug("TVDb IDs: {}".format(tvdb_ids)) logger.debug(f"TVDb IDs: {tvdb_ids}")
tag_nums = [] tag_nums = []
add_count = 0 add_count = 0
if tag is None: if tag is None:
@ -48,8 +48,8 @@ class SonarrAPI:
if tag: if tag:
tag_cache = {} tag_cache = {}
for label in tag: for label in tag:
self.send_post("{}tag".format(self.base_url), {"label": str(label)}) self.send_post(f"{self.base_url}tag", {"label": str(label)})
for t in self.send_get("{}tag".format(self.base_url)): for t in self.send_get(f"{self.base_url}tag"):
tag_cache[t["label"]] = t["id"] tag_cache[t["label"]] = t["id"]
for label in tag: for label in tag:
if label in tag_cache: if label in tag_cache:
@ -65,7 +65,7 @@ class SonarrAPI:
url_json = { url_json = {
"title": show.title, "title": show.title,
"{}".format("qualityProfileId" if self.version == "v3" else "profileId"): self.quality_profile_id, f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": self.quality_profile_id,
"languageProfileId": 1, "languageProfileId": 1,
"tvdbId": int(tvdb_id), "tvdbId": int(tvdb_id),
"titleslug": titleslug, "titleslug": titleslug,
@ -78,17 +78,17 @@ class SonarrAPI:
} }
if tag_nums: if tag_nums:
url_json["tags"] = tag_nums url_json["tags"] = tag_nums
response = self.send_post("{}series".format(self.base_url), url_json) response = self.send_post(f"{self.base_url}series", url_json)
if response.status_code < 400: if response.status_code < 400:
logger.info("Added to Sonarr | {:<6} | {}".format(tvdb_id, show.title)) logger.info(f"Added to Sonarr | {tvdb_id:<6} | {show.title}")
add_count += 1 add_count += 1
else: else:
try: try:
logger.error("Sonarr Error: ({}) {}: ({}) {}".format(tvdb_id, show.title, response.status_code, response.json()[0]["errorMessage"])) logger.error(f"Sonarr Error: ({tvdb_id}) {show.title}: ({response.status_code}) {response.json()[0]['errorMessage']}")
except KeyError: except KeyError:
logger.debug(url_json) logger.debug(url_json)
logger.error("Sonarr Error: {}".format(response.json())) logger.error(f"Sonarr Error: {response.json()}")
logger.info("{} Show{} added to Sonarr".format(add_count, "s" if add_count > 1 else "")) logger.info(f"{add_count} Show{'s' if add_count > 1 else ''} added to Sonarr")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_get(self, url): def send_get(self, url):

@ -8,12 +8,12 @@ logger = logging.getLogger("Plex Meta Manager")
class TautulliAPI: class TautulliAPI:
def __init__(self, params): def __init__(self, params):
try: try:
response = requests.get("{}/api/v2?apikey={}&cmd=get_library_names".format(params["url"], params["apikey"])).json() response = requests.get(f"{params['url']}/api/v2?apikey={params['apikey']}&cmd=get_library_names").json()
except Exception: except Exception:
util.print_stacktrace() util.print_stacktrace()
raise Failed("Tautulli Error: Invalid url") raise Failed("Tautulli Error: Invalid url")
if response["response"]["result"] != "success": if response["response"]["result"] != "success":
raise Failed("Tautulli Error: {}".format(response["response"]["message"])) raise Failed(f"Tautulli Error: {response['response']['message']}")
self.url = params["url"] self.url = params["url"]
self.apikey = params["apikey"] self.apikey = params["apikey"]
@ -25,9 +25,9 @@ class TautulliAPI:
def get_items(self, library, time_range=30, stats_count=20, list_type="popular", stats_count_buffer=20, status_message=True): def get_items(self, library, time_range=30, stats_count=20, list_type="popular", stats_count_buffer=20, status_message=True):
if status_message: if status_message:
logger.info("Processing Tautulli Most {}: {} {}".format("Popular" if list_type == "popular" else "Watched", stats_count, "Movies" if library.is_movie else "Shows")) logger.info(f"Processing Tautulli Most {'Popular' if list_type == 'popular' else 'Watched'}: {stats_count} {'Movies' if library.is_movie else 'Shows'}")
response = self.send_request("{}/api/v2?apikey={}&cmd=get_home_stats&time_range={}&stats_count={}".format(self.url, self.apikey, time_range, int(stats_count) + int(stats_count_buffer))) response = self.send_request(f"{self.url}/api/v2?apikey={self.apikey}&cmd=get_home_stats&time_range={time_range}&stats_count={int(stats_count) + int(stats_count_buffer)}")
stat_id = "{}_{}".format("popular" if list_type == "popular" else "top", "movies" if library.is_movie else "tv") stat_id = f"{'popular' if list_type == 'popular' else 'top'}_{'movies' if library.is_movie else 'tv'}"
items = None items = None
for entry in response["response"]["data"]: for entry in response["response"]["data"]:
@ -47,16 +47,16 @@ class TautulliAPI:
return rating_keys return rating_keys
def get_section_id(self, library_name): def get_section_id(self, library_name):
response = self.send_request("{}/api/v2?apikey={}&cmd=get_library_names".format(self.url, self.apikey)) response = self.send_request(f"{self.url}/api/v2?apikey={self.apikey}&cmd=get_library_names")
section_id = None section_id = None
for entry in response["response"]["data"]: for entry in response["response"]["data"]:
if entry["section_name"] == library_name: if entry["section_name"] == library_name:
section_id = entry["section_id"] section_id = entry["section_id"]
break break
if section_id: return section_id if section_id: return section_id
else: raise Failed("Tautulli Error: No Library named {} in the response".format(library_name)) else: raise Failed(f"Tautulli Error: No Library named {library_name} in the response")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url): def send_request(self, url):
logger.debug("Tautulli URL: {}".format(url.replace(self.apikey, "################################"))) logger.debug(f"Tautulli URL: {url.replace(self.apikey, '################################')}")
return requests.get(url).json() return requests.get(url).json()

@ -32,56 +32,56 @@ def anidb_tests(config):
logger.info("Success | Convert AniDB to TVDb") logger.info("Success | Convert AniDB to TVDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert AniDB to TVDb: {}".format(e)) logger.error(f"Failure | Convert AniDB to TVDb: {e}")
try: try:
config.AniDB.convert_anidb_to_imdb(112) config.AniDB.convert_anidb_to_imdb(112)
logger.info("Success | Convert AniDB to IMDb") logger.info("Success | Convert AniDB to IMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert AniDB to IMDb: {}".format(e)) logger.error(f"Failure | Convert AniDB to IMDb: {e}")
try: try:
config.AniDB.convert_tvdb_to_anidb(81797) config.AniDB.convert_tvdb_to_anidb(81797)
logger.info("Success | Convert TVDb to AniDB") logger.info("Success | Convert TVDb to AniDB")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TVDb to AniDB: {}".format(e)) logger.error(f"Failure | Convert TVDb to AniDB: {e}")
try: try:
config.AniDB.convert_imdb_to_anidb("tt0245429") config.AniDB.convert_imdb_to_anidb("tt0245429")
logger.info("Success | Convert IMDb to AniDB") logger.info("Success | Convert IMDb to AniDB")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert IMDb to AniDB: {}".format(e)) logger.error(f"Failure | Convert IMDb to AniDB: {e}")
try: try:
config.AniDB.get_items("anidb_id", 69, "en", status_message=False) config.AniDB.get_items("anidb_id", 69, "en", status_message=False)
logger.info("Success | Get AniDB ID") logger.info("Success | Get AniDB ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get AniDB ID: {}".format(e)) logger.error(f"Failure | Get AniDB ID: {e}")
try: try:
config.AniDB.get_items("anidb_relation", 69, "en", status_message=False) config.AniDB.get_items("anidb_relation", 69, "en", status_message=False)
logger.info("Success | Get AniDB Relation") logger.info("Success | Get AniDB Relation")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get AniDB Relation: {}".format(e)) logger.error(f"Failure | Get AniDB Relation: {e}")
try: try:
config.AniDB.get_items("anidb_popular", 30, "en", status_message=False) config.AniDB.get_items("anidb_popular", 30, "en", status_message=False)
logger.info("Success | Get AniDB Popular") logger.info("Success | Get AniDB Popular")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get AniDB Popular: {}".format(e)) logger.error(f"Failure | Get AniDB Popular: {e}")
try: try:
config.AniDB.validate_anidb_list(["69", "112"], "en") config.AniDB.validate_anidb_list(["69", "112"], "en")
logger.info("Success | Validate AniDB List") logger.info("Success | Validate AniDB List")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Validate AniDB List: {}".format(e)) logger.error(f"Failure | Validate AniDB List: {e}")
else: else:
util.separator("AniDB Not Configured") util.separator("AniDB Not Configured")
@ -92,15 +92,15 @@ def imdb_tests(config):
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) 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") if len(tmdb_ids) == 1000: logger.info("Success | IMDb URL get TMDb IDs")
else: logger.error("Failure | IMDb URL get TMDb IDs: {} Should be 1000".format(len(tmdb_ids))) else: logger.error(f"Failure | IMDb URL get TMDb IDs: {len(tmdb_ids)} Should be 1000")
tmdb_ids, tvdb_ids = config.IMDb.get_items("imdb_list", {"url": "https://www.imdb.com/list/ls026173135/", "limit": 0}, "en", status_message=False) tmdb_ids, tvdb_ids = config.IMDb.get_items("imdb_list", {"url": "https://www.imdb.com/list/ls026173135/", "limit": 0}, "en", status_message=False)
if len(tmdb_ids) == 250: logger.info("Success | IMDb URL get TMDb IDs") if len(tmdb_ids) == 250: logger.info("Success | IMDb URL get TMDb IDs")
else: logger.error("Failure | IMDb URL get TMDb IDs: {} Should be 250".format(len(tmdb_ids))) else: logger.error(f"Failure | IMDb URL get TMDb IDs: {len(tmdb_ids)} Should be 250")
tmdb_ids, tvdb_ids = config.IMDb.get_items("imdb_id", "tt0814243", "en", status_message=False) tmdb_ids, tvdb_ids = config.IMDb.get_items("imdb_id", "tt0814243", "en", status_message=False)
if len(tmdb_ids) == 1: logger.info("Success | IMDb ID get TMDb IDs") if len(tmdb_ids) == 1: logger.info("Success | IMDb ID get TMDb IDs")
else: logger.error("Failure | IMDb ID get TMDb IDs: {} Should be 1".format(len(tmdb_ids))) else: logger.error(f"Failure | IMDb ID get TMDb IDs: {len(tmdb_ids)} Should be 1")
else: else:
util.separator("IMDb Not Configured") util.separator("IMDb Not Configured")
@ -114,35 +114,35 @@ def mal_tests(config):
logger.info("Success | Convert MyAnimeList to TVDb") logger.info("Success | Convert MyAnimeList to TVDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert MyAnimeList to TVDb: {}".format(e)) logger.error(f"Failure | Convert MyAnimeList to TVDb: {e}")
try: try:
config.MyAnimeListIDList.convert_mal_to_tmdb(199) config.MyAnimeListIDList.convert_mal_to_tmdb(199)
logger.info("Success | Convert MyAnimeList to TMDb") logger.info("Success | Convert MyAnimeList to TMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert MyAnimeList to TMDb: {}".format(e)) logger.error(f"Failure | Convert MyAnimeList to TMDb: {e}")
try: try:
config.MyAnimeListIDList.convert_tvdb_to_mal(81797) config.MyAnimeListIDList.convert_tvdb_to_mal(81797)
logger.info("Success | Convert TVDb to MyAnimeList") logger.info("Success | Convert TVDb to MyAnimeList")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TVDb to MyAnimeList: {}".format(e)) logger.error(f"Failure | Convert TVDb to MyAnimeList: {e}")
try: try:
config.MyAnimeListIDList.convert_tmdb_to_mal(129) config.MyAnimeListIDList.convert_tmdb_to_mal(129)
logger.info("Success | Convert TMDb to MyAnimeList") logger.info("Success | Convert TMDb to MyAnimeList")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TMDb to MyAnimeList: {}".format(e)) logger.error(f"Failure | Convert TMDb to MyAnimeList: {e}")
try: try:
config.MyAnimeListIDList.find_mal_ids(21) config.MyAnimeListIDList.find_mal_ids(21)
logger.info("Success | Find MyAnimeList ID") logger.info("Success | Find MyAnimeList ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Find MyAnimeList ID: {}".format(e)) logger.error(f"Failure | Find MyAnimeList ID: {e}")
else: else:
util.separator("MyAnimeListXML Not Configured") util.separator("MyAnimeListXML Not Configured")
@ -168,10 +168,10 @@ def mal_tests(config):
for mal_list_test in mal_list_tests: for mal_list_test in mal_list_tests:
try: try:
config.MyAnimeList.get_items(mal_list_test[0], mal_list_test[1], status_message=False) config.MyAnimeList.get_items(mal_list_test[0], mal_list_test[1], status_message=False)
logger.info("Success | Get Anime using {}".format(util.pretty_names[mal_list_test[0]])) logger.info(f"Success | Get Anime using {util.pretty_names[mal_list_test[0]]}")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Anime using {}: {}".format(util.pretty_names[mal_list_test[0]], e)) logger.error(f"Failure | Get Anime using {util.pretty_names[mal_list_test[0]]}: {e}")
else: else:
util.separator("MyAnimeList Not Configured") util.separator("MyAnimeList Not Configured")
@ -184,21 +184,21 @@ def tautulli_tests(config):
logger.info("Success | Get Section ID") logger.info("Success | Get Section ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Section ID: {}".format(e)) logger.error(f"Failure | Get Section ID: {e}")
try: try:
config.libraries[0].Tautulli.get_popular(config.libraries[0], status_message=False) config.libraries[0].Tautulli.get_popular(config.libraries[0], status_message=False)
logger.info("Success | Get Popular") logger.info("Success | Get Popular")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Popular: {}".format(e)) logger.error(f"Failure | Get Popular: {e}")
try: try:
config.libraries[0].Tautulli.get_top(config.libraries[0], status_message=False) config.libraries[0].Tautulli.get_top(config.libraries[0], status_message=False)
logger.info("Success | Get Top") logger.info("Success | Get Top")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Top: {}".format(e)) logger.error(f"Failure | Get Top: {e}")
else: else:
util.separator("Tautulli Not Configured") util.separator("Tautulli Not Configured")
@ -211,28 +211,28 @@ def tmdb_tests(config):
logger.info("Success | Convert IMDb to TMDb") logger.info("Success | Convert IMDb to TMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert IMDb to TMDb: {}".format(e)) logger.error(f"Failure | Convert IMDb to TMDb: {e}")
try: try:
config.TMDb.convert_tmdb_to_imdb(11) config.TMDb.convert_tmdb_to_imdb(11)
logger.info("Success | Convert TMDb to IMDb") logger.info("Success | Convert TMDb to IMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TMDb to IMDb: {}".format(e)) logger.error(f"Failure | Convert TMDb to IMDb: {e}")
try: try:
config.TMDb.convert_imdb_to_tvdb("tt0458290") config.TMDb.convert_imdb_to_tvdb("tt0458290")
logger.info("Success | Convert IMDb to TVDb") logger.info("Success | Convert IMDb to TVDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert IMDb to TVDb: {}".format(e)) logger.error(f"Failure | Convert IMDb to TVDb: {e}")
try: try:
config.TMDb.convert_tvdb_to_imdb(83268) config.TMDb.convert_tvdb_to_imdb(83268)
logger.info("Success | Convert TVDb to IMDb") logger.info("Success | Convert TVDb to IMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TVDb to IMDb: {}".format(e)) logger.error(f"Failure | Convert TVDb to IMDb: {e}")
tmdb_list_tests = [ tmdb_list_tests = [
([11], "Movie"), ([11], "Movie"),
@ -247,10 +247,10 @@ def tmdb_tests(config):
for tmdb_list_test in tmdb_list_tests: for tmdb_list_test in tmdb_list_tests:
try: try:
config.TMDb.validate_tmdb_list(tmdb_list_test[0], tmdb_type=tmdb_list_test[1]) config.TMDb.validate_tmdb_list(tmdb_list_test[0], tmdb_type=tmdb_list_test[1])
logger.info("Success | Get TMDb {}".format(tmdb_list_test[1])) logger.info(f"Success | Get TMDb {tmdb_list_test[1]}")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get TMDb {}: {}".format(tmdb_list_test[1], e)) logger.error(f"Failure | Get TMDb {tmdb_list_test[1]}: {e}")
tmdb_list_tests = [ tmdb_list_tests = [
("tmdb_discover", {"sort_by": "popularity.desc", "limit": 100}, True), ("tmdb_discover", {"sort_by": "popularity.desc", "limit": 100}, True),
@ -279,10 +279,10 @@ def tmdb_tests(config):
for tmdb_list_test in tmdb_list_tests: for tmdb_list_test in tmdb_list_tests:
try: try:
config.TMDb.get_items(tmdb_list_test[0], tmdb_list_test[1], tmdb_list_test[2], status_message=False) config.TMDb.get_items(tmdb_list_test[0], tmdb_list_test[1], tmdb_list_test[2], status_message=False)
logger.info("Success | Get {} using {}".format("Movies" if tmdb_list_test[2] else "Shows", util.pretty_names[tmdb_list_test[0]])) logger.info(f"Success | Get {'Movies' if tmdb_list_test[2] else 'Shows'} using {util.pretty_names[tmdb_list_test[0]]}")
except Failed as e: except Failed as e:
util.print_stacktrace() 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)) logger.error(f"Failure | Get {'Movies' if tmdb_list_test[2] else 'Shows'} using {util.pretty_names[tmdb_list_test[0]]}: {e}")
else: else:
util.separator("TMDb Not Configured") util.separator("TMDb Not Configured")
@ -295,63 +295,63 @@ def trakt_tests(config):
logger.info("Success | Convert IMDb to TMDb") logger.info("Success | Convert IMDb to TMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert IMDb to TMDb: {}".format(e)) logger.error(f"Failure | Convert IMDb to TMDb: {e}")
try: try:
config.Trakt.convert_tmdb_to_imdb(11) config.Trakt.convert_tmdb_to_imdb(11)
logger.info("Success | Convert TMDb to IMDb") logger.info("Success | Convert TMDb to IMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TMDb to IMDb: {}".format(e)) logger.error(f"Failure | Convert TMDb to IMDb: {e}")
try: try:
config.Trakt.convert_imdb_to_tvdb("tt0458290") config.Trakt.convert_imdb_to_tvdb("tt0458290")
logger.info("Success | Convert IMDb to TVDb") logger.info("Success | Convert IMDb to TVDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert IMDb to TVDb: {}".format(e)) logger.error(f"Failure | Convert IMDb to TVDb: {e}")
try: try:
config.Trakt.convert_tvdb_to_imdb(83268) config.Trakt.convert_tvdb_to_imdb(83268)
logger.info("Success | Convert TVDb to IMDb") logger.info("Success | Convert TVDb to IMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TVDb to IMDb: {}".format(e)) logger.error(f"Failure | Convert TVDb to IMDb: {e}")
try: try:
config.Trakt.convert_tmdb_to_tvdb(11) config.Trakt.convert_tmdb_to_tvdb(11)
logger.info("Success | Convert TMDb to TVDb") logger.info("Success | Convert TMDb to TVDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TMDb to TVDb: {}".format(e)) logger.error(f"Failure | Convert TMDb to TVDb: {e}")
try: try:
config.Trakt.convert_tvdb_to_tmdb(83268) config.Trakt.convert_tvdb_to_tmdb(83268)
logger.info("Success | Convert TVDb to TMDb") logger.info("Success | Convert TVDb to TMDb")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Convert TVDb to TMDb: {}".format(e)) logger.error(f"Failure | Convert TVDb to TMDb: {e}")
try: try:
config.Trakt.validate_trakt_list(["https://trakt.tv/users/movistapp/lists/christmas-movies"]) config.Trakt.validate_trakt_list(["https://trakt.tv/users/movistapp/lists/christmas-movies"])
logger.info("Success | Get List") logger.info("Success | Get List")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get List: {}".format(e)) logger.error(f"Failure | Get List: {e}")
try: try:
config.Trakt.validate_trakt_watchlist(["me"], True) config.Trakt.validate_trakt_watchlist(["me"], True)
logger.info("Success | Get Watchlist Movies") logger.info("Success | Get Watchlist Movies")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Watchlist Movies: {}".format(e)) logger.error(f"Failure | Get Watchlist Movies: {e}")
try: try:
config.Trakt.validate_trakt_watchlist(["me"], False) config.Trakt.validate_trakt_watchlist(["me"], False)
logger.info("Success | Get Watchlist Shows") logger.info("Success | Get Watchlist Shows")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | Get Watchlist Shows: {}".format(e)) logger.error(f"Failure | Get Watchlist Shows: {e}")
trakt_list_tests = [ trakt_list_tests = [
("trakt_list", "https://trakt.tv/users/movistapp/lists/christmas-movies", True), ("trakt_list", "https://trakt.tv/users/movistapp/lists/christmas-movies", True),
@ -364,10 +364,10 @@ def trakt_tests(config):
for trakt_list_test in trakt_list_tests: for trakt_list_test in trakt_list_tests:
try: try:
config.Trakt.get_items(trakt_list_test[0], trakt_list_test[1], trakt_list_test[2], status_message=False) config.Trakt.get_items(trakt_list_test[0], trakt_list_test[1], trakt_list_test[2], status_message=False)
logger.info("Success | Get {} using {}".format("Movies" if trakt_list_test[2] else "Shows", util.pretty_names[trakt_list_test[0]])) logger.info(f"Success | Get {'Movies' if trakt_list_test[2] else 'Shows'} using {util.pretty_names[trakt_list_test[0]]}")
except Failed as e: except Failed as e:
util.print_stacktrace() 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)) logger.error(f"Failure | Get {'Movies' if trakt_list_test[2] else 'Shows'} using {util.pretty_names[trakt_list_test[0]]}: {e}")
else: else:
util.separator("Trakt Not Configured") util.separator("Trakt Not Configured")
@ -377,39 +377,39 @@ def tvdb_tests(config):
tmdb_ids, tvdb_ids = config.TVDb.get_items("tvdb_list", "https://www.thetvdb.com/lists/arrowverse", "en", status_message=False) 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") if len(tvdb_ids) == 10 and len(tmdb_ids) == 0: logger.info("Success | TVDb URL get TVDb IDs and TMDb IDs")
else: logger.error("Failure | TVDb URL get TVDb IDs and TMDb IDs: {} Should be 10 and {} Should be 0".format(len(tvdb_ids), len(tmdb_ids))) else: logger.error(f"Failure | TVDb URL get TVDb IDs and TMDb IDs: {len(tvdb_ids)} Should be 10 and {len(tmdb_ids)} Should be 0")
tmdb_ids, tvdb_ids = config.TVDb.get_items("tvdb_list", "https://www.thetvdb.com/lists/6957", "en", status_message=False) tmdb_ids, tvdb_ids = config.TVDb.get_items("tvdb_list", "https://www.thetvdb.com/lists/6957", "en", status_message=False)
if len(tvdb_ids) == 4 and len(tmdb_ids) == 2: logger.info("Success | TVDb URL get TVDb IDs and TMDb IDs") if len(tvdb_ids) == 4 and len(tmdb_ids) == 2: logger.info("Success | TVDb URL get TVDb IDs and TMDb IDs")
else: logger.error("Failure | TVDb URL get TVDb IDs and TMDb IDs: {} Should be 4 and {} Should be 2".format(len(tvdb_ids), len(tmdb_ids))) else: logger.error(f"Failure | TVDb URL get TVDb IDs and TMDb IDs: {len(tvdb_ids)} Should be 4 and {len(tmdb_ids)} Should be 2")
try: try:
config.TVDb.get_items("tvdb_show", "https://www.thetvdb.com/series/arrow", "en", status_message=False) config.TVDb.get_items("tvdb_show", "https://www.thetvdb.com/series/arrow", "en", status_message=False)
logger.info("Success | TVDb URL get TVDb Series ID") logger.info("Success | TVDb URL get TVDb Series ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | TVDb URL get TVDb Series ID: {}".format(e)) logger.error(f"Failure | TVDb URL get TVDb Series ID: {e}")
try: try:
config.TVDb.get_items("tvdb_show", 279121, "en", status_message=False) config.TVDb.get_items("tvdb_show", 279121, "en", status_message=False)
logger.info("Success | TVDb ID get TVDb Series ID") logger.info("Success | TVDb ID get TVDb Series ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | TVDb ID get TVDb Series ID: {}".format(e)) logger.error(f"Failure | TVDb ID get TVDb Series ID: {e}")
try: try:
config.TVDb.get_items("tvdb_movie", "https://www.thetvdb.com/movies/the-lord-of-the-rings-the-fellowship-of-the-ring", "en", status_message=False) config.TVDb.get_items("tvdb_movie", "https://www.thetvdb.com/movies/the-lord-of-the-rings-the-fellowship-of-the-ring", "en", status_message=False)
logger.info("Success | TVDb URL get TVDb Movie ID") logger.info("Success | TVDb URL get TVDb Movie ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | TVDb URL get TVDb Movie ID: {}".format(e)) logger.error(f"Failure | TVDb URL get TVDb Movie ID: {e}")
try: try:
config.TVDb.get_items("tvdb_movie", 107, "en", status_message=False) config.TVDb.get_items("tvdb_movie", 107, "en", status_message=False)
logger.info("Success | TVDb ID get TVDb Movie ID") logger.info("Success | TVDb ID get TVDb Movie ID")
except Failed as e: except Failed as e:
util.print_stacktrace() util.print_stacktrace()
logger.error("Failure | TVDb ID get TVDb Movie ID: {}".format(e)) logger.error(f"Failure | TVDb ID get TVDb Movie ID: {e}")
else: else:
util.separator("TVDb Not Configured") util.separator("TVDb Not Configured")

@ -13,7 +13,7 @@ class TMDbAPI:
self.TMDb.language = params["language"] self.TMDb.language = params["language"]
response = tmdbv3api.Configuration().info() response = tmdbv3api.Configuration().info()
if hasattr(response, "status_message"): if hasattr(response, "status_message"):
raise Failed("TMDb Error: {}".format(response.status_message)) raise Failed(f"TMDb Error: {response.status_message}")
self.apikey = params["apikey"] self.apikey = params["apikey"]
self.language = params["language"] self.language = params["language"]
self.Movie = tmdbv3api.Movie() self.Movie = tmdbv3api.Movie()
@ -33,17 +33,17 @@ class TMDbAPI:
try: try:
id_to_return = self.Movie.external_ids(tmdb_id)[convert_to] if is_movie else self.TV.external_ids(tmdb_id)[convert_to] 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): 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)) raise Failed(f"TMDb Error: No {convert_to.upper().replace('B_', 'b ')} found for TMDb ID {tmdb_id}")
return id_to_return return id_to_return
except TMDbException: except TMDbException:
raise Failed("TMDb Error: {} TMDb ID: {} not found".format("Movie" if is_movie else "Show", tmdb_id)) raise Failed(f"TMDb Error: {'Movie' if is_movie else 'Show'} TMDb ID: {tmdb_id} not found")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @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): def convert_to_tmdb(self, external_id, external_source, is_movie):
search_results = self.Movie.external(external_id=external_id, external_source=external_source) search_results = self.Movie.external(external_id=external_id, external_source=external_source)
search = search_results["movie_results" if is_movie else "tv_results"] search = search_results["movie_results" if is_movie else "tv_results"]
if len(search) == 1: return int(search[0]["id"]) if len(search) == 1: return int(search[0]["id"])
else: raise Failed("TMDb Error: No TMDb ID found for {} {}".format(external_source.upper().replace("B_", "b "), external_id)) else: raise Failed(f"TMDb Error: No TMDb ID found for {external_source.upper().replace('B_', 'b ')} {external_id}")
def convert_tmdb_to_imdb(self, tmdb_id, is_movie=True): return self.convert_from_tmdb(tmdb_id, "imdb_id", is_movie) def convert_tmdb_to_imdb(self, tmdb_id, is_movie=True): return self.convert_from_tmdb(tmdb_id, "imdb_id", is_movie)
def convert_imdb_to_tmdb(self, imdb_id, is_movie=True): return self.convert_to_tmdb(imdb_id, "imdb_id", is_movie) def convert_imdb_to_tmdb(self, imdb_id, is_movie=True): return self.convert_to_tmdb(imdb_id, "imdb_id", is_movie)
@ -57,48 +57,48 @@ class TMDbAPI:
try: return self.get_collection(tmdb_id) try: return self.get_collection(tmdb_id)
except Failed: except Failed:
try: return self.get_movie(tmdb_id) try: return self.get_movie(tmdb_id)
except Failed: raise Failed("TMDb Error: No Movie or Collection found for TMDb ID {}".format(tmdb_id)) except Failed: raise Failed(f"TMDb Error: No Movie or Collection found for TMDb ID {tmdb_id}")
else: return self.get_show(tmdb_id) else: return self.get_show(tmdb_id)
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_movie(self, tmdb_id): def get_movie(self, tmdb_id):
try: return self.Movie.details(tmdb_id) try: return self.Movie.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Movie found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Movie found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_show(self, tmdb_id): def get_show(self, tmdb_id):
try: return self.TV.details(tmdb_id) try: return self.TV.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Show found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Show found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_collection(self, tmdb_id): def get_collection(self, tmdb_id):
try: return self.Collection.details(tmdb_id) try: return self.Collection.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Collection found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Collection found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_person(self, tmdb_id): def get_person(self, tmdb_id):
try: return self.Person.details(tmdb_id) try: return self.Person.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Person found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Person found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_company(self, tmdb_id): def get_company(self, tmdb_id):
try: return self.Company.details(tmdb_id) try: return self.Company.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Company found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Company found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_network(self, tmdb_id): def get_network(self, tmdb_id):
try: return self.Network.details(tmdb_id) try: return self.Network.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Network found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Network found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_keyword(self, tmdb_id): def get_keyword(self, tmdb_id):
try: return self.Keyword.details(tmdb_id) try: return self.Keyword.details(tmdb_id)
except TMDbException as e: raise Failed("TMDb Error: No Keyword found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No Keyword found for TMDb ID {tmdb_id}: {e}")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_list(self, tmdb_id): def get_list(self, tmdb_id):
try: return self.List.details(tmdb_id, all_details=True) try: return self.List.details(tmdb_id, all_details=True)
except TMDbException as e: raise Failed("TMDb Error: No List found for TMDb ID {}: {}".format(tmdb_id, e)) except TMDbException as e: raise Failed(f"TMDb Error: No List found for TMDb ID {tmdb_id}: {e}")
def get_pagenation(self, method, amount, is_movie): def get_pagenation(self, method, amount, is_movie):
ids = [] ids = []
@ -109,7 +109,7 @@ class TMDbAPI:
elif method == "tmdb_now_playing" and is_movie: tmdb_items = self.Movie.now_playing(x + 1) 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_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) 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)) else: raise Failed(f"TMDb Error: {method} method not supported")
for tmdb_item in tmdb_items: for tmdb_item in tmdb_items:
try: try:
ids.append(tmdb_item.id if is_movie else self.convert_tmdb_to_tvdb(tmdb_item.id)) ids.append(tmdb_item.id if is_movie else self.convert_tmdb_to_tvdb(tmdb_item.id))
@ -142,7 +142,7 @@ class TMDbAPI:
def get_items(self, method, data, is_movie, status_message=True): def get_items(self, method, data, is_movie, status_message=True):
if status_message: if status_message:
logger.debug("Data: {}".format(data)) logger.debug(f"Data: {data}")
pretty = util.pretty_names[method] if method in util.pretty_names else method pretty = util.pretty_names[method] if method in util.pretty_names else method
media_type = "Movie" if is_movie else "Show" media_type = "Movie" if is_movie else "Show"
movie_ids = [] movie_ids = []
@ -170,16 +170,16 @@ class TMDbAPI:
else: show_ids, amount = self.get_discover(attrs, limit, is_movie) else: show_ids, amount = self.get_discover(attrs, limit, is_movie)
if status_message: if status_message:
if method in ["tmdb_company", "tmdb_network", "tmdb_keyword"]: 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")) logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({amount} {media_type}{'' if amount == 1 else 's'})")
elif method == "tmdb_discover": elif method == "tmdb_discover":
logger.info("Processing {}: {} {}{}".format(pretty, amount, media_type, "" if amount == 1 else "s")) logger.info(f"Processing {pretty}: {amount} {media_type}{'' if amount == 1 else 's'}")
for attr, value in attrs.items(): for attr, value in attrs.items():
logger.info(" {}: {}".format(attr, value)) logger.info(f" {attr}: {value}")
elif method in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]: elif method in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]:
if is_movie: movie_ids = self.get_pagenation(method, data, is_movie) if is_movie: movie_ids = self.get_pagenation(method, data, is_movie)
else: show_ids = self.get_pagenation(method, data, is_movie) else: show_ids = self.get_pagenation(method, data, is_movie)
if status_message: if status_message:
logger.info("Processing {}: {} {}{}".format(pretty, data, media_type, "" if data == 1 else "s")) logger.info(f"Processing {pretty}: {data} {media_type}{'' if data == 1 else 's'}")
else: else:
tmdb_id = int(data) tmdb_id = int(data)
if method == "tmdb_list": if method == "tmdb_list":
@ -204,14 +204,14 @@ class TMDbAPI:
try: show_ids.append(self.convert_tmdb_to_tvdb(tmdb_id)) try: show_ids.append(self.convert_tmdb_to_tvdb(tmdb_id))
except Failed: pass except Failed: pass
else: else:
raise Failed("TMDb Error: Method {} not supported".format(method)) raise Failed(f"TMDb Error: Method {method} not supported")
if status_message and len(movie_ids) > 0: if status_message and len(movie_ids) > 0:
logger.info("Processing {}: ({}) {} ({} Movie{})".format(pretty, tmdb_id, tmdb_name, len(movie_ids), "" if len(movie_ids) == 1 else "s")) logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(movie_ids)} Movie{'' if len(movie_ids) == 1 else 's'})")
if status_message and len(show_ids) > 0: if status_message and len(show_ids) > 0:
logger.info("Processing {}: ({}) {} ({} Show{})".format(pretty, tmdb_id, tmdb_name, len(show_ids), "" if len(show_ids) == 1 else "s")) logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(show_ids)} Show{'' if len(show_ids) == 1 else 's'})")
if status_message: if status_message:
logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug("TVDb IDs Found: {}".format(show_ids)) logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids return movie_ids, show_ids
def validate_tmdb_list(self, tmdb_list, tmdb_type): def validate_tmdb_list(self, tmdb_list, tmdb_type):
@ -219,7 +219,7 @@ class TMDbAPI:
for tmdb_id in tmdb_list: for tmdb_id in tmdb_list:
try: tmdb_values.append(self.validate_tmdb(tmdb_id, tmdb_type)) try: tmdb_values.append(self.validate_tmdb(tmdb_id, tmdb_type))
except Failed as e: logger.error(e) except Failed as e: logger.error(e)
if len(tmdb_values) == 0: raise Failed("TMDb Error: No valid TMDb IDs in {}".format(tmdb_list)) if len(tmdb_values) == 0: raise Failed(f"TMDb Error: No valid TMDb IDs in {tmdb_list}")
return tmdb_values return tmdb_values
def validate_tmdb(self, tmdb_id, tmdb_type): def validate_tmdb(self, tmdb_id, tmdb_type):

@ -30,7 +30,7 @@ class TraktAPI:
def get_authorization(self): def get_authorization(self):
url = Trakt["oauth"].authorize_url(self.redirect_uri) url = Trakt["oauth"].authorize_url(self.redirect_uri)
logger.info("Navigate to: {}".format(url)) logger.info(f"Navigate to: {url}")
logger.info("If you get an OAuth error your client_id or client_secret is invalid") logger.info("If you get an OAuth error your client_id or client_secret is invalid")
webbrowser.open(url, new=2) webbrowser.open(url, new=2)
try: pin = util.logger_input("Trakt pin (case insensitive)", timeout=300).strip() try: pin = util.logger_input("Trakt pin (case insensitive)", timeout=300).strip()
@ -70,7 +70,7 @@ class TraktAPI:
"scope": authorization["scope"], "scope": authorization["scope"],
"created_at": authorization["created_at"] "created_at": authorization["created_at"]
} }
logger.info("Saving authorization information to {}".format(self.config_path)) logger.info(f"Saving authorization information to {self.config_path}")
yaml.round_trip_dump(config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi) yaml.round_trip_dump(config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
self.authorization = authorization self.authorization = authorization
Trakt.configuration.defaults.oauth.from_response(self.authorization) Trakt.configuration.defaults.oauth.from_response(self.authorization)
@ -91,7 +91,7 @@ class TraktAPI:
lookup = lookup[0] if isinstance(lookup, list) else lookup lookup = lookup[0] if isinstance(lookup, list) else lookup
if lookup.get_key(to_source): if lookup.get_key(to_source):
return lookup.get_key(to_source) if to_source == "imdb" else int(lookup.get_key(to_source)) return lookup.get_key(to_source) if to_source == "imdb" else int(lookup.get_key(to_source))
raise Failed("No {} ID found for {} ID {}".format(to_source.upper().replace("B", "b"), from_source.upper().replace("B", "b"), external_id)) raise Failed(f"No {to_source.upper().replace('B', 'b')} ID found for {from_source.upper().replace('B', 'b')} ID {external_id}")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def trending(self, amount, is_movie): def trending(self, amount, is_movie):
@ -99,7 +99,7 @@ class TraktAPI:
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def watchlist(self, data, is_movie): def watchlist(self, data, is_movie):
items = Trakt["users/{}/watchlist".format(data)].movies() if is_movie else Trakt["users/{}/watchlist".format(data)].shows() items = Trakt[f"users/{data}/watchlist"].movies() if is_movie else Trakt[f"users/{data}/watchlist"].shows()
if items is None: raise Failed("Trakt Error: No List found") if items is None: raise Failed("Trakt Error: No List found")
else: return [i for i in items] else: return [i for i in items]
@ -119,7 +119,7 @@ class TraktAPI:
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
if len(trakt_values) == 0: if len(trakt_values) == 0:
raise Failed("Trakt Error: No valid Trakt Lists in {}".format(values)) raise Failed(f"Trakt Error: No valid Trakt Lists in {values}")
return trakt_values return trakt_values
def validate_trakt_watchlist(self, values, is_movie): def validate_trakt_watchlist(self, values, is_movie):
@ -131,23 +131,23 @@ class TraktAPI:
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
if len(trakt_values) == 0: if len(trakt_values) == 0:
raise Failed("Trakt Error: No valid Trakt Watchlists in {}".format(values)) raise Failed(f"Trakt Error: No valid Trakt Watchlists in {values}")
return trakt_values return trakt_values
def get_items(self, method, data, is_movie, status_message=True): def get_items(self, method, data, is_movie, status_message=True):
if status_message: if status_message:
logger.debug("Data: {}".format(data)) logger.debug(f"Data: {data}")
pretty = self.aliases[method] if method in self.aliases else method pretty = self.aliases[method] if method in self.aliases else method
media_type = "Movie" if is_movie else "Show" media_type = "Movie" if is_movie else "Show"
if method == "trakt_trending": if method == "trakt_trending":
trakt_items = self.trending(int(data), is_movie) trakt_items = self.trending(int(data), is_movie)
if status_message: if status_message:
logger.info("Processing {}: {} {}{}".format(pretty, data, media_type, "" if data == 1 else "s")) logger.info(f"Processing {pretty}: {data} {media_type}{'' if data == 1 else 's'}")
else: else:
if method == "trakt_watchlist": trakt_items = self.watchlist(data, is_movie) if method == "trakt_watchlist": trakt_items = self.watchlist(data, is_movie)
elif method == "trakt_list": trakt_items = self.standard_list(data) elif method == "trakt_list": trakt_items = self.standard_list(data)
else: raise Failed("Trakt Error: Method {} not supported".format(method)) else: raise Failed(f"Trakt Error: Method {method} not supported")
if status_message: logger.info("Processing {}: {}".format(pretty, data)) if status_message: logger.info(f"Processing {pretty}: {data}")
show_ids = [] show_ids = []
movie_ids = [] movie_ids = []
for trakt_item in trakt_items: for trakt_item in trakt_items:
@ -155,7 +155,7 @@ class TraktAPI:
elif isinstance(trakt_item, Show) and trakt_item.pk[1] not in show_ids: show_ids.append(int(trakt_item.pk[1])) elif isinstance(trakt_item, Show) and trakt_item.pk[1] not in show_ids: show_ids.append(int(trakt_item.pk[1]))
elif (isinstance(trakt_item, (Season, Episode))) and trakt_item.show.pk[1] not in show_ids: show_ids.append(int(trakt_item.show.pk[1])) elif (isinstance(trakt_item, (Season, Episode))) and trakt_item.show.pk[1] not in show_ids: show_ids.append(int(trakt_item.show.pk[1]))
if status_message: if status_message:
logger.debug("Trakt {} Found: {}".format(media_type, trakt_items)) logger.debug(f"Trakt {media_type} Found: {trakt_items}")
logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug("TVDb IDs Found: {}".format(show_ids)) logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids return movie_ids, show_ids

@ -14,24 +14,24 @@ class TVDbObj:
elif is_movie and tvdb_url.startswith((TVDb.movies_url, TVDb.alt_movies_url, TVDb.movie_id_url)): elif is_movie and tvdb_url.startswith((TVDb.movies_url, TVDb.alt_movies_url, TVDb.movie_id_url)):
self.media_type = "Movie" self.media_type = "Movie"
else: else:
raise Failed("TVDb Error: {} must begin with {}".format(tvdb_url, TVDb.movies_url if is_movie else TVDb.series_url)) raise Failed(f"TVDb Error: {tvdb_url} must begin with {TVDb.movies_url if is_movie else TVDb.series_url}")
response = TVDb.send_request(tvdb_url, language) response = TVDb.send_request(tvdb_url, language)
results = response.xpath("//*[text()='TheTVDB.com {} ID']/parent::node()/span/text()".format(self.media_type)) results = response.xpath(f"//*[text()='TheTVDB.com {self.media_type} ID']/parent::node()/span/text()")
if len(results) > 0: if len(results) > 0:
self.id = int(results[0]) self.id = int(results[0])
elif tvdb_url.startswith(TVDb.movie_id_url): 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):])) raise Failed(f"TVDb Error: Could not find a TVDb Movie using TVDb Movie ID: {tvdb_url[len(TVDb.series_id_url):]}")
elif tvdb_url.startswith(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):])) raise Failed(f"TVDb Error: Could not find a TVDb Series using TVDb Series ID: {tvdb_url[len(TVDb.series_id_url):]}")
else: else:
raise Failed("TVDb Error: Could not find a TVDb {} ID at the URL {}".format(self.media_type, tvdb_url)) raise Failed(f"TVDb Error: Could not find a TVDb {self.media_type} ID at the URL {tvdb_url}")
results = response.xpath("//div[@class='change_translation_text' and @data-language='eng']/@data-title") results = response.xpath("//div[@class='change_translation_text' and @data-language='eng']/@data-title")
if len(results) > 0 and len(results[0]) > 0: if len(results) > 0 and len(results[0]) > 0:
self.title = results[0] self.title = results[0]
else: else:
raise Failed("TVDb Error: Name not found from TVDb URL: {}".format(tvdb_url)) raise Failed(f"TVDb Error: Name not found from TVDb URL: {tvdb_url}")
results = response.xpath("//div[@class='row hidden-xs hidden-sm']/div/img/@src") results = response.xpath("//div[@class='row hidden-xs hidden-sm']/div/img/@src")
self.poster_path = results[0] if len(results) > 0 and len(results[0]) > 0 else None self.poster_path = results[0] if len(results) > 0 and len(results[0]) > 0 else None
@ -60,27 +60,27 @@ class TVDbAPI:
self.Trakt = Trakt self.Trakt = Trakt
self.site_url = "https://www.thetvdb.com" self.site_url = "https://www.thetvdb.com"
self.alt_site_url = "https://thetvdb.com" self.alt_site_url = "https://thetvdb.com"
self.list_url = "{}/lists/".format(self.site_url) self.list_url = f"{self.site_url}/lists/"
self.alt_list_url = "{}/lists/".format(self.alt_site_url) self.alt_list_url = f"{self.alt_site_url}/lists/"
self.series_url = "{}/series/".format(self.site_url) self.series_url = f"{self.site_url}/series/"
self.alt_series_url = "{}/series/".format(self.alt_site_url) self.alt_series_url = f"{self.alt_site_url}/series/"
self.movies_url = "{}/movies/".format(self.site_url) self.movies_url = f"{self.site_url}/movies/"
self.alt_movies_url = "{}/movies/".format(self.alt_site_url) self.alt_movies_url = f"{self.alt_site_url}/movies/"
self.series_id_url = "{}/dereferrer/series/".format(self.site_url) self.series_id_url = f"{self.site_url}/dereferrer/series/"
self.movie_id_url = "{}/dereferrer/movie/".format(self.site_url) self.movie_id_url = f"{self.site_url}/dereferrer/movie/"
def get_series(self, language, tvdb_url=None, tvdb_id=None): def get_series(self, language, tvdb_url=None, tvdb_id=None):
if not tvdb_url and not tvdb_id: if not tvdb_url and not tvdb_id:
raise Failed("TVDB Error: get_series 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: elif not tvdb_url and tvdb_id:
tvdb_url = "{}{}".format(self.series_id_url, tvdb_id) tvdb_url = f"{self.series_id_url}{tvdb_id}"
return TVDbObj(tvdb_url, language, False, self) return TVDbObj(tvdb_url, language, False, self)
def get_movie(self, language, tvdb_url=None, tvdb_id=None): def get_movie(self, language, tvdb_url=None, tvdb_id=None):
if not tvdb_url and not tvdb_id: if not tvdb_url and not tvdb_id:
raise Failed("TVDB Error: get_movie requires either tvdb_url or tvdb_id") raise Failed("TVDB Error: get_movie requires either tvdb_url or tvdb_id")
elif not tvdb_url and tvdb_id: elif not tvdb_url and tvdb_id:
tvdb_url = "{}{}".format(self.movie_id_url, tvdb_id) tvdb_url = f"{self.movie_id_url}{tvdb_id}"
return TVDbObj(tvdb_url, language, True, self) return TVDbObj(tvdb_url, language, True, self)
def get_tvdb_ids_from_url(self, tvdb_url, language): def get_tvdb_ids_from_url(self, tvdb_url, language):
@ -94,25 +94,25 @@ class TVDbAPI:
title = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/text()")[0] title = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/text()")[0]
item_url = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/@href")[0] item_url = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/@href")[0]
if item_url.startswith("/series/"): if item_url.startswith("/series/"):
try: show_ids.append(self.get_series(language, tvdb_url="{}{}".format(self.site_url, item_url)).id) try: show_ids.append(self.get_series(language, tvdb_url=f"{self.site_url}{item_url}").id)
except Failed as e: logger.error("{} for series {}".format(e, title)) except Failed as e: logger.error(f"{e} for series {title}")
elif item_url.startswith("/movies/"): elif item_url.startswith("/movies/"):
try: try:
tmdb_id = self.get_movie(language, tvdb_url="{}{}".format(self.site_url, item_url)).tmdb_id tmdb_id = self.get_movie(language, tvdb_url=f"{self.site_url}{item_url}").tmdb_id
if tmdb_id: movie_ids.append(tmdb_id) if tmdb_id: movie_ids.append(tmdb_id)
else: raise Failed("TVDb Error: TMDb ID not found from TVDb URL: {}".format(tvdb_url)) else: raise Failed(f"TVDb Error: TMDb ID not found from TVDb URL: {tvdb_url}")
except Failed as e: except Failed as e:
logger.error("{} for series {}".format(e, title)) logger.error(f"{e} for series {title}")
else: else:
logger.error("TVDb Error: Skipping Movie: {}".format(title)) logger.error(f"TVDb Error: Skipping Movie: {title}")
if len(show_ids) > 0 or len(movie_ids) > 0: if len(show_ids) > 0 or len(movie_ids) > 0:
return movie_ids, show_ids return movie_ids, show_ids
raise Failed("TVDb Error: No TVDb IDs found at {}".format(tvdb_url)) raise Failed(f"TVDb Error: No TVDb IDs found at {tvdb_url}")
except requests.exceptions.MissingSchema: except requests.exceptions.MissingSchema:
util.print_stacktrace() util.print_stacktrace()
raise Failed("TVDb Error: URL Lookup Failed for {}".format(tvdb_url)) raise Failed(f"TVDb Error: URL Lookup Failed for {tvdb_url}")
else: else:
raise Failed("TVDb Error: {} must begin with {}".format(tvdb_url, self.list_url)) raise Failed(f"TVDb Error: {tvdb_url} must begin with {self.list_url}")
@retry(stop_max_attempt_number=6, wait_fixed=10000) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url, language): def send_request(self, url, language):
@ -123,7 +123,7 @@ class TVDbAPI:
show_ids = [] show_ids = []
movie_ids = [] movie_ids = []
if status_message: if status_message:
logger.info("Processing {}: {}".format(pretty, data)) logger.info(f"Processing {pretty}: {data}")
if method == "tvdb_show": if method == "tvdb_show":
try: show_ids.append(self.get_series(language, tvdb_id=int(data)).id) try: show_ids.append(self.get_series(language, tvdb_id=int(data)).id)
except ValueError: show_ids.append(self.get_series(language, tvdb_url=data).id) except ValueError: show_ids.append(self.get_series(language, tvdb_url=data).id)
@ -135,10 +135,10 @@ class TVDbAPI:
movie_ids.extend(tmdb_ids) movie_ids.extend(tmdb_ids)
show_ids.extend(tvdb_ids) show_ids.extend(tvdb_ids)
else: else:
raise Failed("TVDb Error: Method {} not supported".format(method)) raise Failed(f"TVDb Error: Method {method} not supported")
if status_message: if status_message:
logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug("TVDb IDs Found: {}".format(show_ids)) logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids return movie_ids, show_ids
def convert_from_imdb(self, imdb_id): def convert_from_imdb(self, imdb_id):
@ -162,7 +162,7 @@ class TVDbAPI:
try: try:
if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id) if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id)
except Failed: tmdb_id = None except Failed: tmdb_id = None
if not tmdb_id: raise Failed("TVDb Error: No TMDb ID found for IMDb: {}".format(imdb_id)) if not tmdb_id: raise Failed(f"TVDb Error: No TMDb ID found for IMDb: {imdb_id}")
if self.Cache and tmdb_id and update is not False: if self.Cache and tmdb_id and update is not False:
self.Cache.update_imdb("movie", update, imdb_id, tmdb_id) self.Cache.update_imdb("movie", update, imdb_id, tmdb_id)
return tmdb_id return tmdb_id

@ -443,6 +443,9 @@ discover_tv_sort = [
"popularity.desc", "popularity.asc" "popularity.desc", "popularity.asc"
] ]
def tab_new_lines(data):
return str(data).replace("\n", "\n|\t ") if "\n" in str(data) else str(data)
def adjust_space(old_length, display_title): def adjust_space(old_length, display_title):
display_title = str(display_title) display_title = str(display_title)
space_length = old_length - len(display_title) space_length = old_length - len(display_title)
@ -461,30 +464,31 @@ def choose_from_list(datalist, description, data=None, list_type="title", exact=
if len(datalist) > 0: if len(datalist) > 0:
if len(datalist) == 1 and (description != "collection" or datalist[0].title == data): if len(datalist) == 1 and (description != "collection" or datalist[0].title == data):
return datalist[0] return datalist[0]
message = "Multiple {}s Found\n0) {}".format(description, "Create New Collection: {}".format(data) if description == "collection" else "Do Nothing") zero_option = f"Create New Collection: {data}" if description == "collection" else "Do Nothing"
message = f"Multiple {description}s Found\n0) {zero_option}"
for i, d in enumerate(datalist, 1): for i, d in enumerate(datalist, 1):
if list_type == "title": if list_type == "title":
if d.title == data: if d.title == data:
return d return d
message += "\n{}) {}".format(i, d.title) message += f"\n{i}) {d.title}"
else: else:
message += "\n{}) [{}] {}".format(i, d[0], d[1]) message += f"\n{i}) [{d[0]}] {d[1]}"
if exact: if exact:
return None return None
print_multiline(message, info=True) print_multiline(message, info=True)
while True: while True:
try: try:
selection = int(logger_input("Choose {} number".format(description))) - 1 selection = int(logger_input(f"Choose {description} number")) - 1
if selection >= 0: return datalist[selection] if selection >= 0: return datalist[selection]
elif selection == -1: return None elif selection == -1: return None
else: logger.info("Invalid {} number".format(description)) else: logger.info(f"Invalid {description} number")
except IndexError: logger.info("Invalid {} number".format(description)) except IndexError: logger.info(f"Invalid {description} number")
except TimeoutExpired: except TimeoutExpired:
if list_type == "title": if list_type == "title":
logger.warning("Input Timeout: using {}".format(data)) logger.warning(f"Input Timeout: using {data}")
return None return None
else: else:
logger.warning("Input Timeout: using {}".format(datalist[0][1])) logger.warning(f"Input Timeout: using {datalist[0][1]}")
return datalist[0][1] return datalist[0][1]
else: else:
return None return None
@ -516,22 +520,22 @@ def get_year_list(data, method):
end = year_range.group(2) end = year_range.group(2)
if end == "NOW": if end == "NOW":
end = current_year end = current_year
if int(start) < 1800 or int(start) > current_year: logger.error("Collection Error: Skipping {} starting year {} must be between 1800 and {}".format(method, start, current_year)) if int(start) < 1800 or int(start) > current_year: logger.error(f"Collection Error: Skipping {method} starting year {start} must be between 1800 and {current_year}")
elif int(end) < 1800 or int(end) > current_year: logger.error("Collection Error: Skipping {} ending year {} must be between 1800 and {}".format(method, end, current_year)) elif int(end) < 1800 or int(end) > current_year: logger.error(f"Collection Error: Skipping {method} ending year {end} must be between 1800 and {current_year}")
elif int(start) > int(end): logger.error("Collection Error: Skipping {} starting year {} cannot be greater then ending year {}".format(method, start, end)) elif int(start) > int(end): logger.error(f"Collection Error: Skipping {method} starting year {start} cannot be greater then ending year {end}")
else: else:
for i in range(int(start), int(end) + 1): for i in range(int(start), int(end) + 1):
final_years.append(i) final_years.append(i)
else: else:
year = re.search("(\\d+)", str(value)).group(1) year = re.search("(\\d+)", str(value)).group(1)
if int(year) < 1800 or int(year) > 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)) logger.error(f"Collection Error: Skipping {method} year {year} must be between 1800 and {current_year}")
else: else:
if len(str(year)) != len(str(value)): if len(str(year)) != len(str(value)):
logger.warning("Collection Warning: {} can be replaced with {}".format(value, year)) logger.warning(f"Collection Warning: {value} can be replaced with {year}")
final_years.append(year) final_years.append(year)
except AttributeError: except AttributeError:
logger.error("Collection Error: Skipping {} failed to parse year from {}".format(method, value)) logger.error(f"Collection Error: Skipping {method} failed to parse year from {value}")
return final_years return final_years
def logger_input(prompt, timeout=60): def logger_input(prompt, timeout=60):
@ -543,14 +547,14 @@ def alarm_handler(signum, frame):
raise TimeoutExpired raise TimeoutExpired
def unix_input(prompt, timeout=60): def unix_input(prompt, timeout=60):
prompt = "| {}: ".format(prompt) prompt = f"| {prompt}: "
signal.signal(signal.SIGALRM, alarm_handler) signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(timeout) signal.alarm(timeout)
try: return input(prompt) try: return input(prompt)
finally: signal.alarm(0) finally: signal.alarm(0)
def old_windows_input(prompt, timeout=60, timer=time.monotonic): def old_windows_input(prompt, timeout=60, timer=time.monotonic):
prompt = "| {}: ".format(prompt) prompt = f"| {prompt}: "
sys.stdout.write(prompt) sys.stdout.write(prompt)
sys.stdout.flush() sys.stdout.flush()
endtime = timer() + timeout endtime = timer() + timeout
@ -560,13 +564,13 @@ def old_windows_input(prompt, timeout=60, timer=time.monotonic):
result.append(msvcrt.getwche()) result.append(msvcrt.getwche())
if result[-1] == "\n": if result[-1] == "\n":
out = "".join(result[:-1]) out = "".join(result[:-1])
logger.debug("{}{}".format(prompt[2:], out)) logger.debug(f"{prompt[2:]}{out}")
return out return out
time.sleep(0.04) time.sleep(0.04)
raise TimeoutExpired raise TimeoutExpired
def windows_input(prompt, timeout=5): def windows_input(prompt, timeout=5):
sys.stdout.write("| {}: ".format(prompt)) sys.stdout.write(f"| {prompt}: ")
sys.stdout.flush() sys.stdout.flush()
result = [] result = []
start_time = time.time() start_time = time.time()
@ -576,7 +580,7 @@ def windows_input(prompt, timeout=5):
if ord(char) == 13: # enter_key if ord(char) == 13: # enter_key
out = "".join(result) out = "".join(result)
print("") print("")
logger.debug("{}: {}".format(prompt, out)) logger.debug(f"{prompt}: {out}")
return out return out
elif ord(char) >= 32: #space_char elif ord(char) >= 32: #space_char
result.append(char) result.append(char)
@ -606,17 +610,17 @@ def my_except_hook(exctype, value, tb):
def get_id_from_imdb_url(imdb_url): def get_id_from_imdb_url(imdb_url):
match = re.search("(tt\\d+)", str(imdb_url)) match = re.search("(tt\\d+)", str(imdb_url))
if match: return match.group(1) if match: return match.group(1)
else: raise Failed("Regex Error: Failed to parse IMDb ID from IMDb URL: {}".format(imdb_url)) else: raise Failed(f"Regex Error: Failed to parse IMDb ID from IMDb URL: {imdb_url}")
def regex_first_int(data, id_type, default=None): def regex_first_int(data, id_type, default=None):
match = re.search("(\\d+)", str(data)) match = re.search("(\\d+)", str(data))
if match: if match:
return int(match.group(1)) return int(match.group(1))
elif default: elif default:
logger.warning("Regex Warning: Failed to parse {} from {} using {} as default".format(id_type, data, default)) logger.warning(f"Regex Warning: Failed to parse {id_type} from {data} using {default} as default")
return int(default) return int(default)
else: else:
raise Failed("Regex Error: Failed to parse {} from {}".format(id_type, data)) raise Failed(f"Regex Error: Failed to parse {id_type} from {data}")
def remove_not(method): def remove_not(method):
return method[:-4] if method.endswith(".not") else method return method[:-4] if method.endswith(".not") else method
@ -629,20 +633,20 @@ def get_centered_text(text):
text += " " text += " "
space -= 1 space -= 1
side = int(space / 2) side = int(space / 2)
return "{}{}{}".format(" " * side, text, " " * side) return f"{' ' * side}{text}{' ' * side}"
def separator(text=None): def separator(text=None):
logger.handlers[0].setFormatter(logging.Formatter("%(message)-{}s".format(screen_width - 2))) logger.handlers[0].setFormatter(logging.Formatter(f"%(message)-{screen_width - 2}s"))
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s %(message)-{}s".format(screen_width - 2))) logger.handlers[1].setFormatter(logging.Formatter(f"[%(asctime)s] %(filename)-27s %(levelname)-10s %(message)-{screen_width - 2}s"))
logger.info("|{}|".format(separating_character * screen_width)) logger.info(f"|{separating_character * screen_width}|")
if text: if text:
logger.info("| {} |".format(get_centered_text(text))) logger.info(f"| {get_centered_text(text)} |")
logger.info("|{}|".format(separating_character * screen_width)) logger.info(f"|{separating_character * screen_width}|")
logger.handlers[0].setFormatter(logging.Formatter("| %(message)-{}s |".format(screen_width - 2))) logger.handlers[0].setFormatter(logging.Formatter(f"| %(message)-{screen_width - 2}s |"))
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s | %(message)-{}s |".format(screen_width - 2))) logger.handlers[1].setFormatter(logging.Formatter(f"[%(asctime)s] %(filename)-27s %(levelname)-10s | %(message)-{screen_width - 2}s |"))
def print_return(length, text): def print_return(length, text):
print(adjust_space(length, "| {}".format(text)), end="\r") print(adjust_space(length, f"| {text}"), end="\r")
return len(text) + 2 return len(text) + 2
def print_end(length, text=None): def print_end(length, text=None):

@ -15,18 +15,18 @@ parser.add_argument("-w", "--width", dest="width", help="Screen Width (Default:
args = parser.parse_args() args = parser.parse_args()
if not re.match("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$", args.time): 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)) raise util.Failed(f"Argument Error: time argument invalid: {args.time} must be in the HH:MM format")
util.separating_character = args.divider[0] util.separating_character = args.divider[0]
if 90 <= args.width <= 300: if 90 <= args.width <= 300:
util.screen_width = args.width util.screen_width = args.width
else: else:
raise util.Failed("Argument Error: width argument invalid: {} must be an integer between 90 and 300".format(args.width)) raise util.Failed(f"Argument Error: width argument invalid: {args.width} must be an integer between 90 and 300")
default_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config") default_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config")
if args.config and os.path.exists(args.config): default_dir = os.path.join(os.path.dirname(os.path.abspath(args.config))) if args.config and os.path.exists(args.config): default_dir = os.path.join(os.path.dirname(os.path.abspath(args.config)))
elif args.config and not os.path.exists(args.config): raise util.Failed("Config Error: config not found at {}".format(os.path.abspath(args.config))) elif args.config and not os.path.exists(args.config): raise util.Failed(f"Config Error: config not found at {os.path.abspath(args.config)}")
elif not os.path.exists(os.path.join(default_dir, "config.yml")): raise util.Failed("Config Error: config not found at {}".format(os.path.abspath(default_dir))) elif not os.path.exists(os.path.join(default_dir, "config.yml")): raise util.Failed(f"Config Error: config not found at {os.path.abspath(default_dir)}")
os.makedirs(os.path.join(default_dir, "logs"), exist_ok=True) os.makedirs(os.path.join(default_dir, "logs"), exist_ok=True)
@ -34,8 +34,8 @@ logger = logging.getLogger("Plex Meta Manager")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
def fmt_filter(record): def fmt_filter(record):
record.levelname = "[{}]".format(record.levelname) record.levelname = f"[{record.levelname}]"
record.filename = "[{}:{}]".format(record.filename, record.lineno) record.filename = f"[{record.filename}:{record.lineno}]"
return True return True
file_handler = logging.handlers.TimedRotatingFileHandler(os.path.join(default_dir, "logs", "meta.log"), when="midnight", backupCount=10, encoding="utf-8") file_handler = logging.handlers.TimedRotatingFileHandler(os.path.join(default_dir, "logs", "meta.log"), when="midnight", backupCount=10, encoding="utf-8")
@ -71,7 +71,7 @@ def start(config_path, test, daily, collections):
elif test: start_type = "Test " elif test: start_type = "Test "
elif collections: start_type = "Collections " elif collections: start_type = "Collections "
else: start_type = "" else: start_type = ""
util.separator("Starting {}Run".format(start_type)) util.separator(f"Starting {start_type}Run")
try: try:
config = Config(default_dir, config_path) config = Config(default_dir, config_path)
config.update_libraries(test, collections) config.update_libraries(test, collections)
@ -79,7 +79,7 @@ def start(config_path, test, daily, collections):
util.print_stacktrace() util.print_stacktrace()
logger.critical(e) logger.critical(e)
logger.info("") logger.info("")
util.separator("Finished {}Run".format(start_type)) util.separator(f"Finished {start_type}Run")
try: try:
if args.run or args.test or args.collections: if args.run or args.test or args.collections:
@ -95,10 +95,10 @@ try:
if hours < 0: if hours < 0:
hours += 24 hours += 24
minutes = int((seconds % 3600) // 60) minutes = int((seconds % 3600) // 60)
time_str = "{} Hour{} and ".format(hours, "s" if hours > 1 else "") if hours > 0 else "" time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else ""
time_str += "{} Minute{}".format(minutes, "s" if minutes > 1 else "") time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}"
length = util.print_return(length, "Current Time: {} | {} until the daily run at {}".format(current, time_str, args.time)) length = util.print_return(length, f"Current Time: {current} | {time_str} until the daily run at {args.time}")
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
util.separator("Exiting Plex Meta Manager") util.separator("Exiting Plex Meta Manager")

Loading…
Cancel
Save