diff --git a/README.md b/README.md index 878a821a4..2b5308a03 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Plex Meta Manager -#### Version 1.7.0 +#### Version 1.7.1 The original concept for Plex Meta Manager is [Plex Auto Collections](https://github.com/mza921/Plex-Auto-Collections), but this is rewritten from the ground up to be able to include a scheduler, metadata edits, multiple libraries, and logging. Plex Meta Manager is a Python 3 script that can be continuously run using YAML configuration files to update on a schedule the metadata of the movies, shows, and collections in your libraries as well as automatically build collections based on various methods all detailed in the wiki. Some collection examples that the script can automatically build and update daily include Plex Based Searches like actor, genre, or studio collections or Collections based on TMDb, IMDb, Trakt, TVDb, AniDB, or MyAnimeList lists and various other services. @@ -12,7 +12,7 @@ The script is designed to work with most Metadata agents including the new Plex ## Getting Started 1. Install Plex Meta Manager either by installing Python3 and following the [Local Installation Guide](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Local-Installation) - or by installing Docker and following the [Docker Installation Guide](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Docker-Installation) or the [unRAID Installation Guide](https://github.com/meisnate12/Plex-Meta-Manager/wiki/unRAID-Installation) + or by installing Docker and following the [Docker Installation Guide](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Docker-Installation) or the [unRAID Installation Guide](https://github.com/meisnate12/Plex-Meta-Manager/wiki/unRAID-Installation). 2. Once installed, you have to create a [Configuration File](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Configuration-File) filled with all your values to connect to the various services. 3. After that you can start updating Metadata and building automatic Collections by creating a [Metadata File](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Metadata-File) for each Library you want to interact with. 4. Explore the [Wiki](https://github.com/meisnate12/Plex-Meta-Manager/wiki) to see all the different Collection Builders that can be used to create collections. diff --git a/config/Movies.yml.template b/config/Movies.yml.template index 739286c61..14af0613c 100644 --- a/config/Movies.yml.template +++ b/config/Movies.yml.template @@ -1,15 +1,15 @@ templates: Chart Alpha: - sort_title: ++++_<><> + sort_title: +_<><> sync_mode: sync collection_order: alpha Chart Release: - sort_title: ++++_<> + sort_title: +_<> sync_mode: sync collection_order: release Best of: trakt_list: https://trakt.tv/users/lish408/lists/rotten-tomatoes-best-of-<> - sort_title: ++++_Best of <> + sort_title: +_Best of <> sync_mode: sync summary: Rotten Tomatoes Best Movies of <> collection_order: release @@ -17,7 +17,7 @@ templates: optional: - company tmdb_company: <> - sort_title: +++_<> + sort_title: ++_<> sync_mode: sync collection_order: alpha IMDb Genre: @@ -29,24 +29,24 @@ templates: limit: <> - url: https://www.imdb.com/search/title/?title_type=<>&release_date=1990-01-01,&user_rating=5.0,10.0&num_votes=100000,&genres=<<genre>>&sort=user_rating,desc limit: <<limit>> - sort_title: ++_<<collection_name>> + sort_title: +++_<<collection_name>> sync_mode: sync collection_order: alpha Other Genre: - sort_title: ++_<<collection_name>> + sort_title: +++_<<collection_name>> sync_mode: sync collection_order: alpha Actor: actor: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Actor Director: actor: tmdb director: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Actor Director Writer: @@ -54,33 +54,33 @@ templates: director: tmdb writer: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Actor Writer: actor: tmdb writer: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Director: director: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Director Writer: director: tmdb writer: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Writer: writer: tmdb tmdb_person: <<person>> - sort_title: +_<<collection_name>> + sort_title: ++++_<<collection_name>> sync_mode: sync collection_order: release Collection: diff --git a/modules/builder.py b/modules/builder.py index 5a8ff554a..45a835284 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -602,6 +602,7 @@ def get_int(parent, method, data_in, methods_in, default_in, minimum=1, maximum= if method_name == "filters": for filter_name, filter_data in method_data.items(): modifier = filter_name[-4:].lower() + modifier = modifier if modifier in [".not", ".lte", ".gte"] else "" method = filter_name[:-4].lower() if modifier in [".not", ".lte", ".gte"] else filter_name.lower() if method in method_alias: filter_method = f"{method_alias[method]}{modifier}" diff --git a/modules/imdb.py b/modules/imdb.py index ad171288f..b57b8fadf 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -13,13 +13,14 @@ def __init__(self, config): self.config = config self.urls = { "list": "https://www.imdb.com/list/ls", - "search": "https://www.imdb.com/search/title/?" + "search": "https://www.imdb.com/search/title/?", + "keyword": "https://www.imdb.com/search/keyword/?" } def validate_imdb_url(self, imdb_url): imdb_url = imdb_url.strip() - if not imdb_url.startswith(self.urls["list"]) and not imdb_url.startswith(self.urls["search"]): - raise Failed(f"IMDb Error: {imdb_url} must begin with either:\n{self.urls['list']} (For Lists)\n{self.urls['search']} (For Searches)") + if not imdb_url.startswith(self.urls["list"]) and not imdb_url.startswith(self.urls["search"]) and not imdb_url.startswith(self.urls["keyword"]): + raise Failed(f"IMDb Error: {imdb_url} must begin with either:\n{self.urls['list']} (For Lists)\n{self.urls['search']} (For Searches)\n{self.urls['keyword']} (For Keyword Searches)") return imdb_url def get_imdb_ids_from_url(self, imdb_url, language, limit): @@ -32,24 +33,47 @@ def get_imdb_ids_from_url(self, imdb_url, language, limit): 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(f"IMDb Error: Failed to parse URL: {imdb_url}") - try: total = int(re.findall("(\\d+) title", results)[0]) - except IndexError: raise Failed(f"IMDb Error: No Results at URL: {imdb_url}") + if imdb_url.startswith(self.urls["keyword"]): + results = self.send_request(current_url, header).xpath("//div[@class='desc']/text()") + total = None + for result in results: + if "title" in result: + try: + total = int(re.findall("(\\d+) title", result)[0]) + break + except IndexError: + pass + if total is None: + raise Failed(f"IMDb Error: No Results at URL: {imdb_url}") + item_count = 50 + else: + try: results = self.send_request(current_url, header).xpath("//div[@class='desc']/span/text()")[0].replace(",", "") + 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(f"IMDb Error: No Results at URL: {imdb_url}") + item_count = 250 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 "&page=" in current_url: current_url = re.sub("&page=\\d+", "", current_url) if limit < 1 or total < limit: limit = total - remainder = limit % 250 - if remainder == 0: remainder = 250 - num_of_pages = math.ceil(int(limit) / 250) + + remainder = limit % item_count + if remainder == 0: remainder = item_count + num_of_pages = math.ceil(int(limit) / item_count) for i in range(1, num_of_pages + 1): - start_num = (i - 1) * 250 + 1 - 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")) + start_num = (i - 1) * item_count + 1 + length = util.print_return(length, f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * item_count}") + if imdb_url.startswith(self.urls["keyword"]): + response = self.send_request(f"{current_url}&page={i}", header) + else: + response = self.send_request(f"{current_url}&count={remainder if i == num_of_pages else item_count}&start={start_num}", header) + if imdb_url.startswith(self.urls["keyword"]) and i == num_of_pages: + imdb_ids.extend(response.xpath("//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst")[:remainder]) + else: + 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(f"IMDb Error: No Movies Found at {imdb_url}") + else: raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}") @retry(stop_max_attempt_number=6, wait_fixed=10000) def send_request(self, url, header): diff --git a/modules/plex.py b/modules/plex.py index ac7af4e53..5d7b0d619 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -33,6 +33,7 @@ "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 +use_original_title_options = {"default": -1, "no": 0, "yes": 1} filter_alias = { "actor": "actors", "audience_rating": "audienceRating", @@ -577,8 +578,6 @@ def add_advanced_edit(attr, options, key=None, show_library=False): add_advanced_edit("season_display", season_display_options, key="flattenSeasons", show_library=True) add_advanced_edit("episode_ordering", episode_ordering_options, key="showOrdering", show_library=True) add_advanced_edit("metadata_language", metadata_language_options, key="languageOverride") - - use_original_title_options = {"default": -1, "no": 0, "yes": 1} add_advanced_edit("use_original_title", use_original_title_options, key="useOriginalTitle") if len(advance_edits) > 0: diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 475ad3918..c6be81147 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -89,7 +89,7 @@ def fmt_filter(record): util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ") util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ") util.centered(" |___/ ") -util.centered(" Version: 1.7.0 ") +util.centered(" Version: 1.7.1 ") util.separator() if my_tests: diff --git a/requirements.txt b/requirements.txt index 4b08a6e00..b53e2dfab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Remove # Less common, pinned -PlexAPI==4.5.1 +PlexAPI==4.5.2 tmdbv3api==1.7.5 trakt.py==4.3.0 # More common, flexible