[41] add overlay coordinates

pull/858/head
meisnate12 3 years ago
parent f053b7d326
commit 225887b267

@ -1 +1 @@
1.16.5-develop40 1.16.5-develop41

@ -56,18 +56,20 @@ You can specify the Overlay Name in 3 ways.
3. Using a dictionary for more overlay location options. 3. Using a dictionary for more overlay location options.
| Attribute | Description | Required | | Attribute | Description | Required |
|:----------|:--------------------------------------------------------------------------------------------------------------|:--------:| |:---------------|:--------------------------------------------------------------------------------------------------------------|:--------:|
| `name` | Name of the overlay. Each overlay name should be unique. | ✅ | | `name` | Name of the overlay. Each overlay name should be unique. | ✅ |
| `file` | Local location of the Overlay Image. | ❌ | | `file` | Local location of the Overlay Image. | ❌ |
| `url` | URL of Overlay Image Online. | ❌ | | `url` | URL of Overlay Image Online. | ❌ |
| `git` | Location in the [Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) of the Overlay Image. | ❌ | | `git` | Location in the [Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) of the Overlay Image. | ❌ |
| `repo` | Location in the [Custom Repo](../config/settings.md#custom-repo) of the Overlay Image. | ❌ | | `repo` | Location in the [Custom Repo](../config/settings.md#custom-repo) of the Overlay Image. | ❌ |
| `group` | Name of the Grouping for this overlay. **`weight` is required when using `group`** | ❌ | | `group` | Name of the Grouping for this overlay. **`weight` is required when using `group`** | ❌ |
| `weight` | Weight of this overlay in its group. **`group` is required when using `weight`** | ❌ | | `weight` | Weight of this overlay in its group. **`group` is required when using `weight`** | ❌ |
| `x_coordinate` | Top Left X Coordinate of this overlay. **`y_coordinate` is required when using `x_coordinate`** | ❌ |
| `y_coordinate` | Top Left Y Coordinate of this overlay. **`x_coordinate` is required when using `y_coordinate`** | ❌ |
* If `url`, `git`, and `repo` are all not defined then PMM will look in your `config/overlays` folder for a `.png` file named the same as the `name` attribute. * If `url`, `git`, and `repo` are all not defined then PMM will look in your `config/overlays` folder for a `.png` file named the same as the `name` attribute.
* Only one overlay with the highest weight per group will be applied. * Only one overlay with the highest weight per group will be applied.
```yaml ```yaml
overlays: overlays:

@ -271,6 +271,7 @@ class CollectionBuilder:
self.overlay_group = None self.overlay_group = None
self.overlay_weight = None self.overlay_weight = None
self.overlay_path = None self.overlay_path = None
self.overlay_coordinates = None
if self.overlay: if self.overlay:
if "overlay" in methods: if "overlay" in methods:
logger.debug("") logger.debug("")
@ -282,19 +283,33 @@ class CollectionBuilder:
self.overlay = str(data[methods["overlay"]]["name"]) self.overlay = str(data[methods["overlay"]]["name"])
if "group" in data[methods["overlay"]] and data[methods["overlay"]]["group"]: if "group" in data[methods["overlay"]] and data[methods["overlay"]]["group"]:
self.overlay_group = str(data[methods["overlay"]]["group"]) self.overlay_group = str(data[methods["overlay"]]["group"])
if "weight" in data[methods["overlay"]] and data[methods["overlay"]]["weight"]: if "weight" in data[methods["overlay"]] and data[methods["overlay"]]["weight"] is not None:
pri = util.check_num(data[methods["overlay"]]["weight"]) pri = util.check_num(data[methods["overlay"]]["weight"])
if pri is None: if pri is None:
raise Failed(f"{self.Type} Error: overlay weight must be a number") raise Failed(f"{self.Type} Error: overlay weight must be a number")
self.overlay_weight = pri self.overlay_weight = pri
else: if ("group" in data[methods["overlay"]] or "weight" in data[methods["overlay"]]) and (not self.overlay_group or self.overlay_weight is None):
raise Failed(f"{self.Type} Error: overlay group and overlay weight must be used together") raise Failed(f"{self.Type} Error: overlay group and overlay weight must be used together")
x_coordinate = None
if "x_coordinate" in data[methods["x_coordinate"]] and data[methods["overlay"]]["x_coordinate"] is not None:
x_coordinate = util.check_num(data[methods["overlay"]]["x_coordinate"])
if x_coordinate is None or x_coordinate < 0:
raise Failed(f"{self.Type} Error: overlay x_coordinate: {data[methods['overlay']]['x_coordinate']} invalid must be a number 0 or greater")
y_coordinate = None
if "y_coordinate" in data[methods["y_coordinate"]] and data[methods["overlay"]]["y_coordinate"] is not None:
y_coordinate = util.check_num(data[methods["overlay"]]["y_coordinate"])
if y_coordinate is None or y_coordinate < 0:
raise Failed(f"{self.Type} Error: overlay y_coordinate: {data[methods['overlay']]['y_coordinate']} invalid must be a number 0 or greater")
if ("x_coordinate" in data[methods["overlay"]] or "y_coordinate" in data[methods["overlay"]]) and (x_coordinate is None or y_coordinate is None):
raise Failed(f"{self.Type} Error: overlay x_coordinate and overlay y_coordinate must be used together")
if x_coordinate or y_coordinate:
self.overlay_coordinates = (x_coordinate, y_coordinate)
def get_and_save_image(image_url): def get_and_save_image(image_url):
response = self.config.get(image_url) response = self.config.get(image_url)
if response.status_code >= 400: if response.status_code >= 400:
raise Failed(f"{self.Type} Error: Overlay Image not found at: {image_url}") raise Failed(f"{self.Type} Error: Overlay Image not found at: {image_url}")
if "Content-Type" not in response.headers or response.headers["Content-Type"] != "image/png": if "Content-Type" not in response.headers or response.headers["Content-Type"] != "image/png":
raise Failed(f"{self.Type} Error: Overlay Image not a png: {url}") raise Failed(f"{self.Type} Error: Overlay Image not a png: {image_url}")
if not os.path.exists(library.overlay_folder) or not os.path.isdir(library.overlay_folder): if not os.path.exists(library.overlay_folder) or not os.path.isdir(library.overlay_folder):
os.makedirs(library.overlay_folder, exist_ok=False) os.makedirs(library.overlay_folder, exist_ok=False)
logger.info(f"Creating Overlay Folder found at: {library.overlay_folder}") logger.info(f"Creating Overlay Folder found at: {library.overlay_folder}")
@ -331,6 +346,8 @@ class CollectionBuilder:
logger.error(f"Overlay Error: failed to parse overlay blur name: {self.overlay} defaulting to blur(50)") logger.error(f"Overlay Error: failed to parse overlay blur name: {self.overlay} defaulting to blur(50)")
self.overlay = "blur(50)" self.overlay = "blur(50)"
else: else:
if "|" in self.overlay:
raise Failed(f"{self.Type} Error: Overlay Name: {self.overlay} cannot contain '|'")
if not self.overlay_path: if not self.overlay_path:
clean_name, _ = util.validate_filename(self.overlay) clean_name, _ = util.validate_filename(self.overlay)
self.overlay_path = os.path.join(library.overlay_folder, f"{clean_name}.png") self.overlay_path = os.path.join(library.overlay_folder, f"{clean_name}.png")
@ -2293,7 +2310,6 @@ class CollectionBuilder:
elif (not list(set(filter_data) & set(attrs)) and modifier == "") \ elif (not list(set(filter_data) & set(attrs)) and modifier == "") \
or (list(set(filter_data) & set(attrs)) and modifier == ".not"): or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
return False return False
logger.ghost(f"Filtering {display} {item.title}")
return True return True
def run_missing(self): def run_missing(self):

@ -80,6 +80,9 @@ class ConfigFile:
self.requested_libraries = util.get_list(attrs["libraries"]) if "libraries" in attrs else None self.requested_libraries = util.get_list(attrs["libraries"]) if "libraries" in attrs else None
self.requested_metadata_files = util.get_list(attrs["metadata_files"]) if "metadata_files" in attrs else None self.requested_metadata_files = util.get_list(attrs["metadata_files"]) if "metadata_files" in attrs else None
self.resume_from = attrs["resume"] if "resume" in attrs else None self.resume_from = attrs["resume"] if "resume" in attrs else None
self.collection_only = attrs["collection_only"] if "collection_only" in attrs else None
self.operations_only = attrs["operations_only"] if "operations_only" in attrs else None
self.overlays_only = attrs["overlays_only"] if "overlays_only" in attrs else None
current_time = datetime.now() current_time = datetime.now()
yaml.YAML().allow_duplicate_keys = True yaml.YAML().allow_duplicate_keys = True
@ -801,7 +804,7 @@ class ConfigFile:
logger.info("") logger.info("")
logger.separator("Scanning Metadata and Overlay Files", space=False, border=False) logger.separator("Scanning Metadata and Overlay Files", space=False, border=False)
library.scan_files() library.scan_files(self.operations_only, self.overlays_only, self.collection_only)
if not library.metadata_files and not library.library_operation and not self.playlist_files: if not library.metadata_files and not library.library_operation and not self.playlist_files:
logger.info("") logger.info("")
logger.error("Config Error: No valid metadata files, overlay files, playlist files, or library operations found") logger.error("Config Error: No valid metadata files, overlay files, playlist files, or library operations found")

@ -118,24 +118,26 @@ class Library(ABC):
logger.info("") logger.info("")
logger.info(output) logger.info(output)
def scan_files(self): def scan_files(self, operations_only, overlays_only, collection_only):
for file_type, metadata_file, temp_vars, asset_directory in self.metadata_path: if not operations_only and not overlays_only:
try: for file_type, metadata_file, temp_vars, asset_directory in self.metadata_path:
meta_obj = MetadataFile(self.config, self, file_type, metadata_file, temp_vars, asset_directory) try:
if meta_obj.collections: meta_obj = MetadataFile(self.config, self, file_type, metadata_file, temp_vars, asset_directory)
self.collections.extend([c for c in meta_obj.collections]) if meta_obj.collections:
if meta_obj.metadata: self.collections.extend([c for c in meta_obj.collections])
self.metadatas.extend([c for c in meta_obj.metadata]) if meta_obj.metadata:
self.metadata_files.append(meta_obj) self.metadatas.extend([c for c in meta_obj.metadata])
except Failed as e: self.metadata_files.append(meta_obj)
logger.error(e) except Failed as e:
for file_type, overlay_file, temp_vars, asset_directory in self.overlay_path: logger.error(e)
try: if not operations_only and not collection_only:
over_obj = OverlayFile(self.config, self, file_type, overlay_file, temp_vars, asset_directory) for file_type, overlay_file, temp_vars, asset_directory in self.overlay_path:
self.overlays.extend([o.lower() for o in over_obj.overlays]) try:
self.overlay_files.append(over_obj) over_obj = OverlayFile(self.config, self, file_type, overlay_file, temp_vars, asset_directory)
except Failed as e: self.overlays.extend([o.lower() for o in over_obj.overlays])
logger.error(e) self.overlay_files.append(over_obj)
except Failed as e:
logger.error(e)
def upload_images(self, item, poster=None, background=None): def upload_images(self, item, poster=None, background=None):
image = None image = None

@ -21,7 +21,7 @@ class Overlays:
logger.info("") logger.info("")
os.makedirs(self.library.overlay_backup, exist_ok=True) os.makedirs(self.library.overlay_backup, exist_ok=True)
old_overlays = [l for l in self.library.Plex.listFilterChoices("label") if str(l.title).lower().endswith(" overlay")] old_overlays = [la for la in self.library.Plex.listFilterChoices("label") if str(la.title).lower().endswith(" overlay")]
if old_overlays: if old_overlays:
logger.info("") logger.info("")
logger.separator(f"Removing Old Overlays for the {self.library.name} Library") logger.separator(f"Removing Old Overlays for the {self.library.name} Library")
@ -76,7 +76,7 @@ class Overlays:
if self.config.Cache: if self.config.Cache:
image, image_compare, overlay_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) overlay_compare = [] if overlay_compare is None else util.get_list(overlay_compare, split="|")
has_overlay = any([item_tag.tag.lower() == "overlay" for item_tag in item.labels]) has_overlay = any([item_tag.tag.lower() == "overlay" for item_tag in item.labels])
overlay_change = False if has_overlay else True overlay_change = False if has_overlay else True
@ -143,8 +143,12 @@ class Overlays:
new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num)) new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num))
for over_name in over_names: for over_name in over_names:
if not over_name.startswith("blur"): if not over_name.startswith("blur"):
new_poster = new_poster.resize(properties[over_name]["image"].size, Image.ANTIALIAS) if properties[over_name]["coordinates"]:
new_poster.paste(properties[over_name]["image"], (0, 0), properties[over_name]["image"]) new_poster = new_poster.resize((1920, 1080) if isinstance(item, Episode) else (1000, 1500), Image.ANTIALIAS)
new_poster.paste(properties[over_name]["image"], properties[over_name]["coordinates"], properties[over_name]["image"])
else:
new_poster = new_poster.resize(properties[over_name]["image"].size, Image.ANTIALIAS)
new_poster.paste(properties[over_name]["image"], (0, 0), properties[over_name]["image"])
new_poster.save(temp, "PNG") new_poster.save(temp, "PNG")
self.library.upload_poster(item, temp) self.library.upload_poster(item, temp)
self.library.edit_tags("label", item, add_tags=["Overlay"], do_print=False) self.library.edit_tags("label", item, add_tags=["Overlay"], do_print=False)
@ -155,11 +159,11 @@ class Overlays:
logger.stacktrace() logger.stacktrace()
raise Failed(f"{item_title[:60]:<60} | Overlay Error: {e}") raise Failed(f"{item_title[:60]:<60} | Overlay Error: {e}")
elif self.library.show_asset_not_needed: elif self.library.show_asset_not_needed:
logger.error(f"{item_title[:60]:<60} | Overlay Update Not Needed") logger.info(f"{item_title[:60]:<60} | Overlay Update Not Needed")
if self.config.Cache and poster_compare: if self.config.Cache and poster_compare:
self.config.Cache.update_image_map(item.ratingKey, f"{self.library.image_table_name}_overlays", self.config.Cache.update_image_map(item.ratingKey, f"{self.library.image_table_name}_overlays",
item.thumb, poster_compare, overlay=','.join(over_names)) item.thumb, poster_compare, overlay='|'.join(over_names))
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
logger.exorcise() logger.exorcise()
@ -192,7 +196,7 @@ class Overlays:
properties[builder.overlay] = { properties[builder.overlay] = {
"keys": [], "suppress": builder.suppress_overlays, "group": builder.overlay_group, "keys": [], "suppress": builder.suppress_overlays, "group": builder.overlay_group,
"weight": builder.overlay_weight, "path": builder.overlay_path, "updated": False, "weight": builder.overlay_weight, "path": builder.overlay_path, "updated": False,
"image": None "image": None, "coordinates": builder.overlay_coordinates,
} }
for method, value in builder.builders: for method, value in builder.builders:

@ -125,12 +125,12 @@ def add_dict_list(keys, value, dict_map):
else: else:
dict_map[key] = [value] dict_map[key] = [value]
def get_list(data, lower=False, upper=False, split=True, int_list=False): def get_list(data, lower=False, upper=False, split=",", int_list=False):
if data is None: return None if data is None: return None
elif isinstance(data, list): list_data = data elif isinstance(data, list): list_data = data
elif isinstance(data, dict): return [data] elif isinstance(data, dict): return [data]
elif split is False: list_data = [str(data)] elif split is False: list_data = [str(data)]
else: list_data = str(data).split(",") else: list_data = str(data).split(split)
if lower is True: return [str(d).strip().lower() for d in list_data] if lower is True: return [str(d).strip().lower() for d in list_data]
elif upper is True: return [str(d).strip().upper() for d in list_data] elif upper is True: return [str(d).strip().upper() for d in list_data]

@ -168,6 +168,9 @@ def start(attrs):
attrs["read_only"] = read_only_config attrs["read_only"] = read_only_config
attrs["version"] = version attrs["version"] = version
attrs["no_missing"] = no_missing attrs["no_missing"] = no_missing
attrs["collection_only"] = collection_only
attrs["operations_only"] = operations_only
attrs["overlays_only"] = overlays_only
logger.separator(debug=True) logger.separator(debug=True)
logger.debug(f"--config (PMM_CONFIG): {config_file}") logger.debug(f"--config (PMM_CONFIG): {config_file}")
logger.debug(f"--time (PMM_TIME): {times}") logger.debug(f"--time (PMM_TIME): {times}")

Loading…
Cancel
Save