diff --git a/VERSION b/VERSION index 6a9268ad..e50dc0d7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.3-develop39 +1.17.3-develop40 diff --git a/docs/config/operations.md b/docs/config/operations.md index 6aacf9fd..b713e44e 100644 --- a/docs/config/operations.md +++ b/docs/config/operations.md @@ -16,29 +16,29 @@ libraries: The available attributes for the operations attribute are as follows -| Attribute | Description | -|:--------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `assets_for_all` | Search in assets for images for every item in your library.
**Values:** `true` or `false` | -| `delete_collections_with_less` | Deletes every collection with less than the given number of items.
**Values:** number greater than 0 | -| `delete_unmanaged_collections` | Deletes every unmanaged collection
**Values:** `true` or `false` | -| `mass_genre_update` | Updates every item's genres in the library to the chosen site's genres
**Values:**
`tmdb`Use TMDb for Genres
`tvdb`Use TVDb for Genres
`imdb`Use IMDb for Genres
`omdb`Use IMDb through OMDb for Genres
`anidb`Use AniDB Tags for Genres
| -| `mass_content_rating_update` | Updates every item's content rating in the library to the chosen site's content rating
**Values:**
`mdb`Use MdbList for Content Ratings
`mdb_commonsense`Use Commonsense Rating through MDbList for Content Ratings
`omdb`Use IMDb through OMDb for Content Ratings
| -| `mass_original_title_update` | Updates every item's original title in the library to the chosen site's content rating
**Values:**
`anidb`Use AniDB Main Title for Original Titles
`anidb_official`Use AniDB Official Title based on the language attribute in the config file for Original Titles
| -| `mass_originally_available_update` | Updates every item's originally available date in the library to the chosen site's date
**Values:**
`tmdb`Use TMDb Release Date
`tvdb`Use TVDb Release Date
`omdb`Use IMDb Release Date through OMDb
`mdb`Use MdbList Release Date
`anidb`Use AniDB Release Date
| -| `mass_audience_rating_update`/
`mass_critic_rating_update`/
`mass_user_rating_update` | Updates every item's audience/critic/user rating in the library to the chosen site's rating
**Values:**
`tmdb`Use TMDb Rating
`imdb`Use IMDb Rating
`trakt_user`Use Trakt User's Personal Rating
`omdb`Use IMDbRating through OMDb
`mdb`Use MdbList Score
`mdb_imdb`Use IMDb Rating through MDbList
`mdb_metacritic`Use Metacritic Rating through MDbList
`mdb_metacriticuser`Use Metacritic User Rating through MDbList
`mdb_trakt`Use Trakt Rating through MDbList
`mdb_tomatoes`Use Rotten Tomatoes Rating through MDbList
`mdb_tomatoesaudience`Use Rotten Tomatoes Audience Rating through MDbList
`mdb_tmdb`Use TMDb Rating through MDbList
`mdb_letterboxd`Use Letterboxd Rating through MDbList
`mdb_myanimelist`Use MyAnimeList Rating through MDbList
`anidb_rating`Use AniDB Rating
`anidb_average`Use AniDB Average
| -| `mass_episode_audience_rating_update`/
`mass_episode_critic_rating_update`/
`mass_episode_user_rating_update` | Updates every item's episode's audience/critic/user rating in the library to the chosen site's rating
**Values:**
`tmdb`Use TMDb Rating
`imdb`Use IMDb Rating
| -| `mass_imdb_parental_labels` | Updates every item's labels in the library to match the IMDb Parental Guide
**Values** `with_none` or `without_none` | -| `mass_collection_mode` | Updates every Collection in your library to the specified Collection Mode
**Values:** `default`: Library default
`hide`: Hide Collection
`hide_items`: Hide Items in this Collection
`show_items`: Show this Collection and its Items
`default`Library default
`hide`Hide Collection
`hide_items`Hide Items in this Collection
`show_items`Show this Collection and its Items
| -| `update_blank_track_titles` | Search though every track in a music library and replace any blank track titles with the tracks sort title
**Values:** `true` or `false` | -| `remove_title_parentheses` | Search through every title and remove all ending parentheses in an items title if the title isn not locked.
**Values:** `true` or `false` | -| `split_duplicates` | Splits all duplicate movies/shows found in this library
**Values:** `true` or `false` | -| `radarr_add_all` | Adds every item in the library to Radarr. The existing paths in plex will be used as the root folder of each item, if the paths in Plex are not the same as your Radarr paths you can use the `plex_path` and `radarr_path` [Radarr](radarr) details to convert the paths.
**Values:** `true` or `false` | -| `radarr_remove_by_tag` | Removes every item from Radarr with the Tags given
**Values:** List or comma separated string of tags | -| `sonarr_add_all` | Adds every item in the library to Sonarr. The existing paths in plex will be used as the root folder of each item, if the paths in Plex are not the same as your Sonarr paths you can use the `plex_path` and `sonarr_path` [Sonarr](sonarr) details to convert the paths.
**Values:** `true` or `false` | -| `sonarr_remove_by_tag` | Removes every item from Sonarr with the Tags given
**Values:** List or comma separated string of tags | -| [`genre_mapper`](#genre-mapper) | Allows genres to be changed to other genres or be removed from every item in your library.
**Values:** [see below for usage](#genre-mapper) | -| [`content_rating_mapper`](#content-rating-mapper) | Allows content ratings to be changed to other content ratings or be removed from every item in your library.
**Values:** [see below for usage](#content-rating-mapper) | -| [`metadata_backup`](#metadata-backup) | Creates/Maintains a PMM [Metadata File](../metadata/metadata) with a full `metadata` mapping based on the library's items locked attributes.
**Values:** [see below for usage](#metadata-backup) | +| Attribute | Description | +|:--------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `assets_for_all` | Search in assets for images for every item in your library.
**Values:** `true` or `false` | +| `delete_collections_with_less` | Deletes every collection with less than the given number of items.
**Values:** number greater than 0 | +| `delete_unmanaged_collections` | Deletes every unmanaged collection
**Values:** `true` or `false` | +| `mass_genre_update` | Updates every item's genres in the library to the chosen site's genres
**Values:**
`tmdb`Use TMDb for Genres
`tvdb`Use TVDb for Genres
`imdb`Use IMDb for Genres
`omdb`Use IMDb through OMDb for Genres
`anidb`Use AniDB Tags for Genres
`lock`Lock Genre Field
`unlock`Unlock Genre Field
`remove`Remove all Genres and Lock Field
`reset`Remove all Genres and Unlock Field
| +| `mass_content_rating_update` | Updates every item's content rating in the library to the chosen site's content rating
**Values:**
`mdb`Use MdbList for Content Ratings
`mdb_commonsense`Use Commonsense Rating through MDbList for Content Ratings
`omdb`Use IMDb through OMDb for Content Ratings
`lock`Lock Rating Field
`unlock`Unlock Rating Field
`remove`Remove Rating and Lock Field
`reset`Remove Rating and Unlock Field
| +| `mass_original_title_update` | Updates every item's original title in the library to the chosen site's content rating
**Values:**
`anidb`Use AniDB Main Title for Original Titles
`anidb_official`Use AniDB Official Title based on the language attribute in the config file for Original Titles
`lock`Lock Original Title Field
`unlock`Unlock Original Title Field
`remove`Remove Original Title and Lock Field
`reset`Remove Original Title and Unlock Field
| +| `mass_originally_available_update` | Updates every item's originally available date in the library to the chosen site's date
**Values:**
`tmdb`Use TMDb Release Date
`tvdb`Use TVDb Release Date
`omdb`Use IMDb Release Date through OMDb
`mdb`Use MdbList Release Date
`anidb`Use AniDB Release Date
`lock`Lock Originally Available Field
`unlock`Unlock Originally Available Field
`remove`Remove Originally Available and Lock Field
`reset`Remove Originally Available and Unlock Field
| +| `mass_audience_rating_update`/
`mass_critic_rating_update`/
`mass_user_rating_update` | Updates every item's audience/critic/user rating in the library to the chosen site's rating
**Values:**
`tmdb`Use TMDb Rating
`imdb`Use IMDb Rating
`trakt_user`Use Trakt User's Personal Rating
`omdb`Use IMDbRating through OMDb
`mdb`Use MdbList Score
`mdb_imdb`Use IMDb Rating through MDbList
`mdb_metacritic`Use Metacritic Rating through MDbList
`mdb_metacriticuser`Use Metacritic User Rating through MDbList
`mdb_trakt`Use Trakt Rating through MDbList
`mdb_tomatoes`Use Rotten Tomatoes Rating through MDbList
`mdb_tomatoesaudience`Use Rotten Tomatoes Audience Rating through MDbList
`mdb_tmdb`Use TMDb Rating through MDbList
`mdb_letterboxd`Use Letterboxd Rating through MDbList
`mdb_myanimelist`Use MyAnimeList Rating through MDbList
`anidb_rating`Use AniDB Rating
`anidb_average`Use AniDB Average
`lock`Lock Rating Field
`unlock`Unlock Rating Field
`remove`Remove Rating and Lock Field
`reset`Remove Rating and Unlock Field
| +| `mass_episode_audience_rating_update`/
`mass_episode_critic_rating_update`/
`mass_episode_user_rating_update` | Updates every item's episode's audience/critic/user rating in the library to the chosen site's rating
**Values:**
`tmdb`Use TMDb Rating
`imdb`Use IMDb Rating
`lock`Lock Rating Field
`unlock`Unlock Rating Field
`remove`Remove Rating and Lock Field
`reset`Remove Rating and Unlock Field
| +| `mass_imdb_parental_labels` | Updates every item's labels in the library to match the IMDb Parental Guide
**Values** `with_none` or `without_none` | +| `mass_collection_mode` | Updates every Collection in your library to the specified Collection Mode
**Values:** `default`: Library default
`hide`: Hide Collection
`hide_items`: Hide Items in this Collection
`show_items`: Show this Collection and its Items
`default`Library default
`hide`Hide Collection
`hide_items`Hide Items in this Collection
`show_items`Show this Collection and its Items
| +| `update_blank_track_titles` | Search though every track in a music library and replace any blank track titles with the tracks sort title
**Values:** `true` or `false` | +| `remove_title_parentheses` | Search through every title and remove all ending parentheses in an items title if the title isn not locked.
**Values:** `true` or `false` | +| `split_duplicates` | Splits all duplicate movies/shows found in this library
**Values:** `true` or `false` | +| `radarr_add_all` | Adds every item in the library to Radarr. The existing paths in plex will be used as the root folder of each item, if the paths in Plex are not the same as your Radarr paths you can use the `plex_path` and `radarr_path` [Radarr](radarr) details to convert the paths.
**Values:** `true` or `false` | +| `radarr_remove_by_tag` | Removes every item from Radarr with the Tags given
**Values:** List or comma separated string of tags | +| `sonarr_add_all` | Adds every item in the library to Sonarr. The existing paths in plex will be used as the root folder of each item, if the paths in Plex are not the same as your Sonarr paths you can use the `plex_path` and `sonarr_path` [Sonarr](sonarr) details to convert the paths.
**Values:** `true` or `false` | +| `sonarr_remove_by_tag` | Removes every item from Sonarr with the Tags given
**Values:** List or comma separated string of tags | +| [`genre_mapper`](#genre-mapper) | Allows genres to be changed to other genres or be removed from every item in your library.
**Values:** [see below for usage](#genre-mapper) | +| [`content_rating_mapper`](#content-rating-mapper) | Allows content ratings to be changed to other content ratings or be removed from every item in your library.
**Values:** [see below for usage](#content-rating-mapper) | +| [`metadata_backup`](#metadata-backup) | Creates/Maintains a PMM [Metadata File](../metadata/metadata) with a full `metadata` mapping based on the library's items locked attributes.
**Values:** [see below for usage](#metadata-backup) | ## Genre Mapper diff --git a/modules/config.py b/modules/config.py index 7e8d2ecf..c38c33e8 100644 --- a/modules/config.py +++ b/modules/config.py @@ -33,13 +33,32 @@ from retrying import retry logger = util.logger sync_modes = {"append": "Only Add Items to the Collection or Playlist", "sync": "Add & Remove Items from the Collection or Playlist"} -mass_genre_options = {"tmdb": "Use TMDb Metadata", "imdb": "Use IMDb Rating", "omdb": "Use IMDb Metadata through OMDb", "tvdb": "Use TVDb Metadata", "anidb": "Use AniDB Tag Metadata"} -mass_content_options = {"omdb": "Use IMDb Metadata through OMDb", "mdb": "Use MdbList Metadata", "mdb_commonsense": "Use Commonsense Rating through MDbList"} -mass_original_title_options = {"anidb": "Use AniDB Main Title", "anidb_official": "Use AniDB Official Title based on the language attribute in the config file"} -mass_available_options = {"tmdb": "Use TMDb Metadata", "omdb": "Use IMDb Metadata through OMDb", "mdb": "Use MdbList Metadata", "tvdb": "Use TVDb Metadata", "anidb": "Use AniDB Metadata"} imdb_label_options = {"with_none": "Add IMDb Parental Labels including None", "without_none": "Add IMDb Parental Labels including None"} -mass_episode_rating_options = {"tmdb": "Use TMDb Rating", "imdb": "Use IMDb Rating"} +mass_genre_options = { + "lock": "Unlock Genre", "unlock": "Unlock Genre", "remove": "Remove and Lock Genre", "reset": "Remove and Unlock Genre", + "tmdb": "Use TMDb Genres", "imdb": "Use IMDb Genres", "omdb": "Use IMDb Genres through OMDb", "tvdb": "Use TVDb Genres", "anidb": "Use AniDB Tags" +} +mass_content_options = { + "lock": "Unlock Rating", "unlock": "Unlock Rating", "remove": "Remove and Lock Rating", "reset": "Remove and Unlock Rating", + "omdb": "Use IMDb Rating through OMDb", "mdb": "Use MdbList Rating", "mdb_commonsense": "Use Commonsense Rating through MDbList" +} +mass_original_title_options = { + "lock": "Unlock Original Title", "unlock": "Unlock Original Title", "remove": "Remove and Lock Original Title", "reset": "Remove and Unlock Original Title", + "anidb": "Use AniDB Main Title", "anidb_official": "Use AniDB Official Title based on the language attribute in the config file" +} +mass_available_options = { + "lock": "Unlock Originally Available", "unlock": "Unlock Originally Available", "remove": "Remove and Lock Originally Available", "reset": "Remove and Unlock Originally Available", + "tmdb": "Use TMDb Release", "omdb": "Use IMDb Release through OMDb", "mdb": "Use MdbList Release", "tvdb": "Use TVDb Release", "anidb": "Use AniDB Release" +} +mass_episode_rating_options = { + "lock": "Unlock Rating", "unlock": "Unlock Rating", "remove": "Remove and Lock Rating", "reset": "Remove and Unlock Rating", + "tmdb": "Use TMDb Rating", "imdb": "Use IMDb Rating" +} mass_rating_options = { + "lock": "Lock Rating", + "unlock": "Unlock Rating", + "remove": "Remove and Lock Rating", + "reset": "Remove and Unlock Rating", "tmdb": "Use TMDb Rating", "imdb": "Use IMDb Rating", "trakt_user": "Use Trakt User Rating", diff --git a/modules/library.py b/modules/library.py index de42785f..59b16b27 100644 --- a/modules/library.py +++ b/modules/library.py @@ -222,7 +222,7 @@ class Library(ABC): pass @abstractmethod - def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, do_print=True): + def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, do_print=True, locked=True, is_locked=None): pass @abstractmethod diff --git a/modules/operations.py b/modules/operations.py index 267250a4..86db2ef6 100644 --- a/modules/operations.py +++ b/modules/operations.py @@ -93,6 +93,8 @@ class Operations: if self.library.assets_for_all and self.library.asset_directory: self.library.find_and_upload_assets(item, current_labels) + locked_fields = [f.name for f in item.fields if f.locked] + tmdb_id, tvdb_id, imdb_id = self.library.get_ids(item) item.batchEdits() @@ -201,53 +203,75 @@ class Operations: if mdb_item is None: logger.warning(f"No TMDb ID, TVDb ID, or IMDb ID for Guid: {item.guid}") - def get_rating(attribute): - if tmdb_item and attribute == "tmdb": - found_rating = tmdb_item.vote_average - elif imdb_id and attribute == "imdb": - found_rating = self.config.IMDb.get_rating(imdb_id) - elif attribute == "trakt_user" and self.library.is_movie and tmdb_id in trakt_ratings: - found_rating = trakt_ratings[tmdb_id] - elif attribute == "trakt_user" and self.library.is_show and tvdb_id in trakt_ratings: - found_rating = trakt_ratings[tvdb_id] - elif omdb_item and attribute == "omdb": - found_rating = omdb_item.imdb_rating - elif mdb_item and attribute == "mdb": - found_rating = mdb_item.score / 10 if mdb_item.score else None - elif mdb_item and attribute == "mdb_imdb": - found_rating = mdb_item.imdb_rating if mdb_item.imdb_rating else None - elif mdb_item and attribute == "mdb_metacritic": - found_rating = mdb_item.metacritic_rating / 10 if mdb_item.metacritic_rating else None - elif mdb_item and attribute == "mdb_metacriticuser": - found_rating = mdb_item.metacriticuser_rating if mdb_item.metacriticuser_rating else None - elif mdb_item and attribute == "mdb_trakt": - found_rating = mdb_item.trakt_rating / 10 if mdb_item.trakt_rating else None - elif mdb_item and attribute == "mdb_tomatoes": - found_rating = mdb_item.tomatoes_rating / 10 if mdb_item.tomatoes_rating else None - elif mdb_item and attribute == "mdb_tomatoesaudience": - found_rating = mdb_item.tomatoesaudience_rating / 10 if mdb_item.tomatoesaudience_rating else None - elif mdb_item and attribute == "mdb_tmdb": - found_rating = mdb_item.tmdb_rating / 10 if mdb_item.tmdb_rating else None - elif mdb_item and attribute == "mdb_letterboxd": - found_rating = mdb_item.letterboxd_rating * 2 if mdb_item.letterboxd_rating else None - elif mdb_item and attribute == "mdb_myanimelist": - found_rating = mdb_item.myanimelist_rating if mdb_item.myanimelist_rating else None - elif anidb_item and attribute == "anidb_rating": - found_rating = anidb_item.rating - elif anidb_item and attribute == "anidb_average": - found_rating = anidb_item.average - elif anidb_item and attribute == "anidb_score": - found_rating = anidb_item.score - else: - found_rating = None - if found_rating is None: - raise Failed - return found_rating + def update_rating(attribute, item_attr, display): + current = getattr(item, item_attr) + if attribute in ["remove", "reset"] and current: + item.editField(item_attr, None, locked=attribute == "remove") + return f"\n{display} | None" + elif attribute in ["unlock", "reset"] and item_attr in locked_fields: + self.library.edit_query(item, {f"{item_attr}.locked": 0}) + elif attribute in ["lock", "remove"] and item_attr not in locked_fields: + self.library.edit_query(item, {f"{item_attr}.locked": 1}) + elif attribute not in ["lock", "unlock", "remove", "reset"]: + if tmdb_item and attribute == "tmdb": + found_rating = tmdb_item.vote_average + elif imdb_id and attribute == "imdb": + found_rating = self.config.IMDb.get_rating(imdb_id) + elif attribute == "trakt_user" and self.library.is_movie and tmdb_id in trakt_ratings: + found_rating = trakt_ratings[tmdb_id] + elif attribute == "trakt_user" and self.library.is_show and tvdb_id in trakt_ratings: + found_rating = trakt_ratings[tvdb_id] + elif omdb_item and attribute == "omdb": + found_rating = omdb_item.imdb_rating + elif mdb_item and attribute == "mdb": + found_rating = mdb_item.score / 10 if mdb_item.score else None + elif mdb_item and attribute == "mdb_imdb": + found_rating = mdb_item.imdb_rating if mdb_item.imdb_rating else None + elif mdb_item and attribute == "mdb_metacritic": + found_rating = mdb_item.metacritic_rating / 10 if mdb_item.metacritic_rating else None + elif mdb_item and attribute == "mdb_metacriticuser": + found_rating = mdb_item.metacriticuser_rating if mdb_item.metacriticuser_rating else None + elif mdb_item and attribute == "mdb_trakt": + found_rating = mdb_item.trakt_rating / 10 if mdb_item.trakt_rating else None + elif mdb_item and attribute == "mdb_tomatoes": + found_rating = mdb_item.tomatoes_rating / 10 if mdb_item.tomatoes_rating else None + elif mdb_item and attribute == "mdb_tomatoesaudience": + found_rating = mdb_item.tomatoesaudience_rating / 10 if mdb_item.tomatoesaudience_rating else None + elif mdb_item and attribute == "mdb_tmdb": + found_rating = mdb_item.tmdb_rating / 10 if mdb_item.tmdb_rating else None + elif mdb_item and attribute == "mdb_letterboxd": + found_rating = mdb_item.letterboxd_rating * 2 if mdb_item.letterboxd_rating else None + elif mdb_item and attribute == "mdb_myanimelist": + found_rating = mdb_item.myanimelist_rating if mdb_item.myanimelist_rating else None + elif anidb_item and attribute == "anidb_rating": + found_rating = anidb_item.rating + elif anidb_item and attribute == "anidb_average": + found_rating = anidb_item.average + elif anidb_item and attribute == "anidb_score": + found_rating = anidb_item.score + else: + found_rating = None + + if found_rating is None: + logger.info(f"No {display} Found") + elif str(current) != str(found_rating): + item.editField(item_attr, found_rating) + return f"\n{display} | {found_rating}" + return "" + + if self.library.mass_audience_rating_update: + batch_display += update_rating(self.library.mass_audience_rating_update, "audienceRating", "Audience Rating") + + if self.library.mass_critic_rating_update: + batch_display += update_rating(self.library.mass_critic_rating_update, "rating", "Critic Rating") + + if self.library.mass_user_rating_update: + batch_display += update_rating(self.library.mass_user_rating_update, "userRating", "User Rating") if self.library.mass_genre_update or self.library.genre_mapper: try: new_genres = [] - if self.library.mass_genre_update: + if self.library.mass_genre_update and self.library.mass_genre_update not in ["lock", "unlock", "remove", "reset"]: if tmdb_item and self.library.mass_genre_update == "tmdb": new_genres = tmdb_item.genres elif imdb_id and self.library.mass_genre_update == "imdb" and imdb_id in self.config.IMDb.genres: @@ -263,7 +287,7 @@ class Operations: if not new_genres: logger.info(f"No Genres Found") if self.library.genre_mapper: - if not new_genres: + if not new_genres and self.library.mass_genre_update not in ["remove", "reset"]: new_genres = [g.tag for g in item.genres] mapped_genres = [] for genre in new_genres: @@ -273,43 +297,18 @@ class Operations: else: mapped_genres.append(genre) new_genres = mapped_genres - temp_display = self.library.edit_tags('genre', item, sync_tags=new_genres, do_print=False) + temp_display = self.library.edit_tags("genre", item, sync_tags=new_genres, do_print=False, + locked=False if self.library.mass_genre_update in ["unlock", "reset"] else True, + is_locked="genre" in locked_fields) if temp_display: batch_display += f"\n{temp_display}" except Failed: pass - if self.library.mass_audience_rating_update: - try: - new_rating = get_rating(self.library.mass_audience_rating_update) - if str(item.audienceRating) != str(new_rating): - item.editField("audienceRating", new_rating) - batch_display += f"\nAudience Rating | {new_rating}" - except Failed: - logger.info(f"No Audience Rating Found") - - if self.library.mass_critic_rating_update: - try: - new_rating = get_rating(self.library.mass_critic_rating_update) - if str(item.rating) != str(new_rating): - item.editField("rating", new_rating) - batch_display += f"\nCritic Rating | {new_rating}" - except Failed: - logger.info(f"No Critic Rating Found") - - if self.library.mass_user_rating_update: - try: - new_rating = get_rating(self.library.mass_user_rating_update) - if str(item.userRating) != str(new_rating): - item.editField("userRating", new_rating) - batch_display += f"\nUser Rating | {new_rating}" - except Failed: - logger.info(f"No User Rating Found") - if self.library.mass_content_rating_update or self.library.content_rating_mapper: try: new_rating = None - if self.library.mass_content_rating_update: + if self.library.mass_content_rating_update and self.library.mass_genre_update not in ["lock", "unlock", "remove", "reset"]: if omdb_item and self.library.mass_content_rating_update == "omdb": new_rating = omdb_item.content_rating elif mdb_item and self.library.mass_content_rating_update == "mdb": @@ -319,58 +318,82 @@ class Operations: else: raise Failed if self.library.content_rating_mapper: - if new_rating is None: + if new_rating is None and self.library.mass_genre_update not in ["remove", "reset"]: new_rating = item.contentRating if new_rating in self.library.content_rating_mapper: new_rating = self.library.content_rating_mapper[new_rating] - if not new_rating: + + if self.library.mass_content_rating_update in ["remove", "reset"] and item.contentRating: + item.editField("contentRating", None, locked=self.library.mass_content_rating_update == "remove") + batch_display += f"\nContent Rating | None" + elif not new_rating and self.library.mass_genre_update not in ["lock", "unlock", "remove", "reset"]: logger.info(f"No Content Rating Found") elif str(item.contentRating) != str(new_rating): item.editContentRating(new_rating) batch_display += f"\nContent Rating | {new_rating}" + elif self.library.mass_content_rating_update in ["unlock", "reset"] and "contentRating" in locked_fields: + self.library.edit_query(item, {"contentRating.locked": 0}) + elif self.library.mass_content_rating_update in ["lock", "remove"] and "contentRating" not in locked_fields: + self.library.edit_query(item, {"contentRating.locked": 1}) except Failed: pass if self.library.mass_original_title_update: - try: - if anidb_item and self.library.mass_original_title_update == "anidb": - new_original_title = anidb_item.main_title - elif anidb_item and self.library.mass_original_title_update == "anidb_official": - new_original_title = anidb_item.official_title - else: - raise Failed - if not new_original_title: - logger.info(f"No Original Title Found") - elif str(item.originalTitle) != str(new_original_title): - item.editOriginalTitle(new_original_title) - batch_display += f"\nOriginal Title | {new_original_title}" - except Failed: - pass + if self.library.mass_original_title_update in ["remove", "reset"] and item.originalTitle: + item.editField("originalTitle", None, locked=self.library.mass_original_title_update == "remove") + batch_display += f"\nOriginal Title | None" + elif self.library.mass_original_title_update in ["unlock", "reset"] and "originalTitle" in locked_fields: + self.library.edit_query(item, {"originalTitle.locked": 0}) + elif self.library.mass_original_title_update in ["lock", "remove"] and "originalTitle" not in locked_fields: + self.library.edit_query(item, {"originalTitle.locked": 1}) + elif self.library.mass_original_title_update not in ["lock", "unlock", "remove", "reset"]: + try: + if anidb_item and self.library.mass_original_title_update == "anidb": + new_original_title = anidb_item.main_title + elif anidb_item and self.library.mass_original_title_update == "anidb_official": + new_original_title = anidb_item.official_title + else: + raise Failed + if not new_original_title: + logger.info(f"No Original Title Found") + elif str(item.originalTitle) != str(new_original_title): + item.editOriginalTitle(new_original_title) + batch_display += f"\nOriginal Title | {new_original_title}" + except Failed: + pass if self.library.mass_originally_available_update: - try: - if omdb_item and self.library.mass_originally_available_update == "omdb": - new_date = omdb_item.released - elif mdb_item and self.library.mass_originally_available_update == "mdb": - new_date = mdb_item.released - elif tvdb_item and self.library.mass_originally_available_update == "tvdb": - new_date = tvdb_item.release_date - elif tmdb_item and self.library.mass_originally_available_update == "tmdb": - new_date = tmdb_item.release_date if self.library.is_movie else tmdb_item.first_air_date - elif anidb_item and self.library.mass_originally_available_update == "anidb": - new_date = anidb_item.released - else: - raise Failed - if not new_date: - logger.info(f"No Originally Available Date Found") - elif str(item.originallyAvailableAt) != str(new_date): - item.editOriginallyAvailable(new_date) - batch_display += f"\nOriginally Available Date | {new_date.strftime('%Y-%m-%d')}" - except Failed: - pass + if self.library.mass_originally_available_update in ["remove", "reset"] and item.originallyAvailableAt: + item.editField("originallyAvailableAt", None, locked=self.library.mass_originally_available_update == "remove") + batch_display += f"\nOriginally Available Date | None" + elif self.library.mass_originally_available_update in ["unlock", "reset"] and "originallyAvailableAt" in locked_fields: + self.library.edit_query(item, {"originallyAvailableAt.locked": 0}) + elif self.library.mass_originally_available_update in ["lock", "remove"] and "originallyAvailableAt" not in locked_fields: + self.library.edit_query(item, {"originallyAvailableAt.locked": 1}) + elif self.library.mass_originally_available_update not in ["lock", "unlock", "remove", "reset"]: + try: + if omdb_item and self.library.mass_originally_available_update == "omdb": + new_date = omdb_item.released + elif mdb_item and self.library.mass_originally_available_update == "mdb": + new_date = mdb_item.released + elif tvdb_item and self.library.mass_originally_available_update == "tvdb": + new_date = tvdb_item.release_date + elif tmdb_item and self.library.mass_originally_available_update == "tmdb": + new_date = tmdb_item.release_date if self.library.is_movie else tmdb_item.first_air_date + elif anidb_item and self.library.mass_originally_available_update == "anidb": + new_date = anidb_item.released + else: + raise Failed + if not new_date: + logger.info(f"No Originally Available Date Found") + elif str(item.originallyAvailableAt) != str(new_date): + item.editOriginallyAvailable(new_date) + batch_display += f"\nOriginally Available Date | {new_date.strftime('%Y-%m-%d')}" + except Failed: + pass - item.saveEdits() if len(batch_display) > 0: + item.saveEdits() logger.info(f"Batch Edits{batch_display}") episode_ops = [self.library.mass_episode_audience_rating_update, self.library.mass_episode_critic_rating_update, self.library.mass_episode_user_rating_update] @@ -386,52 +409,45 @@ class Operations: item_title = self.library.get_item_sort_title(ep, atr="title") logger.info("") logger.info(f"Processing {item_title}") - def get_episode_rating(attribute): - if tmdb_item and attribute == "tmdb": - try: - return self.config.TMDb.get_episode(tmdb_item.tmdb_id, ep.seasonNumber, ep.episodeNumber).vote_average - except Failed as er: - logger.error(er) - elif imdb_id and attribute == "imdb": - return self.config.IMDb.get_episode_rating(imdb_id, ep.seasonNumber, ep.episodeNumber) - else: - raise Failed + episode_locked_fields = [f.name for f in ep.fields if f.locked] + + def update_episode_rating(attribute, item_attr, display): + current = getattr(ep, item_attr) + if attribute in ["remove", "reset"] and current: + ep.editField(item_attr, None, locked=attribute == "remove") + return f"\n{display} | None" + elif attribute in ["unlock", "reset"] and item_attr in episode_locked_fields: + self.library.edit_query(ep, {f"{item_attr}.locked": 0}) + elif attribute in ["lock", "remove"] and item_attr not in episode_locked_fields: + self.library.edit_query(ep, {f"{item_attr}.locked": 1}) + elif attribute not in ["lock", "unlock", "remove", "reset"]: + found_rating = None + if tmdb_item and attribute == "tmdb": + try: + found_rating = self.config.TMDb.get_episode(tmdb_item.tmdb_id, ep.seasonNumber, ep.episodeNumber).vote_average + except Failed as er: + logger.error(er) + elif imdb_id and attribute == "imdb": + found_rating = self.config.IMDb.get_episode_rating(imdb_id, ep.seasonNumber, ep.episodeNumber) + + if found_rating is None: + logger.info(f"No {display} Found") + elif str(current) != str(found_rating): + ep.editField(item_attr, found_rating) + return f"\n{display} | {found_rating}" + return "" if self.library.mass_episode_audience_rating_update: - try: - new_rating = get_episode_rating(self.library.mass_episode_audience_rating_update) - if not new_rating: - logger.info(f"No Audience Rating Found") - elif str(ep.audienceRating) != str(new_rating): - ep.editField("audienceRating", new_rating) - batch_display += f"\nAudience Rating | {new_rating}" - except Failed: - pass + batch_display += update_episode_rating(self.library.mass_episode_audience_rating_update, "audienceRating", "Audience Rating") if self.library.mass_episode_critic_rating_update: - try: - new_rating = get_episode_rating(self.library.mass_episode_critic_rating_update) - if not new_rating: - logger.info(f"No Critic Rating Found") - elif str(ep.rating) != str(new_rating): - ep.editField("rating", new_rating) - batch_display += f"\nCritic Rating | {new_rating}" - except Failed: - pass + batch_display += update_episode_rating(self.library.mass_episode_critic_rating_update, "rating", "Critic Rating") if self.library.mass_episode_user_rating_update: - try: - new_rating = get_episode_rating(self.library.mass_episode_user_rating_update) - if not new_rating: - logger.info(f"No User Rating Found") - elif str(ep.userRating) != str(new_rating): - ep.editField("userRating", new_rating) - batch_display += f"\nUser Rating | {new_rating}" - except Failed: - pass - - ep.saveEdits() + batch_display += update_episode_rating(self.library.mass_episode_user_rating_update, "userRating", "User Rating") + if len(batch_display) > 0: + ep.saveEdits() logger.info(f"Batch Edits:{batch_display}") if self.library.Radarr and self.library.radarr_add_all_existing: diff --git a/modules/plex.py b/modules/plex.py index 08f53017..8539a939 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -548,6 +548,10 @@ class Plex(Library): def query_data(self, method, data): return method(data) + @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex) + def tag_edit(self, item, attribute, data, locked=True, remove=False): + return item.editTags(attribute, data, locked=locked, remove=remove) + @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed) def query_collection(self, item, collection, locked=True, add=True): if add: @@ -975,12 +979,12 @@ class Plex(Library): logger.stacktrace() return False - def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, do_print=True): + def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, do_print=True, locked=True, is_locked=None): display = "" final = "" key = attribute_translation[attr] if attr in attribute_translation else attr + actual = "similar" if attr == "similar_artist" else attr attr_display = attr.replace("_", " ").title() - attr_call = attr_display.replace(" ", "") if add_tags or remove_tags or sync_tags is not None: _add_tags = add_tags if add_tags else [] _remove_tags = remove_tags if remove_tags else [] @@ -993,13 +997,15 @@ class Plex(Library): _add = [t for t in _add_tags + _sync_tags if t not in _item_tags] _remove = [t for t in _item_tags if (sync_tags is not None and t not in _sync_tags) or t in _remove_tags] if _add: - self.query_data(getattr(obj, f"add{attr_call}"), _add) + self.tag_edit(obj, actual, _add, locked=locked) display += f"+{', +'.join(_add)}" if _remove: - self.query_data(getattr(obj, f"remove{attr_call}"), _remove) + self.tag_edit(obj, actual, _remove, locked=locked, remove=True) if display: display += ", " display += f"-{', -'.join(_remove)}" + if is_locked is not None and not display and is_locked != locked: + self.edit_query(obj, {f"{actual}.locked": 1 if locked else 0}) final = f"{obj.title[:25]:<25} | {attr_display} | {display}" if display else display if do_print and final: logger.info(final)