diff --git a/VERSION b/VERSION index 1741532c..10599f2a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.1-develop24 +1.16.1-develop25 diff --git a/docs/config/operations.md b/docs/config/operations.md index 5b663c5c..86f58f7a 100644 --- a/docs/config/operations.md +++ b/docs/config/operations.md @@ -25,6 +25,7 @@ The available attributes for the operations attribute are as follows | `mass_content_rating_update` | Updates every item's content rating in the library to the chosen site's genres
**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` | diff --git a/modules/cache.py b/modules/cache.py index d78b07d0..b7e8772a 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -203,6 +203,17 @@ class Cache: media_id TEXT, media_type TEXT)""" ) + cursor.execute( + """CREATE TABLE IF NOT EXISTS imdb_parental ( + key INTEGER PRIMARY KEY, + imdb_id TEXT, + nudity TEXT, + violence TEXT, + profanity TEXT, + alcohol TEXT, + frightening TEXT, + expiration_date TEXT)""" + ) cursor.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='image_map'") if cursor.fetchone()[0] > 0: cursor.execute(f"SELECT DISTINCT library FROM image_map") @@ -695,3 +706,33 @@ class Cache: connection.row_factory = sqlite3.Row with closing(connection.cursor()) as cursor: cursor.execute(f"DELETE FROM list_ids WHERE list_key = ?", (list_key,)) + + def query_imdb_parental(self, imdb_id, expiration): + imdb_dict = {} + expired = None + with sqlite3.connect(self.cache_path) as connection: + connection.row_factory = sqlite3.Row + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT * FROM imdb_parental WHERE imdb_id = ?", (imdb_id,)) + row = cursor.fetchone() + if row: + imdb_dict["nudity"] = row["nudity"] if row["nudity"] else "None" + imdb_dict["violence"] = row["violence"] if row["violence"] else "None" + imdb_dict["profanity"] = row["profanity"] if row["profanity"] else "None" + imdb_dict["alcohol"] = row["alcohol"] if row["alcohol"] else "None" + imdb_dict["frightening"] = row["frightening"] if row["frightening"] else "None" + datetime_object = datetime.strptime(row["expiration_date"], "%Y-%m-%d") + time_between_insertion = datetime.now() - datetime_object + expired = time_between_insertion.days > expiration + return imdb_dict, expired + + def update_imdb_parental(self, expired, imdb_id, parental, expiration): + expiration_date = datetime.now() if expired is True else (datetime.now() - timedelta(days=random.randint(1, expiration))) + with sqlite3.connect(self.cache_path) as connection: + connection.row_factory = sqlite3.Row + with closing(connection.cursor()) as cursor: + cursor.execute("INSERT OR IGNORE INTO imdb_parental(imdb_id) VALUES(?)", (imdb_id,)) + update_sql = "UPDATE imdb_parental SET nudity = ?, violence = ?, profanity = ?, alcohol = ?, " \ + "frightening = ?, expiration_date = ? WHERE imdb_id = ?" + cursor.execute(update_sql, (parental["nudity"], parental["violence"], parental["profanity"], parental["alcohol"], + parental["frightening"], expiration_date.strftime("%Y-%m-%d"), imdb_id)) \ No newline at end of file diff --git a/modules/config.py b/modules/config.py index a19e1366..b6fe822d 100644 --- a/modules/config.py +++ b/modules/config.py @@ -34,6 +34,7 @@ sync_modes = {"append": "Only Add Items to the Collection or Playlist", "sync": mass_genre_options = {"tmdb": "Use TMDb Metadata", "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_rating_options = { "tmdb": "Use TMDb Rating", "omdb": "Use IMDb Rating through OMDb", @@ -604,7 +605,8 @@ class ConfigFile: "genre_collections": None, "update_blank_track_titles": None, "mass_content_rating_update": None, - "mass_originally_available_update": None + "mass_originally_available_update": None, + "mass_imdb_parental_labels": None } display_name = f"{params['name']} ({params['mapping_name']})" if lib and "library_name" in lib and lib["library_name"] else params["mapping_name"] @@ -675,6 +677,8 @@ class ConfigFile: 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"]: params["mass_originally_available_update"] = check_for_attribute(lib["operations"], "mass_originally_available_update", test_list=mass_available_options, default_is_none=True, save=False) + if "mass_imdb_parental_labels" in lib["operations"]: + params["mass_imdb_parental_labels"] = check_for_attribute(lib["operations"], "mass_imdb_parental_labels", test_list=imdb_label_options, default_is_none=True, save=False) if "mass_trakt_rating_update" in lib["operations"]: params["mass_trakt_rating_update"] = check_for_attribute(lib["operations"], "mass_trakt_rating_update", var_type="bool", default=False, save=False) if "split_duplicates" in lib["operations"]: diff --git a/modules/imdb.py b/modules/imdb.py index 39e8dbd3..2fb73750 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -128,6 +128,24 @@ class IMDb: return imdb_ids raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}") + def parental_guide(self, imdb_id, ignore_cache=False): + parental_dict = {} + expired = None + if self.config.Cache and not ignore_cache: + parental_dict, expired = self.config.Cache.query_imdb_parental(imdb_id, self.config.expiration) + if parental_dict and expired is False: + return parental_dict + response = self.config.get_html(f"https://www.imdb.com/title/{imdb_id}/parentalguide") + for ptype in ["nudity", "violence", "profanity", "alcohol", "frightening"]: + results = response.xpath(f"//section[@id='advisory-{ptype}']//span[contains(@class,'ipl-status-pill')]/text()") + if results: + parental_dict[ptype] = results[0].strip() + else: + raise Failed(f"IMDb Error: No Item Found for IMDb ID: {imdb_id}") + if self.config.Cache and not ignore_cache: + self.config.Cache.update_imdb_parental(expired, imdb_id, parental_dict, self.config.expiration) + return parental_dict + def _ids_from_chart(self, chart): if chart == "box_office": url = "chart/boxoffice" diff --git a/modules/library.py b/modules/library.py index d0c41f42..a4be913d 100644 --- a/modules/library.py +++ b/modules/library.py @@ -73,6 +73,7 @@ class Library(ABC): self.mass_critic_rating_update = params["mass_critic_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"] self.mass_trakt_rating_update = params["mass_trakt_rating_update"] self.radarr_add_all_existing = params["radarr_add_all_existing"] self.radarr_remove_by_tag = params["radarr_remove_by_tag"] @@ -95,7 +96,7 @@ class Library(ABC): 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.mass_critic_rating_update or self.mass_content_rating_update or self.mass_originally_available_update or self.mass_trakt_rating_update \ + 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.tmdb_collections 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 \ diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 491e0d2b..dc7603dc 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -417,6 +417,7 @@ def library_operations(config, library): logger.debug(f"Mass Critic Rating Update: {library.mass_critic_rating_update}") logger.debug(f"Mass Content Rating Update: {library.mass_content_rating_update}") logger.debug(f"Mass Originally Available Update: {library.mass_originally_available_update}") + logger.debug(f"Mass IMDb Parental Labels: {library.mass_imdb_parental_labels}") logger.debug(f"Mass Trakt Rating Update: {library.mass_trakt_rating_update}") logger.debug(f"Mass Collection Mode Update: {library.mass_collection_mode}") logger.debug(f"Split Duplicates: {library.split_duplicates}") @@ -486,6 +487,13 @@ def library_operations(config, library): except Failed: pass + if library.mass_imdb_parental_labels: + try: + parental_guide = config.IMDb.parental_guide(imdb_id) + labels = [f"{k.capitalize()}:{v}" for k, v in parental_guide.items() if library.mass_imdb_parental_labels == "with_none" or v != "None"] + library.edit_tags("label", item, append_tags=labels) + except Failed: + pass path = os.path.dirname(str(item.locations[0])) if library.is_movie else str(item.locations[0]) if library.Radarr and library.radarr_add_all_existing and tmdb_id: path = path.replace(library.Radarr.plex_path, library.Radarr.radarr_path)