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 /test.py
logs/ logs/
config/* config/*
!config/overlays/
!config/*.template !config/*.template
*.png
!overlay.png
build/ build/
develop-eggs/ develop-eggs/
dist/ 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 datetime import datetime, timedelta
from modules import anidb, anilist, imdb, letterboxd, mal, plex, radarr, sonarr, tautulli, tmdb, trakttv, tvdb, util from modules import anidb, anilist, imdb, letterboxd, mal, plex, radarr, sonarr, tautulli, tmdb, trakttv, tvdb, util
from modules.util import Failed, Image from modules.util import Failed, Image
from PIL import Image
from plexapi.exceptions import BadRequest, NotFound from plexapi.exceptions import BadRequest, NotFound
from plexapi.video import Movie, Show from plexapi.video import Movie, Show
from urllib.parse import quote 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: 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") 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) 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: elif method_name in plex.item_advance_keys:
key, options = plex.item_advance_keys[method_name] 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: 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: except Failed as e:
logger.error(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: 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) 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) self.library.edit_tags("label", item, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags)
advance_edits = {} advance_edits = {}
for method_name, method_data in self.item_details.items(): for method_name, method_data in self.item_details.items():
@ -1676,6 +1698,18 @@ class CollectionBuilder:
advance_edits[key] = options[method_data] 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) 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): def update_details(self):
if not self.obj and self.smart_url: if not self.obj and self.smart_url:
self.library.create_smart_collection(self.name, self.smart_type_key, 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: if current in collection_items:
logger.info(f"{name} Collection | = | {current_title}") logger.info(f"{name} Collection | = | {current_title}")
else: else:
if self.smart_label_collection: self.library.query_data(current.addLabel if self.smart_label_collection else current.addCollection, name)
self.library.query_data(current.addLabel, name)
else:
self.library.query_data(current.addCollection, name)
logger.info(f"{name} Collection | + | {current_title}") 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") 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("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"])) 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): def query_image_map(self, rating_key, library, image_type):
with sqlite3.connect(self.cache_path) as connection: with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row 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)) cursor.execute(f"SELECT * FROM image_map WHERE rating_key = ? AND library = ? AND type = ?", (rating_key, library, image_type))
row = cursor.fetchone() row = cursor.fetchone()
if row and row["location"]: if row and row["location"]:
return row["location"], row["compare"] return row["location"], row["compare"], row["overlay"]
return None, None 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: with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
with closing(connection.cursor()) as cursor: 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("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 import builder, util
from modules.meta import Metadata from modules.meta import Metadata
from modules.util import Failed, Image from modules.util import Failed, Image
import plexapi
from plexapi import utils from plexapi import utils
from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.collection import Collection from plexapi.collection import Collection
from plexapi.server import PlexServer from plexapi.server import PlexServer
from PIL import Image
from retrying import retry from retrying import retry
from ruamel import yaml from ruamel import yaml
from urllib import parse from urllib import parse
@ -334,6 +334,7 @@ class Plex:
self.movie_rating_key_map = {} self.movie_rating_key_map = {}
self.show_rating_key_map = {} self.show_rating_key_map = {}
self.run_again = [] self.run_again = []
self.overlays = []
def get_all_collections(self): def get_all_collections(self):
return self.search(libtype="collection") return self.search(libtype="collection")
@ -426,13 +427,18 @@ class Plex:
item.uploadArt(filepath=image.location) item.uploadArt(filepath=image.location)
self.reload(item) 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 poster_uploaded = False
if poster is not None: if poster is not None:
try: try:
image = None image = None
if self.config.Cache: 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): if str(poster.compare) != str(image_compare):
image = None image = None
if image is None or image != item.thumb: if image is None or image != item.thumb:
@ -445,6 +451,25 @@ class Plex:
util.print_stacktrace() util.print_stacktrace()
logger.error(f"Detail: {poster.attribute} failed to update {poster.message}") 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 background_uploaded = False
if background is not None: if background is not None:
try: try:
@ -465,9 +490,9 @@ class Plex:
if self.config.Cache: if self.config.Cache:
if poster_uploaded: 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: 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) @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): 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 schedule
retrying retrying
pathvalidate pathvalidate
pillow