diff --git a/modules/anidb.py b/modules/anidb.py index b6138fae..1b0f6bf4 100644 --- a/modules/anidb.py +++ b/modules/anidb.py @@ -6,6 +6,8 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = ["anidb_id", "anidb_relation", "anidb_popular"] + class AniDBAPI: def __init__(self, config): self.config = config diff --git a/modules/anilist.py b/modules/anilist.py index 2ae98118..7f40598f 100644 --- a/modules/anilist.py +++ b/modules/anilist.py @@ -5,6 +5,21 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = [ + "anilist_genre", + "anilist_id", + "anilist_popular", + "anilist_relations", + "anilist_season", + "anilist_studio", + "anilist_tag", + "anilist_top_rated" +] +pretty_names = { + "score": "Average Score", + "popular": "Popularity" +} + class AniListAPI: def __init__(self, config): self.config = config @@ -223,15 +238,15 @@ class AniListAPI: elif method == "anilist_season": mal_ids = self.season(data["season"], data["year"], data["sort_by"], data["limit"]) if status_message: - logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {util.anilist_pretty[data['sort_by']]}") + logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {pretty_names[data['sort_by']]}") elif method == "anilist_genre": mal_ids = self.genre(data["genre"], data["sort_by"], data["limit"]) if status_message: - logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Genre: {data['genre']} sorted by {util.anilist_pretty[data['sort_by']]}") + logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Genre: {data['genre']} sorted by {pretty_names[data['sort_by']]}") elif method == "anilist_tag": mal_ids = self.tag(data["tag"], data["sort_by"], data["limit"]) if status_message: - logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Tag: {data['tag']} sorted by {util.anilist_pretty[data['sort_by']]}") + logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Tag: {data['tag']} sorted by {pretty_names[data['sort_by']]}") elif method in ["anilist_studio", "anilist_relations"]: if method == "anilist_studio": mal_ids, name = self.studio(data) else: mal_ids, _, name = self.relations(data) diff --git a/modules/builder.py b/modules/builder.py index fd4ec34d..0668013e 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1,12 +1,147 @@ import glob, logging, os, re from datetime import datetime, timedelta -from modules import util +from modules import anidb, anilist, imdb, letterboxd, mal, plex, tautulli, tmdb, trakttv, tvdb, util from modules.util import Failed from plexapi.collection import Collections from plexapi.exceptions import BadRequest, NotFound logger = logging.getLogger("Plex Meta Manager") +image_file_details = ["file_poster", "file_background", "asset_directory"] +method_alias = { + "actors": "actor", "role": "actor", "roles": "actor", + "content_ratings": "content_rating", "contentRating": "content_rating", "contentRatings": "content_rating", + "countries": "country", + "decades": "decade", + "directors": "director", + "genres": "genre", + "labels": "label", + "studios": "studio", "network": "studio", "networks": "studio", + "producers": "producer", + "writers": "writer", + "years": "year" +} +all_builders = anidb.builders + anilist.builders + imdb.builders + letterboxd.builders + mal.builders + plex.builders + tautulli.builders + tmdb.builders + trakttv.builders + tvdb.builders +dictionary_builders = [ + "filters", + "anilist_genre", + "anilist_season", + "anilist_tag", + "mal_season", + "mal_userlist", + "plex_collectionless", + "plex_search", + "tautulli_popular", + "tautulli_watched", + "tmdb_discover" +] +show_only_builders = [ + "tmdb_network", + "tmdb_show", + "tmdb_show_details", + "tvdb_show", + "tvdb_show_details" +] +movie_only_builders = [ + "letterboxd_list", + "letterboxd_list_details", + "tmdb_collection", + "tmdb_collection_details", + "tmdb_movie", + "tmdb_movie_details", + "tmdb_now_playing", + "tvdb_movie", + "tvdb_movie_details" +] +numbered_builders = [ + "anidb_popular", + "anilist_popular", + "anilist_top_rated", + "mal_all", + "mal_airing", + "mal_upcoming", + "mal_tv", + "mal_ova", + "mal_movie", + "mal_special", + "mal_popular", + "mal_favorite", + "mal_suggested", + "tmdb_popular", + "tmdb_top_rated", + "tmdb_now_playing", + "tmdb_trending_daily", + "tmdb_trending_weekly", + "trakt_trending", + "trakt_popular", + "trakt_recommended", + "trakt_watched", + "trakt_collected" +] +all_details = [ + "sort_title", "content_rating", + "summary", "tmdb_summary", "tmdb_description", "tmdb_biography", "tvdb_summary", "tvdb_description", "trakt_description", "letterboxd_description", + "collection_mode", "collection_order", + "url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster", + "url_background", "tmdb_background", "tvdb_background", "file_background", + "name_mapping", "add_to_arr", "arr_tag", "label", + "show_filtered", "show_missing", "save_missing" +] +collectionless_details = [ + "sort_title", "content_rating", + "summary", "tmdb_summary", "tmdb_description", "tmdb_biography", + "collection_order", "plex_collectionless", + "url_poster", "tmdb_poster", "tmdb_profile", "file_poster", + "url_background", "file_background", + "name_mapping", "label", "label_sync_mode", "test" +] +ignored_details = [ + "run_again", + "schedule", + "sync_mode", + "template", + "test", + "tmdb_person" +] +boolean_details = [ + "add_to_arr", + "show_filtered", + "show_missing", + "save_missing" +] +all_filters = [ + "actor", "actor.not", + "audio_language", "audio_language.not", + "audio_track_title", "audio_track_title.not", + "collection", "collection.not", + "content_rating", "content_rating.not", + "country", "country.not", + "director", "director.not", + "genre", "genre.not", + "max_age", + "originally_available.gte", "originally_available.lte", + "tmdb_vote_count.gte", "tmdb_vote_count.lte", + "duration.gte", "duration.lte", + "original_language", "original_language.not", + "rating.gte", "rating.lte", + "studio", "studio.not", + "subtitle_language", "subtitle_language.not", + "video_resolution", "video_resolution.not", + "writer", "writer.not", + "year", "year.gte", "year.lte", "year.not" +] +movie_only_filters = [ + "audio_language", "audio_language.not", + "audio_track_title", "audio_track_title.not", + "country", "country.not", + "director", "director.not", + "duration.gte", "duration.lte", + "original_language", "original_language.not", + "subtitle_language", "subtitle_language.not", + "video_resolution", "video_resolution.not", + "writer", "writer.not" +] + class CollectionBuilder: def __init__(self, config, library, name, data): self.config = config @@ -221,18 +356,18 @@ class CollectionBuilder: logger.debug("") logger.debug(f"Validating Method: {method_name}") logger.debug(f"Value: {method_data}") - if method_name.lower() in util.method_alias: - method_name = util.method_alias[method_name.lower()] + if method_name.lower() in method_alias: + method_name = method_alias[method_name.lower()] logger.warning(f"Collection Warning: {method_name} attribute will run as {method_name}") else: method_name = method_name.lower() - if method_name in util.show_only_lists and self.library.is_movie: + if method_name in show_only_builders and self.library.is_movie: raise Failed(f"Collection Error: {method_name} attribute only works for show libraries") - elif method_name in util.movie_only_lists and self.library.is_show: + elif method_name in movie_only_builders and self.library.is_show: raise Failed(f"Collection Error: {method_name} attribute only works for movie libraries") - elif method_name in util.movie_only_searches and self.library.is_show: + elif method_name in plex.movie_only_searches and self.library.is_show: raise Failed(f"Collection Error: {method_name} plex search only works for movie libraries") - elif method_name not in util.collectionless_lists and self.collectionless: + elif method_name not in collectionless_details and self.collectionless: raise Failed(f"Collection Error: {method_name} attribute does not work for Collectionless collection") elif method_name == "summary": self.summaries[method_name] = method_data @@ -298,12 +433,12 @@ class CollectionBuilder: else: raise Failed("Collection Error: sync_mode attribute must be either 'append' or 'sync'") elif method_name in ["arr_tag", "label"]: self.details[method_name] = util.get_list(method_data) - elif method_name in util.boolean_details: + elif method_name in boolean_details: if isinstance(method_data, bool): self.details[method_name] = method_data elif str(method_data).lower() in ["t", "true"]: self.details[method_name] = True elif str(method_data).lower() in ["f", "false"]: self.details[method_name] = False else: raise Failed(f"Collection Error: {method_name} attribute must be either true or false") - elif method_name in util.all_details: + elif method_name in all_details: self.details[method_name] = method_data elif method_name in ["title", "title.and", "title.not", "title.begins", "title.ends"]: self.methods.append(("plex_search", [{method_name: util.get_list(method_data, split=False)}])) @@ -315,7 +450,7 @@ class CollectionBuilder: self.methods.append(("plex_search", [{method_name: [util.check_number(method_data, method_name, minimum=0)]}])) elif method_name in ["year", "year.not"]: self.methods.append(("plex_search", [{method_name: util.get_year_list(method_data, current_year, method_name)}])) - elif method_name in util.tmdb_searches: + elif method_name in plex.tmdb_searches: final_values = [] for value in util.get_list(method_data): if value.lower() == "tmdb" and "tmdb_person" in self.details: @@ -324,8 +459,8 @@ class CollectionBuilder: else: final_values.append(value) self.methods.append(("plex_search", [{method_name: self.library.validate_search_list(final_values, os.path.splitext(method_name)[0])}])) - elif method_name in util.plex_searches: - if method_name in util.tmdb_searches: + elif method_name in plex.searches: + if method_name in plex.tmdb_searches: final_values = [] for value in util.get_list(method_data): if value.lower() == "tmdb" and "tmdb_person" in self.details: @@ -387,7 +522,7 @@ class CollectionBuilder: values = util.get_list(method_data, split=False) self.summaries[method_name] = config.Letterboxd.get_list_description(values[0], self.library.Plex.language) self.methods.append((method_name[:-8], values)) - elif method_name in util.dictionary_lists: + elif method_name in dictionary_builders: if isinstance(method_data, dict): def get_int(parent, method, data_in, methods_in, default_in, minimum=1, maximum=None): if method not in methods_in: @@ -404,12 +539,12 @@ class CollectionBuilder: return default_in if method_name == "filters": for filter_name, filter_data in method_data.items(): - if filter_name.lower() in util.method_alias or (filter_name.lower().endswith(".not") and filter_name.lower()[:-4] in util.method_alias): - filter_method = (util.method_alias[filter_name.lower()[:-4]] + filter_name.lower()[-4:]) if filter_name.lower().endswith(".not") else util.method_alias[filter_name.lower()] + if filter_name.lower() in method_alias or (filter_name.lower().endswith(".not") and filter_name.lower()[:-4] in method_alias): + filter_method = (method_alias[filter_name.lower()[:-4]] + filter_name.lower()[-4:]) if filter_name.lower().endswith(".not") else method_alias[filter_name.lower()] logger.warning(f"Collection Warning: {filter_name} filter will run as {filter_method}") else: filter_method = filter_name.lower() - if filter_method in util.movie_only_filters and self.library.is_show: + if filter_method in movie_only_filters and self.library.is_show: raise Failed(f"Collection Error: {filter_method} filter only works for movie libraries") elif filter_data is None: raise Failed(f"Collection Error: {filter_method} filter is blank") @@ -427,7 +562,7 @@ class CollectionBuilder: valid_data = util.get_list(filter_data, lower=True) elif filter_method == "collection": valid_data = filter_data if isinstance(filter_data, list) else [filter_data] - elif filter_method in util.all_filters: + elif filter_method in all_filters: valid_data = util.get_list(filter_data) else: raise Failed(f"Collection Error: {filter_method} filter not supported") @@ -456,16 +591,16 @@ class CollectionBuilder: searches = {} for search_name, search_data in method_data.items(): search, modifier = os.path.splitext(str(search_name).lower()) - if search in util.method_alias: - search = util.method_alias[search] + if search in method_alias: + search = method_alias[search] logger.warning(f"Collection Warning: {str(search_name).lower()} plex search attribute will run as {search}{modifier if modifier else ''}") search_final = f"{search}{modifier}" - if search_final in util.movie_only_searches and self.library.is_show: + if search_final in plex.movie_only_searches and self.library.is_show: raise Failed(f"Collection Error: {search_final} plex search attribute only works for movie libraries") elif search_data is None: raise Failed(f"Collection Error: {search_final} plex search attribute is blank") elif search == "sort_by": - if str(search_data).lower() in util.plex_sort: + if str(search_data).lower() in plex.sorts: searches[search] = str(search_data).lower() else: logger.warning(f"Collection Error: {search_data} is not a valid plex search sort defaulting to title.asc") @@ -481,7 +616,7 @@ class CollectionBuilder: elif (search == "studio" and modifier in ["", ".and", ".not", ".begins", ".ends"]) \ or (search in ["actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label", "producer", "subtitle_language", "writer"] and modifier in ["", ".and", ".not"]) \ or (search == "resolution" and modifier in [""]): - if search_final in util.tmdb_searches: + if search_final in plex.tmdb_searches: final_values = [] for value in util.get_list(search_data): if value.lower() == "tmdb" and "tmdb_person" in self.details: @@ -516,7 +651,7 @@ class CollectionBuilder: for discover_name, discover_data in method_data.items(): discover_final = discover_name.lower() if discover_data: - if (self.library.is_movie and discover_final in util.discover_movie) or (self.library.is_show and discover_final in util.discover_tv): + if (self.library.is_movie and discover_final in tmdb.discover_movie) or (self.library.is_show and discover_final in tmdb.discover_tv): if discover_final == "language": if re.compile("([a-z]{2})-([A-Z]{2})").match(str(discover_data)): new_dictionary[discover_final] = str(discover_data) @@ -528,7 +663,7 @@ class CollectionBuilder: else: raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ^[A-Z]{{2}}$ e.g. US") elif discover_final == "sort_by": - if (self.library.is_movie and discover_data in util.discover_movie_sort) or (self.library.is_show and discover_data in util.discover_tv_sort): + if (self.library.is_movie and discover_data in tmdb.discover_movie_sort) or (self.library.is_show and discover_data in tmdb.discover_tv_sort): new_dictionary[discover_final] = discover_data else: raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} is invalid") @@ -545,7 +680,7 @@ class CollectionBuilder: elif discover_final in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]: if discover_data is True: new_dictionary[discover_final] = discover_data - elif discover_final in util.discover_dates: + elif discover_final in tmdb.discover_dates: new_dictionary[discover_final] = util.check_date(discover_data, f"{method_name} attribute {discover_final}", return_string=True) elif discover_final in ["primary_release_year", "year", "first_air_date_year"]: new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=current_year + 1) @@ -588,10 +723,10 @@ class CollectionBuilder: logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default") elif not method_data[dict_methods["sort_by"]]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default") - elif method_data[dict_methods["sort_by"]] not in util.mal_season_sort: + elif method_data[dict_methods["sort_by"]] not in mal.season_sort: logger.warning(f"Collection Warning: mal_season sort_by attribute {method_data[dict_methods['sort_by']]} invalid must be either 'members' or 'score' using members as default") else: - new_dictionary["sort_by"] = util.mal_season_sort[method_data[dict_methods["sort_by"]]] + new_dictionary["sort_by"] = mal.season_sort[method_data[dict_methods["sort_by"]]] if current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter" elif current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring" @@ -624,19 +759,19 @@ class CollectionBuilder: logger.warning("Collection Warning: mal_season status attribute not found using all as default") elif not method_data[dict_methods["status"]]: logger.warning("Collection Warning: mal_season status attribute is blank using all as default") - elif method_data[dict_methods["status"]] not in util.mal_userlist_status: + elif method_data[dict_methods["status"]] not in mal.userlist_status: logger.warning(f"Collection Warning: mal_season status attribute {method_data[dict_methods['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[method_data[dict_methods["status"]]] + new_dictionary["status"] = mal.userlist_status[method_data[dict_methods["status"]]] if "sort_by" not in dict_methods: logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default") elif not method_data[dict_methods["sort_by"]]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default") - elif method_data[dict_methods["sort_by"]] not in util.mal_userlist_sort: + elif method_data[dict_methods["sort_by"]] not in mal.userlist_sort: logger.warning(f"Collection Warning: mal_season sort_by attribute {method_data[dict_methods['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[method_data[dict_methods["sort_by"]]] + new_dictionary["sort_by"] = mal.userlist_sort[method_data[dict_methods["sort_by"]]] new_dictionary["limit"] = get_int(method_name, "limit", method_data, dict_methods, 100, maximum=1000) self.methods.append((method_name, [new_dictionary])) @@ -688,7 +823,7 @@ class CollectionBuilder: self.methods.append((method_name, [new_dictionary])) else: raise Failed(f"Collection Error: {method_name} attribute is not a dictionary: {method_data}") - elif method_name in util.count_lists: + elif method_name in numbered_builders: list_count = util.regex_first_int(method_data, "List Size", default=10) if list_count < 1: logger.warning(f"Collection Warning: {method_name} must be an integer greater then 0 defaulting to 10") @@ -718,8 +853,8 @@ class CollectionBuilder: self.methods.append((method_name[:-8], values)) else: self.methods.append((method_name, values)) - elif method_name in util.tmdb_lists: - values = config.TMDb.validate_tmdb_list(util.get_int_list(method_data, f"TMDb {util.tmdb_type[method_name]} ID"), util.tmdb_type[method_name]) + elif method_name in tmdb.builders: + values = config.TMDb.validate_tmdb_list(util.get_int_list(method_data, f"TMDb {tmdb.type_map[method_name]} ID"), tmdb.type_map[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) @@ -742,11 +877,11 @@ class CollectionBuilder: self.methods.append((method_name[:-8], values)) else: self.methods.append((method_name, values)) - elif method_name in util.all_lists: + elif method_name in all_builders: self.methods.append((method_name, util.get_list(method_data))) - elif method_name not in util.other_attributes: + elif method_name not in ignored_details: raise Failed(f"Collection Error: {method_name} attribute not supported") - elif method_name in util.all_lists or method_name in util.method_alias or method_name in util.plex_searches: + elif method_name in all_builders or method_name in method_alias or method_name in plex.searches: raise Failed(f"Collection Error: {method_name} attribute is blank") else: logger.warning(f"Collection Warning: {method_name} attribute is blank") @@ -814,11 +949,11 @@ class CollectionBuilder: if search_method == "limit": search_limit = search_data elif search_method == "sort_by": - search_sort = util.plex_sort[search_data] + search_sort = plex.sorts[search_data] else: search, modifier = os.path.splitext(str(search_method).lower()) - final_search = util.search_alias[search] if search in util.search_alias else search - final_mod = util.plex_modifiers[modifier] if modifier in util.plex_modifiers else "" + final_search = plex.search_translation[search] if search in plex.search_translation else search + final_mod = plex.modifiers[modifier] if modifier in plex.modifiers else "" final_method = f"{final_search}{final_mod}" search_terms[final_method] = search_data * 60000 if final_search == "duration" else search_data ors = "" diff --git a/modules/imdb.py b/modules/imdb.py index e5a00717..ad171288 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -6,6 +6,8 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = ["imdb_list", "imdb_id"] + class IMDbAPI: def __init__(self, config): self.config = config diff --git a/modules/letterboxd.py b/modules/letterboxd.py index 17ac2449..7b214a53 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -6,6 +6,8 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = ["letterboxd_list", "letterboxd_list_details"] + class LetterboxdAPI: def __init__(self, config): self.config = config diff --git a/modules/mal.py b/modules/mal.py index 063638fe..ec644a52 100644 --- a/modules/mal.py +++ b/modules/mal.py @@ -6,6 +6,73 @@ from ruamel import yaml logger = logging.getLogger("Plex Meta Manager") +builders = [ + "mal_id", + "mal_all", + "mal_airing", + "mal_upcoming", + "mal_tv", + "mal_ova", + "mal_movie", + "mal_special", + "mal_popular", + "mal_favorite", + "mal_season", + "mal_suggested", + "mal_userlist" +] +mal_ranked_name = { + "mal_all": "all", + "mal_airing": "airing", + "mal_upcoming": "upcoming", + "mal_tv": "tv", + "mal_ova": "ova", + "mal_movie": "movie", + "mal_special": "special", + "mal_popular": "bypopularity", + "mal_favorite": "favorite" +} +season_sort = { + "anime_score": "anime_score", + "anime_num_list_users": "anime_num_list_users", + "score": "anime_score", + "members": "anime_num_list_users" +} +pretty_names = { + "anime_score": "Score", + "anime_num_list_users": "Members", + "list_score": "Score", + "list_updated_at": "Last Updated", + "anime_title": "Title", + "anime_start_date": "Start Date", + "all": "All Anime", + "watching": "Currently Watching", + "completed": "Completed", + "on_hold": "On Hold", + "dropped": "Dropped", + "plan_to_watch": "Plan to Watch" +} +userlist_sort = { + "score": "list_score", + "list_score": "list_score", + "last_updated": "list_updated_at", + "list_updated": "list_updated_at", + "list_updated_at": "list_updated_at", + "title": "anime_title", + "anime_title": "anime_title", + "start_date": "anime_start_date", + "anime_start_date": "anime_start_date" +} +userlist_status = [ + "all", + "watching", + "completed", + "on_hold", + "dropped", + "plan_to_watch" +] + + class MyAnimeListIDList: def __init__(self): self.ids = json.loads(requests.get("https://raw.githubusercontent.com/Fribb/anime-lists/master/animeMapping_full.json").content) @@ -155,14 +222,14 @@ class MyAnimeListAPI: mal_ids = [data] if status_message: logger.info(f"Processing {pretty}: {data}") - elif method in util.mal_ranked_name: - mal_ids = self.get_ranked(util.mal_ranked_name[method], data) + elif method in mal_ranked_name: + mal_ids = self.get_ranked(mal_ranked_name[method], data) if status_message: 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(f"Processing {pretty}: {data['limit']} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {util.mal_pretty[data['sort_by']]}") + logger.info(f"Processing {pretty}: {data['limit']} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {pretty_names[data['sort_by']]}") elif method == "mal_suggested": mal_ids = self.get_suggestions(data) if status_message: @@ -170,7 +237,7 @@ class MyAnimeListAPI: elif method == "mal_userlist": mal_ids = self.get_userlist(data["username"], data["status"], data["sort_by"], data["limit"]) if status_message: - 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']]}") + logger.info(f"Processing {pretty}: {data['limit']} Anime from {self.get_username() if data['username'] == '@me' else data['username']}'s {pretty_names[data['status']]} list sorted by {pretty_names[data['sort_by']]}") else: raise Failed(f"MyAnimeList Error: Method {method} not supported") show_ids = [] diff --git a/modules/plex.py b/modules/plex.py index 0d672d06..34ad6c13 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -12,6 +12,91 @@ from ruamel import yaml logger = logging.getLogger("Plex Meta Manager") +builders = ["plex_all", "plex_collection", "plex_collectionless", "plex_search",] +search_translation = { + "audio_language": "audioLanguage", + "content_rating": "contentRating", + "subtitle_language": "subtitleLanguage", + "added": "addedAt", + "originally_available": "originallyAvailableAt", + "rating": "userRating" +} +episode_sorting_options = {"default": "-1", "oldest": "0", "newest": "1"} +keep_episodes_options = {"all": 0, "5_latest": 5, "3_latest": 3, "latest": 1, "past_3": -3, "past_7": -7, "past_30": -30} +delete_episodes_options = {"never": 0, "day": 1, "week": 7, "refresh": 100} +season_display_options = {"default": -1, "show": 0, "hide": 1} +episode_ordering_options = {"default": None, "tmdb_aired": "tmdbAiring", "tvdb_aired": "airing", "tvdb_dvd": "dvd", "tvdb_absolute": "absolute"} +plex_languages = ["default", "ar-SA", "ca-ES", "cs-CZ", "da-DK", "de-DE", "el-GR", "en-AU", "en-CA", "en-GB", "en-US", + "es-ES", "es-MX", "et-EE", "fa-IR", "fi-FI", "fr-CA", "fr-FR", "he-IL", "hi-IN", "hu-HU", "id-ID", + "it-IT", "ja-JP", "ko-KR", "lt-LT", "lv-LV", "nb-NO", "nl-NL", "pl-PL", "pt-BR", "pt-PT", "ro-RO", + "ru-RU", "sk-SK", "sv-SE", "th-TH", "tr-TR", "uk-UA", "vi-VN", "zh-CN", "zh-HK", "zh-TW"] +metadata_language_options = {lang.lower(): lang for lang in plex_languages} +metadata_language_options["default"] = None +filter_alias = { + "actor": "actors", + "collection": "collections", + "content_rating": "contentRating", + "country": "countries", + "director": "directors", + "genre": "genres", + "originally_available": "originallyAvailableAt", + "tmdb_vote_count": "vote_count", + "writer": "writers" +} +searches = [ + "title", "title.and", "title.not", "title.begins", "title.ends", + "studio", "studio.and", "studio.not", "studio.begins", "studio.ends", + "actor", "actor.and", "actor.not", + "audio_language", "audio_language.and", "audio_language.not", + "collection", "collection.and", "collection.not", + "content_rating", "content_rating.and", "content_rating.not", + "country", "country.and", "country.not", + "director", "director.and", "director.not", + "genre", "genre.and", "genre.not", + "label", "label.and", "label.not", + "producer", "producer.and", "producer.not", + "subtitle_language", "subtitle_language.and", "subtitle_language.not", + "writer", "writer.and", "writer.not", + "decade", "resolution", + "added.before", "added.after", + "originally_available.before", "originally_available.after", + "duration.greater", "duration.less", + "rating.greater", "rating.less", + "year", "year.not", "year.greater", "year.less" +] +movie_only_searches = [ + "audio_language", "audio_language.and", "audio_language.not", + "country", "country.and", "country.not", + "subtitle_language", "subtitle_language.and", "subtitle_language.not", + "decade", "resolution", + "originally_available.before", "originally_available.after", + "duration.greater", "duration.less" +] +tmdb_searches = [ + "actor", "actor.and", "actor.not", + "director", "director.and", "director.not", + "producer", "producer.and", "producer.not", + "writer", "writer.and", "writer.not" +] +sorts = { + "title.asc": "titleSort:asc", "title.desc": "titleSort:desc", + "originally_available.asc": "originallyAvailableAt:asc", "originally_available.desc": "originallyAvailableAt:desc", + "critic_rating.asc": "rating:asc", "critic_rating.desc": "rating:desc", + "audience_rating.asc": "audienceRating:asc", "audience_rating.desc": "audienceRating:desc", + "duration.asc": "duration:asc", "duration.desc": "duration:desc", + "added.asc": "addedAt:asc", "added.desc": "addedAt:desc" +} +modifiers = { + ".and": "&", + ".not": "!", + ".begins": "<", + ".ends": ">", + ".before": "<<", + ".after": ">>", + ".greater": ">>", + ".less": "<<" +} + class PlexAPI: def __init__(self, params, TMDb, TVDb): try: @@ -98,7 +183,7 @@ class PlexAPI: else: return {c.title.lower(): c.title for c in self.Plex.listFilterChoices(search_name)} def validate_search_list(self, data, search_name): - final_search = util.search_alias[search_name] if search_name in util.search_alias else search_name + final_search = search_translation[search_name] if search_name in search_translation else search_name search_choices = self.get_search_choices(final_search, key=final_search.endswith("Language")) valid_list = [] for value in util.get_list(data): @@ -160,7 +245,7 @@ class PlexAPI: for filter_method, filter_data in filters: modifier = filter_method[-4:] method = filter_method[:-4] if modifier in [".not", ".lte", ".gte"] else filter_method - method_name = util.filter_alias[method] if method in util.filter_alias else method + method_name = filter_alias[method] if method in filter_alias else method if method_name == "max_age": threshold_date = datetime.now() - timedelta(days=filter_data) if current.originallyAvailableAt is None or current.originallyAvailableAt < threshold_date: diff --git a/modules/tautulli.py b/modules/tautulli.py index 5b9bc517..c258c62e 100644 --- a/modules/tautulli.py +++ b/modules/tautulli.py @@ -5,6 +5,8 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = ["tautulli_popular", "tautulli_watched"] + class TautulliAPI: def __init__(self, params): try: diff --git a/modules/tmdb.py b/modules/tmdb.py index da8a98c9..4df51913 100644 --- a/modules/tmdb.py +++ b/modules/tmdb.py @@ -7,6 +7,107 @@ from tmdbv3api.exceptions import TMDbException logger = logging.getLogger("Plex Meta Manager") +builders = [ + "tmdb_actor", + "tmdb_actor_details", + "tmdb_collection", + "tmdb_collection_details", + "tmdb_company", + "tmdb_crew", + "tmdb_crew_details", + "tmdb_director", + "tmdb_director_details", + "tmdb_discover", + "tmdb_keyword", + "tmdb_list", + "tmdb_list_details", + "tmdb_movie", + "tmdb_movie_details", + "tmdb_network", + "tmdb_now_playing", + "tmdb_popular", + "tmdb_producer", + "tmdb_producer_details", + "tmdb_show", + "tmdb_show_details", + "tmdb_top_rated", + "tmdb_trending_daily", + "tmdb_trending_weekly", + "tmdb_writer", + "tmdb_writer_details" +] +type_map = { + "tmdb_actor": "Person", + "tmdb_actor_details": "Person", + "tmdb_collection": "Collection", + "tmdb_collection_details": "Collection", + "tmdb_company": "Company", + "tmdb_crew": "Person", + "tmdb_crew_details": "Person", + "tmdb_director": "Person", + "tmdb_director_details": "Person", + "tmdb_keyword": "Keyword", + "tmdb_list": "List", + "tmdb_list_details": "List", + "tmdb_movie": "Movie", + "tmdb_movie_details": "Movie", + "tmdb_network": "Network", + "tmdb_person": "Person", + "tmdb_producer": "Person", + "tmdb_producer_details": "Person", + "tmdb_show": "Show", + "tmdb_show_details": "Show", + "tmdb_writer": "Person", + "tmdb_writer_details": "Person" +} +discover_movie = [ + "language", "with_original_language", "region", "sort_by", + "certification_country", "certification", "certification.lte", "certification.gte", + "include_adult", + "primary_release_year", "primary_release_date.gte", "primary_release_date.lte", + "release_date.gte", "release_date.lte", "year", + "vote_count.gte", "vote_count.lte", + "vote_average.gte", "vote_average.lte", + "with_cast", "with_crew", "with_people", + "with_companies", + "with_genres", "without_genres", + "with_keywords", "without_keywords", + "with_runtime.gte", "with_runtime.lte" +] +discover_tv = [ + "language", "with_original_language", "timezone", "sort_by", + "air_date.gte", "air_date.lte", + "first_air_date.gte", "first_air_date.lte", "first_air_date_year", + "vote_count.gte", "vote_count.lte", + "vote_average.gte", "vote_average.lte", + "with_genres", "without_genres", + "with_keywords", "without_keywords", + "with_networks", "with_companies", + "with_runtime.gte", "with_runtime.lte", + "include_null_first_air_dates", + "screened_theatrically" +] +discover_dates = [ + "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" +] +discover_movie_sort = [ + "popularity.asc", "popularity.desc", + "release_date.asc", "release_date.desc", + "revenue.asc", "revenue.desc", + "primary_release_date.asc", "primary_release_date.desc", + "original_title.asc", "original_title.desc", + "vote_average.asc", "vote_average.desc", + "vote_count.asc", "vote_count.desc" +] +discover_tv_sort = [ + "vote_average.desc", "vote_average.asc", + "first_air_date.desc", "first_air_date.asc", + "popularity.desc", "popularity.asc" +] + class TMDbAPI: def __init__(self, params): self.TMDb = tmdbv3api.TMDb() @@ -156,7 +257,7 @@ class TMDbAPI: def get_discover(self, attrs, amount, is_movie): ids = [] count = 0 - for date_attr in util.discover_dates: + for date_attr in discover_dates: if date_attr in attrs: attrs[date_attr] = datetime.strftime(datetime.strptime(attrs[date_attr], "%m/%d/%Y"), "%Y-%m-%d") self.Discover.discover_movies(attrs) if is_movie else self.Discover.discover_tv_shows(attrs) diff --git a/modules/trakttv.py b/modules/trakttv.py index aecbe6b3..b24263ea 100644 --- a/modules/trakttv.py +++ b/modules/trakttv.py @@ -11,6 +11,18 @@ from trakt.objects.show import Show logger = logging.getLogger("Plex Meta Manager") +builders = [ + "trakt_collected", + "trakt_collection", + "trakt_list", + "trakt_list_details", + "trakt_popular", + "trakt_recommended", + "trakt_trending", + "trakt_watched", + "trakt_watchlist" +] + class TraktAPI: def __init__(self, params, authorization=None): self.base_url = "https://api.trakt.tv" diff --git a/modules/tvdb.py b/modules/tvdb.py index 4d7a5b8a..02e28a8c 100644 --- a/modules/tvdb.py +++ b/modules/tvdb.py @@ -6,6 +6,15 @@ from retrying import retry logger = logging.getLogger("Plex Meta Manager") +builders = [ + "tvdb_list", + "tvdb_list_details", + "tvdb_movie", + "tvdb_movie_details", + "tvdb_show", + "tvdb_show_details" +] + class TVDbObj: def __init__(self, tvdb_url, language, is_movie, TVDb): tvdb_url = tvdb_url.strip() diff --git a/modules/util.py b/modules/util.py index 24d3cba4..34bd631d 100644 --- a/modules/util.py +++ b/modules/util.py @@ -22,38 +22,6 @@ def retry_if_not_failed(exception): separating_character = "=" screen_width = 100 -method_alias = { - "actors": "actor", "role": "actor", "roles": "actor", - "content_ratings": "content_rating", "contentRating": "content_rating", "contentRatings": "content_rating", - "countries": "country", - "decades": "decade", - "directors": "director", - "genres": "genre", - "labels": "label", - "studios": "studio", "network": "studio", "networks": "studio", - "producers": "producer", - "writers": "writer", - "years": "year" -} -search_alias = { - "audio_language": "audioLanguage", - "content_rating": "contentRating", - "subtitle_language": "subtitleLanguage", - "added": "addedAt", - "originally_available": "originallyAvailableAt", - "rating": "userRating" -} -filter_alias = { - "actor": "actors", - "collection": "collections", - "content_rating": "contentRating", - "country": "countries", - "director": "directors", - "genre": "genres", - "originally_available": "originallyAvailableAt", - "tmdb_vote_count": "vote_count", - "writer": "writers" -} days_alias = { "monday": 0, "mon": 0, "m": 0, "tuesday": 1, "tues": 1, "tue": 1, "tu": 1, "t": 1, @@ -170,64 +138,6 @@ pretty_names = { "tvdb_show": "TVDb Show", "tvdb_show_details": "TVDb Show" } -plex_languages = ["default", "ar-SA", "ca-ES", "cs-CZ", "da-DK", "de-DE", "el-GR", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", - "es-MX", "et-EE", "fa-IR", "fi-FI", "fr-CA", "fr-FR", "he-IL", "hi-IN", "hu-HU", "id-ID", "it-IT", - "ja-JP", "ko-KR", "lt-LT", "lv-LV", "nb-NO", "nl-NL", "pl-PL", "pt-BR", "pt-PT", "ro-RO", "ru-RU", - "sk-SK", "sv-SE", "th-TH", "tr-TR", "uk-UA", "vi-VN", "zh-CN", "zh-HK", "zh-TW"] -mal_ranked_name = { - "mal_all": "all", - "mal_airing": "airing", - "mal_upcoming": "upcoming", - "mal_tv": "tv", - "mal_ova": "ova", - "mal_movie": "movie", - "mal_special": "special", - "mal_popular": "bypopularity", - "mal_favorite": "favorite" -} -mal_season_sort = { - "anime_score": "anime_score", - "anime_num_list_users": "anime_num_list_users", - "score": "anime_score", - "members": "anime_num_list_users" -} -mal_pretty = { - "anime_score": "Score", - "anime_num_list_users": "Members", - "list_score": "Score", - "list_updated_at": "Last Updated", - "anime_title": "Title", - "anime_start_date": "Start Date", - "all": "All Anime", - "watching": "Currently Watching", - "completed": "Completed", - "on_hold": "On Hold", - "dropped": "Dropped", - "plan_to_watch": "Plan to Watch" -} -mal_userlist_sort = { - "score": "list_score", - "list_score": "list_score", - "last_updated": "list_updated_at", - "list_updated": "list_updated_at", - "list_updated_at": "list_updated_at", - "title": "anime_title", - "anime_title": "anime_title", - "start_date": "anime_start_date", - "anime_start_date": "anime_start_date" -} -mal_userlist_status = [ - "all", - "watching", - "completed", - "on_hold", - "dropped", - "plan_to_watch" -] -anilist_pretty = { - "score": "Average Score", - "popular": "Popularity" -} pretty_ids = { "anidbid": "AniDB", "imdbid": "IMDb", @@ -236,354 +146,6 @@ pretty_ids = { "thetvdb_id": "TVDb", "tvdbid": "TVDb" } -all_lists = [ - "anidb_id", - "anidb_relation", - "anidb_popular", - "anilist_genre", - "anilist_id", - "anilist_popular", - "anilist_relations", - "anilist_season", - "anilist_studio", - "anilist_tag", - "anilist_top_rated", - "imdb_list", - "imdb_id", - "letterboxd_list", - "letterboxd_list_details", - "mal_id", - "mal_all", - "mal_airing", - "mal_upcoming", - "mal_tv", - "mal_ova", - "mal_movie", - "mal_special", - "mal_popular", - "mal_favorite", - "mal_season", - "mal_suggested", - "mal_userlist", - "plex_collection", - "plex_search", - "tautulli_popular", - "tautulli_watched", - "tmdb_actor", - "tmdb_actor_details", - "tmdb_collection", - "tmdb_collection_details", - "tmdb_company", - "tmdb_crew", - "tmdb_crew_details", - "tmdb_director", - "tmdb_director_details", - "tmdb_discover", - "tmdb_keyword", - "tmdb_list", - "tmdb_list_details", - "tmdb_movie", - "tmdb_movie_details", - "tmdb_network", - "tmdb_now_playing", - "tmdb_popular", - "tmdb_producer", - "tmdb_producer_details", - "tmdb_show", - "tmdb_show_details", - "tmdb_top_rated", - "tmdb_trending_daily", - "tmdb_trending_weekly", - "tmdb_writer", - "tmdb_writer_details", - "trakt_collected", - "trakt_collection", - "trakt_list", - "trakt_list_details", - "trakt_popular", - "trakt_recommended", - "trakt_trending", - "trakt_watched", - "trakt_watchlist", - "tvdb_list", - "tvdb_list_details", - "tvdb_movie", - "tvdb_movie_details", - "tvdb_show", - "tvdb_show_details" -] -collectionless_lists = [ - "sort_title", "content_rating", - "summary", "tmdb_summary", "tmdb_description", "tmdb_biography", - "collection_order", "plex_collectionless", - "url_poster", "tmdb_poster", "tmdb_profile", "file_poster", - "url_background", "file_background", - "name_mapping", "label", "label_sync_mode", "test" -] -other_attributes = [ - "run_again", - "schedule", - "sync_mode", - "template", - "test", - "tmdb_person" -] -dictionary_lists = [ - "filters", - "anilist_genre", - "anilist_season", - "anilist_tag", - "mal_season", - "mal_userlist", - "plex_collectionless", - "plex_search", - "tautulli_popular", - "tautulli_watched", - "tmdb_discover" -] -show_only_lists = [ - "tmdb_network", - "tmdb_show", - "tmdb_show_details", - "tvdb_show", - "tvdb_show_details" -] -movie_only_lists = [ - "letterboxd_list", - "letterboxd_list_details", - "tmdb_collection", - "tmdb_collection_details", - "tmdb_movie", - "tmdb_movie_details", - "tmdb_now_playing", - "tvdb_movie", - "tvdb_movie_details" -] -count_lists = [ - "anidb_popular", - "anilist_popular", - "anilist_top_rated", - "mal_all", - "mal_airing", - "mal_upcoming", - "mal_tv", - "mal_ova", - "mal_movie", - "mal_special", - "mal_popular", - "mal_favorite", - "mal_suggested", - "tmdb_popular", - "tmdb_top_rated", - "tmdb_now_playing", - "tmdb_trending_daily", - "tmdb_trending_weekly", - "trakt_trending", - "trakt_popular", - "trakt_recommended", - "trakt_watched", - "trakt_collected" -] -tmdb_lists = [ - "tmdb_actor", - "tmdb_actor_details", - "tmdb_collection", - "tmdb_collection_details", - "tmdb_company", - "tmdb_crew", - "tmdb_crew_details", - "tmdb_director", - "tmdb_director_details", - "tmdb_discover", - "tmdb_keyword", - "tmdb_list", - "tmdb_list_details", - "tmdb_movie", - "tmdb_movie_details", - "tmdb_network", - "tmdb_now_playing", - "tmdb_popular", - "tmdb_producer", - "tmdb_producer_details", - "tmdb_show", - "tmdb_show_details", - "tmdb_top_rated", - "tmdb_trending_daily", - "tmdb_trending_weekly", - "tmdb_writer", - "tmdb_writer_details" -] -tmdb_type = { - "tmdb_actor": "Person", - "tmdb_actor_details": "Person", - "tmdb_collection": "Collection", - "tmdb_collection_details": "Collection", - "tmdb_company": "Company", - "tmdb_crew": "Person", - "tmdb_crew_details": "Person", - "tmdb_director": "Person", - "tmdb_director_details": "Person", - "tmdb_keyword": "Keyword", - "tmdb_list": "List", - "tmdb_list_details": "List", - "tmdb_movie": "Movie", - "tmdb_movie_details": "Movie", - "tmdb_network": "Network", - "tmdb_person": "Person", - "tmdb_producer": "Person", - "tmdb_producer_details": "Person", - "tmdb_show": "Show", - "tmdb_show_details": "Show", - "tmdb_writer": "Person", - "tmdb_writer_details": "Person" -} -plex_searches = [ - "title", "title.and", "title.not", "title.begins", "title.ends", - "studio", "studio.and", "studio.not", "studio.begins", "studio.ends", - "actor", "actor.and", "actor.not", - "audio_language", "audio_language.and", "audio_language.not", - "collection", "collection.and", "collection.not", - "content_rating", "content_rating.and", "content_rating.not", - "country", "country.and", "country.not", - "director", "director.and", "director.not", - "genre", "genre.and", "genre.not", - "label", "label.and", "label.not", - "producer", "producer.and", "producer.not", - "subtitle_language", "subtitle_language.and", "subtitle_language.not", - "writer", "writer.and", "writer.not", - "decade", "resolution", - "added.before", "added.after", - "originally_available.before", "originally_available.after", - "duration.greater", "duration.less", - "rating.greater", "rating.less", - "year", "year.not", "year.greater", "year.less" -] -plex_sort = { - "title.asc": "titleSort:asc", "title.desc": "titleSort:desc", - "originally_available.asc": "originallyAvailableAt:asc", "originally_available.desc": "originallyAvailableAt:desc", - "critic_rating.asc": "rating:asc", "critic_rating.desc": "rating:desc", - "audience_rating.asc": "audienceRating:asc", "audience_rating.desc": "audienceRating:desc", - "duration.asc": "duration:asc", "duration.desc": "duration:desc", - "added.asc": "addedAt:asc", "added.desc": "addedAt:desc" -} -plex_modifiers = { - ".and": "&", - ".not": "!", - ".begins": "<", - ".ends": ">", - ".before": "<<", - ".after": ">>", - ".greater": ">>", - ".less": "<<" -} -movie_only_searches = [ - "audio_language", "audio_language.and", "audio_language.not", - "country", "country.and", "country.not", - "subtitle_language", "subtitle_language.and", "subtitle_language.not", - "decade", "resolution", - "originally_available.before", "originally_available.after", - "duration.greater", "duration.less" -] -tmdb_searches = [ - "actor", "actor.and", "actor.not", - "director", "director.and", "director.not", - "producer", "producer.and", "producer.not", - "writer", "writer.and", "writer.not" -] -all_filters = [ - "actor", "actor.not", - "audio_language", "audio_language.not", - "audio_track_title", "audio_track_title.not", - "collection", "collection.not", - "content_rating", "content_rating.not", - "country", "country.not", - "director", "director.not", - "genre", "genre.not", - "max_age", - "originally_available.gte", "originally_available.lte", - "tmdb_vote_count.gte", "tmdb_vote_count.lte", - "duration.gte", "duration.lte", - "original_language", "original_language.not", - "rating.gte", "rating.lte", - "studio", "studio.not", - "subtitle_language", "subtitle_language.not", - "video_resolution", "video_resolution.not", - "writer", "writer.not", - "year", "year.gte", "year.lte", "year.not" -] -movie_only_filters = [ - "audio_language", "audio_language.not", - "audio_track_title", "audio_track_title.not", - "country", "country.not", - "director", "director.not", - "duration.gte", "duration.lte", - "original_language", "original_language.not", - "subtitle_language", "subtitle_language.not", - "video_resolution", "video_resolution.not", - "writer", "writer.not" -] -boolean_details = [ - "add_to_arr", - "show_filtered", - "show_missing", - "save_missing" -] -all_details = [ - "sort_title", "content_rating", - "summary", "tmdb_summary", "tmdb_description", "tmdb_biography", "tvdb_summary", "tvdb_description", "trakt_description", "letterboxd_description", - "collection_mode", "collection_order", - "url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster", - "url_background", "tmdb_background", "tvdb_background", "file_background", - "name_mapping", "add_to_arr", "arr_tag", "label", - "show_filtered", "show_missing", "save_missing" -] -discover_movie = [ - "language", "with_original_language", "region", "sort_by", - "certification_country", "certification", "certification.lte", "certification.gte", - "include_adult", - "primary_release_year", "primary_release_date.gte", "primary_release_date.lte", - "release_date.gte", "release_date.lte", "year", - "vote_count.gte", "vote_count.lte", - "vote_average.gte", "vote_average.lte", - "with_cast", "with_crew", "with_people", - "with_companies", - "with_genres", "without_genres", - "with_keywords", "without_keywords", - "with_runtime.gte", "with_runtime.lte" -] -discover_tv = [ - "language", "with_original_language", "timezone", "sort_by", - "air_date.gte", "air_date.lte", - "first_air_date.gte", "first_air_date.lte", "first_air_date_year", - "vote_count.gte", "vote_count.lte", - "vote_average.gte", "vote_average.lte", - "with_genres", "without_genres", - "with_keywords", "without_keywords", - "with_networks", "with_companies", - "with_runtime.gte", "with_runtime.lte", - "include_null_first_air_dates", - "screened_theatrically" -] -discover_dates = [ - "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" -] -discover_movie_sort = [ - "popularity.asc", "popularity.desc", - "release_date.asc", "release_date.desc", - "revenue.asc", "revenue.desc", - "primary_release_date.asc", "primary_release_date.desc", - "original_title.asc", "original_title.desc", - "vote_average.asc", "vote_average.desc", - "vote_count.asc", "vote_count.desc" -] -discover_tv_sort = [ - "vote_average.desc", "vote_average.asc", - "first_air_date.desc", "first_air_date.asc", - "popularity.desc", "popularity.asc" -] def tab_new_lines(data): return str(data).replace("\n", "\n|\t ") if "\n" in str(data) else str(data)