[34] assets fix

pull/858/head
meisnate12 3 years ago
parent 3249bb15b5
commit 8e802cdeb7

@ -1 +1 @@
1.16.5-develop33
1.16.5-develop34

@ -2,7 +2,6 @@ import os, re, time
from datetime import datetime, timedelta
from modules import anidb, anilist, flixpatrol, icheckmovies, imdb, letterboxd, mal, plex, radarr, reciperr, sonarr, tautulli, tmdb, trakt, tvdb, mdblist, util
from modules.util import Failed, ImageData, NotScheduled, NotScheduledRange
from PIL import Image
from plexapi.audio import Artist, Album, Track
from plexapi.exceptions import BadRequest, NotFound
from plexapi.video import Movie, Show, Season, Episode
@ -464,7 +463,7 @@ class CollectionBuilder:
logger.debug(f"Value: {data[methods['delete_not_scheduled']]}")
self.details["delete_not_scheduled"] = util.parse(self.Type, "delete_not_scheduled", self.data, datatype="bool", methods=methods, default=False)
if "schedule" in methods and not self.config.requested_collections:
if "schedule" in methods and not self.config.requested_collections and not self.overlay:
logger.debug("")
logger.debug("Validating Method: schedule")
if not self.data[methods["schedule"]]:
@ -473,7 +472,7 @@ class CollectionBuilder:
logger.debug(f"Value: {self.data[methods['schedule']]}")
err = None
try:
util.schedule_check("schedule", self.data[methods['schedule']], self.current_time, self.config.run_hour)
util.schedule_check("schedule", self.data[methods["schedule"]], self.current_time, self.config.run_hour)
except NotScheduledRange as e:
err = e
except NotScheduled as e:
@ -2607,11 +2606,7 @@ class CollectionBuilder:
if self.details["name_mapping"]: name_mapping = self.details["name_mapping"]
else: logger.error(f"{self.Type} Error: name_mapping attribute is blank")
final_name, _ = util.validate_filename(name_mapping)
poster_image, background_image, asset_location = self.library.find_assets(
name="poster" if self.details["asset_folders"] else final_name,
folder_name=final_name if self.details["asset_folders"] else None,
prefix=f"{name_mapping}'s "
)
poster_image, background_image, asset_location, _ = self.library.find_item_assets(name_mapping, asset_directory=self.asset_directory)
if poster_image:
self.posters["asset_directory"] = poster_image
if background_image:

@ -26,7 +26,7 @@ from modules.tautulli import Tautulli
from modules.tmdb import TMDb
from modules.trakt import Trakt
from modules.tvdb import TVDb
from modules.util import Failed, NotScheduled
from modules.util import Failed, NotScheduled, NotScheduledRange
from modules.webhooks import Webhooks
from retrying import retry
from ruamel import yaml
@ -367,7 +367,7 @@ class ConfigFile:
self.Webhooks = Webhooks(self, self.webhooks, notifiarr=self.NotifiarrFactory)
try:
self.Webhooks.start_time_hooks(self.start_time)
if self.version and (self.version[1] != self.latest_version[1] or (self.version[2] and self.version[2] < self.latest_version[2])):
if self.version and self.latest_version and (self.version[1] != self.latest_version[1] or (self.version[2] and self.version[2] < self.latest_version[2])):
self.Webhooks.version_hooks(self.version, self.latest_version)
except Failed as e:
logger.stacktrace()
@ -751,16 +751,37 @@ class ConfigFile:
params["overlay_path"] = []
params["remove_overlays"] = False
if lib and "overlay_path" in lib:
if not lib["overlay_path"]:
raise Failed("Config Error: overlay_path attribute is blank")
files = util.load_files(lib["overlay_path"], "overlay_path")
if not files:
raise Failed("Config Error: No Paths Found for overlay_path")
for file in util.get_list(lib["overlay_path"], split=False):
if isinstance(file, dict) \
and ("remove_overlays" in file and file["remove_overlays"] is True) \
or ("revert_overlays" in file and file["revert_overlays"] is True):
params["remove_overlays"] = True
try:
if not lib["overlay_path"]:
raise Failed("Config Error: overlay_path attribute is blank")
files = util.load_files(lib["overlay_path"], "overlay_path")
if not files:
raise Failed("Config Error: No Paths Found for overlay_path")
for file in util.get_list(lib["overlay_path"], split=False):
if isinstance(file, dict):
if ("remove_overlays" in file and file["remove_overlays"] is True) \
or ("revert_overlays" in file and file["revert_overlays"] is True):
params["remove_overlays"] = True
if "schedule" in file and file["schedule"]:
logger.debug(f"Value: {file['schedule']}")
err = None
try:
util.schedule_check("schedule", file["schedule"], current_time, self.run_hour)
except NotScheduledRange as e:
err = e
except NotScheduled as e:
if not self.ignore_schedules:
err = e
if err:
raise NotScheduled(f"{err}\n\nOverlays not scheduled to run")
except NotScheduled as e:
logger.error(e)
params["overlay_path"] = []
params["remove_overlays"] = False
params["overlay_path"] = files
logger.info("")

@ -139,7 +139,7 @@ class Library(ABC):
except Failed as e:
logger.error(e)
def upload_images(self, item, poster=None, background=None, overlay=None):
def upload_images(self, item, poster=None, background=None):
image = None
image_compare = None
poster_uploaded = False
@ -160,40 +160,6 @@ class Library(ABC):
logger.stacktrace()
logger.error(f"Detail: {poster.attribute} failed to update {poster.message}")
if overlay is not None:
overlay_name, overlay_folder, overlay_image = overlay
self.reload(item)
item_labels = {item_tag.tag.lower(): item_tag.tag for item_tag in item.labels}
for item_label in item_labels:
if item_label.endswith(" overlay") and item_label != f"{overlay_name.lower()} overlay":
raise Failed(f"Overlay Error: Poster already has an existing Overlay: {item_labels[item_label]}")
if poster_uploaded or image is None or image != item.thumb or f"{overlay_name.lower()} overlay" not in item_labels:
if not item.posterUrl:
raise Failed(f"Overlay Error: No existing poster to Overlay for {item.title}")
response = self.config.get(item.posterUrl)
if response.status_code >= 400:
raise Failed(f"Overlay Error: Overlay Failed for {item.title}")
ext = "jpg" if response.headers["Content-Type"] == "image/jpegss" else "png"
temp_image = os.path.join(overlay_folder, f"temp.{ext}")
with open(temp_image, "wb") as handler:
handler.write(response.content)
shutil.copyfile(temp_image, os.path.join(overlay_folder, f"{item.ratingKey}.{ext}"))
while util.is_locked(temp_image):
time.sleep(1)
try:
new_poster = Image.open(temp_image).convert("RGBA")
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_poster(item, temp_image)
self.edit_tags("label", item, add_tags=[f"{overlay_name} Overlay"])
self.reload(item, force=True)
poster_uploaded = True
logger.info(f"Detail: Overlay: {overlay_name} applied to {item.title}")
except (OSError, BadRequest) as e:
logger.stacktrace()
raise Failed(f"Overlay Error: {e}")
background_uploaded = False
if background is not None:
try:

@ -320,6 +320,7 @@ class MetadataFile(DataFile):
auto_list = {}
all_keys = []
dynamic_data = None
logger.debug(exclude)
def _check_dict(check_dict):
for ck, cv in check_dict.items():
all_keys.append(ck)

@ -1,6 +1,8 @@
import os, re
from modules import util
from modules.util import Failed
from plexapi.audio import Artist
from plexapi.video import Show
from ruamel import yaml
logger = util.logger
@ -76,7 +78,53 @@ class Operations:
current_labels = [la.tag for la in item.labels] if self.library.assets_for_all or self.library.mass_imdb_parental_labels else []
if self.library.assets_for_all and "Overlay" not in current_labels:
self.library.update_asset(item)
poster, background, item_dir, name = self.library.find_item_assets(item)
if item_dir:
if poster or background:
self.library.upload_images(item, poster=poster, background=background)
if isinstance(item, Show):
missing_seasons = ""
missing_episodes = ""
found_season = False
found_episode = False
for season in self.library.query(item.seasons):
season_poster, season_background, _, _ = self.library.find_item_assets(season, item_asset_directory=item_dir)
if season_poster:
found_season = True
elif self.library.show_missing_season_assets and season.seasonNumber > 0:
missing_seasons += f"\nMissing Season {season.seasonNumber} Poster"
if season_poster or season_background:
self.library.upload_images(season, poster=season_poster, background=season_background)
for episode in self.library.query(season.episodes):
if episode.seasonEpisode:
episode_poster, episode_background, _, _ = self.library.find_item_assets(episode, item_asset_directory=item_dir)
if episode_poster or episode_background:
found_episode = True
self.library.upload_images(episode, poster=episode_poster, background=episode_background)
elif self.library.show_missing_episode_assets:
missing_episodes += f"\nMissing {episode.seasonEpisode.upper()} Title Card"
if (found_season and missing_seasons) or (found_episode and missing_episodes):
logger.info(f"Missing Posters for {item.title}{missing_seasons}{missing_episodes}")
if isinstance(item, Artist):
missing_assets = ""
found_album = False
for album in self.library.query(item.albums):
album_poster, album_background, _, _ = self.library.find_item_assets(album, item_asset_directory=item_dir)
if album_poster or album_background:
found_album = True
elif self.library.show_missing_season_assets:
missing_assets += f"\nMissing Album {album.title} Poster"
if album_poster or album_background:
self.library.upload_images(album, poster=album_poster, background=album_background)
if self.library.show_missing_season_assets and found_album and missing_assets:
logger.info(f"Missing Album Posters for {item.title}{missing_assets}")
elif self.library.asset_folders:
logger.warning(f"Asset Warning: No asset folder found called '{name}'")
elif not poster and not background and self.library.show_missing_assets:
logger.warning(f"Asset Warning: No poster or background found in the assets folder '{item_dir}'")
tmdb_id, tvdb_id, imdb_id = self.library.get_ids(item)
@ -381,7 +429,15 @@ class Operations:
logger.separator(f"Unmanaged Collection Assets Check for {self.library.name} Library", space=False, border=False)
logger.info("")
for col in unmanaged_collections:
self.library.update_asset(col)
poster, background, item_dir, name = self.library.find_item_assets(col)
if item_dir:
if poster or background:
self.library.upload_images(col, poster=poster, background=background)
if self.library.asset_folders and item_dir is None:
logger.warning(f"Asset Warning: No asset folder found called '{name}'")
elif not poster and not background and self.library.show_missing_assets:
logger.warning(f"Asset Warning: No poster or background found in an assets folder for '{name}'")
if self.library.mass_collection_mode:
logger.info("")
logger.separator(f"Unmanaged Mass Collection Mode for {self.library.name} Library", space=False, border=False)

@ -2,6 +2,7 @@ import os, re, time
from modules import util
from modules.builder import CollectionBuilder
from modules.util import Failed
from plexapi.audio import Album
from plexapi.exceptions import BadRequest
from plexapi.video import Movie, Show, Season, Episode
from PIL import Image, ImageFilter
@ -63,13 +64,22 @@ class Overlays:
logger.info("")
logger.separator(f"Applying Overlays for the {self.library.name} Library")
logger.info("")
for i, (over_key, (item, over_names)) in enumerate(sorted(key_to_overlays.items(), key=lambda io: io[1][0].titleSort), 1):
def get_item_sort_title(item_to_sort):
if isinstance(item_to_sort, Album):
return f"{item_to_sort.titleSort} Album {item_to_sort.title}"
elif isinstance(item_to_sort, Season):
return f"{item_to_sort.titleSort} Season {item_to_sort.seasonNumber}"
elif isinstance(item_to_sort, Episode):
return f"{item_to_sort.titleSort} {item_to_sort.seasonEpisode.upper()}"
else:
return item_to_sort.titleSort
for i, (over_key, (item, over_names)) in enumerate(sorted(key_to_overlays.items(), key=lambda io: get_item_sort_title(io[1][0])), 1):
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")
image, image_compare, overlay_compare = self.config.Cache.query_image_map(item.ratingKey, f"{self.library.image_table_name}_overlays")
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])
@ -83,8 +93,7 @@ class Overlays:
for over_name in over_names:
if over_name not in overlay_compare or properties[over_name]["updated"]:
overlay_change = True
poster, _, item_dir = self.find_asset(item)
poster, _, _, _ = self.library.find_item_assets(item)
has_original = None
changed_image = False
@ -145,7 +154,7 @@ class Overlays:
logger.stacktrace()
raise Failed(f"Overlay Error: {e}")
else:
logger.error(f"Overlay Not Needed for {item.title}")
logger.error(f"Overlay Update Not Needed for {item.title}")
if self.config.Cache and poster_compare:
self.config.Cache.update_image_map(item.ratingKey, self.library.image_table_name, item.thumb,
@ -249,14 +258,6 @@ class Overlays:
key_to_overlays[over_key][1].remove(v)
return key_to_overlays, properties
def find_asset(self, item):
clean_asset_name, _ = util.validate_filename(item.title)
return self.library.find_assets(
name="poster" if self.library.asset_folders else clean_asset_name,
folder_name=clean_asset_name if self.library.asset_folders else None,
prefix=f"{item.title}'s "
)
def find_poster_url(self, item):
if isinstance(item, Movie):
if item.ratingKey in self.library.movie_rating_key_map:
@ -276,7 +277,7 @@ class Overlays:
return items if not ignore else [o for o in items if o.ratingKey not in ignore]
def remove_overlay(self, item, label, locations):
poster, _, item_dir = self.find_asset(item)
poster, _, _, _ = self.library.find_item_assets(item)
is_url = False
original = None
if poster:

@ -1,5 +1,8 @@
import os, plexapi, requests
from datetime import datetime
from plexapi.base import PlexObject
from modules import builder, util
from modules.library import Library
from modules.util import Failed, ImageData
@ -809,118 +812,101 @@ class Plex(Library):
logger.info(final)
return final
def update_asset(self, item, folders=None, create=None, asset_directory=None):
if isinstance(item, (Movie, Artist, Show)):
starting = item.show() if isinstance(item, (Episode, Season)) else item
path_test = str(starting.locations[0])
if not os.path.dirname(path_test):
path_test = path_test.replace("\\", "/")
name = os.path.basename(os.path.dirname(path_test) if isinstance(starting, Movie) else path_test)
elif isinstance(item, (Collection, Playlist)):
name, _ = util.validate_filename(item.title)
else:
return None, None, None
if folders is None:
folders = self.asset_folders
if create is None:
create = self.create_asset_folders
def find_item_assets(self, item, item_asset_directory=None, asset_directory=None):
poster = None
background = None
item_dir = None
folder_name = None
if asset_directory is None:
asset_directory = self.asset_directory
poster, background, item_dir = self.find_assets(
name="poster" if folders else name,
folder_name=name if folders else None,
prefix=f"{item.title}'s "
)
if item_dir and self.dimensional_asset_rename and (not poster or not background):
for file in util.glob_filter(os.path.join(item_dir, "*.*")):
if file.lower().endswith((".jpg", ".png", ".jpeg")):
image = Image.open(file)
_w, _h = image.size
image.close()
if not poster and _h >= _w:
new_path = os.path.join(os.path.dirname(file), f"poster{os.path.splitext(file)[1].lower()}")
os.rename(file, new_path)
poster = ImageData("asset_directory", os.path.abspath(new_path), prefix=f"{item.title}'s ", is_url=False)
elif not background and _w > _h:
new_path = os.path.join(os.path.dirname(file), f"background{os.path.splitext(file)[1].lower()}")
os.rename(file, new_path)
background = ImageData("asset_directory", os.path.abspath(new_path), prefix=f"{item.title}'s ", is_poster=False, is_url=False)
if poster and background:
break
is_top_level = isinstance(item, (Movie, Artist, Show, Collection, Playlist))
if isinstance(item, Album):
prefix = f"{item.title} Album {item.title}'s "
file_name = item.title
elif isinstance(item, Season):
prefix = f"{item.title} Season {item.seasonNumber}'s "
file_name = f"Season{'0' if item.seasonNumber < 10 else ''}{item.seasonNumber}"
elif isinstance(item, Episode):
prefix = f"{item.title} {item.seasonEpisode.upper()}'s "
file_name = item.seasonEpisode.upper()
else:
prefix = f"{item.title if is_top_level else item}'s "
file_name = "poster"
if not item_asset_directory:
if isinstance(item, (Movie, Artist, Album, Show, Episode, Season)):
starting = item.show() if isinstance(item, (Episode, Season)) else item
path_test = str(starting.locations[0])
if not os.path.dirname(path_test):
path_test = path_test.replace("\\", "/")
folder_name = os.path.basename(os.path.dirname(path_test) if isinstance(starting, Movie) else path_test)
elif isinstance(item, (Collection, Playlist)):
folder_name, _ = util.validate_filename(item.title)
else:
folder_name, _ = util.validate_filename(item)
if poster or background:
self.upload_images(item, poster=poster, background=background)
if not self.asset_folders:
file_name = folder_name if file_name == "poster" else f"{folder_name}_{file_name}"
if isinstance(item, Show):
missing_seasons = ""
missing_episodes = ""
found_season = False
found_episode = False
for season in self.query(item.seasons):
season_name = f"Season{'0' if season.seasonNumber < 10 else ''}{season.seasonNumber}"
season_poster, season_background, _ = self.find_assets(
name=season_name,
folder_name=name,
item_directory=item_dir,
prefix=f"{item.title} Season {season.seasonNumber}'s "
)
if season_poster:
found_season = True
elif self.show_missing_season_assets and season.seasonNumber > 0:
missing_seasons += f"\nMissing Season {season.seasonNumber} Poster"
if season_poster or season_background:
self.upload_images(season, poster=season_poster, background=season_background)
for episode in self.query(season.episodes):
if episode.seasonEpisode:
episode_poster, episode_background, _ = self.find_assets(
name=episode.seasonEpisode.upper(),
folder_name=name,
item_directory=item_dir,
prefix=f"{item.title} {episode.seasonEpisode.upper()}'s "
)
if episode_poster or episode_background:
found_episode = True
self.upload_images(episode, poster=episode_poster, background=episode_background)
elif self.show_missing_episode_assets:
missing_episodes += f"\nMissing {episode.seasonEpisode.upper()} Title Card"
if (found_season and missing_seasons) or (found_episode and missing_episodes):
output = f"Missing Posters for {item.title}"
if found_season:
output += missing_seasons
if found_episode:
output += missing_episodes
logger.info(output)
if isinstance(item, Artist):
missing_assets = ""
found_album = False
for album in self.query(item.albums):
album_poster, album_background, _ = self.find_assets(
name=album.title,
folder_name=name,
item_directory=item_dir,
prefix=f"{item.title} Album {album.title}'s "
)
if album_poster:
found_album = True
for ad in asset_directory:
if self.asset_folders:
if os.path.isdir(os.path.join(ad, folder_name)):
item_asset_directory = os.path.join(ad, folder_name)
else:
for n in range(1, self.asset_depth + 1):
new_path = ad
for i in range(1, n + 1):
new_path = os.path.join(new_path, "*")
matches = util.glob_filter(os.path.join(new_path, folder_name))
if len(matches) > 0:
item_asset_directory = os.path.abspath(matches[0])
else:
missing_assets += f"\nMissing Album {album.title} Poster"
if album_poster or album_background:
self.upload_images(album, poster=album_poster, background=album_background)
if self.show_missing_season_assets and found_album and missing_assets:
logger.info(f"Missing Album Posters for {item.title}{missing_assets}")
if create and folders and item_dir is None:
filename, _ = util.validate_filename(name)
item_dir = os.path.join(asset_directory[0], filename)
os.makedirs(item_dir, exist_ok=True)
logger.info(f"Asset Directory Created: {item_dir}")
elif folders and item_dir is None:
logger.warning(f"Asset Warning: No asset folder found called '{name}'")
elif not poster and not background and self.show_missing_assets:
logger.warning(f"Asset Warning: No poster or background found in an assets folder for '{name}'")
return poster, background, item_dir
matches = util.glob_filter(os.path.join(ad, f"{file_name}.*"))
if len(matches) > 0:
item_asset_directory = ad
if item_asset_directory:
break
if not item_asset_directory:
if self.create_asset_folders and self.asset_folders:
item_asset_directory = os.path.join(asset_directory[0], folder_name)
os.makedirs(item_asset_directory, exist_ok=True)
logger.info(f"Asset Directory Created: {item_asset_directory}")
raise Failed(f"Asset Error: Unable to find asset {'folder' if self.asset_folders else 'file'}: {folder_name if self.asset_folders else file_name}")
poster_filter = os.path.join(item_asset_directory, f"{file_name}.*")
background_filter = os.path.join(item_asset_directory, "background.*" if file_name == "poster" else f"{file_name}_background.*")
poster_matches = util.glob_filter(poster_filter)
if len(poster_matches) > 0:
poster = ImageData("asset_directory", os.path.abspath(poster_matches[0]), prefix=prefix, is_url=False)
background_matches = util.glob_filter(background_filter)
if len(background_matches) > 0:
background = ImageData("asset_directory", os.path.abspath(background_matches[0]), prefix=prefix, is_poster=False, is_url=False)
if is_top_level and self.asset_folders and self.dimensional_asset_rename and (not poster or not background):
for file in util.glob_filter(os.path.join(item_asset_directory, "*.*")):
if file.lower().endswith((".jpg", ".png", ".jpeg")):
try:
image = Image.open(file)
_w, _h = image.size
image.close()
if not poster and _h >= _w:
new_path = os.path.join(os.path.dirname(file), f"poster{os.path.splitext(file)[1].lower()}")
os.rename(file, new_path)
poster = ImageData("asset_directory", os.path.abspath(new_path), prefix=f"{item.title}'s ", is_url=False)
elif not background and _w > _h:
new_path = os.path.join(os.path.dirname(file), f"background{os.path.splitext(file)[1].lower()}")
os.rename(file, new_path)
background = ImageData("asset_directory", os.path.abspath(new_path), prefix=f"{item.title}'s ", is_poster=False, is_url=False)
if poster and background:
break
except OSError:
logger.error(f"Asset Error: Failed to open image: {file}")
return poster, background, item_dir, folder_name
def get_ids(self, item):
tmdb_id = None

@ -86,6 +86,9 @@ screen_width = get_arg("PMM_WIDTH", args.width, arg_int=True)
debug = get_arg("PMM_DEBUG", args.debug, arg_bool=True)
trace = get_arg("PMM_TRACE", args.trace, arg_bool=True)
if collections or metadata_files:
collection_only = True
if screen_width < 90 or screen_width > 300:
print(f"Argument Error: width argument invalid: {screen_width} must be an integer between 90 and 300 using the default 100")
screen_width = 100

Loading…
Cancel
Save