mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-25 22:10:22 +00:00
[30] add mass episode ratings
This commit is contained in:
parent
64d2d8874f
commit
43d08bad15
8 changed files with 227 additions and 64 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.17.0-develop29
|
1.17.0-develop30
|
||||||
|
|
|
@ -16,28 +16,29 @@ libraries:
|
||||||
|
|
||||||
The available attributes for the operations attribute are as follows
|
The available attributes for the operations attribute are as follows
|
||||||
|
|
||||||
| Attribute | Description |
|
| Attribute | Description |
|
||||||
|:--------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|:--------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `assets_for_all` | Search in assets for images for every item in your library.<br>**Values:** `true` or `false` |
|
| `assets_for_all` | Search in assets for images for every item in your library.<br>**Values:** `true` or `false` |
|
||||||
| `delete_collections_with_less` | Deletes every collection with less than the given number of items.<br>**Values:** number greater than 0 |
|
| `delete_collections_with_less` | Deletes every collection with less than the given number of items.<br>**Values:** number greater than 0 |
|
||||||
| `delete_unmanaged_collections` | Deletes every unmanaged collection<br>**Values:** `true` or `false` |
|
| `delete_unmanaged_collections` | Deletes every unmanaged collection<br>**Values:** `true` or `false` |
|
||||||
| `mass_genre_update` | Updates every item's genres in the library to the chosen site's genres<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb for Genres</td></tr><tr><td>`tvdb`</td><td>Use TVDb for Genres</td></tr><tr><td>`omdb`</td><td>Use IMDb through OMDb for Genres</td></tr><tr><td>`anidb`</td><td>Use AniDB Tags for Genres</td></tr></table> |
|
| `mass_genre_update` | Updates every item's genres in the library to the chosen site's genres<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb for Genres</td></tr><tr><td>`tvdb`</td><td>Use TVDb for Genres</td></tr><tr><td>`imdb`</td><td>Use IMDb for Genres</td></tr><tr><td>`omdb`</td><td>Use IMDb through OMDb for Genres</td></tr><tr><td>`anidb`</td><td>Use AniDB Tags for Genres</td></tr></table> |
|
||||||
| `mass_content_rating_update` | Updates every item's content rating in the library to the chosen site's content rating<br>**Values:** <table class="clearTable"><tr><td>`mdb`</td><td>Use MdbList for Content Ratings</td></tr><tr><td>`mdb_commonsense`</td><td>Use Commonsense Rating through MDbList for Content Ratings</td></tr><tr><td>`omdb`</td><td>Use IMDb through OMDb for Content Ratings</td></tr></table> |
|
| `mass_content_rating_update` | Updates every item's content rating in the library to the chosen site's content rating<br>**Values:** <table class="clearTable"><tr><td>`mdb`</td><td>Use MdbList for Content Ratings</td></tr><tr><td>`mdb_commonsense`</td><td>Use Commonsense Rating through MDbList for Content Ratings</td></tr><tr><td>`omdb`</td><td>Use IMDb through OMDb for Content Ratings</td></tr></table> |
|
||||||
| `mass_originally_available_update` | Updates every item's originally available date in the library to the chosen site's date<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb Release Date</td></tr><tr><td>`tvdb`</td><td>Use TVDb Release Date</td></tr><tr><td>`omdb`</td><td>Use IMDb Release Date through OMDb</td></tr><tr><td>`mdb`</td><td>Use MdbList Release Date</td></tr><tr><td>`anidb`</td><td>Use AniDB Release Date</td></tr></table> |
|
| `mass_originally_available_update` | Updates every item's originally available date in the library to the chosen site's date<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb Release Date</td></tr><tr><td>`tvdb`</td><td>Use TVDb Release Date</td></tr><tr><td>`omdb`</td><td>Use IMDb Release Date through OMDb</td></tr><tr><td>`mdb`</td><td>Use MdbList Release Date</td></tr><tr><td>`anidb`</td><td>Use AniDB Release Date</td></tr></table> |
|
||||||
| `mass_audience_rating_update`/<br>`mass_critic_rating_update` | Updates every item's audience/critic rating in the library to the chosen site's rating<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb Rating</td></tr><tr><td>`omdb`</td><td>Use IMDbRating through OMDb</td></tr><tr><td>`mdb`</td><td>Use MdbList Score</td></tr><tr><td>`mdb_imdb`</td><td>Use IMDb Rating through MDbList</td></tr><tr><td>`mdb_metacritic`</td><td>Use Metacritic Rating through MDbList</td></tr><tr><td>`mdb_metacriticuser`</td><td>Use Metacritic User Rating through MDbList</td></tr><tr><td>`mdb_trakt`</td><td>Use Trakt Rating through MDbList</td></tr><tr><td>`mdb_tomatoes`</td><td>Use Rotten Tomatoes Rating through MDbList</td></tr><tr><td>`mdb_tomatoesaudience`</td><td>Use Rotten Tomatoes Audience Rating through MDbList</td></tr><tr><td>`mdb_tmdb`</td><td>Use TMDb Rating through MDbList</td></tr><tr><td>`mdb_letterboxd`</td><td>Use Letterboxd Rating through MDbList</td></tr><tr><td>`anidb_rating`</td><td>Use AniDB Rating</td></tr><tr><td>`anidb_average`</td><td>Use AniDB Average</td></tr></table> |
|
| `mass_audience_rating_update`/<br>`mass_critic_rating_update`/<br>`mass_user_rating_update` | Updates every item's audience/critic/user rating in the library to the chosen site's rating<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb Rating</td></tr><tr><td>`imdb`</td><td>Use IMDb Rating</td></tr><tr><td>`omdb`</td><td>Use IMDbRating through OMDb</td></tr><tr><td>`mdb`</td><td>Use MdbList Score</td></tr><tr><td>`mdb_imdb`</td><td>Use IMDb Rating through MDbList</td></tr><tr><td>`mdb_metacritic`</td><td>Use Metacritic Rating through MDbList</td></tr><tr><td>`mdb_metacriticuser`</td><td>Use Metacritic User Rating through MDbList</td></tr><tr><td>`mdb_trakt`</td><td>Use Trakt Rating through MDbList</td></tr><tr><td>`mdb_tomatoes`</td><td>Use Rotten Tomatoes Rating through MDbList</td></tr><tr><td>`mdb_tomatoesaudience`</td><td>Use Rotten Tomatoes Audience Rating through MDbList</td></tr><tr><td>`mdb_tmdb`</td><td>Use TMDb Rating through MDbList</td></tr><tr><td>`mdb_letterboxd`</td><td>Use Letterboxd Rating through MDbList</td></tr><tr><td>`anidb_rating`</td><td>Use AniDB Rating</td></tr><tr><td>`anidb_average`</td><td>Use AniDB Average</td></tr></table> |
|
||||||
| `mass_imdb_parental_labels` | Updates every item's labels in the library to match the IMDb Parental Guide<br>**Values** `with_none` or `without_none` |
|
| `mass_episode_audience_rating_update`/<br>`mass_episode_critic_rating_update`/<br>`mass_episode_user_rating_update` | Updates every item's episode's audience/critic/user rating in the library to the chosen site's rating<br>**Values:** <table class="clearTable"><tr><td>`tmdb`</td><td>Use TMDb Rating</td></tr><tr><td>`imdb`</td><td>Use IMDb Rating</td></tr></table> |
|
||||||
| `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<br>**Values:** `true` or `false` |
|
| `mass_imdb_parental_labels` | Updates every item's labels in the library to match the IMDb Parental Guide<br>**Values** `with_none` or `without_none` |
|
||||||
| `mass_collection_mode` | Updates every Collection in your library to the specified Collection Mode<br>**Values:** `default`: Library default<br>`hide`: Hide Collection<br>`hide_items`: Hide Items in this Collection<br>`show_items`: Show this Collection and its Items<table class="clearTable"><tr><td>`default`</td><td>Library default</td></tr><tr><td>`hide`</td><td>Hide Collection</td></tr><tr><td>`hide_items`</td><td>Hide Items in this Collection</td></tr><tr><td>`show_items`</td><td>Show this Collection and its Items</td></tr></table> |
|
| `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<br>**Values:** `true` or `false` |
|
||||||
| `update_blank_track_titles` | Search though every track in a music library and replace any blank track titles with the tracks sort title<br>**Values:** `true` or `false` |
|
| `mass_collection_mode` | Updates every Collection in your library to the specified Collection Mode<br>**Values:** `default`: Library default<br>`hide`: Hide Collection<br>`hide_items`: Hide Items in this Collection<br>`show_items`: Show this Collection and its Items<table class="clearTable"><tr><td>`default`</td><td>Library default</td></tr><tr><td>`hide`</td><td>Hide Collection</td></tr><tr><td>`hide_items`</td><td>Hide Items in this Collection</td></tr><tr><td>`show_items`</td><td>Show this Collection and its Items</td></tr></table> |
|
||||||
| `remove_title_parentheses` | Search through every title and remove all ending parentheses in an items title if the title isn not locked.<br>**Values:** `true` or `false` |
|
| `update_blank_track_titles` | Search though every track in a music library and replace any blank track titles with the tracks sort title<br>**Values:** `true` or `false` |
|
||||||
| `split_duplicates` | Splits all duplicate movies/shows found in this library<br>**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.<br>**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.<br>**Values:** `true` or `false` |
|
| `split_duplicates` | Splits all duplicate movies/shows found in this library<br>**Values:** `true` or `false` |
|
||||||
| `radarr_remove_by_tag` | Removes every item from Radarr with the Tags given<br>**Values:** List or comma separated string of tags |
|
| `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.<br>**Values:** `true` or `false` |
|
||||||
| `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.<br>**Values:** `true` or `false` |
|
| `radarr_remove_by_tag` | Removes every item from Radarr with the Tags given<br>**Values:** List or comma separated string of tags |
|
||||||
| `sonarr_remove_by_tag` | Removes every item from Sonarr with the Tags given<br>**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.<br>**Values:** `true` or `false` |
|
||||||
| [`genre_mapper`](#genre-mapper) | Allows genres to be changed to other genres or be removed from every item in your library.<br>**Values:** [see below for usage](#genre-mapper) |
|
| `sonarr_remove_by_tag` | Removes every item from Sonarr with the Tags given<br>**Values:** List or comma separated string of tags |
|
||||||
| [`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.<br>**Values:** [see below for usage](#content-rating-mapper) |
|
| [`genre_mapper`](#genre-mapper) | Allows genres to be changed to other genres or be removed from every item in your library.<br>**Values:** [see below for usage](#genre-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.<br>**Values:** [see below for usage](#metadata-backup) |
|
| [`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.<br>**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.<br>**Values:** [see below for usage](#metadata-backup) |
|
||||||
|
|
||||||
## Genre Mapper
|
## Genre Mapper
|
||||||
|
|
||||||
|
|
|
@ -33,12 +33,14 @@ from retrying import retry
|
||||||
logger = util.logger
|
logger = util.logger
|
||||||
|
|
||||||
sync_modes = {"append": "Only Add Items to the Collection or Playlist", "sync": "Add & Remove Items from the Collection or Playlist"}
|
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_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"}
|
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"}
|
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 = {
|
mass_rating_options = {
|
||||||
"tmdb": "Use TMDb Rating",
|
"tmdb": "Use TMDb Rating",
|
||||||
|
"imdb": "Use IMDb Rating",
|
||||||
"omdb": "Use IMDb Rating through OMDb",
|
"omdb": "Use IMDb Rating through OMDb",
|
||||||
"mdb": "Use MdbList Average Score",
|
"mdb": "Use MdbList Average Score",
|
||||||
"mdb_imdb": "Use IMDb Rating through MDbList",
|
"mdb_imdb": "Use IMDb Rating through MDbList",
|
||||||
|
@ -584,6 +586,9 @@ class ConfigFile:
|
||||||
"mass_originally_available_update": None,
|
"mass_originally_available_update": None,
|
||||||
"mass_imdb_parental_labels": None,
|
"mass_imdb_parental_labels": None,
|
||||||
"remove_title_parentheses": 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"]
|
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)
|
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"]:
|
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)
|
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"]:
|
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)
|
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"]:
|
if "mass_originally_available_update" in lib["operations"]:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import math, re, time
|
import csv, gzip, math, os, re, requests, shutil, time
|
||||||
from modules import util
|
from modules import util
|
||||||
from modules.util import Failed
|
from modules.util import Failed
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
@ -30,6 +30,9 @@ urls = {
|
||||||
class IMDb:
|
class IMDb:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self._ratings = None
|
||||||
|
self._genres = None
|
||||||
|
self._episode_ratings = None
|
||||||
|
|
||||||
def validate_imdb_lists(self, err_type, imdb_lists, language):
|
def validate_imdb_lists(self, err_type, imdb_lists, language):
|
||||||
valid_lists = []
|
valid_lists = []
|
||||||
|
@ -185,3 +188,71 @@ class IMDb:
|
||||||
return [(_i, "imdb") for _i in self._ids_from_chart(data)]
|
return [(_i, "imdb") for _i in self._ids_from_chart(data)]
|
||||||
else:
|
else:
|
||||||
raise Failed(f"IMDb Error: Method {method} not supported")
|
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]
|
||||||
|
|
|
@ -77,6 +77,10 @@ class Library(ABC):
|
||||||
self.mass_genre_update = params["mass_genre_update"]
|
self.mass_genre_update = params["mass_genre_update"]
|
||||||
self.mass_audience_rating_update = params["mass_audience_rating_update"]
|
self.mass_audience_rating_update = params["mass_audience_rating_update"]
|
||||||
self.mass_critic_rating_update = params["mass_critic_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_content_rating_update = params["mass_content_rating_update"]
|
||||||
self.mass_originally_available_update = params["mass_originally_available_update"]
|
self.mass_originally_available_update = params["mass_originally_available_update"]
|
||||||
self.mass_imdb_parental_labels = params["mass_imdb_parental_labels"]
|
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.stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0, "names": []}
|
||||||
self.status = {}
|
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 \
|
self.items_library_operation = True if self.assets_for_all or self.mass_genre_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.mass_audience_rating_update or self.mass_critic_rating_update or self.mass_user_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
|
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 \
|
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.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
|
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:
|
if self.asset_directory:
|
||||||
logger.info("")
|
logger.info("")
|
||||||
|
|
|
@ -2,8 +2,6 @@ import os, re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from modules import plex, util
|
from modules import plex, util
|
||||||
from modules.util import Failed, YAML
|
from modules.util import Failed, YAML
|
||||||
from plexapi.audio import Artist
|
|
||||||
from plexapi.video import Show
|
|
||||||
|
|
||||||
logger = util.logger
|
logger = util.logger
|
||||||
|
|
||||||
|
@ -23,6 +21,10 @@ class Operations:
|
||||||
logger.debug(f"Mass Genre Update: {self.library.mass_genre_update}")
|
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 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 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 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 Originally Available Update: {self.library.mass_originally_available_update}")
|
||||||
logger.debug(f"Mass IMDb Parental Labels: {self.library.mass_imdb_parental_labels}")
|
logger.debug(f"Mass IMDb Parental Labels: {self.library.mass_imdb_parental_labels}")
|
||||||
|
@ -136,21 +138,18 @@ class Operations:
|
||||||
|
|
||||||
omdb_item = None
|
omdb_item = None
|
||||||
if any([o == "omdb" for o in self.library.meta_operations]):
|
if any([o == "omdb" for o in self.library.meta_operations]):
|
||||||
if self.config.OMDb.limit is False:
|
if self.config.OMDb.limit is not False:
|
||||||
if tmdb_id and not imdb_id:
|
logger.error("Daily OMDb Limit Reached")
|
||||||
imdb_id = self.config.Convert.tmdb_to_imdb(tmdb_id)
|
elif not imdb_id:
|
||||||
elif tvdb_id and not imdb_id:
|
logger.info(f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}")
|
||||||
imdb_id = self.config.Convert.tvdb_to_imdb(tvdb_id)
|
else:
|
||||||
if imdb_id:
|
try:
|
||||||
try:
|
omdb_item = self.config.OMDb.get_omdb(imdb_id)
|
||||||
omdb_item = self.config.OMDb.get_omdb(imdb_id)
|
except Failed as e:
|
||||||
except Failed as e:
|
logger.error(str(e))
|
||||||
logger.error(str(e))
|
except Exception:
|
||||||
except Exception:
|
logger.error(f"IMDb ID: {imdb_id}")
|
||||||
logger.error(f"IMDb ID: {imdb_id}")
|
raise
|
||||||
raise
|
|
||||||
else:
|
|
||||||
logger.info(f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}")
|
|
||||||
|
|
||||||
tvdb_item = None
|
tvdb_item = None
|
||||||
if any([o == "tvdb" for o in self.library.meta_operations]):
|
if any([o == "tvdb" for o in self.library.meta_operations]):
|
||||||
|
@ -200,6 +199,8 @@ class Operations:
|
||||||
def get_rating(attribute):
|
def get_rating(attribute):
|
||||||
if tmdb_item and attribute == "tmdb":
|
if tmdb_item and attribute == "tmdb":
|
||||||
return tmdb_item.vote_average
|
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":
|
elif omdb_item and attribute == "omdb":
|
||||||
return omdb_item.imdb_rating
|
return omdb_item.imdb_rating
|
||||||
elif mdb_item and attribute == "mdb":
|
elif mdb_item and attribute == "mdb":
|
||||||
|
@ -233,6 +234,8 @@ class Operations:
|
||||||
if self.library.mass_genre_update:
|
if self.library.mass_genre_update:
|
||||||
if tmdb_item and self.library.mass_genre_update == "tmdb":
|
if tmdb_item and self.library.mass_genre_update == "tmdb":
|
||||||
new_genres = tmdb_item.genres
|
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":
|
elif omdb_item and self.library.mass_genre_update == "omdb":
|
||||||
new_genres = omdb_item.genres
|
new_genres = omdb_item.genres
|
||||||
elif tvdb_item and self.library.mass_genre_update == "tvdb":
|
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")
|
logger.info(f"{item.title[:25]:<25} | No Rating Found")
|
||||||
elif str(item.rating) != str(new_rating):
|
elif str(item.rating) != str(new_rating):
|
||||||
item.editField("rating", 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:
|
except Failed:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -327,6 +341,62 @@ class Operations:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
item.saveEdits()
|
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:
|
if self.library.Radarr and self.library.radarr_add_all_existing:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -3,7 +3,6 @@ from datetime import datetime
|
||||||
from modules import plex, util
|
from modules import plex, util
|
||||||
from modules.builder import CollectionBuilder
|
from modules.builder import CollectionBuilder
|
||||||
from modules.util import Failed, NotScheduled
|
from modules.util import Failed, NotScheduled
|
||||||
from plexapi.audio import Album
|
|
||||||
from plexapi.exceptions import BadRequest
|
from plexapi.exceptions import BadRequest
|
||||||
from plexapi.video import Movie, Show, Season, Episode
|
from plexapi.video import Movie, Show, Season, Episode
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image, ImageFilter
|
||||||
|
@ -37,7 +36,7 @@ class Overlays:
|
||||||
logger.separator(f"Removing {old_overlay.title}")
|
logger.separator(f"Removing {old_overlay.title}")
|
||||||
logger.info("")
|
logger.info("")
|
||||||
for i, item in enumerate(label_items, 1):
|
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}")
|
logger.ghost(f"Restoring {old_overlay.title}: {i}/{len(label_items)} {item_title}")
|
||||||
self.remove_overlay(item, item_title, old_overlay.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")
|
os.path.join(self.library.overlay_folder, old_overlay.title[:-8], f"{item.ratingKey}.png")
|
||||||
|
@ -61,7 +60,7 @@ class Overlays:
|
||||||
if remove_overlays:
|
if remove_overlays:
|
||||||
logger.separator(f"Removing Overlays for the {self.library.name} Library")
|
logger.separator(f"Removing Overlays for the {self.library.name} Library")
|
||||||
for i, item in enumerate(remove_overlays, 1):
|
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}")
|
logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item_title}")
|
||||||
self.remove_overlay(item, item_title, "Overlay", [
|
self.remove_overlay(item, item_title, "Overlay", [
|
||||||
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png"),
|
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png"),
|
||||||
|
@ -75,9 +74,9 @@ class Overlays:
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.separator(f"Applying Overlays for the {self.library.name} Library")
|
logger.separator(f"Applying Overlays for the {self.library.name} Library")
|
||||||
logger.info("")
|
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:
|
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}")
|
logger.ghost(f"Overlaying: {i}/{len(key_to_overlays)} {item_title}")
|
||||||
image_compare = None
|
image_compare = None
|
||||||
overlay_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}")
|
logger.separator(f"Finished {self.library.name} Library Overlays\nOverlays Run Time: {overlay_run_time}")
|
||||||
return 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):
|
def compile_overlays(self):
|
||||||
key_to_item = {}
|
key_to_item = {}
|
||||||
properties = {}
|
properties = {}
|
||||||
|
@ -334,7 +323,7 @@ class Overlays:
|
||||||
if item.ratingKey not in properties[builder.overlay.name].keys:
|
if item.ratingKey not in properties[builder.overlay.name].keys:
|
||||||
properties[builder.overlay.name].keys.append(item.ratingKey)
|
properties[builder.overlay.name].keys.append(item.ratingKey)
|
||||||
if added_titles:
|
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}")
|
logger.info(f"{len(added_titles) if added_titles else 'No'} Items found for {builder.overlay.name}")
|
||||||
except NotScheduled as e:
|
except NotScheduled as e:
|
||||||
logger.info(e)
|
logger.info(e)
|
||||||
|
|
|
@ -1184,6 +1184,16 @@ class Plex(Library):
|
||||||
|
|
||||||
return map_key, attrs
|
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):
|
def split(self, text):
|
||||||
attribute, modifier = os.path.splitext(str(text).lower())
|
attribute, modifier = os.path.splitext(str(text).lower())
|
||||||
attribute = method_alias[attribute] if attribute in method_alias else attribute
|
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 == "") \
|
elif (not list(set(filter_data) & set(attrs)) and modifier == "") \
|
||||||
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
Loading…
Reference in a new issue