mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-10 06:54:21 +00:00
changed to f-Strings
This commit is contained in:
parent
e5c43e0fa7
commit
4c8ccaa171
16 changed files with 483 additions and 474 deletions
|
@ -23,14 +23,14 @@ class AniDBAPI:
|
|||
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_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 from_id == "tvdbid": return [int(i) for i in ids]
|
||||
if len(ids[0]) > 0:
|
||||
try: return ids[0].split(",") if to_id == "imdbid" else int(ids[0])
|
||||
except ValueError: raise Failed("AniDB Error: No {} ID found for {} ID: {}".format(util.pretty_ids[to_id], util.pretty_ids[from_id], input_id))
|
||||
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("AniDB Error: {} ID: {} not found".format(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(f"AniDB Error: No {util.pretty_ids[to_id]} ID found for {util.pretty_ids[from_id]} 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)
|
||||
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")
|
||||
|
||||
def validate_anidb_id(self, anidb_id, language):
|
||||
response = self.send_request("{}/{}".format(self.urls["anime"], anidb_id), language)
|
||||
ids = response.xpath("//*[text()='a{}']/text()".format(anidb_id))
|
||||
response = self.send_request(f"{self.urls['anime']}/{anidb_id}", language)
|
||||
ids = response.xpath(f"//*[text()='a{anidb_id}']/text()")
|
||||
if len(ids) > 0:
|
||||
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):
|
||||
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")
|
||||
|
||||
def validate_anidb_list(self, anidb_list, language):
|
||||
|
@ -60,22 +60,22 @@ class AniDBAPI:
|
|||
logger.error(e)
|
||||
if len(anidb_values) > 0:
|
||||
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):
|
||||
pretty = util.pretty_names[method] if method in util.pretty_names else method
|
||||
if status_message:
|
||||
logger.debug("Data: {}".format(data))
|
||||
logger.debug(f"Data: {data}")
|
||||
anime_ids = []
|
||||
if method == "anidb_popular":
|
||||
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])
|
||||
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)
|
||||
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 = []
|
||||
movie_ids = []
|
||||
for anidb_id in anime_ids:
|
||||
|
@ -85,11 +85,11 @@ class AniDBAPI:
|
|||
else: raise Failed
|
||||
except Failed:
|
||||
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:
|
||||
logger.debug("AniDB IDs Found: {}".format(anime_ids))
|
||||
logger.debug("TMDb IDs Found: {}".format(movie_ids))
|
||||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
logger.debug(f"AniDB IDs Found: {anime_ids}")
|
||||
logger.debug(f"TMDb IDs Found: {movie_ids}")
|
||||
logger.debug(f"TVDb IDs Found: {show_ids}")
|
||||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id):
|
||||
|
@ -121,6 +121,6 @@ class AniDBAPI:
|
|||
if tmdb_id: output_tmdb_ids.append(tmdb_id)
|
||||
if self.Cache and tmdb_id and expired is not False:
|
||||
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]
|
||||
else: return output_tmdb_ids
|
||||
|
|
|
@ -37,13 +37,13 @@ class CollectionBuilder:
|
|||
elif not data_template["name"]:
|
||||
raise Failed("Collection Error: template sub-attribute name is blank")
|
||||
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):
|
||||
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:
|
||||
for tm in data_template:
|
||||
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 = self.library.templates[template_name]
|
||||
|
@ -55,7 +55,7 @@ class CollectionBuilder:
|
|||
if template["default"][dv]:
|
||||
default[dv] = template["default"][dv]
|
||||
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:
|
||||
raise Failed("Collection Error: template sub-attribute default is not a dictionary")
|
||||
else:
|
||||
|
@ -67,13 +67,13 @@ class CollectionBuilder:
|
|||
def replace_txt(txt):
|
||||
txt = str(txt)
|
||||
for template_method in data_template:
|
||||
if template_method != "name" and "<<{}>>".format(template_method) in txt:
|
||||
txt = txt.replace("<<{}>>".format(template_method), str(data_template[template_method]))
|
||||
if template_method != "name" and f"<<{template_method}>>" in txt:
|
||||
txt = txt.replace(f"<<{template_method}>>", str(data_template[template_method]))
|
||||
if "<<collection_name>>" in txt:
|
||||
txt = txt.replace("<<collection_name>>", str(self.name))
|
||||
for dm in default:
|
||||
if "<<{}>>".format(dm) in txt:
|
||||
txt = txt.replace("<<{}>>".format(dm), str(default[dm]))
|
||||
if f"<<{dm}>>" in txt:
|
||||
txt = txt.replace(f"<<{dm}>>", str(default[dm]))
|
||||
if txt in ["true", "True"]: return True
|
||||
elif txt in ["false", "False"]: return False
|
||||
else:
|
||||
|
@ -103,7 +103,7 @@ class CollectionBuilder:
|
|||
attr = replace_txt(template[m])
|
||||
self.data[m] = attr
|
||||
else:
|
||||
raise Failed("Collection Error: template attribute {} is blank".format(m))
|
||||
raise Failed(f"Collection Error: template attribute {m} is blank")
|
||||
|
||||
skip_collection = True
|
||||
if "schedule" not in data:
|
||||
|
@ -127,48 +127,48 @@ class CollectionBuilder:
|
|||
if run_time.startswith("week"):
|
||||
if param.lower() in util.days_alias:
|
||||
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():
|
||||
skip_collection = False
|
||||
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"):
|
||||
try:
|
||||
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):
|
||||
skip_collection = False
|
||||
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:
|
||||
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"):
|
||||
match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param)
|
||||
if match:
|
||||
month = int(match.group(1))
|
||||
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)):
|
||||
skip_collection = False
|
||||
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:
|
||||
logger.error("Collection Error: failed to parse schedule: {}".format(schedule))
|
||||
logger.error(f"Collection Error: failed to parse schedule: {schedule}")
|
||||
else:
|
||||
logger.error("Collection Error: schedule attribute {} invalid".format(schedule))
|
||||
logger.error(f"Collection Error: schedule attribute {schedule} invalid")
|
||||
if self.schedule is None:
|
||||
skip_collection = False
|
||||
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.sync = self.library.sync_mode == "sync"
|
||||
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))
|
||||
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"]))
|
||||
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(f"Collection Warning: {self.library.sync_mode} sync_mode invalid using general: {data['sync_mode']}")
|
||||
else: self.sync = data["sync_mode"] == "sync"
|
||||
|
||||
if "tmdb_person" in data:
|
||||
|
@ -180,35 +180,35 @@ class CollectionBuilder:
|
|||
if "summary" not in self.details and hasattr(person, "biography") and person.biography:
|
||||
self.details["summary"] = person.biography
|
||||
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
|
||||
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:
|
||||
raise Failed("Collection Error: tmdb_person attribute is blank")
|
||||
|
||||
for m in data:
|
||||
if "tmdb" in m and not config.TMDb: raise Failed("Collection Error: {} requires TMDb to be configured".format(m))
|
||||
elif "trakt" in m and not config.Trakt: raise Failed("Collection Error: {} requires Trakt todo be configured".format(m))
|
||||
elif "imdb" in m and not config.IMDb: raise Failed("Collection Error: {} requires TMDb or Trakt to be configured".format(m))
|
||||
elif "tautulli" in m and not self.library.Tautulli: raise Failed("Collection Error: {} requires Tautulli to be configured".format(m))
|
||||
elif "mal" in m and not config.MyAnimeList: raise Failed("Collection Error: {} requires MyAnimeList 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(f"Collection Error: {m} requires Trakt todo be configured")
|
||||
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(f"Collection Error: {m} requires Tautulli to be configured")
|
||||
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:
|
||||
logger.debug("")
|
||||
logger.debug("Method: {}".format(m))
|
||||
logger.debug("Value: {}".format(data[m]))
|
||||
logger.debug(f"Method: {m}")
|
||||
logger.debug(f"Value: {data[m]}")
|
||||
if m in util.method_alias:
|
||||
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:
|
||||
method_name = m
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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":
|
||||
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":
|
||||
|
@ -221,28 +221,28 @@ class CollectionBuilder:
|
|||
elif data[m] == "show_items": self.details[method_name] = "showItems"
|
||||
else: self.details[method_name] = data[m]
|
||||
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":
|
||||
if data[m] in ["release", "alpha"]:
|
||||
self.details[method_name] = data[m]
|
||||
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":
|
||||
self.posters.append(("url", data[m], method_name))
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
self.backgrounds.append(("url", data[m], method_name))
|
||||
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":
|
||||
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":
|
||||
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'")
|
||||
|
@ -250,7 +250,7 @@ class CollectionBuilder:
|
|||
self.details[method_name] = util.get_list(data[m])
|
||||
elif method_name in util.boolean_details:
|
||||
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:
|
||||
self.details[method_name] = data[m]
|
||||
elif method_name in ["year", "year.not"]:
|
||||
|
@ -302,24 +302,24 @@ class CollectionBuilder:
|
|||
elif method_name in util.dictionary_lists:
|
||||
if isinstance(data[m], dict):
|
||||
def get_int(parent, method, data_in, default_in, minimum=1, maximum=None):
|
||||
if method not in data_in: logger.warning("Collection Warning: {} {} attribute not found using {} as default".format(parent, method, default))
|
||||
elif not data_in[method]: logger.warning("Collection Warning: {} {} attribute is blank using {} as default".format(parent, method, default))
|
||||
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(f"Collection Warning: {parent} {method} attribute is blank using {default} as default")
|
||||
elif isinstance(data_in[method], int) and data_in[method] >= minimum:
|
||||
if maximum is None or data_in[method] <= maximum: return data_in[method]
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer <= {} using {} as default".format(parent, method, data_in[method], maximum, default))
|
||||
else: logger.warning("Collection Warning: {} {} attribute {} invalid must an integer >= {} using {} as default".format(parent, method, data_in[method], minimum, default))
|
||||
else: logger.warning(f"Collection Warning: {parent} {method} attribute {data_in[method]} invalid must an integer <= {maximum} using {default} as 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
|
||||
if method_name == "filters":
|
||||
for f in data[m]:
|
||||
if f in util.method_alias or (f.endswith(".not") and f[:-4] in util.method_alias):
|
||||
filter_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:
|
||||
filter_method = f
|
||||
if filter_method in util.movie_only_filters and self.library.is_show: raise Failed("Collection Error: {} filter only works for movie libraries".format(filter_method))
|
||||
elif data[m][f] is None: raise Failed("Collection Error: {} filter is blank".format(filter_method))
|
||||
elif filter_method in util.all_filters: self.filters.append((filter_method, data[m][f]))
|
||||
else: raise Failed("Collection Error: {} filter not supported".format(filter_method))
|
||||
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(f"Collection Error: {filter_method} filter is blank")
|
||||
elif filter_method in util.all_filters: self.filters.append((filter_method, data[m][f]))
|
||||
else: raise Failed(f"Collection Error: {filter_method} filter not supported")
|
||||
elif method_name == "plex_collectionless":
|
||||
new_dictionary = {}
|
||||
prefix_list = []
|
||||
|
@ -343,13 +343,13 @@ class CollectionBuilder:
|
|||
for s in data[m]:
|
||||
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]
|
||||
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:
|
||||
search = s
|
||||
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:
|
||||
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"]:
|
||||
years = util.get_year_list(data[m][s], search)
|
||||
if len(years) > 0:
|
||||
|
@ -359,7 +359,7 @@ class CollectionBuilder:
|
|||
used.append(util.remove_not(search))
|
||||
searches.append((search, util.get_list(data[m][s])))
|
||||
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]))
|
||||
elif method_name == "tmdb_discover":
|
||||
new_dictionary = {"limit": 100}
|
||||
|
@ -371,71 +371,71 @@ class CollectionBuilder:
|
|||
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(attr_data)):
|
||||
new_dictionary[attr] = str(attr_data)
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US".format(m, attr, attr_data))
|
||||
raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US")
|
||||
elif attr == "region":
|
||||
if re.compile("^[A-Z]{2}$").match(str(attr_data)):
|
||||
new_dictionary[attr] = str(attr_data)
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: {} must match pattern ^[A-Z]{{2}}$ e.g. US".format(m, attr, attr_data))
|
||||
raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern ^[A-Z]{{2}}$ e.g. US")
|
||||
elif attr == "sort_by":
|
||||
if (self.library.is_movie and attr_data in util.discover_movie_sort) or (self.library.is_show and attr_data in util.discover_tv_sort):
|
||||
new_dictionary[attr] = attr_data
|
||||
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":
|
||||
if "certification" in data[m] or "certification.lte" in data[m] or "certification.gte" in data[m]:
|
||||
new_dictionary[attr] = attr_data
|
||||
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"]:
|
||||
if "certification_country" in data[m]:
|
||||
new_dictionary[attr] = attr_data
|
||||
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"]:
|
||||
if attr_data is True:
|
||||
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"]:
|
||||
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("-")
|
||||
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)):
|
||||
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:
|
||||
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"]:
|
||||
if isinstance(attr_data, int) and 1800 < attr_data < 2200:
|
||||
new_dictionary[attr] = attr_data
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {}: must be a valid year e.g. 1990".format(m, attr))
|
||||
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"]:
|
||||
if (isinstance(attr_data, int) or isinstance(attr_data, float)) and 0 < attr_data:
|
||||
new_dictionary[attr] = attr_data
|
||||
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"]:
|
||||
new_dictionary[attr] = attr_data
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute {} not supported".format(m, attr))
|
||||
raise Failed(f"Collection Error: {m} attribute {attr} not supported")
|
||||
elif attr == "limit":
|
||||
if isinstance(attr_data, int) and attr_data > 0:
|
||||
new_dictionary[attr] = attr_data
|
||||
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:
|
||||
raise Failed("Collection Error: {} attribute {} not supported".format(m, attr))
|
||||
raise Failed(f"Collection Error: {m} attribute {attr} not supported")
|
||||
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:
|
||||
self.methods.append((method_name, [new_dictionary]))
|
||||
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:
|
||||
new_dictionary = {}
|
||||
if method_name == "tautulli_popular": new_dictionary["list_type"] = "popular"
|
||||
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_size"] = get_int(method_name, "list_size", data[m], 10)
|
||||
|
@ -445,7 +445,7 @@ class CollectionBuilder:
|
|||
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")
|
||||
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"]]
|
||||
|
||||
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 [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"]))
|
||||
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 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"]))
|
||||
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(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(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"]
|
||||
|
||||
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")
|
||||
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"]]
|
||||
|
||||
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 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"]]
|
||||
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", data[m], 100, maximum=1000)
|
||||
self.methods.append((method_name, [new_dictionary]))
|
||||
else:
|
||||
raise Failed("Collection Error: {} attribute is not a dictionary: {}".format(m, data[m]))
|
||||
raise Failed(f"Collection Error: {m} attribute is not a dictionary: {data[m]}")
|
||||
elif method_name in util.count_lists:
|
||||
list_count = util.regex_first_int(data[m], "List Size", default=20)
|
||||
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
|
||||
self.methods.append((method_name, [list_count]))
|
||||
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 in ["tmdb_collection_details", "tmdb_movie_details", "tmdb_show_details"]:
|
||||
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:
|
||||
self.details["summary"] = item.overview
|
||||
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:
|
||||
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:
|
||||
item = config.TMDb.get_list(values[0])
|
||||
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:
|
||||
self.methods.append((method_name, util.get_list(data[m])))
|
||||
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:
|
||||
raise Failed("Collection Error: {} attribute is blank".format(m))
|
||||
raise Failed(f"Collection Error: {m} attribute is blank")
|
||||
|
||||
self.do_arr = False
|
||||
if self.library.Radarr:
|
||||
|
@ -523,8 +523,8 @@ class CollectionBuilder:
|
|||
items_found = 0
|
||||
for method, values in self.methods:
|
||||
logger.debug("")
|
||||
logger.debug("Method: {}".format(method))
|
||||
logger.debug("Values: {}".format(values))
|
||||
logger.debug(f"Method: {method}")
|
||||
logger.debug(f"Values: {values}")
|
||||
pretty = util.pretty_names[method] if method in util.pretty_names else method
|
||||
for value in values:
|
||||
items = []
|
||||
|
@ -545,9 +545,9 @@ class CollectionBuilder:
|
|||
else: missing_shows.append(show_id)
|
||||
return items_found_inside
|
||||
logger.info("")
|
||||
logger.debug("Value: {}".format(value))
|
||||
logger.debug(f"Value: {value}")
|
||||
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_found += len(items)
|
||||
elif method == "plex_collection":
|
||||
|
@ -563,8 +563,9 @@ class CollectionBuilder:
|
|||
search_terms[final_method] = search_list
|
||||
ors = ""
|
||||
for o, param in enumerate(attr_pair[1]):
|
||||
ors += "{}{}".format(" OR " if o > 0 else "{}(".format(attr_pair[0]), param)
|
||||
logger.info("\t\t AND {})".format(ors) if i > 0 else "Processing {}: {})".format(pretty, ors))
|
||||
or_des = " OR " if o > 0 else f"{attr_pair[0]}("
|
||||
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_found += len(items)
|
||||
elif method == "plex_collectionless":
|
||||
|
@ -585,7 +586,7 @@ class CollectionBuilder:
|
|||
all_items = self.library.Plex.all()
|
||||
length = 0
|
||||
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
|
||||
for collection in item.collections:
|
||||
if collection.tag.lower() in good_collections:
|
||||
|
@ -594,7 +595,7 @@ class CollectionBuilder:
|
|||
if add_item:
|
||||
items.append(item)
|
||||
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:
|
||||
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)
|
||||
|
@ -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 "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))
|
||||
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)
|
||||
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):
|
||||
missing_movies_with_names.append((title, missing_id))
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
self.library.add_missing(collection_name, missing_movies_with_names, True)
|
||||
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())
|
||||
missing_shows_with_names.append((title, missing_id))
|
||||
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:
|
||||
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:
|
||||
self.library.add_missing(collection_name, missing_shows_with_names, False)
|
||||
if self.do_arr and self.library.Sonarr:
|
||||
|
@ -659,10 +660,10 @@ class CollectionBuilder:
|
|||
count_removed = 0
|
||||
for ratingKey, item in rating_key_map.items():
|
||||
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)
|
||||
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("")
|
||||
|
||||
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":
|
||||
for label in (la for la in item_labels if la not in labels):
|
||||
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):
|
||||
collection.addLabel(label)
|
||||
logger.info("Detail: Label {} added".format(label))
|
||||
logger.info(f"Detail: Label {label} added")
|
||||
|
||||
if self.library.asset_directory:
|
||||
name_mapping = self.name
|
||||
|
@ -703,14 +704,14 @@ class CollectionBuilder:
|
|||
if self.details["name_mapping"]: name_mapping = self.details["name_mapping"]
|
||||
else: logger.error("Collection Error: name_mapping attribute is blank")
|
||||
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):
|
||||
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:
|
||||
for match in matches:
|
||||
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:
|
||||
for match in matches:
|
||||
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
|
||||
if 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:
|
||||
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:
|
||||
logger.warning("No Files Found: {}".format(os.path.join(path, folder)))
|
||||
logger.warning(f"No Files Found: {os.path.join(path, folder)}")
|
||||
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")
|
||||
if not poster and "poster" in self.details: poster = self.details["poster"]
|
||||
if poster:
|
||||
if poster[0] == "url": collection.uploadPoster(url=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")
|
||||
if not background and "background" in self.details: background = self.details["background"]
|
||||
if background:
|
||||
if background[0] == "url": collection.uploadArt(url=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:
|
||||
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:
|
||||
connection.row_factory = sqlite3.Row
|
||||
with closing(connection.cursor()) as cursor:
|
||||
cursor.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='guids'")
|
||||
if cursor.fetchone()[0] == 0:
|
||||
logger.info("Initializing cache database at {}".format(cache))
|
||||
logger.info(f"Initializing cache database at {cache}")
|
||||
cursor.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS guids (
|
||||
INTEGER PRIMARY KEY,
|
||||
|
@ -34,7 +34,7 @@ class Cache:
|
|||
media_type TEXT)"""
|
||||
)
|
||||
else:
|
||||
logger.info("Using cache database at {}".format(cache))
|
||||
logger.info(f"Using cache database at {cache}")
|
||||
self.expiration = expiration
|
||||
self.cache_path = cache
|
||||
|
||||
|
@ -73,7 +73,7 @@ class Cache:
|
|||
with sqlite3.connect(self.cache_path) as connection:
|
||||
connection.row_factory = sqlite3.Row
|
||||
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()
|
||||
if row and row[to_id]:
|
||||
datetime_object = datetime.strptime(row["expiration_date"], "%Y-%m-%d")
|
||||
|
|
|
@ -23,10 +23,10 @@ class Config:
|
|||
def __init__(self, default_dir, config_path=None):
|
||||
logger.info("Locating config...")
|
||||
if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path)
|
||||
elif config_path and not os.path.exists(config_path): raise Failed("Config Error: config not found at {}".format(os.path.abspath(config_path)))
|
||||
elif 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"))
|
||||
else: raise Failed("Config Error: config not found at {}".format(os.path.abspath(default_dir)))
|
||||
logger.info("Using {} as config".format(self.config_path))
|
||||
else: raise Failed(f"Config Error: config not found at {os.path.abspath(default_dir)}")
|
||||
logger.info(f"Using {self.config_path} as config")
|
||||
|
||||
yaml.YAML().allow_duplicate_keys = True
|
||||
try:
|
||||
|
@ -74,7 +74,7 @@ class Config:
|
|||
yaml.round_trip_dump(new_config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
|
||||
self.data = new_config
|
||||
except yaml.scanner.ScannerError as e:
|
||||
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):
|
||||
endline = ""
|
||||
|
@ -85,28 +85,28 @@ class Config:
|
|||
data = None
|
||||
do_print = 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:
|
||||
message = "{} not found".format(text)
|
||||
message = f"{text} not found"
|
||||
if parent and save is True:
|
||||
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}
|
||||
elif attribute not in loaded_config[parent]: loaded_config[parent][attribute] = default
|
||||
else: endline = ""
|
||||
yaml.round_trip_dump(loaded_config, open(self.config_path, "w"), indent=ind_in, block_seq_indent=bsi_in)
|
||||
elif not data[attribute] and data[attribute] is not False:
|
||||
if default_is_none is True: return None
|
||||
else: message = "{} is blank".format(text)
|
||||
else: message = f"{text} is blank"
|
||||
elif var_type == "bool":
|
||||
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":
|
||||
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":
|
||||
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_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"
|
||||
elif var_type == "lower_list": return util.get_list(data[attribute], lower=True)
|
||||
elif test_list is None or data[attribute] in test_list: return data[attribute]
|
||||
else: message = "{}: {} is an invalid input".format(text, data[attribute])
|
||||
else: message = f"{text}: {data[attribute]} is an invalid input"
|
||||
if var_type == "path" and default and os.path.exists(os.path.abspath(default)):
|
||||
return default
|
||||
elif var_type == "path" and default:
|
||||
default = None
|
||||
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:
|
||||
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:
|
||||
message = message + " using {} as default".format(default)
|
||||
message = message + f" using {default} as default"
|
||||
message = message + endline
|
||||
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 len(options) > 0:
|
||||
message = message + "\n" + options
|
||||
raise Failed("Config Error: {}".format(message))
|
||||
raise Failed(f"Config Error: {message}")
|
||||
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:
|
||||
util.print_multiline(options)
|
||||
return default
|
||||
|
@ -163,7 +163,7 @@ class Config:
|
|||
except Failed as e: raise Failed(e)
|
||||
self.tmdb["language"] = check_for_attribute(self.data, "language", parent="tmdb", default="en")
|
||||
self.TMDb = TMDbAPI(self.tmdb)
|
||||
logger.info("TMDb Connection {}".format("Failed" if self.TMDb is None else "Successful"))
|
||||
logger.info(f"TMDb Connection {'Failed' if self.TMDb is None else 'Successful'}")
|
||||
else:
|
||||
raise Failed("Config Error: tmdb attribute not found")
|
||||
|
||||
|
@ -181,7 +181,7 @@ class Config:
|
|||
self.Trakt = TraktAPI(self.trakt, authorization)
|
||||
except Failed as e:
|
||||
logger.error(e)
|
||||
logger.info("Trakt Connection {}".format("Failed" if self.Trakt is None else "Successful"))
|
||||
logger.info(f"Trakt Connection {'Failed' if self.Trakt is None else 'Successful'}")
|
||||
else:
|
||||
logger.warning("trakt attribute not found")
|
||||
|
||||
|
@ -200,7 +200,7 @@ class Config:
|
|||
self.MyAnimeList = MyAnimeListAPI(self.mal, self.MyAnimeListIDList, authorization)
|
||||
except Failed as e:
|
||||
logger.error(e)
|
||||
logger.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful"))
|
||||
logger.info(f"My Anime List Connection {'Failed' if self.MyAnimeList is None else 'Successful'}")
|
||||
else:
|
||||
logger.warning("mal attribute not found")
|
||||
|
||||
|
@ -249,10 +249,10 @@ class Config:
|
|||
params = {}
|
||||
if "library_name" in libs[lib] and libs[lib]["library_name"]:
|
||||
params["name"] = str(libs[lib]["library_name"])
|
||||
logger.info("Connecting to {} ({}) Library...".format(params["name"], lib))
|
||||
logger.info(f"Connecting to {params['name']} ({lib}) Library...")
|
||||
else:
|
||||
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)
|
||||
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)
|
||||
|
||||
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["plex"] = {}
|
||||
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"]["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)
|
||||
logger.info("{} Library Connection Successful".format(params["name"]))
|
||||
logger.info(f"{params['name']} Library Connection Successful")
|
||||
except Failed as e:
|
||||
util.print_multiline(e)
|
||||
logger.info("{} Library Connection Failed".format(params["name"]))
|
||||
logger.info(f"{params['name']} Library Connection Failed")
|
||||
continue
|
||||
|
||||
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 = {}
|
||||
try:
|
||||
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))
|
||||
except Failed as 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]:
|
||||
logger.info("Connecting to {} library's Sonarr...".format(params["name"]))
|
||||
logger.info(f"Connecting to {params['name']} library's Sonarr...")
|
||||
sonarr_params = {}
|
||||
try:
|
||||
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))
|
||||
except Failed as 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]:
|
||||
logger.info("Connecting to {} library's Tautulli...".format(params["name"]))
|
||||
logger.info(f"Connecting to {params['name']} library's Tautulli...")
|
||||
tautulli_params = {}
|
||||
try:
|
||||
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))
|
||||
except Failed as 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)
|
||||
|
||||
util.separator()
|
||||
|
||||
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:
|
||||
raise Failed("Plex Error: No Plex libraries were found")
|
||||
|
||||
|
@ -338,15 +338,15 @@ class Config:
|
|||
for library in self.libraries:
|
||||
os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout)
|
||||
logger.info("")
|
||||
util.separator("{} Library".format(library.name))
|
||||
util.separator(f"{library.name} Library")
|
||||
try: library.update_metadata(self.TMDb, test)
|
||||
except Failed as e: logger.error(e)
|
||||
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
|
||||
if collections:
|
||||
logger.info("")
|
||||
util.separator("Mapping {} Library".format(library.name))
|
||||
util.separator(f"Mapping {library.name} Library")
|
||||
logger.info("")
|
||||
movie_map, show_map = self.map_guids(library)
|
||||
for c in collections:
|
||||
|
@ -366,7 +366,7 @@ class Config:
|
|||
continue
|
||||
try:
|
||||
logger.info("")
|
||||
util.separator("{} Collection".format(c))
|
||||
util.separator(f"{c} Collection")
|
||||
logger.info("")
|
||||
|
||||
rating_key_map = {}
|
||||
|
@ -399,7 +399,7 @@ class Config:
|
|||
for i, f in enumerate(builder.filters):
|
||||
if i == 0:
|
||||
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)
|
||||
|
||||
|
@ -413,10 +413,10 @@ class Config:
|
|||
|
||||
except Exception as e:
|
||||
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:
|
||||
logger.info("")
|
||||
util.separator("Unmanaged Collections in {} Library".format(library.name))
|
||||
util.separator(f"Unmanaged Collections in {library.name} Library")
|
||||
logger.info("")
|
||||
unmanaged_count = 0
|
||||
collections_in_plex = [str(plex_col) for plex_col in collections]
|
||||
|
@ -433,15 +433,15 @@ class Config:
|
|||
movie_map = {}
|
||||
show_map = {}
|
||||
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()
|
||||
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:
|
||||
id_type, main_id = self.get_id(item, library, length)
|
||||
except BadRequest:
|
||||
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
|
||||
if isinstance(main_id, list):
|
||||
if id_type == "movie":
|
||||
|
@ -451,7 +451,7 @@ class Config:
|
|||
else:
|
||||
if id_type == "movie": movie_map[main_id] = item.ratingKey
|
||||
elif id_type == "show": show_map[main_id] = item.ratingKey
|
||||
util.print_end(length, "Processed {} {}".format(len(items), "Movies" if library.is_movie else "Shows"))
|
||||
util.print_end(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}")
|
||||
return movie_map, show_map
|
||||
|
||||
def get_id(self, item, library, length):
|
||||
|
@ -484,10 +484,10 @@ class Config:
|
|||
elif item_type == "hama":
|
||||
if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1))
|
||||
elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1)
|
||||
else: error_message = "Hama Agent ID: {} not supported".format(check_id)
|
||||
else: error_message = f"Hama Agent ID: {check_id} not supported"
|
||||
elif item_type == "myanimelist": mal_id = check_id
|
||||
elif item_type == "local": error_message = "No match in Plex"
|
||||
else: error_message = "Agent {} not supported".format(item_type)
|
||||
else: error_message = f"Agent {item_type} not supported"
|
||||
|
||||
if not error_message:
|
||||
if anidb_id and not tvdb_id:
|
||||
|
@ -501,7 +501,7 @@ class Config:
|
|||
ids = self.MyAnimeListIDList.find_mal_ids(mal_id)
|
||||
if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: tvdb_id = int(ids["thetvdb_id"])
|
||||
elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: tmdb_id = int(ids["themoviedb_id"])
|
||||
else: raise Failed("MyAnimeList Error: MyAnimeList ID: {} has no other IDs associated with it".format(mal_id))
|
||||
else: raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} has no other IDs associated with it")
|
||||
except Failed:
|
||||
pass
|
||||
if mal_id and not tvdb_id:
|
||||
|
@ -560,8 +560,8 @@ class Config:
|
|||
elif self.Trakt: api_name = "Trakt"
|
||||
else: api_name = None
|
||||
|
||||
if tmdb_id and imdb_id: id_name = "TMDb ID: {} or IMDb ID: {}".format(tmdb_id, imdb_id)
|
||||
elif imdb_id and tvdb_id: id_name = "IMDb ID: {} or TVDb ID: {}".format(imdb_id, tvdb_id)
|
||||
if tmdb_id and imdb_id: id_name = f"TMDb ID: {tmdb_id} or IMDb ID: {imdb_id}"
|
||||
elif imdb_id and tvdb_id: id_name = f"IMDb ID: {imdb_id} or TVDb ID: {tvdb_id}"
|
||||
elif tmdb_id: id_name = "TMDb ID: {}".format(tmdb_id)
|
||||
elif imdb_id: id_name = "IMDb ID: {}".format(imdb_id)
|
||||
elif tvdb_id: id_name = "TVDb ID: {}".format(tvdb_id)
|
||||
|
@ -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 isinstance(tmdb_id, list):
|
||||
for i in range(len(tmdb_id)):
|
||||
util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id[i] if tmdb_id[i] else "None", imdb_id[i] if imdb_id[i] else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title))
|
||||
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)
|
||||
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))
|
||||
|
|
|
@ -18,21 +18,21 @@ class IMDbAPI:
|
|||
def get_imdb_ids_from_url(self, imdb_url, language, limit):
|
||||
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/?"):
|
||||
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"):
|
||||
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))
|
||||
current_url = "https://www.imdb.com/search/title/?lists=ls{}".format(list_id)
|
||||
except AttributeError: raise Failed(f"IMDb Error: Failed to parse List ID from {imdb_url}")
|
||||
current_url = f"https://www.imdb.com/search/title/?lists=ls{list_id}"
|
||||
else:
|
||||
current_url = imdb_url
|
||||
header = {"Accept-Language": language}
|
||||
length = 0
|
||||
imdb_ids = []
|
||||
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])
|
||||
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 "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url)
|
||||
if limit < 1 or total < limit: limit = total
|
||||
|
@ -41,12 +41,12 @@ class IMDbAPI:
|
|||
num_of_pages = math.ceil(int(limit) / 250)
|
||||
for i in range(1, num_of_pages + 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))
|
||||
response = self.send_request("{}&count={}&start={}".format(current_url, remainder if i == num_of_pages else 250, start_num), header)
|
||||
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(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"))
|
||||
util.print_end(length)
|
||||
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)
|
||||
def send_request(self, url, header):
|
||||
|
@ -55,34 +55,35 @@ class IMDbAPI:
|
|||
def get_items(self, method, data, language, status_message=True):
|
||||
pretty = util.pretty_names[method] if method in util.pretty_names else method
|
||||
if status_message:
|
||||
logger.debug("Data: {}".format(data))
|
||||
logger.debug(f"Data: {data}")
|
||||
show_ids = []
|
||||
movie_ids = []
|
||||
if method == "imdb_id":
|
||||
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)
|
||||
if tmdb_id: movie_ids.append(tmdb_id)
|
||||
if tvdb_id: show_ids.append(tvdb_id)
|
||||
elif method == "imdb_list":
|
||||
if status_message:
|
||||
logger.info("Processing {}: {}".format(pretty, "{} Items at {}".format(data["limit"], data["url"]) if data["limit"] > 0 else data["url"]))
|
||||
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"])
|
||||
total_ids = len(imdb_ids)
|
||||
length = 0
|
||||
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:
|
||||
tmdb_id, tvdb_id = self.convert_from_imdb(imdb_id, language)
|
||||
if tmdb_id: movie_ids.append(tmdb_id)
|
||||
if tvdb_id: show_ids.append(tvdb_id)
|
||||
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:
|
||||
raise Failed("IMDb Error: Method {} not supported".format(method))
|
||||
raise Failed(f"IMDb Error: Method {method} not supported")
|
||||
if status_message:
|
||||
logger.debug("TMDb IDs Found: {}".format(movie_ids))
|
||||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
logger.debug(f"TMDb IDs Found: {movie_ids}")
|
||||
logger.debug(f"TVDb IDs Found: {show_ids}")
|
||||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id, language):
|
||||
|
@ -123,7 +124,7 @@ class IMDbAPI:
|
|||
try:
|
||||
if tvdb_id and not from_cache: self.TVDb.get_series(language, tvdb_id=tvdb_id)
|
||||
except Failed: tvdb_id = None
|
||||
if not tmdb_id and not tvdb_id: raise Failed("IMDb Error: No TMDb ID or TVDb ID found for IMDb: {}".format(imdb_id))
|
||||
if not tmdb_id and not tvdb_id: raise Failed(f"IMDb Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}")
|
||||
if self.Cache:
|
||||
if tmdb_id and update_tmdb is not False:
|
||||
self.Cache.update_imdb("movie", update_tmdb, imdb_id, tmdb_id)
|
||||
|
|
|
@ -18,13 +18,13 @@ class MyAnimeListIDList:
|
|||
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:
|
||||
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):
|
||||
for mal in self.ids:
|
||||
if "mal_id" in mal and int(mal["mal_id"]) == int(mal_id):
|
||||
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:
|
||||
def __init__(self, params, MyAnimeListIDList_in, authorization=None):
|
||||
|
@ -47,9 +47,9 @@ class MyAnimeListAPI:
|
|||
|
||||
def get_authorization(self):
|
||||
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("Navigate to: {}".format(url))
|
||||
logger.info(f"Navigate to: {url}")
|
||||
logger.info("")
|
||||
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")
|
||||
|
@ -106,7 +106,7 @@ class MyAnimeListAPI:
|
|||
"expires_in": authorization["expires_in"],
|
||||
"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)
|
||||
self.authorization = authorization
|
||||
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)
|
||||
def send_request(self, url, authorization=None):
|
||||
new_authorization = authorization if authorization else self.authorization
|
||||
response = requests.get(url, headers={"Authorization": "Bearer {}".format(new_authorization["access_token"])}).json()
|
||||
if "error" in response: raise Failed("MyAnimeList Error: {}".format(response["error"]))
|
||||
response = requests.get(url, headers={"Authorization": f"Bearer {new_authorization['access_token']}"}).json()
|
||||
if "error" in response: raise Failed(f"MyAnimeList Error: {response['error']}")
|
||||
else: return response
|
||||
|
||||
def parse_mal_ids(self, data):
|
||||
|
@ -131,50 +131,51 @@ class MyAnimeListAPI:
|
|||
return mal_ids
|
||||
|
||||
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):
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
def get_items(self, method, data, status_message=True):
|
||||
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
|
||||
if method == "mal_id":
|
||||
mal_ids = [data]
|
||||
if status_message:
|
||||
logger.info("Processing {}: {}".format(pretty, data))
|
||||
logger.info(f"Processing {pretty}: {data}")
|
||||
elif method in util.mal_ranked_name:
|
||||
mal_ids = self.get_ranked(util.mal_ranked_name[method], data)
|
||||
if status_message:
|
||||
logger.info("Processing {}: {} Anime".format(pretty, data))
|
||||
logger.info(f"Processing {pretty}: {data} Anime")
|
||||
elif method == "mal_season":
|
||||
mal_ids = self.get_season(data["season"], data["year"], data["sort_by"], data["limit"])
|
||||
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":
|
||||
mal_ids = self.get_suggestions(data)
|
||||
if status_message:
|
||||
logger.info("Processing {}: {} Anime".format(pretty, data))
|
||||
logger.info(f"Processing {pretty}: {data} Anime")
|
||||
elif method == "mal_userlist":
|
||||
mal_ids = self.get_userlist(data["username"], data["status"], data["sort_by"], data["limit"])
|
||||
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:
|
||||
raise Failed("MyAnimeList Error: Method {} not supported".format(method))
|
||||
raise Failed(f"MyAnimeList Error: Method {method} not supported")
|
||||
show_ids = []
|
||||
movie_ids = []
|
||||
for mal_id in mal_ids:
|
||||
|
@ -182,12 +183,12 @@ class MyAnimeListAPI:
|
|||
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"]))
|
||||
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:
|
||||
if status_message:
|
||||
logger.error(e)
|
||||
if status_message:
|
||||
logger.debug("MyAnimeList IDs Found: {}".format(mal_ids))
|
||||
logger.debug("Shows Found: {}".format(show_ids))
|
||||
logger.debug("Movies Found: {}".format(movie_ids))
|
||||
logger.debug(f"MyAnimeList IDs Found: {mal_ids}")
|
||||
logger.debug(f"Shows Found: {show_ids}")
|
||||
logger.debug(f"Movies Found: {movie_ids}")
|
||||
return movie_ids, show_ids
|
||||
|
|
104
modules/plex.py
104
modules/plex.py
|
@ -15,23 +15,23 @@ class PlexAPI:
|
|||
def __init__(self, params, TMDb, TVDb):
|
||||
try: self.PlexServer = PlexServer(params["plex"]["url"], params["plex"]["token"], timeout=params["plex"]["timeout"])
|
||||
except Unauthorized: raise Failed("Plex Error: Plex token is invalid")
|
||||
except ValueError as e: raise Failed("Plex Error: {}".format(e))
|
||||
except ValueError as e: raise Failed(f"Plex Error: {e}")
|
||||
except requests.exceptions.ConnectionError:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Plex Error: Plex url is invalid")
|
||||
self.is_movie = params["library_type"] == "movie"
|
||||
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)
|
||||
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"))
|
||||
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):
|
||||
if attribute in self.data:
|
||||
if 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("Config Warning: {} attribute is blank".format(attribute))
|
||||
else: logger.warning(f"Config Warning: {attribute} must be a dictionary")
|
||||
else: logger.warning(f"Config Warning: {attribute} attribute is blank")
|
||||
return None
|
||||
|
||||
self.metadata = get_dict("metadata")
|
||||
|
@ -43,7 +43,7 @@ class PlexAPI:
|
|||
|
||||
if 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.TVDb = TVDb
|
||||
|
@ -51,7 +51,7 @@ class PlexAPI:
|
|||
self.Sonarr = None
|
||||
self.Tautulli = None
|
||||
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.asset_directory = params["asset_directory"]
|
||||
self.sync_mode = params["sync_mode"]
|
||||
|
@ -93,7 +93,7 @@ class PlexAPI:
|
|||
def get_collection(self, data):
|
||||
collection = util.choose_from_list(self.search(str(data), libtype="collection"), "collection", str(data), exact=True)
|
||||
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):
|
||||
valid_collections = []
|
||||
|
@ -101,7 +101,7 @@ class PlexAPI:
|
|||
try: valid_collections.append(self.get_collection(collection))
|
||||
except Failed as e: logger.error(e)
|
||||
if len(valid_collections) == 0:
|
||||
raise Failed("Collection Error: No valid Plex Collections in {}".format(collections))
|
||||
raise Failed(f"Collection Error: No valid Plex Collections in {collections}")
|
||||
return valid_collections
|
||||
|
||||
def add_missing(self, collection, items, is_movie):
|
||||
|
@ -117,7 +117,7 @@ class PlexAPI:
|
|||
try:
|
||||
yaml.round_trip_dump(self.missing, open(self.missing_path, "w"))
|
||||
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):
|
||||
name = collection.title if isinstance(collection, Collections) else collection
|
||||
|
@ -129,11 +129,11 @@ class PlexAPI:
|
|||
try:
|
||||
current = self.fetchItem(item.ratingKey if isinstance(item, (Movie, Show)) else int(item))
|
||||
except (BadRequest, NotFound):
|
||||
logger.error("Plex Error: Item {} not found".format(item))
|
||||
logger.error(f"Plex Error: Item {item} not found")
|
||||
continue
|
||||
match = True
|
||||
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:
|
||||
modifier = f[0][-4:]
|
||||
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:
|
||||
pass
|
||||
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
|
||||
if (modifier == ".not" and movie.original_language in terms) or (modifier != ".not" and movie.original_language not in terms):
|
||||
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"):
|
||||
match = False
|
||||
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:
|
||||
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
|
||||
else: current.addCollection(name)
|
||||
elif show_filtered is True:
|
||||
logger.info("{} Collection | X | {}".format(name, current.title))
|
||||
media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "")
|
||||
util.print_end(length, "{} {} Processed".format(total, media_type))
|
||||
logger.info(f"{name} Collection | X | {current.title}")
|
||||
media_type = f"{'Movie' if self.is_movie else 'Show'}{'s' if total > 1 else ''}"
|
||||
util.print_end(length, f"{total} {media_type} Processed")
|
||||
return rating_key_map
|
||||
|
||||
def search_item(self, data, year=None):
|
||||
|
@ -202,7 +202,7 @@ class PlexAPI:
|
|||
|
||||
def update_metadata(self, TMDb, test):
|
||||
logger.info("")
|
||||
util.separator("{} Library Metadata".format(self.name))
|
||||
util.separator(f"{self.name} Library Metadata")
|
||||
logger.info("")
|
||||
if not self.metadata:
|
||||
raise Failed("No metadata to edit")
|
||||
|
@ -217,7 +217,7 @@ class PlexAPI:
|
|||
now = datetime.now()
|
||||
if self.metadata[m]["year"] is None: logger.error("Metadata Error: year attribute is blank")
|
||||
elif not isinstance(self.metadata[m]["year"], int): logger.error("Metadata Error: year attribute must be an integer")
|
||||
elif self.metadata[m]["year"] not in range(1800, now.year + 2): logger.error("Metadata Error: year attribute must be between 1800-{}".format(now.year + 1))
|
||||
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"]
|
||||
|
||||
title = m
|
||||
|
@ -228,7 +228,7 @@ class PlexAPI:
|
|||
item = self.search_item(title, year=year)
|
||||
|
||||
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 self.metadata[m]["alt_title"] is None:
|
||||
|
@ -238,11 +238,12 @@ class PlexAPI:
|
|||
item = self.search_item(alt_title, year=year)
|
||||
|
||||
if item is None:
|
||||
logger.error("Plex Error: Item {} not found".format(m))
|
||||
logger.error("Skipping {}".format(m))
|
||||
logger.error(f"Plex Error: Item {m} not found")
|
||||
logger.error(f"Skipping {m}")
|
||||
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
|
||||
try:
|
||||
|
@ -267,10 +268,10 @@ class PlexAPI:
|
|||
if key is None: key = name
|
||||
if value is None: value = group[name]
|
||||
if str(current) != str(value):
|
||||
edits["{}.value".format(key)] = value
|
||||
edits["{}.locked".format(key)] = 1
|
||||
edits[f"{key}.value"] = value
|
||||
edits[f"{key}.locked"] = 1
|
||||
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("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)
|
||||
|
@ -283,16 +284,16 @@ class PlexAPI:
|
|||
add_edit("tagline", item_tagline, self.metadata[m], value=tagline)
|
||||
add_edit("summary", item.summary, self.metadata[m], value=summary)
|
||||
if len(edits) > 0:
|
||||
logger.debug("Details Update: {}".format(edits))
|
||||
logger.debug(f"Details Update: {edits}")
|
||||
try:
|
||||
item.edit(**edits)
|
||||
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:
|
||||
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:
|
||||
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 = []
|
||||
|
||||
|
@ -311,10 +312,10 @@ class PlexAPI:
|
|||
elif self.metadata[m]["genre_sync_mode"] == "sync":
|
||||
for genre in (g for g in item_genres if g not in genres):
|
||||
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):
|
||||
item.addGenre(genre)
|
||||
logger.info("Detail: Genre {} added".format(genre))
|
||||
logger.info(f"Detail: Genre {genre} added")
|
||||
|
||||
if "label" in self.metadata[m]:
|
||||
if self.metadata[m]["label"]:
|
||||
|
@ -326,10 +327,10 @@ class PlexAPI:
|
|||
elif self.metadata[m]["label_sync_mode"] == "sync":
|
||||
for label in (la for la in item_labels if la not in labels):
|
||||
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):
|
||||
item.addLabel(label)
|
||||
logger.info("Detail: Label {} added".format(label))
|
||||
logger.info(f"Detail: Label {label} added")
|
||||
else:
|
||||
logger.error("Metadata Error: label attribute is blank")
|
||||
|
||||
|
@ -337,10 +338,10 @@ class PlexAPI:
|
|||
if self.metadata[m]["seasons"]:
|
||||
for season_id in self.metadata[m]["seasons"]:
|
||||
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):
|
||||
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:
|
||||
|
||||
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:
|
||||
logger.error("Metadata Error: sub attribute is blank")
|
||||
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)"):
|
||||
title = title[:-6]
|
||||
else:
|
||||
|
@ -361,18 +362,18 @@ class PlexAPI:
|
|||
add_edit("title", season.title, self.metadata[m]["seasons"][season_id], value=title)
|
||||
add_edit("summary", season.summary, self.metadata[m]["seasons"][season_id])
|
||||
if len(edits) > 0:
|
||||
logger.debug("Season: {} Details Update: {}".format(season_id, edits))
|
||||
logger.debug(f"Season: {season_id} Details Update: {edits}")
|
||||
try:
|
||||
season.edit(**edits)
|
||||
season.reload()
|
||||
logger.info("Season: {} Details Update Successful".format(season_id))
|
||||
logger.info(f"Season: {season_id} Details Update Successful")
|
||||
except BadRequest:
|
||||
util.print_stacktrace()
|
||||
logger.error("Season: {} Details Update Failed".format(season_id))
|
||||
logger.error(f"Season: {season_id} Details Update Failed")
|
||||
else:
|
||||
logger.info("Season: {} Details Update Not Needed".format(season_id))
|
||||
logger.info(f"Season: {season_id} Details Update Not Needed")
|
||||
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:
|
||||
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")
|
||||
episode_id = int(output[0])
|
||||
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)
|
||||
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:
|
||||
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"]
|
||||
|
@ -397,7 +398,7 @@ class PlexAPI:
|
|||
if self.metadata[m]["episodes"][episode_str]["sub"] is None:
|
||||
logger.error("Metadata Error: sub attribute is blank")
|
||||
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)"):
|
||||
title = title[:-6]
|
||||
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("summary", episode.summary, self.metadata[m]["episodes"][episode_str])
|
||||
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:
|
||||
episode.edit(**edits)
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
logger.error("Metadata Error: episodes attribute is blank")
|
||||
|
|
|
@ -7,27 +7,27 @@ logger = logging.getLogger("Plex Meta Manager")
|
|||
|
||||
class RadarrAPI:
|
||||
def __init__(self, tmdb, params):
|
||||
self.url_params = {"apikey": "{}".format(params["token"])}
|
||||
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/")
|
||||
self.url_params = {"apikey": f"{params['token']}"}
|
||||
self.base_url = f"{params['url']}/api{'/v3' if params['version'] == 'v3' else ''}/"
|
||||
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:
|
||||
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":
|
||||
raise Failed("Radarr Error: Invalid API Key")
|
||||
if "version" not in result:
|
||||
raise Failed("Radarr Error: Unexpected Response Check URL")
|
||||
self.quality_profile_id = None
|
||||
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:
|
||||
profiles += ", "
|
||||
profiles += profile["name"]
|
||||
if profile["name"] == params["quality_profile"]:
|
||||
self.quality_profile_id = 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.url = params["url"]
|
||||
self.version = params["version"]
|
||||
|
@ -39,7 +39,7 @@ class RadarrAPI:
|
|||
|
||||
def add_tmdb(self, tmdb_ids, tag=None):
|
||||
logger.info("")
|
||||
logger.debug("TMDb IDs: {}".format(tmdb_ids))
|
||||
logger.debug(f"TMDb IDs: {tmdb_ids}")
|
||||
tag_nums = []
|
||||
add_count = 0
|
||||
if tag is None:
|
||||
|
@ -47,8 +47,8 @@ class RadarrAPI:
|
|||
if tag:
|
||||
tag_cache = {}
|
||||
for label in tag:
|
||||
self.send_post("{}tag".format(self.base_url), {"label": str(label)})
|
||||
for t in self.send_get("{}tag".format(self.base_url)):
|
||||
self.send_post(f"{self.base_url}tag", {"label": str(label)})
|
||||
for t in self.send_get(f"{self.base_url}tag"):
|
||||
tag_cache[t["label"]] = t["id"]
|
||||
for label in tag:
|
||||
if label in tag_cache:
|
||||
|
@ -63,20 +63,20 @@ class RadarrAPI:
|
|||
try:
|
||||
year = movie.release_date.split("-")[0]
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 = {
|
||||
"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),
|
||||
"tmdbid": int(tmdb_id),
|
||||
"titleslug": titleslug,
|
||||
|
@ -87,17 +87,17 @@ class RadarrAPI:
|
|||
}
|
||||
if 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:
|
||||
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
|
||||
else:
|
||||
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:
|
||||
logger.debug(url_json)
|
||||
logger.error("Radarr Error: {}".format(response.json()))
|
||||
logger.info("{} Movie{} added to Radarr".format(add_count, "s" if add_count > 1 else ""))
|
||||
logger.error(f"Radarr Error: {response.json()}")
|
||||
logger.info(f"{add_count} Movie{'s' if add_count > 1 else ''} added to Radarr")
|
||||
|
||||
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||
def send_get(self, url):
|
||||
|
|
|
@ -7,27 +7,27 @@ logger = logging.getLogger("Plex Meta Manager")
|
|||
|
||||
class SonarrAPI:
|
||||
def __init__(self, tvdb, params, language):
|
||||
self.url_params = {"apikey": "{}".format(params["token"])}
|
||||
self.base_url = "{}/api{}".format(params["url"], "/v3/" if params["version"] == "v3" else "/")
|
||||
self.url_params = {"apikey": f"{params['token']}"}
|
||||
self.base_url = f"{params['url']}/api{'/v3/' if params['version'] == 'v3' else '/'}"
|
||||
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:
|
||||
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":
|
||||
raise Failed("Sonarr Error: Invalid API Key")
|
||||
if "version" not in result:
|
||||
raise Failed("Sonarr Error: Unexpected Response Check URL")
|
||||
self.quality_profile_id = None
|
||||
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:
|
||||
profiles += ", "
|
||||
profiles += profile["name"]
|
||||
if profile["name"] == params["quality_profile"]:
|
||||
self.quality_profile_id = 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.language = language
|
||||
self.url = params["url"]
|
||||
|
@ -40,7 +40,7 @@ class SonarrAPI:
|
|||
|
||||
def add_tvdb(self, tvdb_ids, tag=None):
|
||||
logger.info("")
|
||||
logger.debug("TVDb IDs: {}".format(tvdb_ids))
|
||||
logger.debug(f"TVDb IDs: {tvdb_ids}")
|
||||
tag_nums = []
|
||||
add_count = 0
|
||||
if tag is None:
|
||||
|
@ -48,8 +48,8 @@ class SonarrAPI:
|
|||
if tag:
|
||||
tag_cache = {}
|
||||
for label in tag:
|
||||
self.send_post("{}tag".format(self.base_url), {"label": str(label)})
|
||||
for t in self.send_get("{}tag".format(self.base_url)):
|
||||
self.send_post(f"{self.base_url}tag", {"label": str(label)})
|
||||
for t in self.send_get(f"{self.base_url}tag"):
|
||||
tag_cache[t["label"]] = t["id"]
|
||||
for label in tag:
|
||||
if label in tag_cache:
|
||||
|
@ -65,7 +65,7 @@ class SonarrAPI:
|
|||
|
||||
url_json = {
|
||||
"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,
|
||||
"tvdbId": int(tvdb_id),
|
||||
"titleslug": titleslug,
|
||||
|
@ -78,17 +78,17 @@ class SonarrAPI:
|
|||
}
|
||||
if 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:
|
||||
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
|
||||
else:
|
||||
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:
|
||||
logger.debug(url_json)
|
||||
logger.error("Sonarr Error: {}".format(response.json()))
|
||||
logger.info("{} Show{} added to Sonarr".format(add_count, "s" if add_count > 1 else ""))
|
||||
logger.error(f"Sonarr Error: {response.json()}")
|
||||
logger.info(f"{add_count} Show{'s' if add_count > 1 else ''} added to Sonarr")
|
||||
|
||||
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||
def send_get(self, url):
|
||||
|
|
|
@ -8,12 +8,12 @@ logger = logging.getLogger("Plex Meta Manager")
|
|||
class TautulliAPI:
|
||||
def __init__(self, params):
|
||||
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:
|
||||
util.print_stacktrace()
|
||||
raise Failed("Tautulli Error: Invalid url")
|
||||
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.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):
|
||||
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"))
|
||||
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)))
|
||||
stat_id = "{}_{}".format("popular" if list_type == "popular" else "top", "movies" if library.is_movie else "tv")
|
||||
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(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 = f"{'popular' if list_type == 'popular' else 'top'}_{'movies' if library.is_movie else 'tv'}"
|
||||
|
||||
items = None
|
||||
for entry in response["response"]["data"]:
|
||||
|
@ -47,16 +47,16 @@ class TautulliAPI:
|
|||
return rating_keys
|
||||
|
||||
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
|
||||
for entry in response["response"]["data"]:
|
||||
if entry["section_name"] == library_name:
|
||||
section_id = entry["section_id"]
|
||||
break
|
||||
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)
|
||||
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()
|
||||
|
|
|
@ -32,56 +32,56 @@ def anidb_tests(config):
|
|||
logger.info("Success | Convert AniDB to TVDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert AniDB to TVDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert AniDB to TVDb: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.convert_anidb_to_imdb(112)
|
||||
logger.info("Success | Convert AniDB to IMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert AniDB to IMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert AniDB to IMDb: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.convert_tvdb_to_anidb(81797)
|
||||
logger.info("Success | Convert TVDb to AniDB")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TVDb to AniDB: {}".format(e))
|
||||
logger.error(f"Failure | Convert TVDb to AniDB: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.convert_imdb_to_anidb("tt0245429")
|
||||
logger.info("Success | Convert IMDb to AniDB")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert IMDb to AniDB: {}".format(e))
|
||||
logger.error(f"Failure | Convert IMDb to AniDB: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.get_items("anidb_id", 69, "en", status_message=False)
|
||||
logger.info("Success | Get AniDB ID")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get AniDB ID: {}".format(e))
|
||||
logger.error(f"Failure | Get AniDB ID: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.get_items("anidb_relation", 69, "en", status_message=False)
|
||||
logger.info("Success | Get AniDB Relation")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get AniDB Relation: {}".format(e))
|
||||
logger.error(f"Failure | Get AniDB Relation: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.get_items("anidb_popular", 30, "en", status_message=False)
|
||||
logger.info("Success | Get AniDB Popular")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get AniDB Popular: {}".format(e))
|
||||
logger.error(f"Failure | Get AniDB Popular: {e}")
|
||||
|
||||
try:
|
||||
config.AniDB.validate_anidb_list(["69", "112"], "en")
|
||||
logger.info("Success | Validate AniDB List")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Validate AniDB List: {}".format(e))
|
||||
logger.error(f"Failure | Validate AniDB List: {e}")
|
||||
|
||||
else:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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:
|
||||
util.separator("IMDb Not Configured")
|
||||
|
@ -114,35 +114,35 @@ def mal_tests(config):
|
|||
logger.info("Success | Convert MyAnimeList to TVDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert MyAnimeList to TVDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert MyAnimeList to TVDb: {e}")
|
||||
|
||||
try:
|
||||
config.MyAnimeListIDList.convert_mal_to_tmdb(199)
|
||||
logger.info("Success | Convert MyAnimeList to TMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert MyAnimeList to TMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert MyAnimeList to TMDb: {e}")
|
||||
|
||||
try:
|
||||
config.MyAnimeListIDList.convert_tvdb_to_mal(81797)
|
||||
logger.info("Success | Convert TVDb to MyAnimeList")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TVDb to MyAnimeList: {}".format(e))
|
||||
logger.error(f"Failure | Convert TVDb to MyAnimeList: {e}")
|
||||
|
||||
try:
|
||||
config.MyAnimeListIDList.convert_tmdb_to_mal(129)
|
||||
logger.info("Success | Convert TMDb to MyAnimeList")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TMDb to MyAnimeList: {}".format(e))
|
||||
logger.error(f"Failure | Convert TMDb to MyAnimeList: {e}")
|
||||
|
||||
try:
|
||||
config.MyAnimeListIDList.find_mal_ids(21)
|
||||
logger.info("Success | Find MyAnimeList ID")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Find MyAnimeList ID: {}".format(e))
|
||||
logger.error(f"Failure | Find MyAnimeList ID: {e}")
|
||||
|
||||
else:
|
||||
util.separator("MyAnimeListXML Not Configured")
|
||||
|
@ -168,10 +168,10 @@ def mal_tests(config):
|
|||
for mal_list_test in mal_list_tests:
|
||||
try:
|
||||
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:
|
||||
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:
|
||||
util.separator("MyAnimeList Not Configured")
|
||||
|
||||
|
@ -184,21 +184,21 @@ def tautulli_tests(config):
|
|||
logger.info("Success | Get Section ID")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Section ID: {}".format(e))
|
||||
logger.error(f"Failure | Get Section ID: {e}")
|
||||
|
||||
try:
|
||||
config.libraries[0].Tautulli.get_popular(config.libraries[0], status_message=False)
|
||||
logger.info("Success | Get Popular")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Popular: {}".format(e))
|
||||
logger.error(f"Failure | Get Popular: {e}")
|
||||
|
||||
try:
|
||||
config.libraries[0].Tautulli.get_top(config.libraries[0], status_message=False)
|
||||
logger.info("Success | Get Top")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Top: {}".format(e))
|
||||
logger.error(f"Failure | Get Top: {e}")
|
||||
else:
|
||||
util.separator("Tautulli Not Configured")
|
||||
|
||||
|
@ -211,28 +211,28 @@ def tmdb_tests(config):
|
|||
logger.info("Success | Convert IMDb to TMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert IMDb to TMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert IMDb to TMDb: {e}")
|
||||
|
||||
try:
|
||||
config.TMDb.convert_tmdb_to_imdb(11)
|
||||
logger.info("Success | Convert TMDb to IMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TMDb to IMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TMDb to IMDb: {e}")
|
||||
|
||||
try:
|
||||
config.TMDb.convert_imdb_to_tvdb("tt0458290")
|
||||
logger.info("Success | Convert IMDb to TVDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert IMDb to TVDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert IMDb to TVDb: {e}")
|
||||
|
||||
try:
|
||||
config.TMDb.convert_tvdb_to_imdb(83268)
|
||||
logger.info("Success | Convert TVDb to IMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TVDb to IMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TVDb to IMDb: {e}")
|
||||
|
||||
tmdb_list_tests = [
|
||||
([11], "Movie"),
|
||||
|
@ -247,10 +247,10 @@ def tmdb_tests(config):
|
|||
for tmdb_list_test in tmdb_list_tests:
|
||||
try:
|
||||
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:
|
||||
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_discover", {"sort_by": "popularity.desc", "limit": 100}, True),
|
||||
|
@ -279,10 +279,10 @@ def tmdb_tests(config):
|
|||
for tmdb_list_test in tmdb_list_tests:
|
||||
try:
|
||||
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:
|
||||
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:
|
||||
util.separator("TMDb Not Configured")
|
||||
|
||||
|
@ -295,63 +295,63 @@ def trakt_tests(config):
|
|||
logger.info("Success | Convert IMDb to TMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert IMDb to TMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert IMDb to TMDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_tmdb_to_imdb(11)
|
||||
logger.info("Success | Convert TMDb to IMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TMDb to IMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TMDb to IMDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_imdb_to_tvdb("tt0458290")
|
||||
logger.info("Success | Convert IMDb to TVDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert IMDb to TVDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert IMDb to TVDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_tvdb_to_imdb(83268)
|
||||
logger.info("Success | Convert TVDb to IMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TVDb to IMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TVDb to IMDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_tmdb_to_tvdb(11)
|
||||
logger.info("Success | Convert TMDb to TVDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TMDb to TVDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TMDb to TVDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.convert_tvdb_to_tmdb(83268)
|
||||
logger.info("Success | Convert TVDb to TMDb")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Convert TVDb to TMDb: {}".format(e))
|
||||
logger.error(f"Failure | Convert TVDb to TMDb: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.validate_trakt_list(["https://trakt.tv/users/movistapp/lists/christmas-movies"])
|
||||
logger.info("Success | Get List")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get List: {}".format(e))
|
||||
logger.error(f"Failure | Get List: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.validate_trakt_watchlist(["me"], True)
|
||||
logger.info("Success | Get Watchlist Movies")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Watchlist Movies: {}".format(e))
|
||||
logger.error(f"Failure | Get Watchlist Movies: {e}")
|
||||
|
||||
try:
|
||||
config.Trakt.validate_trakt_watchlist(["me"], False)
|
||||
logger.info("Success | Get Watchlist Shows")
|
||||
except Failed as e:
|
||||
util.print_stacktrace()
|
||||
logger.error("Failure | Get Watchlist Shows: {}".format(e))
|
||||
logger.error(f"Failure | Get Watchlist Shows: {e}")
|
||||
|
||||
trakt_list_tests = [
|
||||
("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:
|
||||
try:
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
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:
|
||||
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")
|
||||
except Failed as e:
|
||||
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:
|
||||
config.TVDb.get_items("tvdb_show", 279121, "en", status_message=False)
|
||||
logger.info("Success | TVDb ID get TVDb Series ID")
|
||||
except Failed as e:
|
||||
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:
|
||||
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")
|
||||
except Failed as e:
|
||||
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:
|
||||
config.TVDb.get_items("tvdb_movie", 107, "en", status_message=False)
|
||||
logger.info("Success | TVDb ID get TVDb Movie ID")
|
||||
except Failed as e:
|
||||
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:
|
||||
util.separator("TVDb Not Configured")
|
||||
|
|
|
@ -13,7 +13,7 @@ class TMDbAPI:
|
|||
self.TMDb.language = params["language"]
|
||||
response = tmdbv3api.Configuration().info()
|
||||
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.language = params["language"]
|
||||
self.Movie = tmdbv3api.Movie()
|
||||
|
@ -33,17 +33,17 @@ class TMDbAPI:
|
|||
try:
|
||||
id_to_return = self.Movie.external_ids(tmdb_id)[convert_to] if is_movie else self.TV.external_ids(tmdb_id)[convert_to]
|
||||
if not id_to_return or (convert_to == "tvdb_id" and id_to_return == 0):
|
||||
raise Failed("TMDb Error: No {} found for TMDb ID {}".format(convert_to.upper().replace("B_", "b "), tmdb_id))
|
||||
raise Failed(f"TMDb Error: No {convert_to.upper().replace('B_', 'b ')} found for TMDb ID {tmdb_id}")
|
||||
return id_to_return
|
||||
except TMDbException:
|
||||
raise Failed("TMDb Error: {} TMDb ID: {} not found".format("Movie" if is_movie else "Show", tmdb_id))
|
||||
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)
|
||||
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 = search_results["movie_results" if is_movie else "tv_results"]
|
||||
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_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)
|
||||
except Failed:
|
||||
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)
|
||||
|
||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
|
||||
def get_movie(self, 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)
|
||||
def get_show(self, 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)
|
||||
def get_collection(self, 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)
|
||||
def get_person(self, 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)
|
||||
def get_company(self, 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)
|
||||
def get_network(self, 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)
|
||||
def get_keyword(self, 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)
|
||||
def get_list(self, tmdb_id):
|
||||
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):
|
||||
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_trending_daily": tmdb_items = self.Trending.movie_day(x + 1) if is_movie else self.Trending.tv_day(x + 1)
|
||||
elif method == "tmdb_trending_weekly": tmdb_items = self.Trending.movie_week(x + 1) if is_movie else self.Trending.tv_week(x + 1)
|
||||
else: raise Failed("TMDb Error: {} method not supported".format(method))
|
||||
else: raise Failed(f"TMDb Error: {method} method not supported")
|
||||
for tmdb_item in tmdb_items:
|
||||
try:
|
||||
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):
|
||||
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
|
||||
media_type = "Movie" if is_movie else "Show"
|
||||
movie_ids = []
|
||||
|
@ -170,16 +170,16 @@ class TMDbAPI:
|
|||
else: show_ids, amount = self.get_discover(attrs, limit, is_movie)
|
||||
if status_message:
|
||||
if method in ["tmdb_company", "tmdb_network", "tmdb_keyword"]:
|
||||
logger.info("Processing {}: ({}) {} ({} {}{})".format(pretty, tmdb_id, tmdb_name, amount, media_type, "" if amount == 1 else "s"))
|
||||
logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({amount} {media_type}{'' if amount == 1 else 's'})")
|
||||
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():
|
||||
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"]:
|
||||
if is_movie: movie_ids = self.get_pagenation(method, data, is_movie)
|
||||
else: show_ids = self.get_pagenation(method, data, is_movie)
|
||||
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:
|
||||
tmdb_id = int(data)
|
||||
if method == "tmdb_list":
|
||||
|
@ -204,14 +204,14 @@ class TMDbAPI:
|
|||
try: show_ids.append(self.convert_tmdb_to_tvdb(tmdb_id))
|
||||
except Failed: pass
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
logger.debug("TMDb IDs Found: {}".format(movie_ids))
|
||||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
logger.debug(f"TMDb IDs Found: {movie_ids}")
|
||||
logger.debug(f"TVDb IDs Found: {show_ids}")
|
||||
return movie_ids, show_ids
|
||||
|
||||
def validate_tmdb_list(self, tmdb_list, tmdb_type):
|
||||
|
@ -219,7 +219,7 @@ class TMDbAPI:
|
|||
for tmdb_id in tmdb_list:
|
||||
try: tmdb_values.append(self.validate_tmdb(tmdb_id, tmdb_type))
|
||||
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
|
||||
|
||||
def validate_tmdb(self, tmdb_id, tmdb_type):
|
||||
|
|
|
@ -30,7 +30,7 @@ class TraktAPI:
|
|||
|
||||
def get_authorization(self):
|
||||
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")
|
||||
webbrowser.open(url, new=2)
|
||||
try: pin = util.logger_input("Trakt pin (case insensitive)", timeout=300).strip()
|
||||
|
@ -70,7 +70,7 @@ class TraktAPI:
|
|||
"scope": authorization["scope"],
|
||||
"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)
|
||||
self.authorization = authorization
|
||||
Trakt.configuration.defaults.oauth.from_response(self.authorization)
|
||||
|
@ -91,7 +91,7 @@ class TraktAPI:
|
|||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
if 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)
|
||||
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)
|
||||
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")
|
||||
else: return [i for i in items]
|
||||
|
||||
|
@ -119,7 +119,7 @@ class TraktAPI:
|
|||
except Failed as e:
|
||||
logger.error(e)
|
||||
if len(trakt_values) == 0:
|
||||
raise Failed("Trakt Error: No valid Trakt Lists in {}".format(values))
|
||||
raise Failed(f"Trakt Error: No valid Trakt Lists in {values}")
|
||||
return trakt_values
|
||||
|
||||
def validate_trakt_watchlist(self, values, is_movie):
|
||||
|
@ -131,23 +131,23 @@ class TraktAPI:
|
|||
except Failed as e:
|
||||
logger.error(e)
|
||||
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
|
||||
|
||||
def get_items(self, method, data, is_movie, status_message=True):
|
||||
if status_message:
|
||||
logger.debug("Data: {}".format(data))
|
||||
logger.debug(f"Data: {data}")
|
||||
pretty = self.aliases[method] if method in self.aliases else method
|
||||
media_type = "Movie" if is_movie else "Show"
|
||||
if method == "trakt_trending":
|
||||
trakt_items = self.trending(int(data), is_movie)
|
||||
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:
|
||||
if method == "trakt_watchlist": trakt_items = self.watchlist(data, is_movie)
|
||||
elif method == "trakt_list": trakt_items = self.standard_list(data)
|
||||
else: raise Failed("Trakt Error: Method {} not supported".format(method))
|
||||
if status_message: logger.info("Processing {}: {}".format(pretty, data))
|
||||
else: raise Failed(f"Trakt Error: Method {method} not supported")
|
||||
if status_message: logger.info(f"Processing {pretty}: {data}")
|
||||
show_ids = []
|
||||
movie_ids = []
|
||||
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, (Season, Episode))) and trakt_item.show.pk[1] not in show_ids: show_ids.append(int(trakt_item.show.pk[1]))
|
||||
if status_message:
|
||||
logger.debug("Trakt {} Found: {}".format(media_type, trakt_items))
|
||||
logger.debug("TMDb IDs Found: {}".format(movie_ids))
|
||||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
logger.debug(f"Trakt {media_type} Found: {trakt_items}")
|
||||
logger.debug(f"TMDb IDs Found: {movie_ids}")
|
||||
logger.debug(f"TVDb IDs Found: {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)):
|
||||
self.media_type = "Movie"
|
||||
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)
|
||||
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:
|
||||
self.id = int(results[0])
|
||||
elif tvdb_url.startswith(TVDb.movie_id_url):
|
||||
raise Failed("TVDb Error: Could not find a TVDb Movie using TVDb Movie ID: {}".format(tvdb_url[len(TVDb.series_id_url):]))
|
||||
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):
|
||||
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:
|
||||
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")
|
||||
if len(results) > 0 and len(results[0]) > 0:
|
||||
self.title = results[0]
|
||||
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")
|
||||
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.site_url = "https://www.thetvdb.com"
|
||||
self.alt_site_url = "https://thetvdb.com"
|
||||
self.list_url = "{}/lists/".format(self.site_url)
|
||||
self.alt_list_url = "{}/lists/".format(self.alt_site_url)
|
||||
self.series_url = "{}/series/".format(self.site_url)
|
||||
self.alt_series_url = "{}/series/".format(self.alt_site_url)
|
||||
self.movies_url = "{}/movies/".format(self.site_url)
|
||||
self.alt_movies_url = "{}/movies/".format(self.alt_site_url)
|
||||
self.series_id_url = "{}/dereferrer/series/".format(self.site_url)
|
||||
self.movie_id_url = "{}/dereferrer/movie/".format(self.site_url)
|
||||
self.list_url = f"{self.site_url}/lists/"
|
||||
self.alt_list_url = f"{self.alt_site_url}/lists/"
|
||||
self.series_url = f"{self.site_url}/series/"
|
||||
self.alt_series_url = f"{self.alt_site_url}/series/"
|
||||
self.movies_url = f"{self.site_url}/movies/"
|
||||
self.alt_movies_url = f"{self.alt_site_url}/movies/"
|
||||
self.series_id_url = f"{self.site_url}/dereferrer/series/"
|
||||
self.movie_id_url = f"{self.site_url}/dereferrer/movie/"
|
||||
|
||||
def get_series(self, language, tvdb_url=None, tvdb_id=None):
|
||||
if not tvdb_url and not tvdb_id:
|
||||
raise Failed("TVDB Error: get_series requires either tvdb_url or tvdb_id")
|
||||
elif not tvdb_url and tvdb_id:
|
||||
tvdb_url = "{}{}".format(self.series_id_url, tvdb_id)
|
||||
tvdb_url = f"{self.series_id_url}{tvdb_id}"
|
||||
return TVDbObj(tvdb_url, language, False, self)
|
||||
|
||||
def get_movie(self, language, tvdb_url=None, tvdb_id=None):
|
||||
if not tvdb_url and not tvdb_id:
|
||||
raise Failed("TVDB Error: get_movie requires either tvdb_url or 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)
|
||||
|
||||
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]
|
||||
item_url = item.xpath(".//div[@class='col-xs-12 col-sm-9 mt-2']//a/@href")[0]
|
||||
if item_url.startswith("/series/"):
|
||||
try: show_ids.append(self.get_series(language, tvdb_url="{}{}".format(self.site_url, item_url)).id)
|
||||
except Failed as e: logger.error("{} for series {}".format(e, title))
|
||||
try: show_ids.append(self.get_series(language, tvdb_url=f"{self.site_url}{item_url}").id)
|
||||
except Failed as e: logger.error(f"{e} for series {title}")
|
||||
elif item_url.startswith("/movies/"):
|
||||
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)
|
||||
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:
|
||||
logger.error("{} for series {}".format(e, title))
|
||||
logger.error(f"{e} for series {title}")
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
def send_request(self, url, language):
|
||||
|
@ -123,7 +123,7 @@ class TVDbAPI:
|
|||
show_ids = []
|
||||
movie_ids = []
|
||||
if status_message:
|
||||
logger.info("Processing {}: {}".format(pretty, data))
|
||||
logger.info(f"Processing {pretty}: {data}")
|
||||
if method == "tvdb_show":
|
||||
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)
|
||||
|
@ -135,10 +135,10 @@ class TVDbAPI:
|
|||
movie_ids.extend(tmdb_ids)
|
||||
show_ids.extend(tvdb_ids)
|
||||
else:
|
||||
raise Failed("TVDb Error: Method {} not supported".format(method))
|
||||
raise Failed(f"TVDb Error: Method {method} not supported")
|
||||
if status_message:
|
||||
logger.debug("TMDb IDs Found: {}".format(movie_ids))
|
||||
logger.debug("TVDb IDs Found: {}".format(show_ids))
|
||||
logger.debug(f"TMDb IDs Found: {movie_ids}")
|
||||
logger.debug(f"TVDb IDs Found: {show_ids}")
|
||||
return movie_ids, show_ids
|
||||
|
||||
def convert_from_imdb(self, imdb_id):
|
||||
|
@ -162,7 +162,7 @@ class TVDbAPI:
|
|||
try:
|
||||
if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id)
|
||||
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:
|
||||
self.Cache.update_imdb("movie", update, imdb_id, tmdb_id)
|
||||
return tmdb_id
|
||||
|
|
|
@ -443,6 +443,9 @@ discover_tv_sort = [
|
|||
"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):
|
||||
display_title = str(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) == 1 and (description != "collection" or datalist[0].title == data):
|
||||
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):
|
||||
if list_type == "title":
|
||||
if d.title == data:
|
||||
return d
|
||||
message += "\n{}) {}".format(i, d.title)
|
||||
message += f"\n{i}) {d.title}"
|
||||
else:
|
||||
message += "\n{}) [{}] {}".format(i, d[0], d[1])
|
||||
message += f"\n{i}) [{d[0]}] {d[1]}"
|
||||
if exact:
|
||||
return None
|
||||
print_multiline(message, info=True)
|
||||
while True:
|
||||
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]
|
||||
elif selection == -1: return None
|
||||
else: logger.info("Invalid {} number".format(description))
|
||||
except IndexError: logger.info("Invalid {} number".format(description))
|
||||
else: logger.info(f"Invalid {description} number")
|
||||
except IndexError: logger.info(f"Invalid {description} number")
|
||||
except TimeoutExpired:
|
||||
if list_type == "title":
|
||||
logger.warning("Input Timeout: using {}".format(data))
|
||||
logger.warning(f"Input Timeout: using {data}")
|
||||
return None
|
||||
else:
|
||||
logger.warning("Input Timeout: using {}".format(datalist[0][1]))
|
||||
logger.warning(f"Input Timeout: using {datalist[0][1]}")
|
||||
return datalist[0][1]
|
||||
else:
|
||||
return None
|
||||
|
@ -516,22 +520,22 @@ def get_year_list(data, method):
|
|||
end = year_range.group(2)
|
||||
if end == "NOW":
|
||||
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))
|
||||
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(start) > int(end): logger.error("Collection Error: Skipping {} starting year {} cannot be greater then ending year {}".format(method, start, end))
|
||||
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(f"Collection Error: Skipping {method} ending year {end} must be between 1800 and {current_year}")
|
||||
elif int(start) > int(end): logger.error(f"Collection Error: Skipping {method} starting year {start} cannot be greater then ending year {end}")
|
||||
else:
|
||||
for i in range(int(start), int(end) + 1):
|
||||
final_years.append(i)
|
||||
else:
|
||||
year = re.search("(\\d+)", str(value)).group(1)
|
||||
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:
|
||||
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)
|
||||
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
|
||||
|
||||
def logger_input(prompt, timeout=60):
|
||||
|
@ -543,14 +547,14 @@ def alarm_handler(signum, frame):
|
|||
raise TimeoutExpired
|
||||
|
||||
def unix_input(prompt, timeout=60):
|
||||
prompt = "| {}: ".format(prompt)
|
||||
prompt = f"| {prompt}: "
|
||||
signal.signal(signal.SIGALRM, alarm_handler)
|
||||
signal.alarm(timeout)
|
||||
try: return input(prompt)
|
||||
finally: signal.alarm(0)
|
||||
|
||||
def old_windows_input(prompt, timeout=60, timer=time.monotonic):
|
||||
prompt = "| {}: ".format(prompt)
|
||||
prompt = f"| {prompt}: "
|
||||
sys.stdout.write(prompt)
|
||||
sys.stdout.flush()
|
||||
endtime = timer() + timeout
|
||||
|
@ -560,13 +564,13 @@ def old_windows_input(prompt, timeout=60, timer=time.monotonic):
|
|||
result.append(msvcrt.getwche())
|
||||
if result[-1] == "\n":
|
||||
out = "".join(result[:-1])
|
||||
logger.debug("{}{}".format(prompt[2:], out))
|
||||
logger.debug(f"{prompt[2:]}{out}")
|
||||
return out
|
||||
time.sleep(0.04)
|
||||
raise TimeoutExpired
|
||||
|
||||
def windows_input(prompt, timeout=5):
|
||||
sys.stdout.write("| {}: ".format(prompt))
|
||||
sys.stdout.write(f"| {prompt}: ")
|
||||
sys.stdout.flush()
|
||||
result = []
|
||||
start_time = time.time()
|
||||
|
@ -576,7 +580,7 @@ def windows_input(prompt, timeout=5):
|
|||
if ord(char) == 13: # enter_key
|
||||
out = "".join(result)
|
||||
print("")
|
||||
logger.debug("{}: {}".format(prompt, out))
|
||||
logger.debug(f"{prompt}: {out}")
|
||||
return out
|
||||
elif ord(char) >= 32: #space_char
|
||||
result.append(char)
|
||||
|
@ -606,17 +610,17 @@ def my_except_hook(exctype, value, tb):
|
|||
def get_id_from_imdb_url(imdb_url):
|
||||
match = re.search("(tt\\d+)", str(imdb_url))
|
||||
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):
|
||||
match = re.search("(\\d+)", str(data))
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
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)
|
||||
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):
|
||||
return method[:-4] if method.endswith(".not") else method
|
||||
|
@ -629,20 +633,20 @@ def get_centered_text(text):
|
|||
text += " "
|
||||
space -= 1
|
||||
side = int(space / 2)
|
||||
return "{}{}{}".format(" " * side, text, " " * side)
|
||||
return f"{' ' * side}{text}{' ' * side}"
|
||||
|
||||
def separator(text=None):
|
||||
logger.handlers[0].setFormatter(logging.Formatter("%(message)-{}s".format(screen_width - 2)))
|
||||
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s %(message)-{}s".format(screen_width - 2)))
|
||||
logger.info("|{}|".format(separating_character * screen_width))
|
||||
logger.handlers[0].setFormatter(logging.Formatter(f"%(message)-{screen_width - 2}s"))
|
||||
logger.handlers[1].setFormatter(logging.Formatter(f"[%(asctime)s] %(filename)-27s %(levelname)-10s %(message)-{screen_width - 2}s"))
|
||||
logger.info(f"|{separating_character * screen_width}|")
|
||||
if text:
|
||||
logger.info("| {} |".format(get_centered_text(text)))
|
||||
logger.info("|{}|".format(separating_character * screen_width))
|
||||
logger.handlers[0].setFormatter(logging.Formatter("| %(message)-{}s |".format(screen_width - 2)))
|
||||
logger.handlers[1].setFormatter(logging.Formatter("[%(asctime)s] %(filename)-27s %(levelname)-10s | %(message)-{}s |".format(screen_width - 2)))
|
||||
logger.info(f"| {get_centered_text(text)} |")
|
||||
logger.info(f"|{separating_character * screen_width}|")
|
||||
logger.handlers[0].setFormatter(logging.Formatter(f"| %(message)-{screen_width - 2}s |"))
|
||||
logger.handlers[1].setFormatter(logging.Formatter(f"[%(asctime)s] %(filename)-27s %(levelname)-10s | %(message)-{screen_width - 2}s |"))
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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]
|
||||
if 90 <= args.width <= 300:
|
||||
util.screen_width = args.width
|
||||
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")
|
||||
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 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 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(f"Config Error: config not found at {os.path.abspath(default_dir)}")
|
||||
|
||||
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)
|
||||
|
||||
def fmt_filter(record):
|
||||
record.levelname = "[{}]".format(record.levelname)
|
||||
record.filename = "[{}:{}]".format(record.filename, record.lineno)
|
||||
record.levelname = f"[{record.levelname}]"
|
||||
record.filename = f"[{record.filename}:{record.lineno}]"
|
||||
return True
|
||||
|
||||
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 collections: start_type = "Collections "
|
||||
else: start_type = ""
|
||||
util.separator("Starting {}Run".format(start_type))
|
||||
util.separator(f"Starting {start_type}Run")
|
||||
try:
|
||||
config = Config(default_dir, config_path)
|
||||
config.update_libraries(test, collections)
|
||||
|
@ -79,7 +79,7 @@ def start(config_path, test, daily, collections):
|
|||
util.print_stacktrace()
|
||||
logger.critical(e)
|
||||
logger.info("")
|
||||
util.separator("Finished {}Run".format(start_type))
|
||||
util.separator(f"Finished {start_type}Run")
|
||||
|
||||
try:
|
||||
if args.run or args.test or args.collections:
|
||||
|
@ -95,10 +95,10 @@ try:
|
|||
if hours < 0:
|
||||
hours += 24
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
time_str = "{} Hour{} and ".format(hours, "s" if hours > 1 else "") if hours > 0 else ""
|
||||
time_str += "{} Minute{}".format(minutes, "s" if minutes > 1 else "")
|
||||
time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 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)
|
||||
except KeyboardInterrupt:
|
||||
util.separator("Exiting Plex Meta Manager")
|
||||
|
|
Loading…
Reference in a new issue