From 8e8a14ab354cad29e34e49fd6286dd2766d0ef52 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 25 Jan 2023 02:43:25 -0500 Subject: [PATCH] [26] add mass_studio_update --- VERSION | 2 +- docs/config/operations.md | 19 +++++++++++++++++++ modules/anidb.py | 31 +++++++++++++++++++++++-------- modules/cache.py | 37 ++++++++++++++++++++++++++----------- modules/config.py | 6 +++++- modules/library.py | 3 ++- modules/mal.py | 1 + modules/operations.py | 30 +++++++++++++++++++++++++++++- 8 files changed, 106 insertions(+), 23 deletions(-) diff --git a/VERSION b/VERSION index d5a9994f..0b82aa6a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.3-develop25 +1.18.3-develop26 diff --git a/docs/config/operations.md b/docs/config/operations.md index 2a3df92f..260fbded 100644 --- a/docs/config/operations.md +++ b/docs/config/operations.md @@ -23,6 +23,7 @@ The available attributes for the operations attribute are as follows | [Mass Genre Update](#mass-genre-update) | Updates every item's genres in the library to the chosen site's genres. | ✅ | ✅ | ✅ | | [Mass Content Rating Update](#mass-content-rating-update) | Updates every item's content rating in the library to the chosen site's content rating. | ✅ | ✅ | ❌ | | [Mass Original Title Update](#mass-original-title-update) | Updates every item's original title in the library to the chosen site's original title. | ✅ | ✅ | ❌ | +| [Mass Studio Update](#mass-studio-update) | Updates every item's studio in the library to the chosen site's studio. | ✅ | ✅ | ❌ | | [Mass Originally Available Update](#mass-originally-available-update) | Updates every item's originally available date in the library to the chosen site's date. | ✅ | ✅ | ❌ | | [Mass * Rating Update](#mass--rating-update) | Updates every item's audience/critic/user rating in the library to the chosen site's rating. | ✅ | ✅ | ❌ | | [Mass Episode * Rating Update](#mass-episode--rating-update) | Updates every item's episode's audience/critic/user rating in the library to the chosen site's rating. | ❌ | ✅ | ❌ | @@ -137,6 +138,24 @@ Updates every item's original title in the library to the chosen site's original | `remove` | Remove Original Title and Lock Field | | `reset` | Remove Original Title and Unlock Field | +## Mass Studio Update + +Updates every item's studio in the library to the chosen site's studio. + +**Attribute:** `mass_studio_update` + +**Values:** + +| Value | Description | +|:---------|:---------------------------------------| +| `anidb` | Use AniDB Animation Work for Studio | +| `mal` | Use MyAnimeList Studio for Studio | +| `tmdb` | Use TMDb Studio for Studio | +| `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. diff --git a/modules/anidb.py b/modules/anidb.py index 8be6b595..1af50c90 100644 --- a/modules/anidb.py +++ b/modules/anidb.py @@ -22,29 +22,29 @@ class AniDBObj: self.anidb_id = anidb_id self._data = data - def _parse(attr, xpath, is_list=False, is_dict=False, is_float=False, is_date=False, fail=False): + def _parse(attr, xpath, is_list=False, is_dict=False, is_int=False, is_float=False, is_date=False, fail=False): try: if isinstance(data, dict): if is_list: return data[attr].split("|") if data[attr] else [] elif is_dict: return json.loads(data[attr]) - elif is_float: - return util.check_num(data[attr], is_int=False) + elif is_int or is_float: + return util.check_num(data[attr], is_int=is_int) elif is_date: return datetime.strptime(data[attr], "%Y-%m-%d") else: return data[attr] parse_results = data.xpath(xpath) - if len(parse_results) > 0: + if is_dict: + return {ta.get("xml:lang"): ta.text_content() for ta in parse_results} + elif len(parse_results) > 0: parse_results = [r.strip() for r in parse_results if len(r) > 0] if parse_results: if is_list: return parse_results - elif is_dict: - return {ta.get("xml:lang"): ta.text_content() for ta in parse_results} - elif is_float: - return float(parse_results[0]) + elif is_int or is_float: + return util.check_num(parse_results[0], is_int=is_int) elif is_date: return datetime.strptime(parse_results[0], "%Y-%m-%d") else: @@ -63,11 +63,26 @@ class AniDBObj: self.main_title = _parse("main_title", "//anime/titles/title[@type='main']/text()", fail=True) self.titles = _parse("titles", "//anime/titles/title[@type='official']", is_dict=True) self.official_title = self.titles[self._anidb.language] if self._anidb.language in self.titles else self.main_title + self.studio = _parse("studio", "//anime/creators/name[@type='Animation Work']/text()") self.rating = _parse("rating", "//anime/ratings/permanent/text()", is_float=True) self.average = _parse("average", "//anime/ratings/temporary/text()", is_float=True) self.score = _parse("score", "//anime/ratings/review/text()", is_float=True) self.released = _parse("released", "//anime/startdate/text()", is_date=True) self.tags = _parse("tags", "//anime/tags/tag[@infobox='true']/name/text()", is_list=True) + self.mal_id = _parse("mal_id", "//anime/resources/resource[@type='2']/externalentity/identifier/text()", is_int=True) + self.imdb_id = _parse("imdb_id", "//anime/resources/resource[@type='43']/externalentity/identifier/text()") + if isinstance(data, dict): + self.tmdb_id = _parse("tmdb_id", "", is_int=True) + self.tmdb_type = _parse("tmdb_type", "") + else: + tmdb = _parse("tmdb", "//anime/resources/resource[@type='44']/externalentity/identifier/text()", is_list=True) + self.tmdb_id = None + self.tmdb_type = None + for i in tmdb: + try: + self.tmdb_id = int(i) + except ValueError: + self.tmdb_type = i class AniDB: diff --git a/modules/cache.py b/modules/cache.py index cf05cc98..7ff790d4 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -30,6 +30,8 @@ class Cache: cursor.execute("DROP TABLE IF EXISTS tvdb_data") cursor.execute("DROP TABLE IF EXISTS tvdb_data2") cursor.execute("DROP TABLE IF EXISTS overlay_ratings") + cursor.execute("DROP TABLE IF EXISTS anidb_data") + cursor.execute("DROP TABLE IF EXISTS mal_data") cursor.execute( """CREATE TABLE IF NOT EXISTS guids_map ( key INTEGER PRIMARY KEY, @@ -121,20 +123,25 @@ class Cache: expiration_date TEXT)""" ) cursor.execute( - """CREATE TABLE IF NOT EXISTS anidb_data ( + """CREATE TABLE IF NOT EXISTS anidb_data2 ( key INTEGER PRIMARY KEY, anidb_id INTEGER UNIQUE, main_title TEXT, titles TEXT, + studio TEXT, rating REAL, average REAL, score REAL, released TEXT, tags TEXT, + mal_id INTEGER, + imdb_id TEXT, + tmdb_id INTEGER, + tmdb_type TEXT, expiration_date TEXT)""" ) cursor.execute( - """CREATE TABLE IF NOT EXISTS mal_data ( + """CREATE TABLE IF NOT EXISTS mal_data2 ( key INTEGER PRIMARY KEY, mal_id INTEGER UNIQUE, title TEXT, @@ -148,6 +155,7 @@ class Cache: rank INTEGER, popularity TEXT, genres TEXT, + studio TEXT, expiration_date TEXT)""" ) cursor.execute( @@ -518,16 +526,21 @@ class Cache: with sqlite3.connect(self.cache_path) as connection: connection.row_factory = sqlite3.Row with closing(connection.cursor()) as cursor: - cursor.execute("SELECT * FROM anidb_data WHERE anidb_id = ?", (anidb_id,)) + cursor.execute("SELECT * FROM anidb_data2 WHERE anidb_id = ?", (anidb_id,)) row = cursor.fetchone() if row: anidb_dict["main_title"] = row["main_title"] anidb_dict["titles"] = row["titles"] if row["titles"] else None + anidb_dict["studio"] = row["studio"] if row["studio"] else None anidb_dict["rating"] = row["rating"] if row["rating"] else None anidb_dict["average"] = row["average"] if row["average"] else None anidb_dict["score"] = row["score"] if row["score"] else None anidb_dict["released"] = row["released"] if row["released"] else None anidb_dict["tags"] = row["tags"] if row["tags"] else None + anidb_dict["mal_id"] = row["mal_id"] if row["mal_id"] else None + anidb_dict["imdb_id"] = row["imdb_id"] if row["imdb_id"] else None + anidb_dict["tmdb_id"] = row["tmdb_id"] if row["tmdb_id"] else None + anidb_dict["tmdb_type"] = row["tmdb_type"] if row["tmdb_type"] 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 @@ -538,12 +551,13 @@ class Cache: 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 anidb_data(anidb_id) VALUES(?)", (anidb_id,)) - update_sql = "UPDATE anidb_data SET main_title = ?, titles = ?, rating = ?, average = ?, score = ?, " \ - "released = ?, tags = ?, expiration_date = ? WHERE anidb_id = ?" + cursor.execute("INSERT OR IGNORE INTO anidb_data2(anidb_id) VALUES(?)", (anidb_id,)) + update_sql = "UPDATE anidb_data SET main_title = ?, titles = ?, studio = ?, rating = ?, average = ?, score = ?, " \ + "released = ?, tags = ?, mal_id = ?, imdb_id = ?, tmdb_id = ?, tmdb_type = ?, expiration_date = ? WHERE anidb_id = ?" cursor.execute(update_sql, ( - anidb.main_title, str(anidb.titles), anidb.rating, anidb.average, anidb.score, + anidb.main_title, str(anidb.titles), anidb.studio, anidb.rating, anidb.average, anidb.score, anidb.released.strftime("%Y-%m-%d") if anidb.released else None, "|".join(anidb.tags), + anidb_id.mal_id, anidb.imdb_id, anidb.tmdb_id, anidb.tmdb_type, expiration_date.strftime("%Y-%m-%d"), anidb_id )) @@ -553,7 +567,7 @@ class Cache: with sqlite3.connect(self.cache_path) as connection: connection.row_factory = sqlite3.Row with closing(connection.cursor()) as cursor: - cursor.execute("SELECT * FROM mal_data WHERE mal_id = ?", (mal_id,)) + cursor.execute("SELECT * FROM mal_data2 WHERE mal_id = ?", (mal_id,)) row = cursor.fetchone() if row: mal_dict["title"] = row["title"] @@ -567,6 +581,7 @@ class Cache: mal_dict["rank"] = row["rank"] if row["rank"] else None mal_dict["popularity"] = row["popularity"] if row["popularity"] else None mal_dict["genres"] = row["genres"] if row["genres"] else None + mal_dict["studio"] = row["studio"] if row["studio"] 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 @@ -577,12 +592,12 @@ class Cache: 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 mal_data(mal_id) VALUES(?)", (mal_id,)) + cursor.execute("INSERT OR IGNORE INTO mal_data2(mal_id) VALUES(?)", (mal_id,)) update_sql = "UPDATE mal_data SET title = ?, title_english = ?, title_japanese = ?, status = ?, airing = ?, " \ - "aired = ?, rating = ?, score = ?, rank = ?, popularity = ?, genres = ? , expiration_date = ? WHERE mal_id = ?" + "aired = ?, rating = ?, score = ?, rank = ?, popularity = ?, genres = ?, studio = ? expiration_date = ? WHERE mal_id = ?" cursor.execute(update_sql, ( mal.title, mal.title_english, mal.title_japanese, mal.status, mal.airing, mal.aired.strftime("%Y-%m-%d") if mal.aired else None, - mal.rating, mal.score, mal.rank, mal.popularity, "|".join(mal.genres), expiration_date.strftime("%Y-%m-%d"), mal_id + mal.rating, mal.score, mal.rank, mal.popularity, "|".join(mal.genres), mal.studio, expiration_date.strftime("%Y-%m-%d"), mal_id )) def query_tmdb_movie(self, tmdb_id, expiration): diff --git a/modules/config.py b/modules/config.py index d50f3748..14f7e9c3 100644 --- a/modules/config.py +++ b/modules/config.py @@ -44,6 +44,10 @@ mass_content_options = { "omdb": "Use IMDb Rating through OMDb", "mdb": "Use MdbList Rating", "mdb_commonsense": "Use Commonsense Rating through MDbList", "mdb_commonsense0": "Use Commonsense Rating with Zero Padding through MDbList", "mal": "Use MyAnimeList Rating" } +mass_studio_options = { + "lock": "Unlock Rating", "unlock": "Unlock Rating", "remove": "Remove and Lock Rating", "reset": "Remove and Unlock Rating", + "tmdb": "Use TMDb Studio", "anidb": "Use AniDB Animation Work", "mal": "Use MyAnimeList Studio" +} 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", @@ -90,7 +94,7 @@ reset_overlay_options = {"tmdb": "Reset to TMDb poster", "plex": "Reset to Plex library_operations = { "assets_for_all": "bool", "split_duplicates": "bool", "update_blank_track_titles": "bool", "remove_title_parentheses": "bool", "radarr_add_all_existing": "bool", "radarr_remove_by_tag": "bool", "sonarr_add_all_existing": "bool", "sonarr_remove_by_tag": "bool", - "mass_genre_update": mass_genre_options, "mass_content_rating_update": mass_content_options, + "mass_genre_update": mass_genre_options, "mass_content_rating_update": mass_content_options, "mass_studio_update": mass_studio_options, "mass_audience_rating_update": mass_rating_options, "mass_episode_audience_rating_update": mass_episode_rating_options, "mass_critic_rating_update": mass_rating_options, "mass_episode_critic_rating_update": mass_episode_rating_options, "mass_user_rating_update": mass_rating_options, "mass_episode_user_rating_update": mass_episode_rating_options, diff --git a/modules/library.py b/modules/library.py index 329596e7..0a5fbc6f 100644 --- a/modules/library.py +++ b/modules/library.py @@ -74,6 +74,7 @@ class Library(ABC): self.ignore_imdb_ids = params["ignore_imdb_ids"] self.assets_for_all = params["assets_for_all"] self.delete_collections = params["delete_collections"] + self.mass_studio_update = params["mass_studio_update"] 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"] @@ -112,7 +113,7 @@ class Library(ABC): 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_original_title_update\ - or self.mass_imdb_parental_labels or self.genre_mapper or self.content_rating_mapper \ + or self.mass_imdb_parental_labels or self.genre_mapper or self.content_rating_mapper or self.mass_studio_update\ or self.radarr_add_all_existing or self.sonarr_add_all_existing or self.mass_poster_update or self.mass_background_update else False self.library_operation = True if self.items_library_operation or self.delete_collections or self.mass_collection_mode \ or self.radarr_remove_by_tag or self.sonarr_remove_by_tag or self.show_unmanaged or self.show_unconfigured \ diff --git a/modules/mal.py b/modules/mal.py index 94963451..282f7519 100644 --- a/modules/mal.py +++ b/modules/mal.py @@ -75,6 +75,7 @@ class MyAnimeListObj: self.rank = self._data["rank"] self.popularity = self._data["popularity"] self.genres = [] if not self._data["genres"] else self._data["genres"].split("|") if cache else [g["name"] for g in self._data["genres"]] + self.studio = self._data["studio"] if cache else self._data["studios"][0]["name"] if self._data["studios"] else None class MyAnimeList: diff --git a/modules/operations.py b/modules/operations.py index 1695056c..80db93dd 100644 --- a/modules/operations.py +++ b/modules/operations.py @@ -10,7 +10,7 @@ meta_operations = [ "mass_audience_rating_update", "mass_user_rating_update", "mass_critic_rating_update", "mass_episode_audience_rating_update", "mass_episode_user_rating_update", "mass_episode_critic_rating_update", "mass_genre_update", "mass_content_rating_update", "mass_originally_available_update", "mass_original_title_update", - "mass_poster_update", "mass_background_update" + "mass_poster_update", "mass_background_update", "mass_studio_update" ] class Operations: @@ -425,6 +425,34 @@ class Operations: except Failed: pass + if self.library.mass_studio_update: + if self.library.mass_studio_update in ["remove", "reset"] and item.studio: + item.editField("studio", None, locked=self.library.mass_studio_update == "remove") + batch_display += f"\nStudio | None" + elif self.library.mass_studio_update in ["unlock", "reset"] and "studio" in locked_fields: + self.library.edit_query(item, {"originalTitle.locked": 0}) + batch_display += f"\nStudio | Unlocked" + elif self.library.mass_studio_update in ["lock", "remove"] and "studio" not in locked_fields: + self.library.edit_query(item, {"studio.locked": 1}) + batch_display += f"\nStudio | Locked" + elif self.library.mass_studio_update not in ["lock", "unlock", "remove", "reset"]: + try: + if anidb_item and self.library.mass_studio_update == "anidb": + new_studio = anidb_item.studio + elif mal_item and self.library.mass_studio_update == "mal": + new_studio = mal_item.studio + elif tmdb_item and self.library.mass_studio_update == "tmdb": + new_studio = tmdb_item.studio + else: + raise Failed + if not new_studio: + logger.info(f"No Studio Found") + elif str(item.studio) != str(new_studio): + item.editStudio(new_studio) + batch_display += f"\nStudio | {new_studio}" + except Failed: + pass + if self.library.mass_originally_available_update: 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")