mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-10 06:54:21 +00:00
#365 added collection_level
This commit is contained in:
parent
ddc81cde11
commit
ec0b1ba329
4 changed files with 125 additions and 49 deletions
|
@ -4,7 +4,7 @@ from modules import anidb, anilist, icheckmovies, imdb, letterboxd, mal, plex, r
|
||||||
from modules.util import Failed, ImageData
|
from modules.util import Failed, ImageData
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from plexapi.exceptions import BadRequest, NotFound
|
from plexapi.exceptions import BadRequest, NotFound
|
||||||
from plexapi.video import Movie, Show
|
from plexapi.video import Movie, Show, Season, Episode
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
logger = logging.getLogger("Plex Meta Manager")
|
logger = logging.getLogger("Plex Meta Manager")
|
||||||
|
@ -64,7 +64,7 @@ filter_translation = {
|
||||||
modifier_alias = {".greater": ".gt", ".less": ".lt"}
|
modifier_alias = {".greater": ".gt", ".less": ".lt"}
|
||||||
all_builders = anidb.builders + anilist.builders + icheckmovies.builders + imdb.builders + letterboxd.builders + \
|
all_builders = anidb.builders + anilist.builders + icheckmovies.builders + imdb.builders + letterboxd.builders + \
|
||||||
mal.builders + plex.builders + stevenlu.builders + tautulli.builders + tmdb.builders + trakt.builders + tvdb.builders
|
mal.builders + plex.builders + stevenlu.builders + tautulli.builders + tmdb.builders + trakt.builders + tvdb.builders
|
||||||
show_only_builders = ["tmdb_network", "tmdb_show", "tmdb_show_details", "tvdb_show", "tvdb_show_details"]
|
show_only_builders = ["tmdb_network", "tmdb_show", "tmdb_show_details", "tvdb_show", "tvdb_show_details", "collection_level"]
|
||||||
movie_only_builders = [
|
movie_only_builders = [
|
||||||
"letterboxd_list", "letterboxd_list_details", "icheckmovies_list", "icheckmovies_list_details", "stevenlu_popular",
|
"letterboxd_list", "letterboxd_list_details", "icheckmovies_list", "icheckmovies_list_details", "stevenlu_popular",
|
||||||
"tmdb_collection", "tmdb_collection_details", "tmdb_movie", "tmdb_movie_details", "tmdb_now_playing",
|
"tmdb_collection", "tmdb_collection_details", "tmdb_movie", "tmdb_movie_details", "tmdb_now_playing",
|
||||||
|
@ -78,13 +78,19 @@ poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "f
|
||||||
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
|
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
|
||||||
boolean_details = ["visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "item_assets", "missing_only_released"]
|
boolean_details = ["visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "item_assets", "missing_only_released"]
|
||||||
string_details = ["sort_title", "content_rating", "name_mapping"]
|
string_details = ["sort_title", "content_rating", "name_mapping"]
|
||||||
ignored_details = ["smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "tmdb_person", "build_collection", "collection_order", "validate_builders"]
|
ignored_details = [
|
||||||
details = ["collection_mode", "collection_order", "label"] + boolean_details + string_details
|
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test",
|
||||||
|
"tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders"
|
||||||
|
]
|
||||||
|
details = ["collection_mode", "collection_order", "collection_level", "label"] + boolean_details + string_details
|
||||||
collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \
|
collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \
|
||||||
poster_details + background_details + summary_details + string_details
|
poster_details + background_details + summary_details + string_details
|
||||||
item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay"] + list(plex.item_advance_keys.keys())
|
item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay"] + list(plex.item_advance_keys.keys())
|
||||||
radarr_details = ["radarr_add", "radarr_add_existing", "radarr_folder", "radarr_monitor", "radarr_search", "radarr_availability", "radarr_quality", "radarr_tag"]
|
radarr_details = ["radarr_add", "radarr_add_existing", "radarr_folder", "radarr_monitor", "radarr_search", "radarr_availability", "radarr_quality", "radarr_tag"]
|
||||||
sonarr_details = ["sonarr_add", "sonarr_add_existing", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series", "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag"]
|
sonarr_details = [
|
||||||
|
"sonarr_add", "sonarr_add_existing", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series",
|
||||||
|
"sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag"
|
||||||
|
]
|
||||||
all_filters = [
|
all_filters = [
|
||||||
"actor", "actor.not",
|
"actor", "actor.not",
|
||||||
"audio_language", "audio_language.not",
|
"audio_language", "audio_language.not",
|
||||||
|
@ -130,7 +136,7 @@ movie_only_filters = [
|
||||||
"writer", "writer.not"
|
"writer", "writer.not"
|
||||||
]
|
]
|
||||||
show_only_filters = ["first_episode_aired", "last_episode_aired", "network"]
|
show_only_filters = ["first_episode_aired", "last_episode_aired", "network"]
|
||||||
smart_invalid = ["collection_order"]
|
smart_invalid = ["collection_order", "collection_level"]
|
||||||
smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label"] + radarr_details + sonarr_details
|
smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label"] + radarr_details + sonarr_details
|
||||||
custom_sort_builders = [
|
custom_sort_builders = [
|
||||||
"tmdb_list", "tmdb_popular", "tmdb_now_playing", "tmdb_top_rated",
|
"tmdb_list", "tmdb_popular", "tmdb_now_playing", "tmdb_top_rated",
|
||||||
|
@ -142,6 +148,10 @@ custom_sort_builders = [
|
||||||
"mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_movie", "mal_ova", "mal_special",
|
"mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_movie", "mal_ova", "mal_special",
|
||||||
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio"
|
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio"
|
||||||
]
|
]
|
||||||
|
parts_collection_valid = [
|
||||||
|
"plex_search", "trakt_list", "trakt_list_details", "collection_mode", "label", "visible_library",
|
||||||
|
"visible_home", "visible_shared", "show_missing", "save_missing", "missing_only_released"
|
||||||
|
] + summary_details + poster_details + background_details + string_details
|
||||||
|
|
||||||
class CollectionBuilder:
|
class CollectionBuilder:
|
||||||
def __init__(self, config, library, metadata, name, no_missing, data):
|
def __init__(self, config, library, metadata, name, no_missing, data):
|
||||||
|
@ -165,6 +175,7 @@ class CollectionBuilder:
|
||||||
self.sonarr_details = {}
|
self.sonarr_details = {}
|
||||||
self.missing_movies = []
|
self.missing_movies = []
|
||||||
self.missing_shows = []
|
self.missing_shows = []
|
||||||
|
self.missing_parts = []
|
||||||
self.builders = []
|
self.builders = []
|
||||||
self.filters = []
|
self.filters = []
|
||||||
self.tmdb_filters = []
|
self.tmdb_filters = []
|
||||||
|
@ -411,6 +422,21 @@ class CollectionBuilder:
|
||||||
else:
|
else:
|
||||||
raise Failed(f"Collection Error: {self.data[methods['collection_order']]} collection_order invalid\n\trelease (Order Collection by release dates)\n\talpha (Order Collection Alphabetically)\n\tcustom (Custom Order Collection)")
|
raise Failed(f"Collection Error: {self.data[methods['collection_order']]} collection_order invalid\n\trelease (Order Collection by release dates)\n\talpha (Order Collection Alphabetically)\n\tcustom (Custom Order Collection)")
|
||||||
|
|
||||||
|
self.collection_level = "movie" if self.library.is_movie else "show"
|
||||||
|
if "collection_level" in methods:
|
||||||
|
logger.debug("")
|
||||||
|
logger.debug("Validating Method: collection_level")
|
||||||
|
if self.data[methods["collection_level"]] is None:
|
||||||
|
raise Failed(f"Collection Warning: collection_level attribute is blank")
|
||||||
|
else:
|
||||||
|
logger.debug(f"Value: {self.data[methods['collection_level']]}")
|
||||||
|
if self.data[methods["collection_level"]].lower() in plex.collection_level_options:
|
||||||
|
self.collection_level = self.data[methods["collection_level"]].lower()
|
||||||
|
else:
|
||||||
|
raise Failed(f"Collection Error: {self.data[methods['collection_level']]} collection_level invalid\n\tseason (Collection at the Season Level)\n\tepisode (Collection at the Episode Level)")
|
||||||
|
self.parts_collection = self.collection_level in ["season", "episode"]
|
||||||
|
self.media_type = self.collection_level.capitalize()
|
||||||
|
|
||||||
if "tmdb_person" in methods:
|
if "tmdb_person" in methods:
|
||||||
logger.debug("")
|
logger.debug("")
|
||||||
logger.debug("Validating Method: tmdb_person")
|
logger.debug("Validating Method: tmdb_person")
|
||||||
|
@ -479,6 +505,8 @@ class CollectionBuilder:
|
||||||
cant_interact("smart_url", "collectionless")
|
cant_interact("smart_url", "collectionless")
|
||||||
cant_interact("smart_url", "run_again")
|
cant_interact("smart_url", "run_again")
|
||||||
cant_interact("smart_label_collection", "smart_url", fail=True)
|
cant_interact("smart_label_collection", "smart_url", fail=True)
|
||||||
|
cant_interact("smart_label_collection", "parts_collection", fail=True)
|
||||||
|
cant_interact("smart_url", "parts_collection", fail=True)
|
||||||
|
|
||||||
self.smart = self.smart_url or self.smart_label_collection
|
self.smart = self.smart_url or self.smart_label_collection
|
||||||
|
|
||||||
|
@ -501,6 +529,7 @@ class CollectionBuilder:
|
||||||
elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries")
|
elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries")
|
||||||
elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries")
|
elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries")
|
||||||
elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries")
|
elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries")
|
||||||
|
elif self.parts_collection and method_name not in parts_collection_valid: raise Failed(f"Collection Error: {method_final} attribute does not work with Collection Level: {self.details['collection_level'].capitalize()}")
|
||||||
elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections")
|
elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections")
|
||||||
elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection")
|
elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection")
|
||||||
elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter")
|
elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter")
|
||||||
|
@ -869,7 +898,8 @@ class CollectionBuilder:
|
||||||
for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
|
for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
|
||||||
new_dictionary = {}
|
new_dictionary = {}
|
||||||
if method_name == "plex_search":
|
if method_name == "plex_search":
|
||||||
new_dictionary = self.build_filter("plex_search", dict_data)
|
type_override = f"{self.collection_level}s" if self.collection_level in plex.collection_level_options else None
|
||||||
|
new_dictionary = self.build_filter("plex_search", dict_data, type_override=type_override)
|
||||||
elif method_name == "plex_collectionless":
|
elif method_name == "plex_collectionless":
|
||||||
prefix_list = util.parse("exclude_prefix", dict_data, datatype="list", methods=dict_methods)
|
prefix_list = util.parse("exclude_prefix", dict_data, datatype="list", methods=dict_methods)
|
||||||
exact_list = util.parse("exclude", dict_data, datatype="list", methods=dict_methods)
|
exact_list = util.parse("exclude", dict_data, datatype="list", methods=dict_methods)
|
||||||
|
@ -1070,12 +1100,12 @@ class CollectionBuilder:
|
||||||
for i, input_data in enumerate(ids, 1):
|
for i, input_data in enumerate(ids, 1):
|
||||||
input_id, id_type = input_data
|
input_id, id_type = input_data
|
||||||
util.print_return(f"Parsing ID {i}/{total_ids}")
|
util.print_return(f"Parsing ID {i}/{total_ids}")
|
||||||
if id_type == "tmdb":
|
if id_type == "tmdb" and not self.parts_collection:
|
||||||
if input_id in self.library.movie_map:
|
if input_id in self.library.movie_map:
|
||||||
rating_keys.append(self.library.movie_map[input_id][0])
|
rating_keys.append(self.library.movie_map[input_id][0])
|
||||||
elif input_id not in self.missing_movies:
|
elif input_id not in self.missing_movies:
|
||||||
self.missing_movies.append(input_id)
|
self.missing_movies.append(input_id)
|
||||||
elif id_type in ["tvdb", "tmdb_show"]:
|
elif id_type in ["tvdb", "tmdb_show"] and not self.parts_collection:
|
||||||
if id_type == "tmdb_show":
|
if id_type == "tmdb_show":
|
||||||
try:
|
try:
|
||||||
input_id = self.config.Convert.tmdb_to_tvdb(input_id, fail=True)
|
input_id = self.config.Convert.tmdb_to_tvdb(input_id, fail=True)
|
||||||
|
@ -1086,7 +1116,7 @@ class CollectionBuilder:
|
||||||
rating_keys.append(self.library.show_map[input_id][0])
|
rating_keys.append(self.library.show_map[input_id][0])
|
||||||
elif input_id not in self.missing_shows:
|
elif input_id not in self.missing_shows:
|
||||||
self.missing_shows.append(input_id)
|
self.missing_shows.append(input_id)
|
||||||
elif id_type == "imdb":
|
elif id_type == "imdb" and not self.parts_collection:
|
||||||
if input_id in self.library.imdb_map:
|
if input_id in self.library.imdb_map:
|
||||||
rating_keys.append(self.library.imdb_map[input_id][0])
|
rating_keys.append(self.library.imdb_map[input_id][0])
|
||||||
else:
|
else:
|
||||||
|
@ -1103,6 +1133,28 @@ class CollectionBuilder:
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
continue
|
continue
|
||||||
|
elif id_type == "tvdb_season" and self.collection_level == "season":
|
||||||
|
show_id, season_num = input_id.split("_")
|
||||||
|
if int(show_id) in self.library.show_map:
|
||||||
|
show_item = self.library.fetchItem(self.library.show_map[int(show_id)][0])
|
||||||
|
try:
|
||||||
|
episode_item = show_item.season(season=int(season_num))
|
||||||
|
rating_keys.append(episode_item.ratingKey)
|
||||||
|
except NotFound:
|
||||||
|
self.missing_parts.append(f"{show_item.title} Season: {season_num} Missing")
|
||||||
|
elif int(show_id) not in self.missing_shows:
|
||||||
|
self.missing_shows.append(int(show_id))
|
||||||
|
elif id_type == "tvdb_episode" and self.collection_level == "episode":
|
||||||
|
show_id, season_num, episode_num = input_id.split("_")
|
||||||
|
if int(show_id) in self.library.show_map:
|
||||||
|
show_item = self.library.fetchItem(self.library.show_map[int(show_id)][0])
|
||||||
|
try:
|
||||||
|
episode_item = show_item.episode(season=int(season_num), episode=int(episode_num))
|
||||||
|
rating_keys.append(episode_item.ratingKey)
|
||||||
|
except NotFound:
|
||||||
|
self.missing_parts.append(f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
|
||||||
|
elif int(show_id) not in self.missing_shows:
|
||||||
|
self.missing_shows.append(int(show_id))
|
||||||
util.print_end()
|
util.print_end()
|
||||||
|
|
||||||
if len(rating_keys) > 0:
|
if len(rating_keys) > 0:
|
||||||
|
@ -1125,7 +1177,7 @@ class CollectionBuilder:
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
continue
|
continue
|
||||||
current_title = f"{current.title} ({current.year})" if current.year else current.title
|
current_title = self.item_title(current)
|
||||||
if self.check_filters(current, f"{(' ' * (max_length - len(str(i))))}{i}/{total}"):
|
if self.check_filters(current, f"{(' ' * (max_length - len(str(i))))}{i}/{total}"):
|
||||||
self.rating_keys.append(key)
|
self.rating_keys.append(key)
|
||||||
else:
|
else:
|
||||||
|
@ -1133,7 +1185,7 @@ class CollectionBuilder:
|
||||||
if self.details["show_filtered"] is True:
|
if self.details["show_filtered"] is True:
|
||||||
logger.info(f"{name} Collection | X | {current_title}")
|
logger.info(f"{name} Collection | X | {current_title}")
|
||||||
|
|
||||||
def build_filter(self, method, plex_filter, smart=False):
|
def build_filter(self, method, plex_filter, smart=False, type_override=None):
|
||||||
if smart:
|
if smart:
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.info(f"Validating Method: {method}")
|
logger.info(f"Validating Method: {method}")
|
||||||
|
@ -1149,7 +1201,9 @@ class CollectionBuilder:
|
||||||
if "any" in filter_alias and "all" in filter_alias:
|
if "any" in filter_alias and "all" in filter_alias:
|
||||||
raise Failed(f"Collection Error: Cannot have more then one base")
|
raise Failed(f"Collection Error: Cannot have more then one base")
|
||||||
|
|
||||||
if smart and "type" in filter_alias and self.library.is_show:
|
if type_override:
|
||||||
|
sort_type = type_override
|
||||||
|
elif smart and "type" in filter_alias and self.library.is_show:
|
||||||
if plex_filter[filter_alias["type"]] not in ["shows", "seasons", "episodes"]:
|
if plex_filter[filter_alias["type"]] not in ["shows", "seasons", "episodes"]:
|
||||||
raise Failed(f"Collection Error: type: {plex_filter[filter_alias['type']]} is invalid, must be either shows, season, or episodes")
|
raise Failed(f"Collection Error: type: {plex_filter[filter_alias['type']]} is invalid, must be either shows, season, or episodes")
|
||||||
sort_type = plex_filter[filter_alias["type"]]
|
sort_type = plex_filter[filter_alias["type"]]
|
||||||
|
@ -1388,8 +1442,8 @@ class CollectionBuilder:
|
||||||
|
|
||||||
def fetch_item(self, item):
|
def fetch_item(self, item):
|
||||||
try:
|
try:
|
||||||
current = self.library.fetchItem(item.ratingKey if isinstance(item, (Movie, Show)) else int(item))
|
current = self.library.fetchItem(item.ratingKey if isinstance(item, (Movie, Show, Season, Episode)) else int(item))
|
||||||
if not isinstance(current, (Movie, Show)):
|
if not isinstance(current, (Movie, Show, Season, Episode)):
|
||||||
raise NotFound
|
raise NotFound
|
||||||
return current
|
return current
|
||||||
except (BadRequest, NotFound):
|
except (BadRequest, NotFound):
|
||||||
|
@ -1404,19 +1458,17 @@ class CollectionBuilder:
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
continue
|
continue
|
||||||
current_title = f"{current.title} ({current.year})" if current.year else current.title
|
|
||||||
current_operation = "=" if current in collection_items else "+"
|
current_operation = "=" if current in collection_items else "+"
|
||||||
logger.info(util.adjust_space(f"{name} Collection | {current_operation} | {current_title}"))
|
logger.info(util.adjust_space(f"{name} Collection | {current_operation} | {self.item_title(current)}"))
|
||||||
if current in collection_items:
|
if current in collection_items:
|
||||||
self.plex_map[current.ratingKey] = None
|
self.plex_map[current.ratingKey] = None
|
||||||
elif self.smart_label_collection:
|
elif self.smart_label_collection:
|
||||||
self.library.query_data(current.addLabel, name)
|
self.library.query_data(current.addLabel, name)
|
||||||
else:
|
else:
|
||||||
self.library.query_data(current.addCollection, name)
|
self.library.query_data(current.addCollection, name)
|
||||||
media_type = f"{'Movie' if self.library.is_movie else 'Show'}{'s' if total > 1 else ''}"
|
|
||||||
util.print_end()
|
util.print_end()
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.info(f"{total} {media_type} Processed")
|
logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed")
|
||||||
|
|
||||||
def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False):
|
def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False):
|
||||||
if self.tmdb_filters or check_released:
|
if self.tmdb_filters or check_released:
|
||||||
|
@ -1610,6 +1662,26 @@ class CollectionBuilder:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
if self.run_again:
|
if self.run_again:
|
||||||
self.run_again_shows.extend(missing_tvdb_ids)
|
self.run_again_shows.extend(missing_tvdb_ids)
|
||||||
|
if len(self.missing_parts) > 0 and self.library.is_show and self.details["save_missing"] is True:
|
||||||
|
for missing in self.missing_parts:
|
||||||
|
logger.info(f"{self.name} Collection | X | {missing}")
|
||||||
|
|
||||||
|
def item_title(self, item):
|
||||||
|
if self.collection_level == "season":
|
||||||
|
if f"Season {item.index}" == item.title:
|
||||||
|
return f"{item.parentTitle} {item.title}"
|
||||||
|
else:
|
||||||
|
return f"{item.parentTitle} Season {item.index}: {item.title}"
|
||||||
|
elif self.collection_level == "episode":
|
||||||
|
text = f"{item.grandparentTitle} S{util.add_zero(item.parentIndex)}E{util.add_zero(item.index)}"
|
||||||
|
if f"Season {item.parentIndex}" == item.parentTitle:
|
||||||
|
return f"{text}: {item.title}"
|
||||||
|
else:
|
||||||
|
return f"{text}: {item.parentTitle}: {item.title}"
|
||||||
|
elif self.collection_level == "movie" and item.year:
|
||||||
|
return f"{item.title} ({item.year})"
|
||||||
|
else:
|
||||||
|
return item.title
|
||||||
|
|
||||||
def sync_collection(self):
|
def sync_collection(self):
|
||||||
count_removed = 0
|
count_removed = 0
|
||||||
|
@ -1620,7 +1692,7 @@ class CollectionBuilder:
|
||||||
util.separator(f"Removed from {self.name} Collection", space=False, border=False)
|
util.separator(f"Removed from {self.name} Collection", space=False, border=False)
|
||||||
logger.info("")
|
logger.info("")
|
||||||
self.library.reload(item)
|
self.library.reload(item)
|
||||||
logger.info(f"{self.name} Collection | - | {item.title}")
|
logger.info(f"{self.name} Collection | - | {self.item_title(item)}")
|
||||||
if self.smart_label_collection:
|
if self.smart_label_collection:
|
||||||
self.library.query_data(item.removeLabel, self.name)
|
self.library.query_data(item.removeLabel, self.name)
|
||||||
else:
|
else:
|
||||||
|
@ -1628,7 +1700,7 @@ class CollectionBuilder:
|
||||||
count_removed += 1
|
count_removed += 1
|
||||||
if count_removed > 0:
|
if count_removed > 0:
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.info(f"{count_removed} {'Movie' if self.library.is_movie else 'Show'}{'s' if count_removed == 1 else ''} Removed")
|
logger.info(f"{count_removed} {self.collection_level.capitalize()}{'s' if count_removed == 1 else ''} Removed")
|
||||||
|
|
||||||
def update_item_details(self):
|
def update_item_details(self):
|
||||||
add_tags = self.item_details["item_label"] if "item_label" in self.item_details else None
|
add_tags = self.item_details["item_label"] if "item_label" in self.item_details else None
|
||||||
|
@ -1697,7 +1769,7 @@ class CollectionBuilder:
|
||||||
key, options = plex.item_advance_keys[method_name]
|
key, options = plex.item_advance_keys[method_name]
|
||||||
if getattr(item, key) != options[method_data]:
|
if getattr(item, key) != options[method_data]:
|
||||||
advance_edits[key] = options[method_data]
|
advance_edits[key] = options[method_data]
|
||||||
self.library.edit_item(item, item.title, "Movie" if self.library.is_movie else "Show", advance_edits, advanced=True)
|
self.library.edit_item(item, item.title, self.collection_level.capitalize(), advance_edits, advanced=True)
|
||||||
|
|
||||||
if len(tmdb_ids) > 0:
|
if len(tmdb_ids) > 0:
|
||||||
if "item_radarr_tag" in self.item_details:
|
if "item_radarr_tag" in self.item_details:
|
||||||
|
@ -1890,7 +1962,8 @@ class CollectionBuilder:
|
||||||
logger.debug(keys)
|
logger.debug(keys)
|
||||||
logger.debug(self.rating_keys)
|
logger.debug(self.rating_keys)
|
||||||
for key in self.rating_keys:
|
for key in self.rating_keys:
|
||||||
logger.info(f"Moving {keys[key].title} {'after {}'.format(keys[previous].title) if previous else 'to the beginning'}")
|
text = f"after {self.item_title(keys[previous])}" if previous else "to the beginning"
|
||||||
|
logger.info(f"Moving {self.item_title(keys[key])} {text}")
|
||||||
self.library.move_item(self.obj, key, after=previous)
|
self.library.move_item(self.obj, key, after=previous)
|
||||||
previous = key
|
previous = key
|
||||||
|
|
||||||
|
@ -1912,13 +1985,12 @@ class CollectionBuilder:
|
||||||
except (BadRequest, NotFound):
|
except (BadRequest, NotFound):
|
||||||
logger.error(f"Plex Error: Item {rating_key} not found")
|
logger.error(f"Plex Error: Item {rating_key} not found")
|
||||||
continue
|
continue
|
||||||
current_title = f"{current.title} ({current.year})" if current.year else current.title
|
|
||||||
if current in collection_items:
|
if current in collection_items:
|
||||||
logger.info(f"{name} Collection | = | {current_title}")
|
logger.info(f"{name} Collection | = | {self.item_title(current)}")
|
||||||
else:
|
else:
|
||||||
self.library.query_data(current.addLabel if self.smart_label_collection else current.addCollection, name)
|
self.library.query_data(current.addLabel if self.smart_label_collection else current.addCollection, name)
|
||||||
logger.info(f"{name} Collection | + | {current_title}")
|
logger.info(f"{name} Collection | + | {self.item_title(current)}")
|
||||||
logger.info(f"{len(rating_keys)} {'Movie' if self.library.is_movie else 'Show'}{'s' if len(rating_keys) > 1 else ''} Processed")
|
logger.info(f"{len(rating_keys)} {self.collection_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed")
|
||||||
|
|
||||||
if len(self.run_again_movies) > 0:
|
if len(self.run_again_movies) > 0:
|
||||||
logger.info("")
|
logger.info("")
|
||||||
|
|
|
@ -61,6 +61,7 @@ collection_mode_options = {
|
||||||
"show_items": "showItems", "showitems": "showItems"
|
"show_items": "showItems", "showitems": "showItems"
|
||||||
}
|
}
|
||||||
collection_order_options = ["release", "alpha", "custom"]
|
collection_order_options = ["release", "alpha", "custom"]
|
||||||
|
collection_level_options = ["episode", "season"]
|
||||||
collection_mode_keys = {-1: "default", 0: "hide", 1: "hideItems", 2: "showItems"}
|
collection_mode_keys = {-1: "default", 0: "hide", 1: "hideItems", 2: "showItems"}
|
||||||
collection_order_keys = {0: "release", 1: "alpha", 2: "custom"}
|
collection_order_keys = {0: "release", 1: "alpha", 2: "custom"}
|
||||||
item_advance_keys = {
|
item_advance_keys = {
|
||||||
|
@ -395,10 +396,6 @@ class Plex:
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
|
||||||
def _upload_image(self, item, image):
|
def _upload_image(self, item, image):
|
||||||
logger.debug(item)
|
|
||||||
logger.debug(image.is_poster)
|
|
||||||
logger.debug(image.is_url)
|
|
||||||
logger.debug(image.location)
|
|
||||||
if image.is_poster and image.is_url:
|
if image.is_poster and image.is_url:
|
||||||
item.uploadPoster(url=image.location)
|
item.uploadPoster(url=image.location)
|
||||||
elif image.is_poster:
|
elif image.is_poster:
|
||||||
|
@ -411,8 +408,6 @@ class Plex:
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
|
||||||
def upload_file_poster(self, item, image):
|
def upload_file_poster(self, item, image):
|
||||||
logger.debug(item)
|
|
||||||
logger.debug(image)
|
|
||||||
item.uploadPoster(filepath=image)
|
item.uploadPoster(filepath=image)
|
||||||
self.reload(item)
|
self.reload(item)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ sorts = [
|
||||||
"rank", "added", "title", "released", "runtime", "popularity",
|
"rank", "added", "title", "released", "runtime", "popularity",
|
||||||
"percentage", "votes", "random", "my_rating", "watched", "collected"
|
"percentage", "votes", "random", "my_rating", "watched", "collected"
|
||||||
]
|
]
|
||||||
|
id_translation = {"movie": "tmdb", "show": "tvdb", "season": "TVDb Season", "episode": "TVDb Episode"}
|
||||||
|
|
||||||
class Trakt:
|
class Trakt:
|
||||||
def __init__(self, config, params):
|
def __init__(self, config, params):
|
||||||
|
@ -142,26 +143,31 @@ class Trakt:
|
||||||
except Failed:
|
except Failed:
|
||||||
raise Failed(f"Trakt Error: List {data} not found")
|
raise Failed(f"Trakt Error: List {data} not found")
|
||||||
|
|
||||||
def _parse(self, items, top=True, item_type=None):
|
def _parse(self, items, typeless=False, item_type=None):
|
||||||
ids = []
|
ids = []
|
||||||
for item in items:
|
for item in items:
|
||||||
if top:
|
if typeless:
|
||||||
if item_type:
|
|
||||||
data = item[item_type]
|
|
||||||
elif item["type"] in ["movie", "show"]:
|
|
||||||
data = item[item["type"]]
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
data = item
|
data = item
|
||||||
if item_type:
|
current_type = None
|
||||||
id_type = "TMDb" if item_type == "movie" else "TVDb"
|
elif item_type:
|
||||||
|
data = item[item_type]
|
||||||
|
current_type = item_type
|
||||||
|
elif "type" in item and item["type"] in id_translation:
|
||||||
|
data = item["movie" if item["type"] == "movie" else "show"]
|
||||||
|
current_type = item["type"]
|
||||||
else:
|
else:
|
||||||
id_type = "TMDb" if item["type"] == "movie" else "TVDb"
|
continue
|
||||||
if data["ids"][id_type.lower()]:
|
id_type = "tmdb" if item["type"] == "movie" else "tvdb"
|
||||||
ids.append((data["ids"][id_type.lower()], id_type.lower()))
|
if data["ids"][id_type]:
|
||||||
|
final_id = data["ids"][id_type]
|
||||||
|
if current_type == "episode":
|
||||||
|
final_id = f"{final_id}_{item[current_type]['season']}"
|
||||||
|
if current_type in ["episode", "season"]:
|
||||||
|
final_id = f"{final_id}_{item[current_type]['number']}"
|
||||||
|
final_type = f"{id_type}_{current_type}" if current_type in ["episode", "season"] else id_type
|
||||||
|
ids.append((final_id, final_type))
|
||||||
else:
|
else:
|
||||||
logger.error(f"Trakt Error: No {id_type} ID found for {data['title']} ({data['year']})")
|
logger.error(f"Trakt Error: No {id_type.upper().replace('B', 'b')} ID found for {data['title']} ({data['year']})")
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
def _user_list(self, data):
|
def _user_list(self, data):
|
||||||
|
@ -184,7 +190,7 @@ class Trakt:
|
||||||
|
|
||||||
def _pagenation(self, pagenation, amount, is_movie):
|
def _pagenation(self, pagenation, amount, is_movie):
|
||||||
items = self._request(f"/{'movies' if is_movie else 'shows'}/{pagenation}?limit={amount}")
|
items = self._request(f"/{'movies' if is_movie else 'shows'}/{pagenation}?limit={amount}")
|
||||||
return self._parse(items, top=pagenation != "popular", item_type="movie" if is_movie else "show")
|
return self._parse(items, typeless=pagenation == "popular", item_type="movie" if is_movie else "show")
|
||||||
|
|
||||||
def validate_trakt(self, trakt_lists, is_movie, trakt_type="list"):
|
def validate_trakt(self, trakt_lists, is_movie, trakt_type="list"):
|
||||||
values = util.get_list(trakt_lists, split=False)
|
values = util.get_list(trakt_lists, split=False)
|
||||||
|
|
|
@ -66,6 +66,9 @@ def tab_new_lines(data):
|
||||||
def make_ordinal(n):
|
def make_ordinal(n):
|
||||||
return f"{n}{'th' if 11 <= (n % 100) <= 13 else ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]}"
|
return f"{n}{'th' if 11 <= (n % 100) <= 13 else ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]}"
|
||||||
|
|
||||||
|
def add_zero(number):
|
||||||
|
return str(number) if len(str(number)) > 1 else f"0{number}"
|
||||||
|
|
||||||
def add_dict_list(keys, value, dict_map):
|
def add_dict_list(keys, value, dict_map):
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key in dict_map:
|
if key in dict_map:
|
||||||
|
|
Loading…
Reference in a new issue