Added Image Overlay

This commit is contained in:
meisnate12 2021-06-29 23:08:38 -04:00
parent d3db9bf162
commit 3e7cf35eaf
11 changed files with 86 additions and 15 deletions

3
.gitignore vendored
View file

@ -12,7 +12,10 @@ __pycache__/
/test.py
logs/
config/*
!config/overlays/
!config/*.template
*.png
!overlay.png
build/
develop-eggs/
dist/

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -2,6 +2,7 @@ import logging, os, re
from datetime import datetime, timedelta
from modules import anidb, anilist, imdb, letterboxd, mal, plex, radarr, sonarr, tautulli, tmdb, trakttv, tvdb, util
from modules.util import Failed, Image
from PIL import Image
from plexapi.exceptions import BadRequest, NotFound
from plexapi.video import Movie, Show
from urllib.parse import quote
@ -627,6 +628,14 @@ class CollectionBuilder:
if "item_label.remove" in self.data and "item_label.sync" in self.data:
raise Failed(f"Collection Error: Cannot use item_label.remove and item_label.sync together")
self.item_details[method_final] = util.get_list(method_data)
elif method_name == "item_overlay":
overlay = os.path.join(config.default_dir, "overlays", method_data, "overlay.png")
if not os.path.exists(overlay):
raise Failed(f"Collection Error: {method_data} overlay image not found at {overlay}")
if method_data in self.library.overlays:
raise Failed(f"Each Overlay can only be used once per Library")
self.library.overlays.append(method_data)
self.item_details[method_name] = method_data
elif method_name in plex.item_advance_keys:
key, options = plex.item_advance_keys[method_name]
if method_name in advance_new_agent and self.library.agent not in plex.new_plex_agents:
@ -1664,9 +1673,22 @@ class CollectionBuilder:
except Failed as e:
logger.error(e)
overlay = None
overlay_folder = None
rating_keys = []
if "item_overlay" in self.item_details:
overlay_name = self.item_details["item_overlay"]
rating_keys = self.config.Cache.query_image_map_overlay(self.library.original_mapping_name, "poster", overlay_name)
overlay_folder = os.path.join(self.config.default_dir, "overlays", overlay_name)
overlay_image = Image.open(os.path.join(overlay_folder, "overlay.png"))
temp_image = os.path.join(overlay_folder, f"temp.png")
overlay = (overlay_name, overlay_folder, overlay_image, temp_image)
for item in items:
if int(item.ratingKey) in rating_keys:
rating_keys.remove(int(item.ratingKey))
poster, background = self.library.update_item_from_assets(item)
self.library.upload_images(item, poster=poster, background=background)
self.library.upload_images(item, poster=poster, background=background, overlay=overlay)
self.library.edit_tags("label", item, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags)
advance_edits = {}
for method_name, method_data in self.item_details.items():
@ -1676,6 +1698,18 @@ class CollectionBuilder:
advance_edits[key] = options[method_data]
self.library.edit_item(item, item.title, "Movie" if self.library.is_movie else "Show", advance_edits, advanced=True)
for rating_key in rating_keys:
try:
item = self.fetch_item(rating_key)
except Failed as e:
logger.error(e)
continue
og_image = os.path.join(overlay_folder, f"{rating_key}.png")
if os.path.exists(og_image):
self.library._upload_file_poster(item, og_image)
os.remove(og_image)
self.config.Cache.update_image_map(item.ratingKey, self.library.original_mapping_name, "poster", "", "", "")
def update_details(self):
if not self.obj and self.smart_url:
self.library.create_smart_collection(self.name, self.smart_type_key, self.smart_url)
@ -1835,10 +1869,7 @@ class CollectionBuilder:
if current in collection_items:
logger.info(f"{name} Collection | = | {current_title}")
else:
if self.smart_label_collection:
self.library.query_data(current.addLabel, name)
else:
self.library.query_data(current.addCollection, name)
self.library.query_data(current.addLabel if self.smart_label_collection else current.addCollection, name)
logger.info(f"{name} Collection | + | {current_title}")
logger.info(f"{len(rating_keys)} {'Movie' if self.library.is_movie else 'Show'}{'s' if len(rating_keys) > 1 else ''} Processed")

View file

@ -233,6 +233,17 @@ class Cache:
cursor.execute("INSERT OR IGNORE INTO anime_map(anidb) VALUES(?)", (anime_ids["anidb"],))
cursor.execute("UPDATE anime_map SET anilist = ?, myanimelist = ?, kitsu = ?, expiration_date = ? WHERE anidb = ?", (anime_ids["anidb"], anime_ids["myanimelist"], anime_ids["kitsu"], expiration_date.strftime("%Y-%m-%d"), anime_ids["anidb"]))
def query_image_map_overlay(self, library, image_type, overlay):
rks = []
with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row
with closing(connection.cursor()) as cursor:
cursor.execute(f"SELECT * FROM image_map WHERE overlay = ? AND library = ? AND type = ?", (overlay, library, image_type))
rows = cursor.fetchall()
for row in rows:
rks.append(int(row["rating_key"]))
return rks
def query_image_map(self, rating_key, library, image_type):
with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row
@ -240,12 +251,12 @@ class Cache:
cursor.execute(f"SELECT * FROM image_map WHERE rating_key = ? AND library = ? AND type = ?", (rating_key, library, image_type))
row = cursor.fetchone()
if row and row["location"]:
return row["location"], row["compare"]
return None, None
return row["location"], row["compare"], row["overlay"]
return None, None, None
def update_image_map(self, rating_key, library, image_type, location, compare):
def update_image_map(self, rating_key, library, image_type, location, compare, overlay):
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 image_map(rating_key, library, type) VALUES(?, ?, ?)", (rating_key, library, image_type))
cursor.execute("UPDATE image_map SET location = ?, compare = ? WHERE rating_key = ? AND library = ? AND type = ?", (location, compare, rating_key, library, image_type))
cursor.execute("UPDATE image_map SET location = ?, compare = ?, overlay = ? WHERE rating_key = ? AND library = ? AND type = ?", (location, compare, overlay, rating_key, library, image_type))

View file

@ -1,12 +1,12 @@
import glob, logging, os, requests
import glob, logging, os, plexapi, requests, shutil
from modules import builder, util
from modules.meta import Metadata
from modules.util import Failed, Image
import plexapi
from plexapi import utils
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.collection import Collection
from plexapi.server import PlexServer
from PIL import Image
from retrying import retry
from ruamel import yaml
from urllib import parse
@ -334,6 +334,7 @@ class Plex:
self.movie_rating_key_map = {}
self.show_rating_key_map = {}
self.run_again = []
self.overlays = []
def get_all_collections(self):
return self.search(libtype="collection")
@ -426,13 +427,18 @@ class Plex:
item.uploadArt(filepath=image.location)
self.reload(item)
def upload_images(self, item, poster=None, background=None):
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
def _upload_file_poster(self, item, image):
item.uploadPoster(filepath=image)
self.reload(item)
def upload_images(self, item, poster=None, background=None, overlay=None):
poster_uploaded = False
if poster is not None:
try:
image = None
if self.config.Cache:
image, image_compare = self.config.Cache.query_image_map(item.ratingKey, self.original_mapping_name, "poster")
image, image_compare, _ = self.config.Cache.query_image_map(item.ratingKey, self.original_mapping_name, "poster")
if str(poster.compare) != str(image_compare):
image = None
if image is None or image != item.thumb:
@ -445,6 +451,25 @@ class Plex:
util.print_stacktrace()
logger.error(f"Detail: {poster.attribute} failed to update {poster.message}")
overlay_name = ""
if overlay is not None:
overlay_name, overlay_folder, overlay_image, temp_image = overlay
image_overlay = None
if self.config.Cache:
image, _, image_overlay = self.config.Cache.query_image_map(item.ratingKey, self.original_mapping_name, "poster")
if poster_uploaded or not image_overlay or image_overlay != overlay_name:
og_image = requests.get(item.posterUrl).content
with open(temp_image, "wb") as handler:
handler.write(og_image)
shutil.copyfile(temp_image, os.path.join(overlay_folder, f"{item.ratingKey}.png"))
new_poster = Image.open(temp_image)
new_poster = new_poster.resize(overlay_image.size, Image.ANTIALIAS)
new_poster.paste(overlay_image, (0, 0), overlay_image)
new_poster.save(temp_image)
self._upload_file_poster(item, temp_image)
poster_uploaded = True
logger.info(f"Detail: Overlay: {overlay_name} applied to {item.title}")
background_uploaded = False
if background is not None:
try:
@ -465,9 +490,9 @@ class Plex:
if self.config.Cache:
if poster_uploaded:
self.config.Cache.update_image_map(item.ratingKey, self.original_mapping_name, "poster", item.thumb, poster.compare)
self.config.Cache.update_image_map(item.ratingKey, self.original_mapping_name, "poster", item.thumb, poster.compare if poster else "", overlay_name)
if background_uploaded:
self.config.Cache.update_image_map(item.ratingKey, self.original_mapping_name, "background", item.art, background.compare)
self.config.Cache.update_image_map(item.ratingKey, self.original_mapping_name, "background", item.art, background.compare, "")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def get_search_choices(self, search_name, title=True):

BIN
overlays.psd Normal file

Binary file not shown.

View file

@ -11,3 +11,4 @@ ruamel.yaml
schedule
retrying
pathvalidate
pillow