diff --git a/VERSION b/VERSION index 73da5def..97f73ef8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.3-develop133 +1.17.3-develop138 diff --git a/defaults/overlays/status.yml b/defaults/overlays/status.yml index 1f93f921..33a1898c 100644 --- a/defaults/overlays/status.yml +++ b/defaults/overlays/status.yml @@ -40,7 +40,7 @@ templates: episode_air_date: <> plex_all: <> filters: - tmdb_status1: <> + tmdb_status: <> overlays: diff --git a/defaults/translations/fr.yml b/defaults/translations/fr.yml index 9ae1513c..7d9d99ea 100644 --- a/defaults/translations/fr.yml +++ b/defaults/translations/fr.yml @@ -24,7 +24,7 @@ variables: item: Article key_names: Action: Action - Actors: Acteurs et actrices + Actors: Acteurs et Actrices Adventure: Aventure Arabic: Arabes Audio Language: Langue audio @@ -38,7 +38,7 @@ key_names: Czech: Tchèques Danish: Danois Decade: Décennie - Directors: Directeurs et Directrices + Directors: Réalisateurs Drama: Dramatiques Dutch: Néerlandais Easter: Pâques diff --git a/docs/metadata/builders/plex.md b/docs/metadata/builders/plex.md index 444501b5..f3225c00 100644 --- a/docs/metadata/builders/plex.md +++ b/docs/metadata/builders/plex.md @@ -215,7 +215,9 @@ Tag search can take multiple values as a **list or a comma-separated string**. | `decade` | Uses the year tag to match the decade | ✅ | ❌ | ❌ | | `director` | Uses the director tags to match | ✅ | ❌ | ❌ | | `genre` | Uses the genre tags to match | ✅ | ✅ | ❌ | -| `label` | Uses the label tags to match | ✅ | ✅ | ❌ | +| `label` | Uses the label tags to match for top level collections | ✅ | ✅ | ❌ | +| `season_label` | Uses the label tags to match for season collections | ❌ | ✅ | ❌ | +| `episode_label` | Uses the label tags to match for episode collections | ❌ | ✅ | ❌ | | `network` | Uses the network tags to match
**Only works with the New Plex TV Agent** | ❌ | ✅ | ❌ | | `producer` | Uses the actor tags to match | ✅ | ❌ | ❌ | | `resolution` | Uses the resolution tags to match | ✅ | ✅ | ❌ | @@ -228,6 +230,7 @@ Tag search can take multiple values as a **list or a comma-separated string**. | `artist_country` | Uses the Artist's Country attribute to match | ❌ | ❌ | ✅ | | `artist_mood` | Uses the Artist's Mood attribute to match | ❌ | ❌ | ✅ | | `artist_style` | Uses the Artist's Style attribute to match | ❌ | ❌ | ✅ | +| `artist_label` | Uses the Artist's Label attribute to match | ❌ | ❌ | ✅ | | `album_genre` | Uses the Album's Genre attribute to match | ❌ | ❌ | ✅ | | `album_mood` | Uses the Album's Mood attribute to match | ❌ | ❌ | ✅ | | `album_style` | Uses the Album's Style attribute to match | ❌ | ❌ | ✅ | @@ -238,6 +241,7 @@ Tag search can take multiple values as a **list or a comma-separated string**. | `album_label` | Uses the Album's Label attribute to match | ❌ | ❌ | ✅ | | `track_mood` | Uses the Track's Mood attribute to match | ❌ | ❌ | ✅ | | `track_source` | Uses the Track's Style attribute to match | ❌ | ❌ | ✅ | +| `track_label` | Uses the Track's Label attribute to match | ❌ | ❌ | ✅ | ## Date Searches diff --git a/docs/metadata/builders/smart.md b/docs/metadata/builders/smart.md index 8ef82af6..db720a40 100644 --- a/docs/metadata/builders/smart.md +++ b/docs/metadata/builders/smart.md @@ -156,7 +156,9 @@ Tag filter can take multiple values as a **list or a comma-separated string**. | `decade` | Uses the year tag to match the decade | ✅ | ❌ | ❌ | | `director` | Uses the director tags to match | ✅ | ❌ | ❌ | | `genre` | Uses the genre tags to match | ✅ | ✅ | ❌ | -| `label` | Uses the label tags to match | ✅ | ✅ | ❌ | +| `label` | Uses the label tags to match for top level collections | ✅ | ✅ | ❌ | +| `season_label` | Uses the label tags to match for season collections | ❌ | ✅ | ❌ | +| `episode_label` | Uses the label tags to match for episode collections | ❌ | ✅ | ❌ | | `network` | Uses the network tags to match
**Only works with the New Plex TV Agent** | ❌ | ✅ | ❌ | | `producer` | Uses the actor tags to match | ✅ | ❌ | ❌ | | `resolution` | Uses the resolution tags to match | ✅ | ✅ | ❌ | @@ -169,6 +171,7 @@ Tag filter can take multiple values as a **list or a comma-separated string**. | `artist_country` | Uses the Artist's Country attribute to match | ❌ | ❌ | ✅ | | `artist_mood` | Uses the Artist's Mood attribute to match | ❌ | ❌ | ✅ | | `artist_style` | Uses the Artist's Style attribute to match | ❌ | ❌ | ✅ | +| `artist_label` | Uses the Artist's Label attribute to match | ❌ | ❌ | ✅ | | `album_genre` | Uses the Album's Genre attribute to match | ❌ | ❌ | ✅ | | `album_mood` | Uses the Album's Mood attribute to match | ❌ | ❌ | ✅ | | `album_style` | Uses the Album's Style attribute to match | ❌ | ❌ | ✅ | @@ -179,6 +182,7 @@ Tag filter can take multiple values as a **list or a comma-separated string**. | `album_label` | Uses the Album's Label attribute to match | ❌ | ❌ | ✅ | | `track_mood` | Uses the Track's Mood attribute to match | ❌ | ❌ | ✅ | | `track_source` | Uses the Track's Style attribute to match | ❌ | ❌ | ✅ | +| `track_label` | Uses the Track's Label attribute to match | ❌ | ❌ | ✅ | ## Date Filters diff --git a/modules/builder.py b/modules/builder.py index 6121e59e..91ecf84f 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1880,7 +1880,6 @@ class CollectionBuilder: def build_url_arg(arg, mod=None, arg_s=None, mod_s=None): arg_key = plex.search_translation[attr] if attr in plex.search_translation else attr - arg_key = f"{sort_type}.label" if arg_key == "label" and sort_type in ["season", "episode", "album", "track"] else arg_key arg_key = plex.show_translation[arg_key] if self.library.is_show and arg_key in plex.show_translation else arg_key if mod is None: mod = plex.modifier_translation[modifier] if modifier in plex.modifier_translation else modifier diff --git a/modules/config.py b/modules/config.py index a0020ca9..b63d7143 100644 --- a/modules/config.py +++ b/modules/config.py @@ -765,7 +765,10 @@ class ConfigFile: "add_blank_entries": True } if lib["operations"]["metadata_backup"] and isinstance(lib["operations"]["metadata_backup"], dict): - params["metadata_backup"]["path"] = check_for_attribute(lib["operations"]["metadata_backup"], "path", var_type="path", default=params["metadata_backup"]["path"], save=False) + try: + params["metadata_backup"]["path"] = check_for_attribute(lib["operations"]["metadata_backup"], "path", var_type="path", save=False) + except Failed as e: + logger.debug(f"{e} using default {params['metadata_backup']['path']}") params["metadata_backup"]["exclude"] = check_for_attribute(lib["operations"]["metadata_backup"], "exclude", var_type="comma_list", default_is_none=True, save=False) params["metadata_backup"]["sync_tags"] = check_for_attribute(lib["operations"]["metadata_backup"], "sync_tags", var_type="bool", default=False, save=False) params["metadata_backup"]["add_blank_entries"] = check_for_attribute(lib["operations"]["metadata_backup"], "add_blank_entries", var_type="bool", default=True, save=False) diff --git a/modules/meta.py b/modules/meta.py index 15a2bf9f..7702cbf2 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -262,12 +262,15 @@ class DataFile: language = variables["language"] if "language" in variables else "default" translation_variables = {k: v[language if language in v else "default"] for k, v in self.translations.items()} + key_name_variables = {} for var_key, var_value in self.key_names.items(): if var_key == "library_type" and language in var_value: variables[var_key] = var_value[language].lower() variables[f"{var_key}U"] = var_value[language] elif language in var_value: - translation_variables[var_key] = var_value[language] + key_name_variables[var_key] = var_value[language] + if "key_name" in variables and variables["key_name"] in key_name_variables: + variables["key_name"] = key_name_variables[variables["key_name"]] def replace_var(input_item, search_dicts): if not isinstance(search_dicts, list): diff --git a/modules/overlay.py b/modules/overlay.py index f60d8b72..b5f67fbe 100644 --- a/modules/overlay.py +++ b/modules/overlay.py @@ -1,13 +1,16 @@ import os, re, time from datetime import datetime -from PIL import Image, ImageColor, ImageDraw, ImageFont from modules import util from modules.util import Failed +from PIL import Image, ImageColor, ImageDraw, ImageFont +from plexapi.audio import Album +from plexapi.video import Episode logger = util.logger portrait_dim = (1000, 1500) landscape_dim = (1920, 1080) +square_dim = (1000, 1000) old_special_text = [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "0", "%", "#"]] float_vars = ["audience_rating", "critic_rating", "user_rating"] int_vars = ["runtime", "season_number", "episode_number", "episode_count", "versions"] @@ -106,6 +109,13 @@ def parse_cords(data, parent, required=False): return horizontal_align, horizontal_offset, vertical_align, vertical_offset +def get_canvas_size(item): + if isinstance(item, Episode): + return landscape_dim + elif isinstance(item, Album): + return square_dim + else: + return portrait_dim class Overlay: def __init__(self, config, library, original_mapping_name, overlay_data, suppress, level): @@ -122,6 +132,8 @@ class Overlay: self.landscape_box = None self.portrait = None self.portrait_box = None + self.square = None + self.square_box = None self.group = None self.queue = None self.weight = None @@ -326,9 +338,11 @@ class Overlay: box = self.image.size if self.image else None self.portrait, self.portrait_box = self.get_backdrop(portrait_dim, box=box, text=self.name[5:-1]) self.landscape, self.landscape_box = self.get_backdrop(landscape_dim, box=box, text=self.name[5:-1]) + self.square, self.square_box = self.get_backdrop(square_dim, box=box, text=self.name[5:-1]) elif self.name.startswith("backdrop"): self.portrait, self.portrait_box = self.get_backdrop(portrait_dim, box=self.back_box) self.landscape, self.landscape_box = self.get_backdrop(landscape_dim, box=self.back_box) + self.square, self.square_box = self.get_backdrop(square_dim, box=self.back_box) else: if not self.path: clean_name, _ = util.validate_filename(self.name) @@ -345,6 +359,7 @@ class Overlay: if self.has_coordinates(): self.portrait, self.portrait_box = self.get_backdrop(portrait_dim, box=self.image.size) self.landscape, self.landscape_box = self.get_backdrop(landscape_dim, box=self.image.size) + self.square, self.square_box = self.get_backdrop(square_dim, box=self.image.size) if self.config.Cache: self.config.Cache.update_image_map(self.mapping_name, f"{self.library.image_table_name}_overlays", self.mapping_name, overlay_size) except OSError: @@ -500,3 +515,11 @@ class Overlay: else: ha, ho, va, vo = new_cords return get_cord(ho, canvas_box[0], box[0], ha), get_cord(vo, canvas_box[1], box[1], va) + + def get_canvas(self, item): + if isinstance(item, Episode): + return self.landscape, self.landscape_box + elif isinstance(item, Album): + return self.square, self.square_box + else: + return self.portrait, self.portrait_box diff --git a/modules/overlays.py b/modules/overlays.py index b124ef29..ccdb987f 100644 --- a/modules/overlays.py +++ b/modules/overlays.py @@ -193,8 +193,7 @@ class Overlays: logger.error(f"{item_title[:60]:<60} | Overlay Error: No poster found") elif self.library.reapply_overlays or changed_image or overlay_change: try: - canvas_width = 1920 if isinstance(item, Episode) else 1000 - canvas_height = 1080 if isinstance(item, Episode) else 1500 + canvas_width, canvas_height = overlay.get_canvas_size(item) new_poster = Image.open(poster.location if poster else has_original) \ .convert("RGB").resize((canvas_width, canvas_height), Image.ANTIALIAS) @@ -296,20 +295,18 @@ class Overlays: continue new_poster.paste(overlay_image, (0, 0), overlay_image) else: - overlay_image = current_overlay.landscape if isinstance(item, Episode) else current_overlay.portrait - addon_box = current_overlay.landscape_box if isinstance(item, Episode) else current_overlay.portrait_box + overlay_image, addon_box = current_overlay.get_canvas(item) new_poster.paste(overlay_image, (0, 0), overlay_image) if current_overlay.image: new_poster.paste(current_overlay.image, addon_box, current_overlay.image) elif current_overlay.name == "backdrop": - overlay_image = current_overlay.landscape if isinstance(item, Episode) else current_overlay.portrait + overlay_image, _ = current_overlay.get_canvas(item) new_poster.paste(overlay_image, (0, 0), overlay_image) else: if current_overlay.has_coordinates(): + overlay_image, overlay_box = current_overlay.get_canvas(item) if current_overlay.portrait is not None: - overlay_image = current_overlay.landscape if isinstance(item, Episode) else current_overlay.portrait new_poster.paste(overlay_image, (0, 0), overlay_image) - overlay_box = current_overlay.landscape_box if isinstance(item, Episode) else current_overlay.portrait_box new_poster.paste(current_overlay.image, overlay_box, current_overlay.image) else: new_poster = new_poster.resize(current_overlay.image.size, Image.ANTIALIAS) diff --git a/modules/plex.py b/modules/plex.py index 8f3281d1..0c10636d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -51,6 +51,8 @@ search_translation = { "unplayed_episodes": "show.unwatchedLeaves", "season_collection": "season.collection", "episode_collection": "episode.collection", + "season_label": "season.label", + "episode_label": "episode.label", "artist_title": "artist.title", "artist_user_rating": "artist.userRating", "artist_genre": "artist.genre", @@ -61,6 +63,7 @@ search_translation = { "artist_added": "artist.addedAt", "artist_last_played": "artist.lastViewedAt", "artist_unmatched": "artist.unmatched", + "artist_label": "artist.label", "album_title": "album.title", "album_year": "album.year", "album_decade": "album.decade", @@ -90,7 +93,8 @@ search_translation = { "track_last_rated": "track.lastRatedAt", "track_added": "track.addedAt", "track_trash": "track.trash", - "track_source": "track.source" + "track_source": "track.source", + "track_label": "track.label" } show_translation = { "title": "show.title", @@ -254,6 +258,8 @@ show_only_searches = [ "network", "network.not", "season_collection", "season_collection.not", "episode_collection", "episode_collection.not", + "season_label", "season_label.not", + "episode_label", "episode_label.not", "episode_title", "episode_title.not", "episode_title.is", "episode_title.isnot", "episode_title.begins", "episode_title.ends", "episode_added", "episode_added.not", "episode_added.before", "episode_added.after", "episode_air_date", "episode_air_date.not", @@ -291,8 +297,8 @@ search_display = {"added": "Date Added", "release": "Release Date", "hdr": "HDR" tag_attributes = [ "actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label", "network", "producer", "resolution", "studio", "subtitle_language", "writer", "season_collection", "episode_collection", "edition", - "artist_genre", "artist_collection", "artist_country", "artist_mood", "artist_style", "album_genre", "album_mood", - "album_style", "album_format", "album_type", "album_collection", "album_source", "album_label", "track_mood", "track_source" + "artist_genre", "artist_collection", "artist_country", "artist_mood", "artist_label", "artist_style", "album_genre", "album_mood", + "album_style", "album_format", "album_type", "album_collection", "album_source", "album_label", "track_mood", "track_source", "track_label" ] tag_modifiers = ["", ".not", ".regex"] no_not_mods = ["resolution", "decade", "album_decade"]