diff --git a/VERSION b/VERSION index fbdb6630..d18e1bf6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.5-develop76 +1.16.5-develop77 diff --git a/docs/metadata/metadata/movie.md b/docs/metadata/metadata/movie.md index f430f915..e95c440b 100644 --- a/docs/metadata/metadata/movie.md +++ b/docs/metadata/metadata/movie.md @@ -78,7 +78,24 @@ metadata: ## Movies -Each movie is defined by the mapping name which must be the same as the movie name in the library unless an `alt_title` is specified. +Each metadata definition is defined by the mapping name which can link to a movie in multiple ways. + +* Mapping name must match the movie name in Plex exactly unless an `alt_title` is specified. +* Mapping name must match the TMDb ID or IMDb ID mapped to the movie. + +**Note:** to search for a movie titled with a number surround the number in quotes like in the example below. + +```yaml +metadata: + Star Wars: # Matches via the Name "Star Wars" + edits... + 299534: # Matches via TMDb ID: 299534 + edits... + tt4154756: # Matches via IMDb ID: tt4154756 + edits... + "9": # Matches via the Name "9" + edits... +``` ## Metadata Edits diff --git a/docs/metadata/metadata/show.md b/docs/metadata/metadata/show.md index a426f88c..da6b9a28 100644 --- a/docs/metadata/metadata/show.md +++ b/docs/metadata/metadata/show.md @@ -59,7 +59,24 @@ metadata: ## Shows -Each show is defined by the mapping name which must be the same as the show name in the library unless an `alt_title` is specified. +Each metadata definition is defined by the mapping name which can link to a show in multiple ways. + +* Mapping name must match the show name in Plex exactly unless an `alt_title` is specified. +* Mapping name must match the TVDb ID or IMDb ID mapped to the show. + +**Note:** to search for a show titled with a number surround the number in quotes like in the example below. + +```yaml +metadata: + Game of Thrones: # Matches via the Name "Game of Thrones" + edits... + 366524: # Matches via TVDb ID: 366524 + edits... + tt10234724: # Matches via IMDb ID: tt10234724 + edits... + "24": # Matches via the Name "24" + edits... +``` ### Seasons diff --git a/modules/builder.py b/modules/builder.py index 6b9dc7a0..9ecdd032 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -2395,7 +2395,12 @@ class CollectionBuilder: key, options = plex.item_advance_keys[method_name] if key in prefs and getattr(item, key) != options[method_data]: advance_edits[key] = options[method_data] - self.library.edit_item(item, item.title, self.collection_level.capitalize(), advance_edits, advanced=True) + if advance_edits: + logger.debug(f"Details Update: {advance_edits}") + if self.library.edit_advance(item, advance_edits): + logger.info(f"{item.title} Advanced Details Update Successful") + else: + logger.error(f"{item.title} Advanced Details Update Failed") if "item_tmdb_season_titles" in self.item_details and item.ratingKey in self.library.show_rating_key_map: try: diff --git a/modules/library.py b/modules/library.py index 0dc31fbc..e2e1d7c8 100644 --- a/modules/library.py +++ b/modules/library.py @@ -126,7 +126,7 @@ class Library(ABC): if meta_obj.collections: self.collections.extend([c for c in meta_obj.collections]) if meta_obj.metadata: - self.metadatas.extend([c for c in meta_obj.metadata]) + self.metadatas.extend([m for m in meta_obj.metadata]) self.metadata_files.append(meta_obj) except Failed as e: logger.error(e) @@ -275,8 +275,6 @@ class Library(ABC): return items def map_guids(self, items): - logger.separator(f"Mapping {self.type} Library: {self.name}", space=False, border=False) - logger.info("") for i, item in enumerate(items, 1): logger.ghost(f"Processing: {i}/{len(items)} {item.title}") if item.ratingKey not in self.movie_rating_key_map and item.ratingKey not in self.show_rating_key_map: diff --git a/modules/meta.py b/modules/meta.py index 84afdfa7..b6a08710 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -33,7 +33,7 @@ default_templates = { "trakt_people_list": {"tmdb_person": f"<>", "plex_search": {"all": {"actor": "tmdb"}}} } -def get_dict(attribute, attr_data, check_list=None, lower=False): +def get_dict(attribute, attr_data, check_list=None, make_str=False): if check_list is None: check_list = [] if attr_data and attribute in attr_data: @@ -41,8 +41,9 @@ def get_dict(attribute, attr_data, check_list=None, lower=False): if isinstance(attr_data[attribute], dict): new_dict = {} for _name, _data in attr_data[attribute].items(): - if lower and str(_name).lower() in check_list or not lower and _name in check_list: - logger.warning(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {str(_name).lower() if lower else _name}") + if make_str and str(_name) in check_list or not make_str and _name in check_list: + new_name = f'"{str(_name)}"' if make_str or not isinstance(_name, int) else _name + logger.warning(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {new_name}") elif _data is None: logger.warning(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data") elif not isinstance(_data, dict): @@ -50,7 +51,7 @@ def get_dict(attribute, attr_data, check_list=None, lower=False): elif attribute == "templates": new_dict[str(_name)] = (_data, {}) else: - new_dict[str(_name)] = _data + new_dict[str(_name) if make_str else _name] = _data return new_dict else: logger.error(f"Config Error: {attribute} must be a dictionary") @@ -737,45 +738,68 @@ class MetadataFile(DataFile): logger.error(f"{description} Details Update Failed") logger.info("") - logger.separator() - logger.info("") - year = None - if "year" in methods and not self.library.is_music: - if meta[methods["year"]] is None: - raise Failed("Metadata Error: year attribute is blank") - try: - year_value = int(str(meta[methods["year"]])) - if 1800 <= year_value <= next_year: - year = year_value - except ValueError: - pass - if year is None: - raise Failed(f"Metadata Error: year attribute must be an integer between 1800 and {next_year}") - - title = mapping_name - if "title" in methods: - if meta[methods["title"]] is None: - logger.error("Metadata Error: title attribute is blank") + if (isinstance(mapping_name, int) or mapping_name.startswith("tt")) and not self.library.is_music: + if isinstance(mapping_name, int): + id_type = "TMDb" if self.library.is_movie else "TVDb" else: - title = meta[methods["title"]] + id_type = "IMDb" + logger.separator(f"{id_type} ID: {mapping_name} Metadata", space=False, border=False) + logger.info("") + if self.library.is_movie and mapping_name in self.library.movie_map: + item = self.library.fetchItem(self.library.movie_map[mapping_name][0]) + elif self.library.is_show and mapping_name in self.library.show_map: + item = self.library.fetchItem(self.library.show_map[mapping_name][0]) + elif mapping_name in self.library.imdb_map: + item = self.library.fetchItem(self.library.imdb_map[mapping_name][0]) + else: + logger.error(f"Metadata Error: {id_type} ID not mapped") + continue + title = item.title + if "title" in methods: + if meta[methods["title"]] is None: + logger.error("Metadata Error: title attribute is blank") + else: + title = meta[methods["title"]] + else: + logger.separator(f"{mapping_name} Metadata", space=False, border=False) + logger.info("") + year = None + if "year" in methods and not self.library.is_music: + if meta[methods["year"]] is None: + raise Failed("Metadata Error: year attribute is blank") + try: + year_value = int(str(meta[methods["year"]])) + if 1800 <= year_value <= next_year: + year = year_value + except ValueError: + pass + if year is None: + raise Failed(f"Metadata Error: year attribute must be an integer between 1800 and {next_year}") - item = self.library.search_item(title, year=year) + title = mapping_name + if "title" in methods: + if meta[methods["title"]] is None: + logger.error("Metadata Error: title attribute is blank") + else: + title = meta[methods["title"]] - if item is None and "alt_title" in methods: - if meta[methods["alt_title"]] is None: - logger.error("Metadata Error: alt_title attribute is blank") - else: - alt_title = meta["alt_title"] - item = self.library.search_item(alt_title, year=year) - if item is None: - item = self.library.search_item(alt_title) + item = self.library.search_item(title, year=year) - if item is None: - logger.error(f"Plex Error: Item {mapping_name} not found") - logger.error(f"Skipping {mapping_name}") - continue + if item is None and "alt_title" in methods: + if meta[methods["alt_title"]] is None: + logger.error("Metadata Error: alt_title attribute is blank") + else: + alt_title = meta[methods["alt_title"]] + item = self.library.search_item(alt_title, year=year) + if item is None: + item = self.library.search_item(alt_title) - logger.info(f"Updating {self.library.type}: {title}...") + if item is None: + logger.error(f"Plex Error: Item {mapping_name} not found") + logger.error(f"Skipping {mapping_name}") + continue + + logger.separator(f"{title} Metadata", space=False, border=False) tmdb_item = None tmdb_is_movie = None @@ -853,8 +877,12 @@ class MetadataFile(DataFile): logger.info(f"Detail: {advance_edit} updated to {method_data}") else: logger.error(f"Metadata Error: {advance_edit} attribute is blank") - if self.library.edit_item(item, mapping_name, self.library.type, advance_edits, advanced=True): - updated = True + if advance_edits: + if self.library.edit_advance(item, advance_edits): + updated = True + logger.info(f"{mapping_name} Advanced Details Update Successful") + else: + logger.error(f"{mapping_name} Advanced Details Update Failed") logger.info(f"{self.library.type}: {mapping_name} Details Update {'Complete' if updated else 'Not Needed'}") diff --git a/modules/plex.py b/modules/plex.py index de32717e..6a444ac0 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -849,19 +849,15 @@ class Plex(Library): return d return None - def edit_item(self, item, name, item_type, edits, advanced=False): - if len(edits) > 0: - logger.debug(f"Details Update: {edits}") - try: - self.edit_query(item, edits, advanced=advanced) - if advanced and ("languageOverride" in edits or "useOriginalTitle" in edits): - self.query(item.refresh) - logger.info(f"{item_type}: {name}{' Advanced' if advanced else ''} Details Update Successful") - return True - except BadRequest: - logger.stacktrace() - logger.error(f"{item_type}: {name}{' Advanced' if advanced else ''} Details Update Failed") - return False + def edit_advance(self, item, edits): + try: + self.edit_query(item, edits, advanced=True) + if "languageOverride" in edits or "useOriginalTitle" in edits: + self.query(item.refresh) + return True + except BadRequest: + logger.stacktrace() + return False def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, do_print=True): display = "" diff --git a/plex_meta_manager.py b/plex_meta_manager.py index a49c14a7..b7569614 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -419,7 +419,7 @@ def update_libraries(config): if len(title) > longest: longest = len(title) - def print_status( status): + def print_status(status): logger.info(f"{'Title':^{longest}} | + | = | - | Run Time | {'Status'}") breaker = f"{logger.separating_character * longest}|{logger.separating_character * 7}|{logger.separating_character * 7}|{logger.separating_character * 7}|{logger.separating_character * 10}|" logger.separator(breaker, space=False, border=False, side_space=False, left=True)