Plex-Meta-Manager/modules/overlays.py

265 lines
15 KiB
Python
Raw Normal View History

2022-04-20 22:56:37 +00:00
import os, re, time
2022-04-18 18:16:39 +00:00
from modules import util
from modules.builder import CollectionBuilder
from modules.util import Failed
from plexapi.exceptions import BadRequest
2022-04-20 04:06:42 +00:00
from plexapi.video import Movie, Show, Season, Episode
2022-04-20 05:27:00 +00:00
from PIL import Image, ImageFilter
2022-04-18 18:16:39 +00:00
logger = util.logger
class Overlays:
def __init__(self, config, library):
self.config = config
self.library = library
self.overlays = []
def run_overlays(self):
logger.info("")
logger.separator(f"{self.library.name} Library Overlays")
logger.info("")
os.makedirs(self.library.overlay_backup, exist_ok=True)
2022-04-21 21:44:08 +00:00
key_to_item = {}
2022-04-20 04:06:42 +00:00
key_to_overlays = {}
2022-04-21 21:44:08 +00:00
settings = {}
2022-04-19 22:21:05 +00:00
if self.library.remove_overlays:
logger.info("")
logger.separator(f"Removing Overlays for the {self.library.name} Library")
logger.info("")
else:
2022-04-18 18:16:39 +00:00
for overlay_file in self.library.overlay_files:
for k, v in overlay_file.overlays.items():
2022-04-19 22:21:05 +00:00
try:
builder = CollectionBuilder(self.config, overlay_file, k, v, library=self.library, overlay=True)
logger.info("")
2022-04-18 18:16:39 +00:00
2022-04-19 22:21:05 +00:00
logger.separator(f"Gathering Items for {k} Overlay", space=False, border=False)
2022-04-21 21:44:08 +00:00
if builder.overlay not in settings:
settings[builder.overlay] = {
"keys": [], "suppress": builder.suppress_overlays, "group": builder.overlay_group,
2022-04-23 02:48:26 +00:00
"weight": builder.overlay_weight, "path": builder.overlay_path, "updated": False, "image": None
2022-04-21 20:56:32 +00:00
}
for method, value in builder.builders:
logger.debug("")
logger.debug(f"Builder: {method}: {value}")
logger.info("")
builder.filter_and_save_items(builder.gather_ids(method, value))
2022-04-18 18:16:39 +00:00
2022-04-19 22:21:05 +00:00
if builder.filters or builder.tmdb_filters:
logger.info("")
for filter_key, filter_value in builder.filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}")
for filter_key, filter_value in builder.tmdb_filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}")
2022-04-18 18:16:39 +00:00
2022-04-21 20:56:32 +00:00
added_titles = []
if builder.added_items:
for item in builder.added_items:
key_to_item[item.ratingKey] = item
added_titles.append(item.title)
2022-04-21 21:44:08 +00:00
if item.ratingKey not in settings[builder.overlay]["keys"]:
settings[builder.overlay]["keys"].append(item.ratingKey)
2022-04-21 20:56:32 +00:00
if added_titles:
logger.debug(f"{len(added_titles)} Titles Found: {added_titles}")
logger.info(f"{len(added_titles) if added_titles else 'No'} Items found for {builder.overlay}")
2022-04-19 22:21:05 +00:00
except Failed as e:
logger.error(e)
2022-04-21 20:56:32 +00:00
overlay_groups = {}
2022-04-21 21:44:08 +00:00
for overlay_name, over_attrs in settings.items():
if over_attrs["group"]:
if over_attrs["group"] not in overlay_groups:
overlay_groups[over_attrs["group"]] = {}
overlay_groups[over_attrs["group"]][overlay_name] = over_attrs["weight"]
2022-04-21 20:56:32 +00:00
for rk in over_attrs["keys"]:
for suppress_name in over_attrs["suppress"]:
2022-04-21 21:44:08 +00:00
if suppress_name in settings and rk in settings[suppress_name]["keys"]:
settings[suppress_name]["keys"].remove(rk)
2022-04-21 20:56:32 +00:00
if not overlay_name.startswith("blur"):
2022-04-20 05:27:00 +00:00
image_compare = None
if self.config.Cache:
_, image_compare, _ = self.config.Cache.query_image_map(overlay_name, f"{self.library.image_table_name}_overlays")
2022-04-23 02:48:26 +00:00
overlay_size = os.stat(settings[overlay_name]["path"]).st_size
2022-04-21 21:44:08 +00:00
settings[overlay_name]["updated"] = not image_compare or str(overlay_size) != str(image_compare)
2022-04-23 02:48:26 +00:00
settings[overlay_name]["image"] = Image.open(settings[overlay_name]["path"]).convert("RGBA")
2022-04-20 05:27:00 +00:00
if self.config.Cache:
self.config.Cache.update_image_map(overlay_name, f"{self.library.image_table_name}_overlays", overlay_name, overlay_size)
2022-04-21 20:56:32 +00:00
2022-04-21 21:44:08 +00:00
for overlay_name, over_attrs in settings.items():
2022-04-21 20:56:32 +00:00
for over_key in over_attrs["keys"]:
2022-04-20 04:06:42 +00:00
if over_key not in key_to_overlays:
key_to_overlays[over_key] = (key_to_item[over_key], [])
key_to_overlays[over_key][1].append(overlay_name)
2022-04-18 18:16:39 +00:00
2022-04-21 20:56:32 +00:00
for over_key, (item, over_names) in key_to_overlays.items():
group_status = {}
for over_name in over_names:
for overlay_group, group_names in overlay_groups.items():
if over_name in group_names:
if overlay_group not in group_status:
group_status[overlay_group] = []
group_status[overlay_group].append(over_name)
for gk, gv in group_status.items():
if len(gv) > 1:
final = None
for v in gv:
if final is None or overlay_groups[gk][v] > overlay_groups[gk][final]:
final = v
for v in gv:
if final != v:
key_to_overlays[over_key][1].remove(v)
def find_poster_url(plex_item):
2022-04-20 04:06:42 +00:00
if isinstance(plex_item, 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
2022-04-20 04:06:42 +00:00
elif isinstance(plex_item, (Show, Season, Episode)):
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
2022-04-18 18:16:39 +00:00
def get_overlay_items(libtype=None):
2022-04-20 04:06:42 +00:00
return [o for o in self.library.search(label="Overlay", libtype=libtype) if o.ratingKey not in key_to_overlays]
2022-04-18 18:16:39 +00:00
remove_overlays = get_overlay_items()
if self.library.is_show:
remove_overlays.extend(get_overlay_items(libtype="episode"))
remove_overlays.extend(get_overlay_items(libtype="season"))
elif self.library.is_music:
remove_overlays.extend(get_overlay_items(libtype="album"))
for i, item in enumerate(remove_overlays, 1):
logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item.title}")
clean_name, _ = util.validate_filename(item.title)
poster, _, item_dir = self.library.find_assets(
name="poster" if self.library.asset_folders else clean_name,
folder_name=clean_name if self.library.asset_folders else None,
prefix=f"{item.title}'s "
)
is_url = False
original = None
2022-04-18 18:16:39 +00:00
if poster:
poster_location = poster.location
elif os.path.exists(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
2022-04-18 18:16:39 +00:00
elif os.path.exists(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
2022-04-18 18:16:39 +00:00
else:
is_url = True
poster_location = find_poster_url(item)
2022-04-18 18:16:39 +00:00
if poster_location:
self.library.upload_poster(item, poster_location, url=is_url)
2022-04-20 04:06:42 +00:00
self.library.edit_tags("label", item, remove_tags=["Overlay"], do_print=False)
if original:
os.remove(original)
2022-04-18 18:16:39 +00:00
else:
logger.error(f"No Poster found to restore for {item.title}")
logger.exorcise()
2022-04-20 04:06:42 +00:00
if key_to_overlays:
logger.info("")
logger.separator(f"Applying Overlays for the {self.library.name} Library")
logger.info("")
2022-04-20 22:56:37 +00:00
for i, (over_key, (item, over_names)) in enumerate(sorted(key_to_overlays.items(), key=lambda io: io[1][0].titleSort), 1):
2022-04-20 04:06:42 +00:00
try:
logger.ghost(f"Overlaying: {i}/{len(key_to_overlays)} {item.title}")
image_compare = None
overlay_compare = None
if self.config.Cache:
image, image_compare, _ = self.config.Cache.query_image_map(item.ratingKey, f"{self.library.image_table_name}_overlays")
2022-04-22 02:57:26 +00:00
2022-04-20 04:06:42 +00:00
overlay_compare = [] if overlay_compare is None else util.get_list(overlay_compare)
has_overlay = any([item_tag.tag.lower() == "overlay" for item_tag in item.labels])
overlay_change = False if has_overlay else True
if not overlay_change:
for oc in overlay_compare:
if oc not in over_names:
overlay_change = True
if not overlay_change:
2022-04-18 18:16:39 +00:00
for over_name in over_names:
2022-04-21 21:44:08 +00:00
if over_name not in overlay_compare or settings[over_name]["updated"]:
2022-04-20 04:06:42 +00:00
overlay_change = True
clean_name, _ = util.validate_filename(item.title)
poster, _, item_dir = self.library.find_assets(
name="poster" if self.library.asset_folders else clean_name,
folder_name=clean_name if self.library.asset_folders else None,
prefix=f"{item.title}'s "
)
has_original = None
changed_image = False
new_backup = None
if poster:
if image_compare and str(poster.compare) != str(image_compare):
changed_image = True
elif has_overlay:
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")
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")
else:
new_backup = find_poster_url(item)
if new_backup is None:
new_backup = item.posterUrl
else:
new_backup = item.posterUrl
if new_backup:
changed_image = True
image_response = self.config.get(new_backup)
if image_response.status_code >= 400:
raise Failed(f"Overlay Error: Poster Download Failed for {item.title}")
i_ext = "jpg" if image_response.headers["Content-Type"] == "image/jpeg" else "png"
backup_image_path = os.path.join(self.library.overlay_backup, f"{item.ratingKey}.{i_ext}")
with open(backup_image_path, "wb") as handler:
handler.write(image_response.content)
while util.is_locked(backup_image_path):
time.sleep(1)
has_original = backup_image_path
poster_compare = None
if poster is None and has_original is None:
logger.error(f"Overlay Error: No poster found for {item.title}")
elif changed_image or overlay_change:
new_poster = Image.open(poster.location if poster else has_original).convert("RGBA")
temp = os.path.join(self.library.overlay_folder, f"temp.png")
try:
2022-04-20 22:56:37 +00:00
blur_num = 0
for over_name in over_names:
if over_name.startswith("blur"):
blur_test = int(re.search("\\(([^)]+)\\)", over_name).group(1))
if blur_test > blur_num:
blur_num = blur_test
if blur_num > 0:
new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num))
2022-04-20 04:06:42 +00:00
for over_name in over_names:
2022-04-20 22:56:37 +00:00
if not over_name.startswith("blur"):
2022-04-21 21:44:08 +00:00
new_poster = new_poster.resize(settings[over_name]["image"].size, Image.ANTIALIAS)
new_poster.paste(settings[over_name]["image"], (0, 0), settings[over_name]["image"])
2022-04-20 04:06:42 +00:00
new_poster.save(temp, "PNG")
self.library.upload_poster(item, temp)
self.library.edit_tags("label", item, add_tags=["Overlay"], do_print=False)
self.library.reload(item, force=True)
2022-04-20 04:06:42 +00:00
poster_compare = poster.compare if poster else item.thumb
logger.info(f"Detail: Overlays: {', '.join(over_names)} applied to {item.title}")
except (OSError, BadRequest) as e:
logger.stacktrace()
raise Failed(f"Overlay Error: {e}")
if self.config.Cache and poster_compare:
self.config.Cache.update_image_map(item.ratingKey, self.library.image_table_name, item.thumb,
poster_compare, overlay=','.join(over_names))
except Failed as e:
logger.error(e)
2022-04-18 18:16:39 +00:00
logger.exorcise()