[57] TVDb Filters

pull/1964/head
meisnate12 1 month ago
parent 437f86f942
commit 265c2b8294

@ -2,7 +2,7 @@
Updated gitpython requirement to 3.1.42
Updated plexapi requirement to 4.5.10
Updated setuptools requirement to 69.1.1
Updated tmdbapis requirement to 1.2.7
Updated tmdbapis requirement to 1.2.9
# Removed Features
Due to FlixPatrol moving a lot of their data behind a paywall and them reworking their pages to remove IMDb IDs and TMDb IDs the flixpatrol builders and default files have been removed. There currently are no plans to re-add them.
@ -12,6 +12,7 @@ Added new [BoxOfficeMojo Builder](https://metamanager.wiki/en/latest/files/build
Added `monitor_existing` to sonarr and radarr. To update the monitored status of items existing in plex to match the `monitor` declared.
Added [Gotify](https://gotify.net/) as a notification service. Thanks @krstn420 for the initial code.
Added [Trakt and MyAnimeList Authentication Page](https://metamanager.wiki/en/latest/config/auth/) allowing users to authenticate against those services directly from the wiki. credit to @chazlarson for developing the script
Added TVDb filters
# Updates
Reworked PMM Default Streaming [Collections](https://metamanager.wiki/en/latest/defaults/both/streaming) and [Overlays](https://metamanager.wiki/en/latest/defaults/overlays/streaming) to utilize TMDB Watch Provider data, allowing users to customize regions without relying on mdblist. This data will be more accurate and up-to-date now.

@ -1 +1 @@
1.20.0-develop56
1.20.0-develop57

@ -56,21 +56,23 @@ String filters can take multiple values **only as a list**.
### Attribute
| String Filter | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:---------------------------------------------------|:-----------------------------------------|:------------------------------------------:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:|:-------------------------------------------:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:|:------------------------------------------:|
| `title` | Uses the title attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } |
| `tmdb_title`<sup>**[2](#table-annotations)**</sup> | Uses the title from TMDb to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `summary` | Uses the summary attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } |
| `studio` | Uses the studio attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `edition` | Uses the edition attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `record_label` | Uses the record label attribute to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } |
| `folder` | Uses the item's folder to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `filepath` | Uses the item's filepath to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } |
| `audio_track_title` | Uses the audio track titles to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } |
| `video_codec` | Uses the video codec tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `video_profile` | Uses the video profile tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `audio_codec` | Uses the audio codec tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `audio_profile` | Uses the audio profile tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| String Filter | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:----------------------------------------------------|:-----------------------------------------|:------------------------------------------:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:|:-------------------------------------------:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:|:------------------------------------------:|
| `title` | Uses the title attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } |
| `tmdb_title`<sup>**[2](#table-annotations)**</sup> | Uses the title from TMDb to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `tvdb_title`<sup>**[2](#table-annotations)**</sup> | Uses the title from TVDb to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `tvdb_status`<sup>**[2](#table-annotations)**</sup> | Uses the status from TVDb to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `summary` | Uses the summary attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } |
| `studio` | Uses the studio attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `edition` | Uses the edition attribute to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `record_label` | Uses the record label attribute to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } |
| `folder` | Uses the item's folder to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `filepath` | Uses the item's filepath to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } |
| `audio_track_title` | Uses the audio track titles to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } |
| `video_codec` | Uses the video codec tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `video_profile` | Uses the video profile tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `audio_codec` | Uses the audio codec tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `audio_profile` | Uses the audio profile tags to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green }<sup>**[1](#table-annotations)**</sup> | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
## Tag Filters
@ -111,6 +113,7 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
| `tmdb_genre`<sup>**[2](#table-annotations)**</sup> | Uses the genres from TMDb to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `tmdb_keyword`<sup>**[2](#table-annotations)**</sup> | Uses the keywords from TMDb to match | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `origin_country`<sup>**[2](#table-annotations)**</sup> | Uses TMDb origin country [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to match<br>Example: `origin_country: us` | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `tvdb_genre`<sup>**[2](#table-annotations)**</sup> | Uses the genres from TVDb to match | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
| `imdb_keyword`<sup>**[2](#table-annotations)**</sup> | Uses the keywords from IMDb to match See [Special](#special-filters) for more attributes | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } | :fontawesome-solid-circle-xmark:{ .red } |
## Boolean Filters

@ -89,7 +89,7 @@ filters_by_type = {
"show_season": ["episodes"],
"artist_album": ["tracks"],
"movie": ["edition", "has_edition", "stinger_rating", "has_stinger"],
"show": ["seasons", "tmdb_status", "tmdb_type", "origin_country", "network", "first_episode_aired", "last_episode_aired", "last_episode_aired_or_never"],
"show": ["seasons", "tmdb_status", "tmdb_type", "origin_country", "network", "first_episode_aired", "last_episode_aired", "last_episode_aired_or_never", "tvdb_title", "tvdb_status", "tvdb_genre"],
"artist": ["albums"],
"album": ["record_label"]
}
@ -106,15 +106,16 @@ tmdb_filters = [
"original_language", "origin_country", "tmdb_vote_count", "tmdb_vote_average", "tmdb_year", "tmdb_keyword", "tmdb_genre",
"first_episode_aired", "last_episode_aired", "last_episode_aired_or_never", "tmdb_status", "tmdb_type", "tmdb_title"
]
tvdb_filters = ["tvdb_title", "tvdb_status", "tvdb_genre"]
imdb_filters = ["imdb_keyword"]
string_filters = [
"title", "summary", "studio", "edition", "record_label", "folder", "filepath", "audio_track_title", "subtitle_track_title", "tmdb_title",
"audio_codec", "audio_profile", "video_codec", "video_profile"
"audio_codec", "audio_profile", "video_codec", "video_profile", "tvdb_title", "tvdb_status"
]
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
tag_filters = [
"actor", "collection", "content_rating", "country", "director", "network", "genre", "label", "producer", "year",
"origin_country", "writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre", "imdb_keyword"
"origin_country", "writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre", "imdb_keyword", "tvdb_genre"
]
tag_modifiers = ["", ".not", ".regex", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]
boolean_filters = ["has_collection", "has_edition", "has_overlay", "has_dolby_vision", "has_stinger"]
@ -2886,6 +2887,13 @@ class CollectionBuilder:
return False
return True
def check_tvdb_filters(self, tvdb_item, filters_in):
for filter_method, filter_data in filters_in:
filter_attr, modifier, filter_final = self.library.split(filter_method)
if self.config.TVDb.item_filter(tvdb_item, filter_attr, modifier, filter_final, filter_data) is False:
return False
return True
def check_imdb_filters(self, imdb_info, filters_in):
for filter_method, filter_data in filters_in:
filter_attr, modifier, filter_final = self.library.split(filter_method)
@ -2943,14 +2951,18 @@ class CollectionBuilder:
item = self.library.reload(item)
final_return = False
tmdb_item = None
tvdb_item = None
imdb_info = None
for filter_list in self.filters:
tmdb_f = []
tvdb_f = []
imdb_f = []
plex_f = []
for k, v in filter_list:
if k.split(".")[0] in tmdb_filters:
tmdb_f.append((k, v))
elif k.split(".")[0] in tvdb_filters:
tvdb_f.append((k, v))
elif k.split(".")[0] in imdb_filters:
imdb_f.append((k, v))
else:
@ -2972,6 +2984,19 @@ class CollectionBuilder:
or_result = False
if not tmdb_item or self.check_tmdb_filters(tmdb_item, tmdb_f, item.ratingKey in self.library.movie_rating_key_map) is False:
or_result = False
if tvdb_f:
if not tvdb_item and isinstance(item, Show):
if item.ratingKey not in self.library.show_rating_key_map:
logger.warning(f"Filter Error: No TVDb ID found for {item.title}")
or_result = False
else:
try:
tvdb_item = self.config.TVDb.get_tvdb_obj(self.library.show_rating_key_map[item.ratingKey])
except Failed as e:
logger.error(e)
or_result = False
if not tvdb_item or self.check_tvdb_filters(tvdb_item, tvdb_f) is False:
or_result = False
if imdb_f:
if not imdb_info and isinstance(item, (Movie, Show)):
if item.ratingKey not in self.library.imdb_rating_key_map:

@ -17,26 +17,13 @@ class Cache:
logger.info(f"Initializing cache database at {self.cache_path}")
else:
logger.info(f"Using cache database at {self.cache_path}")
cursor.execute("DROP TABLE IF EXISTS guids")
cursor.execute("DROP TABLE IF EXISTS guid_map")
cursor.execute("DROP TABLE IF EXISTS imdb_to_tvdb_map")
cursor.execute("DROP TABLE IF EXISTS tmdb_to_tvdb_map")
cursor.execute("DROP TABLE IF EXISTS imdb_map")
cursor.execute("DROP TABLE IF EXISTS mdb_data")
cursor.execute("DROP TABLE IF EXISTS mdb_data2")
cursor.execute("DROP TABLE IF EXISTS mdb_data3")
cursor.execute("DROP TABLE IF EXISTS mdb_data4")
cursor.execute("DROP TABLE IF EXISTS omdb_data")
cursor.execute("DROP TABLE IF EXISTS omdb_data2")
cursor.execute("DROP TABLE IF EXISTS tvdb_data")
cursor.execute("DROP TABLE IF EXISTS tvdb_data2")
cursor.execute("DROP TABLE IF EXISTS tmdb_show_data")
cursor.execute("DROP TABLE IF EXISTS tmdb_show_data2")
cursor.execute("DROP TABLE IF EXISTS overlay_ratings")
cursor.execute("DROP TABLE IF EXISTS anidb_data")
cursor.execute("DROP TABLE IF EXISTS anidb_data2")
cursor.execute("DROP TABLE IF EXISTS anidb_data3")
cursor.execute("DROP TABLE IF EXISTS mal_data")
for old_table in [
"guids", "guid_map", "imdb_to_tvdb_map", "tmdb_to_tvdb_map", "imdb_map",
"mdb_data", "mdb_data2", "mdb_data3", "mdb_data4", "omdb_data", "omdb_data2",
"tvdb_data", "tvdb_data2", "tvdb_data3", "tmdb_show_data", "tmdb_show_data2",
"overlay_ratings", "anidb_data", "anidb_data2", "anidb_data3", "mal_data"
]:
cursor.execute(f"DROP TABLE IF EXISTS {old_table}")
cursor.execute(
"""CREATE TABLE IF NOT EXISTS guids_map (
key INTEGER PRIMARY KEY,
@ -231,11 +218,12 @@ class Cache:
expiration_date TEXT)"""
)
cursor.execute(
"""CREATE TABLE IF NOT EXISTS tvdb_data3 (
"""CREATE TABLE IF NOT EXISTS tvdb_data4 (
key INTEGER PRIMARY KEY,
tvdb_id INTEGER UNIQUE,
type TEXT,
title TEXT,
status TEXT,
summary TEXT,
poster_url TEXT,
background_url TEXT,
@ -783,12 +771,13 @@ 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 tvdb_data3 WHERE tvdb_id = ? and type = ?", (tvdb_id, "movie" if is_movie else "show"))
cursor.execute("SELECT * FROM tvdb_data4 WHERE tvdb_id = ? and type = ?", (tvdb_id, "movie" if is_movie else "show"))
row = cursor.fetchone()
if row:
tvdb_dict["tvdb_id"] = int(row["tvdb_id"]) if row["tvdb_id"] else 0
tvdb_dict["type"] = row["type"] if row["type"] else ""
tvdb_dict["title"] = row["title"] if row["title"] else ""
tvdb_dict["status"] = row["status"] if row["status"] else ""
tvdb_dict["summary"] = row["summary"] if row["summary"] else ""
tvdb_dict["poster_url"] = row["poster_url"] if row["poster_url"] else ""
tvdb_dict["background_url"] = row["background_url"] if row["background_url"] else ""
@ -804,12 +793,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 tvdb_data3(tvdb_id, type) VALUES(?, ?)", (obj.tvdb_id, "movie" if obj.is_movie else "show"))
update_sql = "UPDATE tvdb_data3 SET title = ?, summary = ?, poster_url = ?, background_url = ?, " \
cursor.execute("INSERT OR IGNORE INTO tvdb_data4(tvdb_id, type) VALUES(?, ?)", (obj.tvdb_id, "movie" if obj.is_movie else "show"))
update_sql = "UPDATE tvdb_data4 SET title = ?, status = ?, summary = ?, poster_url = ?, background_url = ?, " \
"release_date = ?, genres = ?, expiration_date = ? WHERE tvdb_id = ? AND type = ?"
tvdb_date = f"{str(obj.release_date.year).zfill(4)}-{str(obj.release_date.month).zfill(2)}-{str(obj.release_date.day).zfill(2)}" if obj.release_date else None
cursor.execute(update_sql, (
obj.title, obj.summary, obj.poster_url, obj.background_url, tvdb_date, "|".join(obj.genres),
obj.title, obj.status, obj.summary, obj.poster_url, obj.background_url, tvdb_date, "|".join(obj.genres),
expiration_date.strftime("%Y-%m-%d"), obj.tvdb_id, "movie" if obj.is_movie else "show"
))

@ -63,6 +63,17 @@ mass_content_options = {
"mdb_age_rating": "Use MDbList Age Rating", "mdb_age_rating0": "Use MDbList Age Rating with Zero Padding",
"mal": "Use MyAnimeList Rating"
}
mass_collection_content_options = {
"lock": "Lock Rating", "unlock": "Unlock Rating", "remove": "Remove and Lock Rating", "reset": "Remove and Unlock Rating",
"highest": "Highest Rating in the collection", "lowest": "Lowest Rating in the collection",
"average": "Highest Rating in the collection"
}
content_rating_default = {
1: [
]
}
mass_studio_options = {
"lock": "Lock 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"
@ -113,7 +124,8 @@ 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": "str", "sonarr_add_all_existing": "bool", "sonarr_remove_by_tag": "str",
"mass_genre_update": mass_genre_options, "mass_content_rating_update": mass_content_options, "mass_studio_update": mass_studio_options,
"mass_content_rating_update": mass_content_options, "mass_collection_content_rating_update": "dict",
"mass_genre_update": mass_genre_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,
@ -904,18 +916,18 @@ class ConfigFile:
section_final[op] = util.check_collection_mode(config_op[op])
elif data_type == "dict":
input_dict = config_op[op]
if op in ["mass_poster_update", "mass_background_update"] and input_dict and not isinstance(input_dict, dict):
if op in ["mass_poster_update", "mass_background_update", "mass_collection_content_rating_update"] and input_dict and not isinstance(input_dict, dict):
input_dict = {"source": input_dict}
if not input_dict or not isinstance(input_dict, dict):
raise Failed(f"Config Error: {op} must be a dictionary")
if op in ["mass_poster_update", "mass_background_update"]:
elif op in ["mass_poster_update", "mass_background_update"]:
section_final[op] = {
"source": check_for_attribute(input_dict, "source", test_list=mass_image_options, default_is_none=True, save=False),
"seasons": check_for_attribute(input_dict, "seasons", var_type="bool", default=True, save=False),
"episodes": check_for_attribute(input_dict, "episodes", var_type="bool", default=True, save=False),
}
if op == "metadata_backup":
elif op == "metadata_backup":
default_path = os.path.join(default_dir, f"{str(library_name)}_Metadata_Backup.yml")
if "path" not in input_dict:
logger.warning(f"Config Warning: path attribute not found using default: {default_path}")
@ -929,7 +941,7 @@ class ConfigFile:
"sync_tags": check_for_attribute(input_dict, "sync_tags", var_type="bool", default=False, save=False),
"add_blank_entries": check_for_attribute(input_dict, "add_blank_entries", var_type="bool", default=True, save=False)
}
if "mapper" in op:
elif "mapper" in op:
section_final[op] = {}
for old_value, new_value in input_dict.items():
if not old_value:
@ -938,12 +950,17 @@ class ConfigFile:
logger.warning(f"Config Warning: {op} value '{new_value}' ignored as it cannot be mapped to itself")
else:
section_final[op][str(old_value)] = str(new_value) if new_value else None # noqa
if op == "delete_collections":
elif op == "delete_collections":
section_final[op] = {
"managed": check_for_attribute(input_dict, "managed", var_type="bool", default_is_none=True, save=False),
"configured": check_for_attribute(input_dict, "configured", var_type="bool", default_is_none=True, save=False),
"less": check_for_attribute(input_dict, "less", var_type="int", default_is_none=True, save=False, int_min=1),
}
elif op == "mass_collection_content_rating_update":
section_final[op] = {
"source": check_for_attribute(input_dict, "source", test_list=mass_collection_content_options, default_is_none=True, save=False),
"ranking": check_for_attribute(input_dict, "ranking", var_type="list", default=content_rating_default, save=False),
}
else:
section_final[op] = check_for_attribute(config_op, op, var_type=data_type, default=False, save=False)

@ -1,4 +1,4 @@
import requests, time
import re, requests, time
from datetime import datetime
from lxml import html
from lxml.etree import ParserError
@ -74,6 +74,7 @@ class TVDbObj:
self.poster_url = data["poster_url"]
self.background_url = data["background_url"]
self.release_date = data["release_date"]
self.status = data["status"]
self.genres = data["genres"].split("|")
else:
self.title, self.summary = parse_title_summary(lang=self._tvdb.language)
@ -95,6 +96,7 @@ class TVDbObj:
self.release_date = datetime.strptime(released, "%B %d, %Y") if released else released # noqa
except ValueError:
self.release_date = None
self.status = parse_page("//strong[text()='Status']/parent::li/span/text()[normalize-space()]")
self.genres = parse_page("//strong[text()='Genres']/parent::li/span/a/text()[normalize-space()]", is_list=True)
@ -249,3 +251,30 @@ class TVDb:
return self._ids_from_url(data)
else:
raise Failed(f"TVDb Error: Method {method} not supported")
def item_filter(self, item, filter_attr, modifier, filter_final, filter_data):
if filter_attr == "tvdb_title":
if util.is_string_filter([item.title], modifier, filter_data):
return False
elif filter_attr == "tvdb_status":
if util.is_string_filter([item.status], modifier, filter_data):
return False
elif filter_attr == "tvdb_genre":
attrs = item.genres
if modifier == ".regex":
has_match = False
for reg in filter_data:
for name in attrs:
if re.compile(reg).search(name):
has_match = True
if has_match is False:
return False
elif modifier in [".count_gt", ".count_gte", ".count_lt", ".count_lte"]:
test_number = len(attrs) if attrs else 0
modifier = f".{modifier[7:]}"
if test_number is None or util.is_number_filter(test_number, modifier, filter_data):
return False
elif (not list(set(filter_data) & set(attrs)) and modifier == "") \
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
return False
return True

Loading…
Cancel
Save