[4] add remove_overlay and overlay fixes

This commit is contained in:
meisnate12 2022-04-19 01:58:38 -04:00
parent 7bc9b7f95b
commit 7f5c066229
5 changed files with 134 additions and 64 deletions

View file

@ -1 +1 @@
1.16.5-develop3 1.16.5-develop4

View file

@ -6,7 +6,7 @@ Overlays and templates are defined within one or more Overlay files, which are l
**To remove all overlays use the `remove_overlays` library operation.** **To remove all overlays use the `remove_overlays` library operation.**
**To change a single overlay original Image either replace the image in the assets folder or remove the `Overlay` shared label and then PMM will overlay the new image** **To change a single overlay original image either replace the image in the assets folder or remove the `Overlay` shared label and then PMM will overlay the new image**
These are the attributes which can be used within the Overlay File: These are the attributes which can be used within the Overlay File:
@ -35,6 +35,26 @@ overlays:
Each section must have the only required attribute, `overlay`. Each section must have the only required attribute, `overlay`.
### Overlay Name
You can specify the Overlay Name in 3 ways.
1. If there is no `overlay` attribute PMM will look in your `config/overlays` folder for a `.png` file named the same as the mapping name of the overlay definition.
```yaml
overlays:
IMDb Top 250:
imdb_chart: top_movies
```
2. If the `overlay` attribute is given a string PMM will look in your `config/overlays` folder for a `.png` file named the same as the string given.
```yaml
overlays:
overlay: IMDb Top 250
IMDb Top 250:
imdb_chart: top_movies
```
3. Using a dictionary for more overlay location options.
| Attribute | Description | Required | | Attribute | Description | Required |
|:----------|:-------------------------------------------------------------------------------------------------------------|:--------:| |:----------|:-------------------------------------------------------------------------------------------------------------|:--------:|
@ -53,7 +73,29 @@ overlays:
imdb_chart: top_movies imdb_chart: top_movies
``` ```
There are three types of attributes that can be utilized within an overlay: ### Remove Overlay
You can add `remove_overlay` to an overlay definition and give it a list or comma separated string of overlay names you want removed from this item if this overlay is attached to the item.
```yaml
overlays:
4K:
plex_search:
all:
resolution: 4K
HDR:
plex_search:
all:
hdr: true
4K-HDR:
remove_overlay:
- 4K
- HDR
plex_search:
all:
resolution: 4K
hdr: true
```
### Builders ### Builders
@ -93,7 +135,7 @@ These filter media items added to the collection by any of the Builders.
overlays: overlays:
4K: 4K:
overlay: overlay:
name: 4K # This will look for a local overlays/4K.png in your configs folder name: 4K # This will look for a local overlays/4K.png in your config folder
plex_search: plex_search:
all: all:
resolution: 4K resolution: 4K

View file

@ -97,7 +97,7 @@ boolean_details = [
scheduled_boolean = ["visible_library", "visible_home", "visible_shared"] scheduled_boolean = ["visible_library", "visible_home", "visible_shared"]
string_details = ["sort_title", "content_rating", "name_mapping"] string_details = ["sort_title", "content_rating", "name_mapping"]
ignored_details = [ ignored_details = [
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "remove_overlay",
"delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", "overlay", "delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", "overlay",
"validate_builders", "libraries", "sync_to_users", "collection_name", "playlist_name", "name", "blank_collection" "validate_builders", "libraries", "sync_to_users", "collection_name", "playlist_name", "name", "blank_collection"
] ]
@ -190,7 +190,7 @@ custom_sort_builders = [
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio" "mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio"
] ]
episode_parts_only = ["plex_pilots"] episode_parts_only = ["plex_pilots"]
overlay_only = ["overlay"] overlay_only = ["overlay", "remove_overlay"]
overlay_attributes = [ overlay_attributes = [
"filters", "limit", "show_missing", "save_missing", "missing_only_released", "minimum_items", "cache_builders", "tmdb_region" "filters", "limit", "show_missing", "save_missing", "missing_only_released", "minimum_items", "cache_builders", "tmdb_region"
] + all_builders + overlay_only ] + all_builders + overlay_only
@ -211,7 +211,7 @@ music_attributes = [
] + details + summary_details + poster_details + background_details ] + details + summary_details + poster_details + background_details
class CollectionBuilder: class CollectionBuilder:
def __init__(self, config, metadata, name, data, library=None, overlay=None): def __init__(self, config, metadata, name, data, library=None, overlay=None, extra=None):
self.config = config self.config = config
self.metadata = metadata self.metadata = metadata
self.mapping_name = name self.mapping_name = name
@ -229,6 +229,14 @@ class CollectionBuilder:
self.type = "collection" self.type = "collection"
self.Type = self.type.capitalize() self.Type = self.type.capitalize()
logger.separator(f"{self.mapping_name} {self.Type}{f' in {self.library.name}' if self.library else ''}")
logger.info("")
if extra:
logger.info(extra)
logger.info("")
logger.separator(f"Validating {self.mapping_name} Attributes", space=False, border=False)
if "name" in methods: if "name" in methods:
name = self.data[methods["name"]] name = self.data[methods["name"]]
elif f"{self.type}_name" in methods: elif f"{self.type}_name" in methods:
@ -256,6 +264,7 @@ class CollectionBuilder:
self.data[attr] = new_attributes[attr] self.data[attr] = new_attributes[attr]
methods[attr.lower()] = attr methods[attr.lower()] = attr
self.remove_overlays = []
if self.overlay: if self.overlay:
if "overlay" in methods: if "overlay" in methods:
logger.debug("") logger.debug("")
@ -294,10 +303,20 @@ class CollectionBuilder:
self.overlay = data[methods["overlay"]] self.overlay = data[methods["overlay"]]
else: else:
self.overlay = self.mapping_name self.overlay = self.mapping_name
logger.warning(f"{self.Type} Warning: No overlay attribute using mapping name {self.mapping_name} as the overlay name")
overlay_path = os.path.join(library.overlay_folder, f"{self.overlay}.png") overlay_path = os.path.join(library.overlay_folder, f"{self.overlay}.png")
if not os.path.exists(overlay_path): if not os.path.exists(overlay_path):
raise Failed(f"{self.Type} Error: Overlay Image not found at: {overlay_path}") raise Failed(f"{self.Type} Error: Overlay Image not found at: {overlay_path}")
if "remove_overlay" in methods:
logger.debug("")
logger.debug("Validating Method: remove_overlay")
logger.debug(f"Value: {data[methods['remove_overlay']]}")
if data[methods["remove_overlay"]]:
self.remove_overlays = util.get_list(data[methods["remove_overlay"]])
else:
logger.error(f"{self.Type} Error: remove_overlay attribute is blank")
if self.playlist: if self.playlist:
if "libraries" in methods: if "libraries" in methods:
logger.debug("") logger.debug("")
@ -388,8 +407,8 @@ class CollectionBuilder:
s_attr = f"sync_to_user{'s' if 'sync_to_users' in methods else ''}" s_attr = f"sync_to_user{'s' if 'sync_to_users' in methods else ''}"
logger.debug("") logger.debug("")
logger.debug(f"Validating Method: {s_attr}") logger.debug(f"Validating Method: {s_attr}")
logger.debug(f"Value: {self.data[methods[s_attr]]}")
if self.data[methods[s_attr]]: if self.data[methods[s_attr]]:
logger.debug(f"Value: {self.data[methods[s_attr]]}")
self.sync_to_users = self.data[methods[s_attr]] self.sync_to_users = self.data[methods[s_attr]]
else: else:
logger.warning(f"Playlist Error: sync_to_users attribute is blank defaulting to playlist_sync_to_users: {self.sync_to_users}") logger.warning(f"Playlist Error: sync_to_users attribute is blank defaulting to playlist_sync_to_users: {self.sync_to_users}")

View file

@ -30,7 +30,10 @@ class Overlays:
builder = CollectionBuilder(self.config, overlay_file, k, v, library=self.library, overlay=True) builder = CollectionBuilder(self.config, overlay_file, k, v, library=self.library, overlay=True)
logger.info("") logger.info("")
logger.separator(f"Running {k} Overlay", space=False, border=False) logger.separator(f"Gathering Items for {k} Overlay", space=False, border=False)
if builder.overlay not in overlay_rating_keys:
overlay_rating_keys[builder.overlay] = []
if builder.filters or builder.tmdb_filters: if builder.filters or builder.tmdb_filters:
logger.info("") logger.info("")
@ -45,13 +48,17 @@ class Overlays:
logger.info("") logger.info("")
builder.filter_and_save_items(builder.gather_ids(method, value)) builder.filter_and_save_items(builder.gather_ids(method, value))
if builder.added_items: if builder.added_items:
if builder.overlay not in overlay_rating_keys:
overlay_rating_keys[builder.overlay] = []
for item in builder.added_items: for item in builder.added_items:
item_keys[item.ratingKey] = item item_keys[item.ratingKey] = item
if item.ratingKey not in overlay_rating_keys[builder.overlay]: if item.ratingKey not in overlay_rating_keys[builder.overlay]:
overlay_rating_keys[builder.overlay].append(item.ratingKey) overlay_rating_keys[builder.overlay].append(item.ratingKey)
if builder.remove_overlays:
for rk in overlay_rating_keys[builder.overlay]:
for remove_overlay in builder.remove_overlays:
if remove_overlay in overlay_rating_keys and rk in overlay_rating_keys[remove_overlay]:
overlay_rating_keys[remove_overlay].remove(rk)
for overlay_name, over_keys in overlay_rating_keys.items(): for overlay_name, over_keys in overlay_rating_keys.items():
clean_name, _ = util.validate_filename(overlay_name) clean_name, _ = util.validate_filename(overlay_name)
image_compare = None image_compare = None
@ -68,6 +75,20 @@ class Overlays:
if self.config.Cache: if self.config.Cache:
self.config.Cache.update_image_map(overlay_name, f"{self.library.image_table_name}_overlays", overlay_name, overlay_size) self.config.Cache.update_image_map(overlay_name, f"{self.library.image_table_name}_overlays", overlay_name, overlay_size)
def find_poster_url(plex_item):
if self.library.is_movie:
if plex_item.ratingKey in self.library.movie_rating_key_map:
return self.config.TMDb.get_movie(self.library.movie_rating_key_map[plex_item.ratingKey]).poster_url
elif self.library.is_show:
check_key = plex_item.ratingKey if isinstance(plex_item, Show) else plex_item.show().ratingKey
tmdb_id = self.config.Convert.tvdb_to_tmdb(self.library.show_rating_key_map[check_key])
if isinstance(plex_item, Show) and plex_item.ratingKey in self.library.show_rating_key_map:
return self.config.TMDb.get_show(tmdb_id).poster_url
elif isinstance(plex_item, Season):
return self.config.TMDb.get_season(tmdb_id, plex_item.seasonNumber).poster_url
elif isinstance(plex_item, Episode):
return self.config.TMDb.get_episode(tmdb_id, plex_item.seasonNumber, plex_item.episodeNumber).still_url
def get_overlay_items(libtype=None): def get_overlay_items(libtype=None):
return [o for o in self.library.search(label="Overlay", libtype=libtype) if o.ratingKey not in item_overlays] return [o for o in self.library.search(label="Overlay", libtype=libtype) if o.ratingKey not in item_overlays]
@ -78,6 +99,11 @@ class Overlays:
elif self.library.is_music: elif self.library.is_music:
remove_overlays.extend(get_overlay_items(libtype="album")) remove_overlays.extend(get_overlay_items(libtype="album"))
if remove_overlays:
logger.info("")
logger.separator(f"Removing Overlays for the {self.library.name} Library")
logger.info("")
for i, item in enumerate(remove_overlays, 1): for i, item in enumerate(remove_overlays, 1):
logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item.title}") logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item.title}")
clean_name, _ = util.validate_filename(item.title) clean_name, _ = util.validate_filename(item.title)
@ -86,29 +112,33 @@ class Overlays:
folder_name=clean_name if self.library.asset_folders else None, folder_name=clean_name if self.library.asset_folders else None,
prefix=f"{item.title}'s " prefix=f"{item.title}'s "
) )
poster_location = None
is_url = False is_url = False
original = None
if poster: if poster:
poster_location = poster.location poster_location = poster.location
elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")): elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")):
poster_location = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png") original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")
poster_location = original
elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")): elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")):
poster_location = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg") original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")
poster_location = original
else: else:
is_url = True is_url = True
if self.library.is_movie: poster_location = find_poster_url(item)
if item.ratingKey in self.library.movie_rating_key_map:
poster_location = self.config.TMDb.get_movie(self.library.movie_rating_key_map[item.ratingKey]).poster_url
elif self.library.is_show:
if item.ratingKey in self.library.show_rating_key_map:
poster_location = self.config.TMDb.get_show(self.config.Convert.tvdb_to_tmdb(self.library.show_rating_key_map[item.ratingKey])).poster_url
if poster_location: if poster_location:
self.library.upload_poster(item, poster_location, url=is_url) self.library.upload_poster(item, poster_location, url=is_url)
self.library.edit_tags("label", item, remove_tags=["Overlay"]) self.library.edit_tags("label", item, remove_tags=["Overlay"])
if original:
os.remove(original)
else: else:
logger.error(f"No Poster found to restore for {item.title}") logger.error(f"No Poster found to restore for {item.title}")
logger.exorcise() logger.exorcise()
if item_overlays:
logger.info("")
logger.separator(f"Applying Overlays for the {self.library.name} Library")
logger.info("")
for i, (over_key, over_names) in enumerate(item_overlays.items(), 1): for i, (over_key, over_names) in enumerate(item_overlays.items(), 1):
try: try:
item = item_keys[over_key] item = item_keys[over_key]
@ -136,43 +166,38 @@ class Overlays:
folder_name=clean_name if self.library.asset_folders else None, folder_name=clean_name if self.library.asset_folders else None,
prefix=f"{item.title}'s " prefix=f"{item.title}'s "
) )
has_original = False has_original = False
changed_image = False changed_image = False
new_backup = None
if poster: if poster:
if image_compare and str(poster.compare) != str(image_compare): if image_compare and str(poster.compare) != str(image_compare):
changed_image = True changed_image = True
else: elif has_overlay:
if os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")): if os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")):
has_original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png") has_original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png")
elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")): elif os.path.exists(os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")):
has_original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg") has_original = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")
else: else:
changed_image = True
self.library.reload(item) self.library.reload(item)
poster_url = item.posterUrl new_backup = find_poster_url(item)
if has_overlay: if new_backup is None:
if self.library.is_movie: new_backup = item.posterUrl
if item.ratingKey in self.library.movie_rating_key_map: else:
poster_url = self.config.TMDb.get_movie(self.library.movie_rating_key_map[item.ratingKey]).poster_url self.library.reload(item)
elif self.library.is_show: new_backup = item.posterUrl
check_key = item.ratingKey if isinstance(item, Show) else item.show().ratingKey if new_backup:
tmdb_id = self.config.Convert.tvdb_to_tmdb(self.library.show_rating_key_map[check_key]) changed_image = True
if isinstance(item, Show) and item.ratingKey in self.library.show_rating_key_map: image_response = self.config.get(new_backup)
poster_url = self.config.TMDb.get_show(tmdb_id).poster_url if image_response.status_code >= 400:
elif isinstance(item, Season): raise Failed(f"Overlay Error: Poster Download Failed for {item.title}")
poster_url = self.config.TMDb.get_season(tmdb_id, item.seasonNumber).poster_url i_ext = "jpg" if image_response.headers["Content-Type"] == "image/jpeg" else "png"
elif isinstance(item, Episode): backup_image_path = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.{i_ext}")
poster_url = self.config.TMDb.get_episode(tmdb_id, item.seasonNumber, item.episodeNumber).still_url with open(backup_image_path, "wb") as handler:
response = self.config.get(poster_url) handler.write(image_response.content)
if response.status_code >= 400: while util.is_locked(backup_image_path):
raise Failed(f"Overlay Error: Poster Download Failed for {item.title}") time.sleep(1)
ext = "jpg" if response.headers["Content-Type"] == "image/jpeg" else "png" has_original = backup_image_path
backup_image = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.{ext}")
with open(backup_image, "wb") as handler:
handler.write(response.content)
while util.is_locked(backup_image):
time.sleep(1)
has_original = backup_image
poster_uploaded = False poster_uploaded = False
if changed_image or overlay_change: if changed_image or overlay_change:

View file

@ -449,15 +449,7 @@ def run_collection(config, library, metadata, requested_collections):
library.status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} library.status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0}
try: try:
logger.separator(f"{mapping_name} Collection in {library.name}") builder = CollectionBuilder(config, metadata, mapping_name, collection_attrs, library=library, extra=output_str)
logger.info("")
if output_str:
logger.info(output_str)
logger.info("")
logger.separator(f"Validating {mapping_name} Attributes", space=False, border=False)
builder = CollectionBuilder(config, metadata, mapping_name, collection_attrs, library=library)
library.stats["names"].append(builder.name) library.stats["names"].append(builder.name)
logger.info("") logger.info("")
@ -625,15 +617,7 @@ def run_playlists(config):
server_name = None server_name = None
library_names = None library_names = None
try: try:
logger.separator(f"{mapping_name} Playlist") builder = CollectionBuilder(config, playlist_file, mapping_name, playlist_attrs, extra=output_str)
logger.info("")
if output_str:
logger.info(output_str)
logger.info("")
logger.separator(f"Validating {mapping_name} Attributes", space=False, border=False)
builder = CollectionBuilder(config, playlist_file, mapping_name, playlist_attrs)
stats["names"].append(builder.name) stats["names"].append(builder.name)
logger.info("") logger.info("")