Merge pull request #176 from meisnate12/develop

v1.7.1
pull/185/head 1.7.1
meisnate12 4 years ago committed by GitHub
commit 0f588aef0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,5 @@
# Plex Meta Manager # 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. 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 ## 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) 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. 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. 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. 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.

@ -1,15 +1,15 @@
templates: templates:
Chart Alpha: Chart Alpha:
sort_title: ++++_<<num>><<collection_name>> sort_title: +_<<num>><<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: alpha collection_order: alpha
Chart Release: Chart Release:
sort_title: ++++_<<collection_name>> sort_title: +_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Best of: Best of:
trakt_list: https://trakt.tv/users/lish408/lists/rotten-tomatoes-best-of-<<year>> trakt_list: https://trakt.tv/users/lish408/lists/rotten-tomatoes-best-of-<<year>>
sort_title: ++++_Best of <<year>> sort_title: +_Best of <<year>>
sync_mode: sync sync_mode: sync
summary: Rotten Tomatoes Best Movies of <<year>> summary: Rotten Tomatoes Best Movies of <<year>>
collection_order: release collection_order: release
@ -17,7 +17,7 @@ templates:
optional: optional:
- company - company
tmdb_company: <<company>> tmdb_company: <<company>>
sort_title: +++_<<collection_name>> sort_title: ++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: alpha collection_order: alpha
IMDb Genre: IMDb Genre:
@ -29,24 +29,24 @@ templates:
limit: <<limit>> limit: <<limit>>
- url: https://www.imdb.com/search/title/?title_type=<<title>>&release_date=1990-01-01,&user_rating=5.0,10.0&num_votes=100000,&genres=<<genre>>&sort=user_rating,desc - url: https://www.imdb.com/search/title/?title_type=<<title>>&release_date=1990-01-01,&user_rating=5.0,10.0&num_votes=100000,&genres=<<genre>>&sort=user_rating,desc
limit: <<limit>> limit: <<limit>>
sort_title: ++_<<collection_name>> sort_title: +++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: alpha collection_order: alpha
Other Genre: Other Genre:
sort_title: ++_<<collection_name>> sort_title: +++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: alpha collection_order: alpha
Actor: Actor:
actor: tmdb actor: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Actor Director: Actor Director:
actor: tmdb actor: tmdb
director: tmdb director: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Actor Director Writer: Actor Director Writer:
@ -54,33 +54,33 @@ templates:
director: tmdb director: tmdb
writer: tmdb writer: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Actor Writer: Actor Writer:
actor: tmdb actor: tmdb
writer: tmdb writer: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Director: Director:
director: tmdb director: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Director Writer: Director Writer:
director: tmdb director: tmdb
writer: tmdb writer: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Writer: Writer:
writer: tmdb writer: tmdb
tmdb_person: <<person>> tmdb_person: <<person>>
sort_title: +_<<collection_name>> sort_title: ++++_<<collection_name>>
sync_mode: sync sync_mode: sync
collection_order: release collection_order: release
Collection: Collection:

@ -602,6 +602,7 @@ class CollectionBuilder:
if method_name == "filters": if method_name == "filters":
for filter_name, filter_data in method_data.items(): for filter_name, filter_data in method_data.items():
modifier = filter_name[-4:].lower() 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() method = filter_name[:-4].lower() if modifier in [".not", ".lte", ".gte"] else filter_name.lower()
if method in method_alias: if method in method_alias:
filter_method = f"{method_alias[method]}{modifier}" filter_method = f"{method_alias[method]}{modifier}"

@ -13,13 +13,14 @@ class IMDbAPI:
self.config = config self.config = config
self.urls = { self.urls = {
"list": "https://www.imdb.com/list/ls", "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): def validate_imdb_url(self, imdb_url):
imdb_url = imdb_url.strip() imdb_url = imdb_url.strip()
if not imdb_url.startswith(self.urls["list"]) and not imdb_url.startswith(self.urls["search"]): 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)") 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 return imdb_url
def get_imdb_ids_from_url(self, imdb_url, language, limit): def get_imdb_ids_from_url(self, imdb_url, language, limit):
@ -32,24 +33,47 @@ class IMDbAPI:
header = {"Accept-Language": language} header = {"Accept-Language": language}
length = 0 length = 0
imdb_ids = [] imdb_ids = []
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(",", "") 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}") except IndexError: raise Failed(f"IMDb Error: Failed to parse URL: {imdb_url}")
try: total = int(re.findall("(\\d+) title", results)[0]) try: total = int(re.findall("(\\d+) title", results)[0])
except IndexError: raise Failed(f"IMDb Error: No Results at URL: {imdb_url}") 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 "&start=" in current_url: current_url = re.sub("&start=\\d+", "", current_url)
if "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url) if "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url)
if "&page=" in current_url: current_url = re.sub("&page=\\d+", "", current_url)
if limit < 1 or total < limit: limit = total if limit < 1 or total < limit: limit = total
remainder = limit % 250
if remainder == 0: remainder = 250 remainder = limit % item_count
num_of_pages = math.ceil(int(limit) / 250) if remainder == 0: remainder = item_count
num_of_pages = math.ceil(int(limit) / item_count)
for i in range(1, num_of_pages + 1): for i in range(1, num_of_pages + 1):
start_num = (i - 1) * 250 + 1 start_num = (i - 1) * 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 * 250}") length = util.print_return(length, f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * item_count}")
response = self.send_request(f"{current_url}&count={remainder if i == num_of_pages else 250}&start={start_num}", header) 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")) imdb_ids.extend(response.xpath("//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst"))
util.print_end(length) util.print_end(length)
if imdb_ids: return imdb_ids if imdb_ids: return imdb_ids
else: raise Failed(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) @retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url, header): def send_request(self, url, header):

@ -33,6 +33,7 @@ plex_languages = ["default", "ar-SA", "ca-ES", "cs-CZ", "da-DK", "de-DE", "el-GR
"ru-RU", "sk-SK", "sv-SE", "th-TH", "tr-TR", "uk-UA", "vi-VN", "zh-CN", "zh-HK", "zh-TW"] "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 = {lang.lower(): lang for lang in plex_languages}
metadata_language_options["default"] = None metadata_language_options["default"] = None
use_original_title_options = {"default": -1, "no": 0, "yes": 1}
filter_alias = { filter_alias = {
"actor": "actors", "actor": "actors",
"audience_rating": "audienceRating", "audience_rating": "audienceRating",
@ -577,8 +578,6 @@ class PlexAPI:
add_advanced_edit("season_display", season_display_options, key="flattenSeasons", show_library=True) 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("episode_ordering", episode_ordering_options, key="showOrdering", show_library=True)
add_advanced_edit("metadata_language", metadata_language_options, key="languageOverride") 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") add_advanced_edit("use_original_title", use_original_title_options, key="useOriginalTitle")
if len(advance_edits) > 0: if len(advance_edits) > 0:

@ -89,7 +89,7 @@ util.centered("| |_) | |/ _ \\ \\/ / | |\\/| |/ _ \\ __/ _` | | |\\/| |/ _` | '_
util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ") util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ")
util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ") util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ")
util.centered(" |___/ ") util.centered(" |___/ ")
util.centered(" Version: 1.7.0 ") util.centered(" Version: 1.7.1 ")
util.separator() util.separator()
if my_tests: if my_tests:

@ -1,6 +1,6 @@
# Remove # Remove
# Less common, pinned # Less common, pinned
PlexAPI==4.5.1 PlexAPI==4.5.2
tmdbv3api==1.7.5 tmdbv3api==1.7.5
trakt.py==4.3.0 trakt.py==4.3.0
# More common, flexible # More common, flexible

Loading…
Cancel
Save