Merge pull request #30 from meisnate12/develop

v1.1.0
pull/54/head v1.1.0
meisnate12 4 years ago committed by GitHub
commit f25a13982d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,5 @@
# Plex Meta Manager
#### Version 1.0.3
#### Version 1.1.0
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.
@ -17,6 +17,6 @@ The script is designed to work with most Metadata agents including the new Plex
* If you're getting an Error or have an Enhancement post in the [Issues](https://github.com/meisnate12/Plex-Meta-Manager/issues)
* If you have a configuration question visit the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions)
* To see user submited Metadata configuration files and you could even add your own go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs)
* To see user submitted Metadata configuration files and you could even add your own go to the [Plex Meta Manager Configs](https://github.com/meisnate12/Plex-Meta-Manager-Configs)
* Pull Request are welcome
* [Buy Me a Pizza](https://www.buymeacoffee.com/meisnate12)

@ -13,7 +13,8 @@ plex: # Can be individually specified pe
token: ####################
sync_mode: append
asset_directory: config/assets
show_unmanaged_collections: true
show_unmanaged: true
show_filtered: false
radarr: # Can be individually specified per library as well
url: http://192.168.1.12:7878
token: ################################

@ -23,9 +23,12 @@ 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[@{}='{}']/@{}".format(from_id, input_id, to_id))
ids = self.id_list.xpath("//anime[contains(@{}, '{}')]/@{}".format(from_id, input_id, to_id))
if len(ids) > 0:
if len(ids[0]) > 0: return ids[0] if to_id == "imdbid" else int(ids[0])
if from_id == "tvdbid": return [int(id) for id 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))
@ -77,7 +80,7 @@ class AniDBAPI:
movie_ids = []
for anidb_id in anime_ids:
try:
tmdb_id, tvdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id), language)
tmdb_id = self.convert_from_imdb(self.convert_anidb_to_imdb(anidb_id), language)
if tmdb_id: movie_ids.append(tmdb_id)
else: raise Failed
except Failed:
@ -90,27 +93,34 @@ class AniDBAPI:
return movie_ids, show_ids
def convert_from_imdb(self, imdb_id, language):
if self.Cache:
tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb_id)
expired = False
if not tmdb_id:
tmdb_id, expired = self.Cache.get_tmdb_from_imdb(imdb_id)
if expired:
tmdb_id = None
else:
tmdb_id = None
from_cache = tmdb_id is not None
output_tmdb_ids = []
if not isinstance(imdb_id, list):
imdb_id = [imdb_id]
for imdb in imdb_id:
if self.Cache:
tmdb_id, tvdb_id = self.Cache.get_ids_from_imdb(imdb)
expired = False
if not tmdb_id:
tmdb_id, expired = self.Cache.get_tmdb_from_imdb(imdb)
if expired:
tmdb_id = None
else:
tmdb_id = None
from_cache = tmdb_id is not None
if not tmdb_id and self.TMDb:
try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id)
except Failed: pass
if not tmdb_id and self.Trakt:
try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb_id)
except Failed: pass
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 self.Cache and tmdb_id and expired is not False:
self.Cache.update_imdb("movie", expired, imdb_id, tmdb_id)
return tmdb_id
if not tmdb_id and self.TMDb:
try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb)
except Failed: pass
if not tmdb_id and self.Trakt:
try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb)
except Failed: pass
try:
if tmdb_id and not from_cache: self.TMDb.get_movie(tmdb_id)
except Failed: tmdb_id = None
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))
elif len(output_tmdb_ids) == 1: return output_tmdb_ids[0]
else: return output_tmdb_ids

@ -129,7 +129,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.Trakt is None else "Successful"))
logger.info("My Anime List Connection {}".format("Failed" if self.MyAnimeList is None else "Successful"))
else:
logger.warning("mal attribute not found")
@ -142,7 +142,8 @@ class Config:
self.general["plex"]["token"] = check_for_attribute(self.data, "token", parent="plex", default_is_none=True) if "plex" in self.data else None
self.general["plex"]["asset_directory"] = check_for_attribute(self.data, "asset_directory", parent="plex", var_type="path", default=os.path.join(default_dir, "assets")) if "plex" in self.data else os.path.join(default_dir, "assets")
self.general["plex"]["sync_mode"] = check_for_attribute(self.data, "sync_mode", parent="plex", default="append", test_list=["append", "sync"], options="| \tappend (Only Add Items to the Collection)\n| \tsync (Add & Remove Items from the Collection)") if "plex" in self.data else "append"
self.general["plex"]["show_unmanaged_collections"] = check_for_attribute(self.data, "show_unmanaged_collections", parent="plex", var_type="bool", default=True) if "plex" in self.data else True
self.general["plex"]["show_unmanaged"] = check_for_attribute(self.data, "show_unmanaged", parent="plex", var_type="bool", default=True) if "plex" in self.data else True
self.general["plex"]["show_filtered"] = check_for_attribute(self.data, "show_filtered", parent="plex", var_type="bool", default=False) if "plex" in self.data else False
self.general["radarr"] = {}
self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", default_is_none=True) if "radarr" in self.data else None
@ -240,15 +241,25 @@ class Config:
else:
logger.warning("Config Warning: sync_mode attribute is blank using general value: {}".format(self.general["plex"]["sync_mode"]))
params["show_unmanaged_collections"] = self.general["plex"]["show_unmanaged_collections"]
if "plex" in libs[lib] and "show_unmanaged_collections" in libs[lib]["plex"]:
if libs[lib]["plex"]["show_unmanaged_collections"]:
if isinstance(libs[lib]["plex"]["show_unmanaged_collections"], bool):
params["plex"]["show_unmanaged_collections"] = libs[lib]["plex"]["show_unmanaged_collections"]
params["show_unmanaged"] = self.general["plex"]["show_unmanaged"]
if "plex" in libs[lib] and "show_unmanaged" in libs[lib]["plex"]:
if libs[lib]["plex"]["show_unmanaged"]:
if isinstance(libs[lib]["plex"]["show_unmanaged"], bool):
params["plex"]["show_unmanaged"] = libs[lib]["plex"]["show_unmanaged"]
else:
logger.warning("Config Warning: plex sub-attribute show_unmanaged_collections must be either true or false using general value: {}".format(self.general["plex"]["show_unmanaged_collections"]))
logger.warning("Config Warning: plex sub-attribute show_unmanaged must be either true or false using general value: {}".format(self.general["plex"]["show_unmanaged"]))
else:
logger.warning("Config Warning: radarr sub-attribute add is blank using general value: {}".format(self.general["radarr"]["add"]))
logger.warning("Config Warning: plex sub-attribute show_unmanaged is blank using general value: {}".format(self.general["plex"]["show_unmanaged"]))
params["show_filtered"] = self.general["plex"]["show_filtered"]
if "plex" in libs[lib] and "show_filtered" in libs[lib]["plex"]:
if libs[lib]["plex"]["show_filtered"]:
if isinstance(libs[lib]["plex"]["show_filtered"], bool):
params["plex"]["show_filtered"] = libs[lib]["plex"]["show_filtered"]
else:
logger.warning("Config Warning: plex sub-attribute show_filtered must be either true or false using general value: {}".format(self.general["plex"]["show_filtered"]))
else:
logger.warning("Config Warning: plex sub-attribute show_filtered is blank using general value: {}".format(self.general["plex"]["show_filtered"]))
params["tmdb"] = self.TMDb
params["tvdb"] = self.TVDb
@ -428,6 +439,7 @@ class Config:
backgrounds_found = []
collectionless = "plex_collectionless" in collections[c]
skip_collection = True
show_filtered = library.show_filtered
if "schedule" not in collections[c]:
skip_collection = False
@ -581,6 +593,9 @@ class Config:
elif method_name == "add_to_arr":
if isinstance(collections[c][m], bool): details[method_name] = collections[c][m]
else: raise Failed("Collection Error: add_to_arr must be either true or false")
elif method_name == "show_filtered":
if isinstance(collections[c][m], bool): show_filtered = collections[c][m]
else: raise Failed("Collection Error: show_filtered must be either true or false using the default false")
elif method_name in util.all_details: details[method_name] = collections[c][m]
elif method_name in ["year", "year.not"]: methods.append(("plex_search", [[(method_name, util.get_year_list(collections[c][m], method_name))]]))
elif method_name in ["decade", "decade.not"]: methods.append(("plex_search", [[(method_name, util.get_int_list(collections[c][m], util.remove_not(method_name)))]]))
@ -640,10 +655,12 @@ class Config:
final_filter = filter
if final_filter in util.movie_only_filters and library.is_show:
logger.error("Collection Error: {} filter only works for movie libraries".format(final_filter))
elif collections[c][m][filter] is None:
logger.error("Collection Error: {} filter is blank".format(final_filter))
elif final_filter in util.all_filters:
filters.append((final_filter, collections[c][m][filter]))
else:
logger.error("Collection Error: {} filter not supported".format(filter))
logger.error("Collection Error: {} filter not supported".format(final_filter))
elif method_name == "plex_collectionless":
new_dictionary = {}
@ -782,13 +799,13 @@ class Config:
elif current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
new_dictionary["year"] = get_int(method_name, "year", collections[c][m], current_time.year, min=1917, max=current_time.year + 1)
new_dictionary["limit"] = get_int(method_name, "limit", collections[c][m], 100, max=500)
if "sort_by" not in collections[c][m]: logger.warning("Collection Error: mal_season sort_by attribute not found using members as default")
elif not collections[c][m]["sort_by"]: logger.warning("Collection Error: mal_season sort_by attribute is blank using members as default")
elif collections[c][m]["sort_by"] not in util.mal_season_sort: logger.warning("Collection Error: mal_season sort_by attribute {} invalid must be either 'members' or 'score' using members as default".format(collections[c][m]["sort_by"]))
if "sort_by" not in collections[c][m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default")
elif not collections[c][m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default")
elif collections[c][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(collections[c][m]["sort_by"]))
else: new_dictionary["sort_by"] = util.mal_season_sort[collections[c][m]["sort_by"]]
if "season" not in collections[c][m]: logger.warning("Collection Error: mal_season season attribute not found using the current season: {} as default".format(new_dictionary["season"]))
elif not collections[c][m]["season"]: logger.warning("Collection Error: mal_season season attribute is blank using the current season: {} as default".format(new_dictionary["season"]))
elif collections[c][m]["season"] not in util.pretty_seasons: logger.warning("Collection Error: mal_season season attribute {} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {} as default".format(collections[c][m]["season"], new_dictionary["season"]))
if "season" not in collections[c][m]: logger.warning("Collection Warning: mal_season season attribute not found using the current season: {} as default".format(new_dictionary["season"]))
elif not collections[c][m]["season"]: logger.warning("Collection Warning: mal_season season attribute is blank using the current season: {} as default".format(new_dictionary["season"]))
elif collections[c][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(collections[c][m]["season"], new_dictionary["season"]))
else: new_dictionary["season"] = collections[c][m]["season"]
methods.append((method_name, [new_dictionary]))
elif method_name == "mal_userlist":
@ -796,13 +813,13 @@ class Config:
if "username" not in collections[c][m]: raise Failed("Collection Error: mal_userlist username attribute is required")
elif not collections[c][m]["username"]: raise Failed("Collection Error: mal_userlist username attribute is blank")
else: new_dictionary["username"] = collections[c][m]["username"]
if "status" not in collections[c][m]: logger.warning("Collection Error: mal_season status attribute not found using all as default")
elif not collections[c][m]["status"]: logger.warning("Collection Error: mal_season status attribute is blank using all as default")
elif collections[c][m]["status"] not in util.mal_userlist_status: logger.warning("Collection Error: mal_season status attribute {} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default".format(collections[c][m]["status"]))
if "status" not in collections[c][m]: logger.warning("Collection Warning: mal_season status attribute not found using all as default")
elif not collections[c][m]["status"]: logger.warning("Collection Warning: mal_season status attribute is blank using all as default")
elif collections[c][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(collections[c][m]["status"]))
else: new_dictionary["status"] = util.mal_userlist_status[collections[c][m]["status"]]
if "sort_by" not in collections[c][m]: logger.warning("Collection Error: mal_season sort_by attribute not found using score as default")
elif not collections[c][m]["sort_by"]: logger.warning("Collection Error: mal_season sort_by attribute is blank using score as default")
elif collections[c][m]["sort_by"] not in util.mal_userlist_sort: logger.warning("Collection Error: mal_season sort_by attribute {} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default".format(collections[c][m]["sort_by"]))
if "sort_by" not in collections[c][m]: logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default")
elif not collections[c][m]["sort_by"]: logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default")
elif collections[c][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(collections[c][m]["sort_by"]))
else: new_dictionary["sort_by"] = util.mal_userlist_sort[collections[c][m]["sort_by"]]
new_dictionary["limit"] = get_int(method_name, "limit", collections[c][m], 100, max=1000)
methods.append((method_name, [new_dictionary]))
@ -940,24 +957,36 @@ class Config:
elif "trakt" in method: items_found += check_map(self.Trakt.get_items(method, value, library.is_movie))
else: logger.error("Collection Error: {} method not supported".format(method))
if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, map=map)
if len(items) > 0: map = library.add_to_collection(collection_obj if collection_obj else collection_name, items, filters, show_filtered, map, movie_map, show_map)
else: logger.error("No items found to add to this collection ")
if len(missing_movies) > 0 or len(missing_shows) > 0:
logger.info("")
if len(missing_movies) > 0:
not_lang = None
terms = None
for filter_method, filter_data in filters:
if filter_method.startswith("original_language"):
terms = filter_data if isinstance(filter_data, list) else [lang.strip().lower() for lang in str(filter_data).split(",")]
not_lang = filter_method.endswith(".not")
break
missing_movies_with_names = []
for missing_id in missing_movies:
try:
title = str(self.TMDb.get_movie(missing_id).title)
missing_movies_with_names.append((title, missing_id))
logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id))
movie = self.TMDb.get_movie(missing_id)
title = str(movie.title)
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))
logger.info("{} Collection | ? | {} (TMDb: {})".format(collection_name, title, missing_id))
elif show_filtered is True:
logger.info("{} Collection | X | {} (TMDb: {})".format(collection_name, title, missing_id))
except Failed as e:
logger.error(e)
logger.info("{} Movie{} Missing".format(len(missing_movies), "s" if len(missing_movies) > 1 else ""))
logger.info("{} Movie{} Missing".format(len(missing_movies_with_names), "s" if len(missing_movies_with_names) > 1 else ""))
library.save_missing(collection_name, missing_movies_with_names, True)
if do_arr and library.Radarr:
library.Radarr.add_tmdb(missing_movies)
library.Radarr.add_tmdb([missing_id for title, missing_id in missing_movies_with_names])
if len(missing_shows) > 0 and library.is_show:
missing_shows_with_names = []
for missing_id in missing_shows:
@ -1040,10 +1069,36 @@ class Config:
if background[0] == "url": plex_collection.uploadArt(url=background[1])
else: plex_collection.uploadArt(filepath=background[1])
logger.info("Detail: {} updated background to [{}] {}".format(background[2], background[0], background[1]))
if library.asset_directory:
path = os.path.join(library.asset_directory, "{}".format(name_mapping))
dirs = [folder for folder in os.listdir(path) if os.path.isdir(os.path.join(path, folder))]
if len(dirs) > 0:
for item in plex_collection.items():
folder = os.path.basename(os.path.dirname(item.locations[0]))
if folder in dirs:
files = [file for file in os.listdir(os.path.join(path, folder)) if os.path.isfile(os.path.join(path, folder, file))]
poster_path = None
background_path = None
for file in files:
if poster_path is None and file.startswith("poster."):
poster_path = os.path.join(path, folder, file)
if background_path is None and file.startswith("background."):
background_path = os.path.join(path, folder, file)
if poster_path:
item.uploadPoster(filepath=poster_path)
logger.info("Detail: asset_directory updated {}'s poster to [file] {}".format(item.title, 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))
if poster_path is None and background_path is None:
logger.warning("No Files Found: {}".format(os.path.join(path, folder)))
else:
logger.warning("No Folder: {}".format(os.path.join(path, folder)))
except Exception as e:
util.print_stacktrace()
logger.error("Unknown Error: {}".format(e))
if library.show_unmanaged_collections is True:
if library.show_unmanaged is True:
logger.info("")
util.seperator("Unmanaged Collections in {} Library".format(library.name))
logger.info("")
@ -1067,10 +1122,14 @@ class Config:
for i, item in enumerate(items, 1):
length = util.print_return(length, "Processing: {}/{} {}".format(i, len(items), item.title))
id_type, main_id = self.get_id(item, library, length)
if id_type == "movie":
movie_map[main_id] = item.ratingKey
elif id_type == "show":
show_map[main_id] = item.ratingKey
if isinstance(main_id, list):
if id_type == "movie":
for m in main_id: movie_map[m] = item.ratingKey
elif id_type == "show":
for m in main_id: show_map[m] = item.ratingKey
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"))
return movie_map, show_map
@ -1130,6 +1189,17 @@ class Config:
if mal_id and not tmdb_id:
try: tmdb_id = self.MyAnimeListIDList.convert_mal_to_tmdb(mal_id)
except Failed: pass
if not tmdb_id and imdb_id and isinstance(imdb_id, list) and self.TMDb:
tmdb_id = []
new_imdb_id = []
for imdb in imdb_id:
try:
temp_tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb)
tmdb_id.append(temp_tmdb_id)
new_imdb_id.append(imdb)
except Failed:
continue
imdb_id = new_imdb_id
if not tmdb_id and imdb_id and self.TMDb:
try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id)
except Failed: pass
@ -1160,18 +1230,6 @@ class Config:
if not tvdb_id and imdb_id and self.Trakt and library.is_show:
try: tvdb_id = self.Trakt.convert_imdb_to_tvdb(imdb_id)
except Failed: pass
if tvdb_id and not anidb_id:
try: anidb_id = self.AniDB.convert_tvdb_to_anidb(tvdb_id)
except Failed: pass
if imdb_id and not anidb_id:
try: anidb_id = self.AniDB.convert_imdb_to_anidb(imdb_id)
except Failed: pass
if tvdb_id and not mal_id:
try: mal_id = self.MyAnimeListIDList.convert_tvdb_to_mal(tvdb_id)
except Failed: pass
if tmdb_id and not mal_id:
try: mal_id = self.MyAnimeListIDList.convert_tmdb_to_mal(tmdb_id)
except Failed: pass
if (not tmdb_id and library.is_movie) or (not tvdb_id and not ((anidb_id or mal_id) and tmdb_id) and library.is_show):
service_name = "TMDb ID" if library.is_movie else "TVDb ID"
@ -1194,8 +1252,13 @@ class Config:
elif id_name: error_message = "Configure TMDb or Trakt to covert {} to {}".format(id_name, service_name)
else: error_message = "No ID to convert to {}".format(service_name)
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):
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))
self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired)
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))
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))
self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired)
if tmdb_id and library.is_movie: return "movie", tmdb_id
elif tvdb_id and library.is_show: return "show", tvdb_id
elif (anidb_id or mal_id) and tmdb_id: return "movie", tmdb_id

@ -68,13 +68,16 @@ class PlexAPI:
except Failed as e: logger.error(e)
logger.info("{} library's Tautulli Connection {}".format(params["name"], "Failed" if self.Tautulli is None else "Successful"))
self.TMDb = params["tmdb"]
self.TVDb = params["tvdb"]
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.metadata_path = params["metadata_path"]
self.asset_directory = params["asset_directory"]
self.sync_mode = params["sync_mode"]
self.show_unmanaged_collections = params["show_unmanaged_collections"]
self.show_unmanaged = params["show_unmanaged"]
self.show_filtered = params["show_filtered"]
self.plex = params["plex"]
self.radarr = params["radarr"]
self.sonarr = params["sonarr"]
@ -160,7 +163,7 @@ class PlexAPI:
except yaml.scanner.ScannerError as e:
logger.error("YAML Error: {}".format(str(e).replace("\n", "\n|\t ")))
def add_to_collection(self, collection, items, filters, map={}):
def add_to_collection(self, collection, items, filters, show_filtered, map, movie_map, show_map):
name = collection.title if isinstance(collection, Collections) else collection
collection_items = collection.items() if isinstance(collection, Collections) else []
total = len(items)
@ -181,6 +184,23 @@ class PlexAPI:
if attr is None or attr < threshold_date:
match = False
break
elif method == "original_language":
terms = f[1] if isinstance(f[1], list) else [lang.lower() for lang in str(f[1]).split(", ")]
tmdb_id = None
movie = None
for key, value in movie_map.items():
if current.ratingKey == value:
try:
movie = self.TMDb.get_movie(key)
break
except Failed:
pass
if movie is None:
logger.warning("Filter Error: No TMDb ID found for {}".format(current.title))
continue
if (modifier == ".not" and movie.original_language in terms) or (modifier != ".not" and movie.original_language not in terms):
match = False
break
elif modifier in [".gte", ".lte"]:
if method == "originallyAvailableAt":
threshold_date = datetime.strptime(f[1], "%m/%d/%y")
@ -212,6 +232,8 @@ class PlexAPI:
util.print_end(length, "{} Collection | {} | {}".format(name, "=" if current in collection_items else "+", current.title))
if current in collection_items: 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))
return map
@ -294,10 +316,8 @@ class PlexAPI:
add_edit("originally_available", str(item.originallyAvailableAt)[:-9], self.metadata[m], key="originallyAvailableAt", value=originally_available)
add_edit("rating", item.rating, self.metadata[m], value=rating)
add_edit("content_rating", item.contentRating, self.metadata[m], key="contentRating")
if self.is_movie:
add_edit("original_title", item.originalTitle, self.metadata[m], key="originalTitle", value=original_title)
elif "original_title" in self.metadata[m]:
logger.error("Metadata Error: original_title does not work with shows")
originalTitle = item.originalTitle if self.is_movie else item._data.attrib.get("originalTitle")
add_edit("original_title", originalTitle, self.metadata[m], key="originalTitle", value=original_title)
add_edit("studio", item.studio, self.metadata[m], value=studio)
add_edit("tagline", item.tagline, self.metadata[m], value=tagline)
add_edit("summary", item.summary, self.metadata[m], value=summary)

@ -121,11 +121,11 @@ class TVDbAPI:
if status_message:
logger.info("Processing {}: {}".format(pretty, data))
if method == "tvdb_show":
try: show_ids.append(self.get_series(language, tvdb_id=int(data)))
except ValueError: show_ids.append(self.get_series(language, tvdb_url=data))
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)
elif method == "tvdb_movie":
try: movie_ids.append(self.get_movie(language, tvdb_id=int(data)))
except ValueError: movie_ids.append(self.get_movie(language, tvdb_url=data))
try: movie_ids.append(self.get_movie(language, tvdb_id=int(data)).id)
except ValueError: movie_ids.append(self.get_movie(language, tvdb_url=data).id)
elif method == "tvdb_list":
tmdb_ids, tvdb_ids = self.get_tvdb_ids_from_url(data, language)
movie_ids.extend(tmdb_ids)

@ -43,6 +43,7 @@ filter_alias = {
"genre": "genres",
"max_age": "max_age",
"originally_available": "originallyAvailableAt",
"original_language": "original_language",
"rating": "rating",
"studio": "studio",
"subtitle_language": "subtitle_language",
@ -361,6 +362,7 @@ all_filters = [
"genre", "genre.not",
"max_age",
"originally_available.gte", "originally_available.lte",
"original_language", "original_language.not",
"rating.gte", "rating.lte",
"studio", "studio.not",
"subtitle_language", "subtitle_language.not",
@ -372,6 +374,7 @@ movie_only_filters = [
"audio_language", "audio_language.not",
"country", "country.not",
"director", "director.not",
"original_language", "original_language.not",
"subtitle_language", "subtitle_language.not",
"video_resolution", "video_resolution.not",
"writer", "writer.not"

@ -56,7 +56,7 @@ logger.info(util.get_centered_text("| |_) | |/ _ \ \/ / | |\/| |/ _ \ __/ _` | |
logger.info(util.get_centered_text("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | "))
logger.info(util.get_centered_text("|_| |_|\___/_/\_\ |_| |_|\___|\__\__,_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| "))
logger.info(util.get_centered_text(" |___/ "))
logger.info(util.get_centered_text(" Version: 1.0.3 "))
logger.info(util.get_centered_text(" Version: 1.1.0 "))
util.seperator()
if args.test:

Loading…
Cancel
Save