diff --git a/VERSION b/VERSION index a8588ceb..c4d63cf7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.0-develop29 +1.17.0-develop30 diff --git a/docs/config/operations.md b/docs/config/operations.md index f98ba93b..22b6d937 100644 --- a/docs/config/operations.md +++ b/docs/config/operations.md @@ -16,28 +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
`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_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` | Updates every item's audience/critic rating in the library to the chosen site's rating
**Values:**
`tmdb`Use TMDb 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
`anidb_rating`Use AniDB Rating
`anidb_average`Use AniDB Average
| -| `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_trakt_rating_update` | Updates every movie/show's user rating in the library to match your custom rating on Trakt if there is one
**Values:** `true` or `false` | -| `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
| +| `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_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
`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
`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_trakt_rating_update` | Updates every movie/show's user rating in the library to match your custom rating on Trakt if there is one
**Values:** `true` or `false` | +| `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 32a0c074..32b09cd5 100644 --- a/modules/config.py +++ b/modules/config.py @@ -33,12 +33,14 @@ 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", "omdb": "Use IMDb Metadata through OMDb", "tvdb": "Use TVDb Metadata", "anidb": "Use AniDB Tag Metadata"} +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_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_rating_options = { "tmdb": "Use TMDb Rating", + "imdb": "Use IMDb Rating", "omdb": "Use IMDb Rating through OMDb", "mdb": "Use MdbList Average Score", "mdb_imdb": "Use IMDb Rating through MDbList", @@ -584,6 +586,9 @@ class ConfigFile: "mass_originally_available_update": None, "mass_imdb_parental_labels": None, "remove_title_parentheses": None, + "mass_episode_audience_rating_update": None, + "mass_episode_critic_rating_update": None, + "mass_episode_user_rating_update": None, } display_name = f"{params['name']} ({params['mapping_name']})" if lib and "library_name" in lib and lib["library_name"] else params["mapping_name"] @@ -652,6 +657,14 @@ class ConfigFile: params["mass_audience_rating_update"] = check_for_attribute(lib["operations"], "mass_audience_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) if "mass_critic_rating_update" in lib["operations"]: params["mass_critic_rating_update"] = check_for_attribute(lib["operations"], "mass_critic_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) + if "mass_user_rating_update" in lib["operations"]: + params["mass_user_rating_update"] = check_for_attribute(lib["operations"], "mass_user_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) + if "mass_episode_audience_rating_update" in lib["operations"]: + params["mass_episode_audience_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_audience_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False) + if "mass_episode_critic_rating_update" in lib["operations"]: + params["mass_episode_critic_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_critic_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False) + if "mass_episode_user_rating_update" in lib["operations"]: + params["mass_episode_user_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_user_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False) if "mass_content_rating_update" in lib["operations"]: params["mass_content_rating_update"] = check_for_attribute(lib["operations"], "mass_content_rating_update", test_list=mass_content_options, default_is_none=True, save=False) if "mass_originally_available_update" in lib["operations"]: diff --git a/modules/imdb.py b/modules/imdb.py index 76e97c8e..445302bc 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -1,4 +1,4 @@ -import math, re, time +import csv, gzip, math, os, re, requests, shutil, time from modules import util from modules.util import Failed from urllib.parse import urlparse, parse_qs @@ -30,6 +30,9 @@ urls = { class IMDb: def __init__(self, config): self.config = config + self._ratings = None + self._genres = None + self._episode_ratings = None def validate_imdb_lists(self, err_type, imdb_lists, language): valid_lists = [] @@ -185,3 +188,71 @@ class IMDb: return [(_i, "imdb") for _i in self._ids_from_chart(data)] else: raise Failed(f"IMDb Error: Method {method} not supported") + + def _interface(self, interface): + gz = os.path.join(self.config.default_dir, f"title.{interface}.tsv.gz") + tsv = os.path.join(self.config.default_dir, f"title.{interface}.tsv") + + if os.path.exists(gz): + os.remove(gz) + if os.path.exists(tsv): + os.remove(tsv) + + with requests.get(f"https://datasets.imdbws.com/title.{interface}.tsv.gz", stream=True) as r: + r.raise_for_status() + total_length = r.headers.get('content-length') + if total_length is not None: + total_length = int(total_length) + dl = 0 + with open(gz, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + dl += len(chunk) + f.write(chunk) + logger.ghost(f"Downloading IMDb Interface: {dl / total_length * 100:6.2f}%") + logger.exorcise() + + with open(tsv, "wb") as f_out: + with gzip.open(gz, "rb") as f_in: + shutil.copyfileobj(f_in, f_out) + + with open(tsv, "r") as t: + if interface == "ratings": + return {line[0]: line[1] for line in csv.reader(t, delimiter="\t")} + elif interface == "basics": + return {line[0]: str(line[-1]).split(",") for line in csv.reader(tsv, delimiter="\t")} + else: + return [line for line in csv.reader(t, delimiter="\t")] + + @property + def ratings(self): + if self._ratings is None: + self._ratings = self._interface("ratings") + return self._ratings + + @property + def genres(self): + if self._genres is None: + self._genres = self._interface("basics") + return self._genres + + @property + def episode_ratings(self): + if self._episode_ratings is None: + self._episode_ratings = {} + for imdb_id, parent_id, season_num, episode_num in self._interface("episode"): + if imdb_id not in self.ratings: + continue + if parent_id not in self._episode_ratings: + self._episode_ratings[parent_id] = {} + if season_num not in self._episode_ratings[parent_id]: + self._episode_ratings[parent_id][season_num] = {} + self._episode_ratings[parent_id][season_num][episode_num] = self.ratings[imdb_id] + return self._episode_ratings + + def get_rating(self, imdb_id): + return self.ratings[imdb_id] if imdb_id in self.ratings else None + + def get_episode_rating(self, imdb_id, season_num, episode_num): + if imdb_id not in self.episode_ratings or season_num not in self.episode_ratings[imdb_id] or episode_num not in self.episode_ratings[imdb_id][season_num]: + return None + return self.episode_ratings[imdb_id][season_num][episode_num] diff --git a/modules/library.py b/modules/library.py index 5637a86c..3f7ca4d4 100644 --- a/modules/library.py +++ b/modules/library.py @@ -77,6 +77,10 @@ class Library(ABC): self.mass_genre_update = params["mass_genre_update"] self.mass_audience_rating_update = params["mass_audience_rating_update"] self.mass_critic_rating_update = params["mass_critic_rating_update"] + self.mass_user_rating_update = params["mass_user_rating_update"] + self.mass_episode_audience_rating_update = params["mass_episode_audience_rating_update"] + self.mass_episode_critic_rating_update = params["mass_episode_critic_rating_update"] + self.mass_episode_user_rating_update = params["mass_episode_user_rating_update"] self.mass_content_rating_update = params["mass_content_rating_update"] self.mass_originally_available_update = params["mass_originally_available_update"] self.mass_imdb_parental_labels = params["mass_imdb_parental_labels"] @@ -101,13 +105,18 @@ class Library(ABC): self.stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0, "names": []} self.status = {} - self.items_library_operation = True if self.assets_for_all or self.mass_genre_update or self.mass_audience_rating_update or self.remove_title_parentheses \ - or self.mass_critic_rating_update or self.mass_content_rating_update or self.mass_originally_available_update or self.mass_imdb_parental_labels or self.mass_trakt_rating_update \ - or self.genre_mapper or self.content_rating_mapper or self.radarr_add_all_existing or self.sonarr_add_all_existing else False + self.items_library_operation = True if self.assets_for_all or self.mass_genre_update or self.remove_title_parentheses \ + or self.mass_audience_rating_update or self.mass_critic_rating_update or self.mass_user_rating_update \ + or self.mass_episode_audience_rating_update or self.mass_episode_critic_rating_update or self.mass_episode_user_rating_update \ + or self.mass_content_rating_update or self.mass_originally_available_update or self.mass_imdb_parental_labels \ + or self.mass_trakt_rating_update or self.genre_mapper or self.content_rating_mapper \ + or self.radarr_add_all_existing or self.sonarr_add_all_existing else False self.library_operation = True if self.items_library_operation or self.delete_unmanaged_collections or self.delete_collections_with_less \ or self.radarr_remove_by_tag or self.sonarr_remove_by_tag or self.mass_collection_mode \ or self.show_unmanaged or self.metadata_backup or self.update_blank_track_titles else False - self.meta_operations = [self.mass_genre_update, self.mass_audience_rating_update, self.mass_critic_rating_update, self.mass_content_rating_update, self.mass_originally_available_update] + self.meta_operations = [self.mass_genre_update, self.mass_audience_rating_update, self.mass_critic_rating_update, + self.mass_user_rating_update, self.mass_episode_audience_rating_update, self.mass_episode_critic_rating_update, + self.mass_episode_user_rating_update, self.mass_content_rating_update, self.mass_originally_available_update] if self.asset_directory: logger.info("") diff --git a/modules/operations.py b/modules/operations.py index e1749269..6d8d265f 100644 --- a/modules/operations.py +++ b/modules/operations.py @@ -2,8 +2,6 @@ import os, re from datetime import datetime from modules import plex, util from modules.util import Failed, YAML -from plexapi.audio import Artist -from plexapi.video import Show logger = util.logger @@ -23,6 +21,10 @@ class Operations: logger.debug(f"Mass Genre Update: {self.library.mass_genre_update}") logger.debug(f"Mass Audience Rating Update: {self.library.mass_audience_rating_update}") logger.debug(f"Mass Critic Rating Update: {self.library.mass_critic_rating_update}") + logger.debug(f"Mass User Rating Update: {self.library.mass_user_rating_update}") + logger.debug(f"Mass Episode Audience Rating Update: {self.library.mass_episode_audience_rating_update}") + logger.debug(f"Mass Episode Critic Rating Update: {self.library.mass_episode_critic_rating_update}") + logger.debug(f"Mass Episode User Rating Update: {self.library.mass_episode_user_rating_update}") logger.debug(f"Mass Content Rating Update: {self.library.mass_content_rating_update}") logger.debug(f"Mass Originally Available Update: {self.library.mass_originally_available_update}") logger.debug(f"Mass IMDb Parental Labels: {self.library.mass_imdb_parental_labels}") @@ -136,21 +138,18 @@ class Operations: omdb_item = None if any([o == "omdb" for o in self.library.meta_operations]): - if self.config.OMDb.limit is False: - if tmdb_id and not imdb_id: - imdb_id = self.config.Convert.tmdb_to_imdb(tmdb_id) - elif tvdb_id and not imdb_id: - imdb_id = self.config.Convert.tvdb_to_imdb(tvdb_id) - if imdb_id: - try: - omdb_item = self.config.OMDb.get_omdb(imdb_id) - except Failed as e: - logger.error(str(e)) - except Exception: - logger.error(f"IMDb ID: {imdb_id}") - raise - else: - logger.info(f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}") + if self.config.OMDb.limit is not False: + logger.error("Daily OMDb Limit Reached") + elif not imdb_id: + logger.info(f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}") + else: + try: + omdb_item = self.config.OMDb.get_omdb(imdb_id) + except Failed as e: + logger.error(str(e)) + except Exception: + logger.error(f"IMDb ID: {imdb_id}") + raise tvdb_item = None if any([o == "tvdb" for o in self.library.meta_operations]): @@ -200,6 +199,8 @@ class Operations: def get_rating(attribute): if tmdb_item and attribute == "tmdb": return tmdb_item.vote_average + elif imdb_id and attribute == "imdb": + return self.config.imdb.get_rating(imdb_id) elif omdb_item and attribute == "omdb": return omdb_item.imdb_rating elif mdb_item and attribute == "mdb": @@ -233,6 +234,8 @@ class Operations: if self.library.mass_genre_update: 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: + new_genres = self.config.imdb.genres[imdb_id] elif omdb_item and self.library.mass_genre_update == "omdb": new_genres = omdb_item.genres elif tvdb_item and self.library.mass_genre_update == "tvdb": @@ -276,7 +279,18 @@ class Operations: logger.info(f"{item.title[:25]:<25} | No Rating Found") elif str(item.rating) != str(new_rating): item.editField("rating", new_rating) - batch_display += f"{item.title[:25]:<25} | Critic Rating | {new_rating}" + batch_display += f"\n{item.title[:25]:<25} | Critic Rating | {new_rating}" + except Failed: + pass + + if self.library.mass_user_rating_update: + try: + new_rating = get_rating(self.library.mass_user_rating_update) + if new_rating is None: + logger.info(f"{item.title[:25]:<25} | No Rating Found") + elif str(item.userRating) != str(new_rating): + item.editField("userRating", new_rating) + batch_display += f"\n{item.title[:25]:<25} | User Rating | {new_rating}" except Failed: pass @@ -327,6 +341,62 @@ class Operations: pass item.saveEdits() + logger.info(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] + + if any([x is not None for x in episode_ops]): + + if any([x == "imdb" for x in episode_ops]) and not imdb_id: + logger.info(f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}") + + for ep in item.episodes(): + ep.batchEdits() + item_title = self.library.get_item_sort_title(ep, atr="title") + + def get_episode_rating(attribute): + if tmdb_id and attribute == "tmdb": + try: + return self.config.TMDb.get_episode(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 + + if self.library.mass_episode_audience_rating_update: + try: + new_rating = get_episode_rating(self.library.mass_episode_audience_rating_update) + if new_rating is None: + logger.info(f"{item_title[:25]:<25} | No Rating Found") + elif str(ep.audienceRating) != str(new_rating): + ep.editField("audienceRating", new_rating) + logger.info(f"\n{item_title[:25]:<25} | Audience Rating | {new_rating}") + except Failed: + pass + + if self.library.mass_episode_critic_rating_update: + try: + new_rating = get_episode_rating(self.library.mass_episode_critic_rating_update) + if new_rating is None: + logger.info(f"{item_title[:25]:<25} | No Rating Found") + elif str(ep.rating) != str(new_rating): + ep.editField("rating", new_rating) + logger.info(f"{item_title[:25]:<25} | Critic Rating | {new_rating}") + except Failed: + pass + + if self.library.mass_episode_user_rating_update: + try: + new_rating = get_episode_rating(self.library.mass_episode_user_rating_update) + if new_rating is None: + logger.info(f"{item_title[:25]:<25} | No Rating Found") + elif str(ep.userRating) != str(new_rating): + ep.editField("userRating", new_rating) + logger.info(f"{item_title[:25]:<25} | User Rating | {new_rating}") + except Failed: + pass if self.library.Radarr and self.library.radarr_add_all_existing: try: diff --git a/modules/overlays.py b/modules/overlays.py index 8c56ceb3..856dfe9d 100644 --- a/modules/overlays.py +++ b/modules/overlays.py @@ -3,7 +3,6 @@ from datetime import datetime from modules import plex, util from modules.builder import CollectionBuilder from modules.util import Failed, NotScheduled -from plexapi.audio import Album from plexapi.exceptions import BadRequest from plexapi.video import Movie, Show, Season, Episode from PIL import Image, ImageFilter @@ -37,7 +36,7 @@ class Overlays: logger.separator(f"Removing {old_overlay.title}") logger.info("") for i, item in enumerate(label_items, 1): - item_title = self.get_item_sort_title(item, atr="title") + item_title = self.library.get_item_sort_title(item, atr="title") logger.ghost(f"Restoring {old_overlay.title}: {i}/{len(label_items)} {item_title}") self.remove_overlay(item, item_title, old_overlay.title, [ os.path.join(self.library.overlay_folder, old_overlay.title[:-8], f"{item.ratingKey}.png") @@ -61,7 +60,7 @@ class Overlays: if remove_overlays: logger.separator(f"Removing Overlays for the {self.library.name} Library") for i, item in enumerate(remove_overlays, 1): - item_title = self.get_item_sort_title(item, atr="title") + item_title = self.library.get_item_sort_title(item, atr="title") logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item_title}") self.remove_overlay(item, item_title, "Overlay", [ os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png"), @@ -75,9 +74,9 @@ class Overlays: logger.info("") logger.separator(f"Applying Overlays for the {self.library.name} Library") logger.info("") - for i, (over_key, (item, over_names)) in enumerate(sorted(key_to_overlays.items(), key=lambda io: self.get_item_sort_title(io[1][0])), 1): + for i, (over_key, (item, over_names)) in enumerate(sorted(key_to_overlays.items(), key=lambda io: self.library.get_item_sort_title(io[1][0])), 1): try: - item_title = self.get_item_sort_title(item, atr="title") + item_title = self.library.get_item_sort_title(item, atr="title") logger.ghost(f"Overlaying: {i}/{len(key_to_overlays)} {item_title}") image_compare = None overlay_compare = None @@ -278,16 +277,6 @@ class Overlays: logger.separator(f"Finished {self.library.name} Library Overlays\nOverlays Run Time: {overlay_run_time}") return overlay_run_time - def get_item_sort_title(self, item_to_sort, atr="titleSort"): - if isinstance(item_to_sort, Album): - return f"{getattr(item_to_sort.artist(), atr)} Album {getattr(item_to_sort, atr)}" - elif isinstance(item_to_sort, Season): - return f"{getattr(item_to_sort.show(), atr)} Season {item_to_sort.seasonNumber}" - elif isinstance(item_to_sort, Episode): - return f"{getattr(item_to_sort.show(), atr)} {item_to_sort.seasonEpisode.upper()}" - else: - return getattr(item_to_sort, atr) - def compile_overlays(self): key_to_item = {} properties = {} @@ -334,7 +323,7 @@ class Overlays: if item.ratingKey not in properties[builder.overlay.name].keys: properties[builder.overlay.name].keys.append(item.ratingKey) if added_titles: - logger.debug(f"{len(added_titles)} Titles Found: {[self.get_item_sort_title(a, atr='title') for a in added_titles]}") + logger.debug(f"{len(added_titles)} Titles Found: {[self.library.get_item_sort_title(a, atr='title') for a in added_titles]}") logger.info(f"{len(added_titles) if added_titles else 'No'} Items found for {builder.overlay.name}") except NotScheduled as e: logger.info(e) diff --git a/modules/plex.py b/modules/plex.py index b1d5953f..2d416205 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -1184,6 +1184,16 @@ class Plex(Library): return map_key, attrs + def get_item_sort_title(self, item_to_sort, atr="titleSort"): + if isinstance(item_to_sort, Album): + return f"{getattr(item_to_sort.artist(), atr)} Album {getattr(item_to_sort, atr)}" + elif isinstance(item_to_sort, Season): + return f"{getattr(item_to_sort.show(), atr)} Season {item_to_sort.seasonNumber}" + elif isinstance(item_to_sort, Episode): + return f"{getattr(item_to_sort.show(), atr)} {item_to_sort.seasonEpisode.upper()}" + else: + return getattr(item_to_sort, atr) + def split(self, text): attribute, modifier = os.path.splitext(str(text).lower()) attribute = method_alias[attribute] if attribute in method_alias else attribute @@ -1356,4 +1366,4 @@ class Plex(Library): elif (not list(set(filter_data) & set(attrs)) and modifier == "") \ or (list(set(filter_data) & set(attrs)) and modifier == ".not"): return False - return True \ No newline at end of file + return True