mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-21 20:13:05 +00:00
Added Image Overlay
This commit is contained in:
parent
d3db9bf162
commit
3e7cf35eaf
11 changed files with 86 additions and 15 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,7 +12,10 @@ __pycache__/
|
|||
/test.py
|
||||
logs/
|
||||
config/*
|
||||
!config/overlays/
|
||||
!config/*.template
|
||||
*.png
|
||||
!overlay.png
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
|
|
BIN
config/overlays/4K-Dolby/overlay.png
Normal file
BIN
config/overlays/4K-Dolby/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
config/overlays/4K-HDR/overlay.png
Normal file
BIN
config/overlays/4K-HDR/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
config/overlays/4K/overlay.png
Normal file
BIN
config/overlays/4K/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
config/overlays/Dolby/overlay.png
Normal file
BIN
config/overlays/Dolby/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
config/overlays/HDR/overlay.png
Normal file
BIN
config/overlays/HDR/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -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")
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
BIN
overlays.psd
Normal file
Binary file not shown.
|
@ -11,3 +11,4 @@ ruamel.yaml
|
|||
schedule
|
||||
retrying
|
||||
pathvalidate
|
||||
pillow
|
||||
|
|
Loading…
Reference in a new issue