diff --git a/VERSION b/VERSION index 9947d0ab..9c58b95a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.2-develop13 +1.17.2-develop14 diff --git a/docs/metadata/overlay.md b/docs/metadata/overlay.md index 0f378750..c88e0b9c 100644 --- a/docs/metadata/overlay.md +++ b/docs/metadata/overlay.md @@ -169,38 +169,38 @@ overlays: back_radius: 30 ``` -#### Special Text Overlays +#### Special Text Variables -You can use the item's metadata to determine the text. You set the `name` to `text(special_text)` and then use the `special_text` attribute to format the text. +You can use the item's metadata to determine the text by adding Special Text Variables to you text Overlay. There are multiple Special Text Variables that can be used when formatting the text. The variables are defined like so `<>` and some can have modifiers like so `<>` where `$` is the modifier. The available options are: -| Special Text Variables & Mods | Movies | Shows | Seasons | Episodes | -|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:| -| `<>`: audience rating (`8.7`, `9.0`)
`<>`: audience rating out of 100 (`87`, `90`)
`<>`: audience rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ❌ | ✅ | -| `<>`: critic rating (`8.7`, `9.0`)
`<>`: critic rating out of 100 (`87`, `90`)
`<>`: critic rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ❌ | ✅ | -| `<>`: user rating (`8.7`, `9.0`)
`<>`: user rating out of 100 (`87`, `90`)
`<>`: user rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ✅ | ✅ | -| `<>`: Title of the Item | ✅ | ✅ | ✅ | ✅ | -| `<<show_title>>`: Title of the Item's Show | ❌ | ❌ | ✅ | ✅ | -| `<<season_title>>`: Title of the Item's Season | ❌ | ❌ | ❌ | ✅ | -| `<<original_title>>`: Original Title of the Item | ✅ | ✅ | ❌ | ❌ | -| `<<content_rating>>`: Content Rating of the Item | ✅ | ✅ | ❌ | ✅ | -| `<<episode_count>>`: Number of Episodes (`1`)<br>`<<episode_countW>`: Number of Episodes As Words (`One`)<br>`<<episode_count0>`: Number of Episodes With 10s Padding (`01`)<br>`<<episode_count00>`: Number of Episodes With 100s Padding (`001`) | ❌ | ✅ | ✅ | ❌ | -| `<<season_number>>`: Season Number (`1`)<br>`<<season_numberW>`: Season Number As Words (`One`)<br>`<<season_number0>`: Season Number With 10s Padding (`01`)<br>`<<season_number00>`: Season Number With 100s Padding (`001`) | ❌ | ❌ | ✅ | ✅ | -| `<<episode_number>>`: Episode Number (`1`)<br>`<<episode_numberW>`: Episode Number As Words (`One`)<br>`<<episode_number0>`: Episode Number With 10s Padding (`01`)<br>`<<episode_number00>`: Episode Number With 100s Padding (`001`) | ❌ | ❌ | ❌ | ✅ | -| `<<runtime>>`: Runtime of the Item in minutes`<<runtimeH>>`: Hours in runtime of the Item<br>`<<runtimeM>>`: Minutes remaining in the hour in the runtime of the Item | ✅ | ❌ | ❌ | ✅ | -| `<<originally_available>>`: Original Available Date of the Item`<<originally_available[FORMAT]>>`: Original Available Date of the Item in the given format. [Format Options](https://strftime.org/) | ✅ | ✅ | ❌ | ✅ | +| Special Text Variables & Mods | Movies | Shows | Seasons | Episodes | +|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:| +| `<<audience_rating>>`: audience rating (`8.7`, `9.0`)<br>`<<audience_rating%>>`: audience rating out of 100 (`87`, `90`)<br>`<<audience_rating#>>`: audience rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ❌ | ✅ | +| `<<critic_rating>>`: critic rating (`8.7`, `9.0`)<br>`<<critic_rating%>>`: critic rating out of 100 (`87`, `90`)<br>`<<critic_rating#>>`: critic rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ❌ | ✅ | +| `<<user_rating>>`: user rating (`8.7`, `9.0`)<br>`<<user_rating%>>`: user rating out of 100 (`87`, `90`)<br>`<<user_rating#>>`: user rating removing `.0` as needed (`8.7`, `9`) | ✅ | ✅ | ✅ | ✅ | +| `<<title>>`: Title of the Item | ✅ | ✅ | ✅ | ✅ | +| `<<show_title>>`: Title of the Item's Show | ❌ | ❌ | ✅ | ✅ | +| `<<season_title>>`: Title of the Item's Season | ❌ | ❌ | ❌ | ✅ | +| `<<original_title>>`: Original Title of the Item | ✅ | ✅ | ❌ | ❌ | +| `<<content_rating>>`: Content Rating of the Item | ✅ | ✅ | ❌ | ✅ | +| `<<episode_count>>`: Number of Episodes (`1`)<br>`<<episode_countW>>`: Number of Episodes As Words (`One`)<br>`<<episode_count0>>`: Number of Episodes With 10s Padding (`01`)<br>`<<episode_count00>>`: Number of Episodes With 100s Padding (`001`) | ❌ | ✅ | ✅ | ❌ | +| `<<season_number>>`: Season Number (`1`)<br>`<<season_numberW>>`: Season Number As Words (`One`)<br>`<<season_number0>>`: Season Number With 10s Padding (`01`)<br>`<<season_number00>>`: Season Number With 100s Padding (`001`) | ❌ | ❌ | ✅ | ✅ | +| `<<episode_number>>`: Episode Number (`1`)<br>`<<episode_numberW>>`: Episode Number As Words (`One`)<br>`<<episode_number0>>`: Episode Number With 10s Padding (`01`)<br>`<<episode_number00>>`: Episode Number With 100s Padding (`001`) | ❌ | ❌ | ❌ | ✅ | +| `<<runtime>>`: Complete Runtime of the Item in minutes (`150`)<br>`<<runtimeH>>`: Hours in runtime of the Item (`2`)<br>`<<runtimeM>>`: Minutes remaining in the hour in the runtime of the Item (`30`) | ✅ | ❌ | ❌ | ✅ | +| `<<originally_available>>`: Original Available Date of the Item<br>`<<originally_available[FORMAT]>>`: Original Available Date of the Item in the given format. [Format Options](https://strftime.org/) | ✅ | ✅ | ❌ | ✅ | Note: You can use the `mass_audience_rating_update` or `mass_critic_rating_update` [Library Operation](../config/operations) to update your plex ratings to various services like `tmdb`, `imdb`, `mdb`, `metacritic`, `letterboxd` and many more. ##### Example + I want to have the audience_rating display with a `%` out of 100 vs 0.0-10.0. ```yaml overlays: audience_rating: overlay: - name: text(special_text) - special_text: <<audience_rating%>>% + name: text(<<audience_rating%>>%) horizontal_offset: 225 horizontal_align: center vertical_offset: 15 @@ -214,6 +214,39 @@ overlays: back_height: 105 ``` +I want to add `S##E##` to all my episode images. +```yaml +overlays: + text_content_rating: + builder_level: episode + overlay: + name: text(S<<season_number0>>E<<episode_number0>>) + horizontal_align: center + vertical_offset: 15 + vertical_align: top + font: fonts/Inter-Medium.ttf + font_size: 63 + font_color: "#FFFFFF" + back_color: "#00000099" + back_radius: 30 + back_width: 300 + back_height: 105 + plex_all: true +``` + +##### Common Special Text Uses +These are some commonly-used examples of Special Text overlays: + +| Special Text | Example Output | +|:------------------------------------------------------------------|--------------------| +| `name: text(S<<season_number0>>E<<episode_number0>>)` | S01E01 | +| `name: text(Season <<season_number>> Episode <<episode_number>>)` | Season 1 Episode 1 | +| `name: text(Season <<season_number>>)` | Season 1 | +| `name: text(Episode <<episode_number>>)` | Episode 1 | +| `name: text(Runtime: <<runtime>>m)` | Runtime: 90m | +| `name: text(Runtime: <<runtimeH>>h <<runtimeM>>m)` | Runtime: 1h 30m | + + #### Text Addon Images You can add an image to accompany the text by specifying the image location using `file`, `url`, `git`, or `repo`. diff --git a/modules/overlay.py b/modules/overlay.py index ef4b055e..2ae7bcc9 100644 --- a/modules/overlay.py +++ b/modules/overlay.py @@ -284,20 +284,14 @@ class Overlay: if text in old_special_text2: text_mod = text[-1] if text[-1] in ["0", "%", "#"] else None text = text if text_mod is None else text[:-1] - self.special_text = f"<<{text}#>>" if text_mod == "#" else f"<<{text}%>>{'' if text_mod == '0' else '%'}" - self.name = "text(special_text)" - elif self.name == "text(special_text)": - if "special_text" not in self.data or not self.data["special_text"]: - raise Failed("Overlay Error: text(special_text) requires the special_text attribute") - if "<<originally_available[" in self.data["special_text"]: - match = re.search("<<originally_available\\[(.+)]>>", self.data["special_text"]) - if match: - try: - datetime.now().strftime(match.group(1)) - except ValueError: - raise Failed("Overlay Error: originally_available date format not valid") - self.special_text = self.data["special_text"] - + self.name = f"text(<<{text}#>>)" if text_mod == "#" else f"text(<<{text}%>>{'' if text_mod == '0' else '%'})" + if "<<originally_available[" in text: + match = re.search("<<originally_available\\[(.+)]>>", text) + if match: + try: + datetime.now().strftime(match.group(1)) + except ValueError: + raise Failed("Overlay Error: originally_available date format not valid") else: 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]) @@ -433,7 +427,7 @@ class Overlay: output += f"{self.back_box[0]}{self.back_box[1]}{self.back_align}" if self.addon_position is not None: output += f"{self.addon_position}{self.addon_offset}" - for value in [self.font_color, self.back_color, self.back_radius, self.back_padding, self.back_line_color, self.back_line_width, self.special_text]: + for value in [self.font_color, self.back_color, self.back_radius, self.back_padding, self.back_line_color, self.back_line_width]: if value is not None: output += f"{value}" return output diff --git a/modules/overlays.py b/modules/overlays.py index d8e6445d..920a4719 100644 --- a/modules/overlays.py +++ b/modules/overlays.py @@ -120,9 +120,9 @@ class Overlays: if self.config.Cache: for over_name in over_names: current_overlay = properties[over_name] - if current_overlay.name == "text(special_text)": + if current_overlay.name.startswith("text"): for cache_key, cache_value in self.config.Cache.query_overlay_special_text(item.ratingKey).items(): - actual = plex.attribute_translation[cache_key] if cache_key in plex.attribute_translation[cache_key] else cache_key + actual = plex.attribute_translation[cache_key] if cache_key in plex.attribute_translation else cache_key if cache_value is None or not hasattr(item, actual) or getattr(item, actual) is None: continue if cache_key in overlay.float_vars: @@ -203,65 +203,63 @@ class Overlays: def get_text(text_overlay): full_text = text_overlay.name[5:-1] - if full_text == "special_text": - full_text = text_overlay.special_text - for format_var in overlay.vars_by_type[text_overlay.level]: - if f"<<{format_var}" in full_text and format_var == "originally_available[": - mod = re.search("<<originally_available\\[(.+)]>>", full_text).group(1) - format_var = "originally_available" - elif f"<<{format_var}>>" in full_text and format_var.endswith("00"): - mod = "00" - format_var = format_var[:-2] - elif f"<<{format_var}>>" in full_text and format_var.endswith(("%", "#", "H", "M", "0")): - mod = format_var[-1] - format_var = format_var[:-1] - elif f"<<{format_var}>>" in full_text: - mod = "" - else: - continue - if format_var == "show_title": - actual_attr = "parentTitle" if text_overlay.level == "season" else "grandparentTitle" - elif format_var in plex.attribute_translation: - actual_attr = plex.attribute_translation[format_var] - else: - actual_attr = format_var - if not hasattr(item, actual_attr) or getattr(item, actual_attr) is None: - logger.warning(f"Overlay Warning: No {full_text} found") - continue - actual_value = getattr(item, actual_attr) - if self.config.Cache: - cache_store = actual_value.strftime("%Y-%m-%d") if format_var in overlay.date_vars else actual_value - self.config.Cache.update_overlay_special_text(item.ratingKey, format_var, cache_store) - sub_value = None - if format_var == "originally_available": - if mod: - sub_value = "<<originally_available\\[(.+)]>>" - final_value = actual_value.strftime(mod) - else: - final_value = actual_value.strftime("%Y-%m-%d") - elif format_var == "runtime": - if mod == "H": - final_value = (actual_value / 60000) // 60 - elif mod == "M": - final_value = (actual_value / 60000) % 60 - else: - final_value = actual_value / 60000 - elif mod == "%": - final_value = int(actual_value * 10) - elif mod == "#": - final_value = str(actual_value)[:-2] if str(actual_value).endswith(".0") else actual_value - elif mod == "W": - final_value = num2words(int(actual_value)) - elif mod == "0": - final_value = f"{int(actual_value):02}" - elif mod == "00": - final_value = f"{int(actual_value):03}" + for format_var in overlay.vars_by_type[text_overlay.level]: + if f"<<{format_var}" in full_text and format_var == "originally_available[": + mod = re.search("<<originally_available\\[(.+)]>>", full_text).group(1) + format_var = "originally_available" + elif f"<<{format_var}>>" in full_text and format_var.endswith("00"): + mod = "00" + format_var = format_var[:-2] + elif f"<<{format_var}>>" in full_text and format_var.endswith(("%", "#", "H", "M", "0")): + mod = format_var[-1] + format_var = format_var[:-1] + elif f"<<{format_var}>>" in full_text: + mod = "" + else: + continue + if format_var == "show_title": + actual_attr = "parentTitle" if text_overlay.level == "season" else "grandparentTitle" + elif format_var in plex.attribute_translation: + actual_attr = plex.attribute_translation[format_var] + else: + actual_attr = format_var + if not hasattr(item, actual_attr) or getattr(item, actual_attr) is None: + logger.warning(f"Overlay Warning: No {full_text} found") + continue + actual_value = getattr(item, actual_attr) + if self.config.Cache: + cache_store = actual_value.strftime("%Y-%m-%d") if format_var in overlay.date_vars else actual_value + self.config.Cache.update_overlay_special_text(item.ratingKey, format_var, cache_store) + sub_value = None + if format_var == "originally_available": + if mod: + sub_value = "<<originally_available\\[(.+)]>>" + final_value = actual_value.strftime(mod) else: - final_value = actual_value - if sub_value: - full_text = re.sub(sub_value, str(final_value), full_text) + final_value = actual_value.strftime("%Y-%m-%d") + elif format_var == "runtime": + if mod == "H": + final_value = int((actual_value / 60000) // 60) + elif mod == "M": + final_value = int((actual_value / 60000) % 60) else: - full_text = full_text.replace(f"<<{format_var}{mod}>>", str(final_value)) + final_value = int(actual_value / 60000) + elif mod == "%": + final_value = int(actual_value * 10) + elif mod == "#": + final_value = str(actual_value)[:-2] if str(actual_value).endswith(".0") else actual_value + elif mod == "W": + final_value = num2words(int(actual_value)) + elif mod == "0": + final_value = f"{int(actual_value):02}" + elif mod == "00": + final_value = f"{int(actual_value):03}" + else: + final_value = actual_value + if sub_value: + full_text = re.sub(sub_value, str(final_value), full_text) + else: + full_text = full_text.replace(f"<<{format_var}{mod}>>", str(final_value)) return str(full_text) for over_name in applied_names: