[11] update to special_text overlays

This commit is contained in:
meisnate12 2022-07-28 00:51:01 -04:00
parent f44589e853
commit c0a372382e
7 changed files with 173 additions and 147 deletions

View file

@ -1 +1 @@
1.17.2-develop10 1.17.2-develop11

View file

@ -93,7 +93,7 @@ There are many attributes available when using overlays to edit how they work.
| `back_radius` | Backdrop Radius for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; | | `back_radius` | Backdrop Radius for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; |
| `back_line_color` | Backdrop Line Color for the Text Overlay.<br>**Value:** Color Hex Code in format `#RGB`, `#RGBA`, `#RRGGBB` or `#RRGGBBAA`. | &#10060; | | `back_line_color` | Backdrop Line Color for the Text Overlay.<br>**Value:** Color Hex Code in format `#RGB`, `#RGBA`, `#RRGGBB` or `#RRGGBBAA`. | &#10060; |
| `back_line_width` | Backdrop Line Width for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; | | `back_line_width` | Backdrop Line Width for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; |
| `text_format` | Text Format for Special Text Overlays.<br>**`text_format` Only works with text overlays**<br>**Value:** Integer 0 or greater | &#10060; | | `special_text` | Text Format for Special Text Overlays.<br>**`special_text` Only works with text overlays** | &#10060; |
| `addon_offset` | Text Addon Image Offset from the text.<br>**`addon_offset` Only works with text overlays**<br>**Value:** Integer 0 or greater | &#10060; | | `addon_offset` | Text Addon Image Offset from the text.<br>**`addon_offset` Only works with text overlays**<br>**Value:** Integer 0 or greater | &#10060; |
| `addon_position` | Text Addon Image Alignment in relation to the text.<br>**`addon_position` Only works with text overlays**<br>**Values:** `left`, `right`, `top`, `bottom` | &#10060; | | `addon_position` | Text Addon Image Alignment in relation to the text.<br>**`addon_position` Only works with text overlays**<br>**Values:** `left`, `right`, `top`, `bottom` | &#10060; |
@ -171,26 +171,26 @@ overlays:
#### Special Text Overlays #### Special Text Overlays
You can use the item's metadata to determine the text. 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.
The final text can be formatted using the `text_format` attribute and the format variables. There are multiple Special Text Variables that can be used when formatting the text. The variables are defined like so `<<name>>` and some can have modifiers like so `<<name$>>` where `$` is the modifier. The available options are:
The available options are: | Special Text Variables | Format Variables & Mods | Movies | Shows | Seasons | Episodes |
|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:|
| audience_rating | `<<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`) | &#9989; | &#9989; | &#10060; | &#9989; |
| critic_rating | `<<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`) | &#9989; | &#9989; | &#10060; | &#9989; |
| user_rating | `<<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`) | &#9989; | &#9989; | &#9989; | &#9989; |
| title | `<<title>>`: Title of the Item | &#9989; | &#9989; | &#9989; | &#9989; |
| show_title | `<<show_title>>`: Title of the Item's Show | &#10060; | &#10060; | &#9989; | &#9989; |
| season_title | `<<season_title>>`: Title of the Item's Season | &#10060; | &#10060; | &#10060; | &#9989; |
| original_title | `<<original_title>>`: Original Title of the Item | &#9989; | &#9989; | &#10060; | &#10060; |
| content_rating | `<<content_rating>>`: Content Rating of the Item | &#9989; | &#9989; | &#10060; | &#9989; |
| episode_count | `<<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`) | &#10060; | &#9989; | &#9989; | &#10060; |
| season_number | `<<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`) | &#10060; | &#10060; | &#9989; | &#9989; |
| episode_number | `<<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`) | &#10060; | &#10060; | &#10060; | &#9989; |
| runtime | `<<runtime>>`: Runtime of the Item in minutes<br>`<<runtimeH>>`: Hours in runtime of the Item<br>`<<runtimeM>>`: Minutes remaining in the hour in the runtime of the Item | &#9989; | &#10060; | &#10060; | &#9989; |
| originally_available | `<<originally_available>>`: Original Available Date of the Item<br>`<<originally_available[DATE_FORMAT_STRING]>>`: Original Available Date of the Item in the given format. [Format Options](https://strftime.org/) | &#9989; | &#9989; | &#10060; | &#9989; |
| Attribute | Notes | Format Variables |
|:---------------------------|:-------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| text(audience_rating) | Doesnt work with Seasons | `<<value>>` -> ratings (`8.7`, `9.0`)<br>`<<value%>>` -> rating out of 100 (`87`, `90`)<br>`<<value#>>` -> rating removing `.0` as needed (`8.7`, `9`) |
| text(critic_rating) | Doesnt work with Seasons | `<<value>>` -> ratings (`8.7`, `9.0`)<br>`<<value%>>` -> rating out of 100 (`87`, `90`)<br>`<<value#>>` -> rating removing `.0` as needed (`8.7`, `9`) |
| text(user_rating) | | `<<value>>` -> ratings (`8.7`, `9.0`)<br>`<<value%>>` -> rating out of 100 (`87`, `90`)<br>`<<value#>>` -> rating removing `.0` as needed (`8.7`, `9`) |
| text(title) | | `<<value>>` -> Title of the Item |
| text(show_title) | Doesnt work with Movies and Shows | `<<value>>` -> Title of the Item's Show |
| text(season_title) | Only works with Episodes | `<<value>>` -> Title of the Item's Season |
| text(original_title) | Only works with Movies and Shows | `<<value>>` -> Original Title of the Item |
| text(episode_count) | Only works with Shows and Seasons | `<<value>>` -> Number of Episodes in the Show or Season |
| text(content_rating) | Doesnt work with Seasons | `<<value>>` -> Content Rating of the Item |
| text(season_episode) | Only works with Seasons and Episodes | `<<season>>` -> Season Number<br>`<<season0>` -> Season Number With 10s Padding<br>`<<season00>>` -> Season Number With 100s Padding<br>`<<episode>>` -> Episode Number<br>`<<episode0>` -> Episode Number With 10s Padding<br>`<<episode00>>` -> Episode Number With 100s Padding |
| text(runtime) | Doesnt work with Shows and Seasons | `<<value>>` -> Runtime of the Item in minutes<br>`<<valueH>>` -> Hours in runtime of the Item<br>`<<valueM>>` -> Minutes remaining in the hour in the runtime of the Item |
| text(originally_available) | Doesnt work with Seasons | `<<value>>` -> Original Available Date of the Item<br>`<<value[DATE_FORMAT_STRING]>>` -> 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. 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.
@ -200,8 +200,8 @@ I want to have the audience_rating display with a `%` out of 100 vs 0.0-10.0.
overlays: overlays:
audience_rating: audience_rating:
overlay: overlay:
name: text(audience_rating) name: text(special_text)
text_format: <<value%>>% special_text: <<audience_rating%>>%
horizontal_offset: 225 horizontal_offset: 225
horizontal_align: center horizontal_align: center
vertical_offset: 15 vertical_offset: 15

View file

@ -260,7 +260,6 @@ class CollectionBuilder:
if level and not self.library.is_movie and not self.playlist: if level and not self.library.is_movie and not self.playlist:
logger.debug("") logger.debug("")
logger.debug("Validating Method: builder_level") logger.debug("Validating Method: builder_level")
level = self.data[methods["builder_level"]]
if level is None: if level is None:
logger.error(f"{self.Type} Error: builder_level attribute is blank") logger.error(f"{self.Type} Error: builder_level attribute is blank")
else: else:
@ -1670,6 +1669,20 @@ class CollectionBuilder:
item = self.fetch_item(rk) item = self.fetch_item(rk)
if self.playlist and isinstance(item, (Show, Season)): if self.playlist and isinstance(item, (Show, Season)):
items.extend(item.episodes()) items.extend(item.episodes())
elif self.builder_level == "movie" and not isinstance(item, Movie):
logger.info(f"Item: {item} is not an Movie")
elif self.builder_level == "show" and not isinstance(item, Show):
logger.info(f"Item: {item} is not an Show")
elif self.builder_level == "episode" and not isinstance(item, Episode):
logger.info(f"Item: {item} is not an Episode")
elif self.builder_level == "season" and not isinstance(item, Season):
logger.info(f"Item: {item} is not a Season")
elif self.builder_level == "artist" and not isinstance(item, Artist):
logger.info(f"Item: {item} is not an Artist")
elif self.builder_level == "album" and not isinstance(item, Album):
logger.info(f"Item: {item} is not an Album")
elif self.builder_level == "track" and not isinstance(item, Track):
logger.info(f"Item: {item} is not a Track")
else: else:
items.append(item) items.append(item)
except Failed as e: except Failed as e:

View file

@ -867,16 +867,16 @@ class Cache:
[(r.name, r.date.strftime("%Y-%m-%d") if r.date else None, [(r.name, r.date.strftime("%Y-%m-%d") if r.date else None,
expiration_date.strftime("%Y-%m-%d"), r.season, r.round) for r in races]) expiration_date.strftime("%Y-%m-%d"), r.season, r.round) for r in races])
def query_overlay_special_text(self, rating_key, data_type): def query_overlay_special_text(self, rating_key):
rating = None attrs = {}
with sqlite3.connect(self.cache_path) as connection: with sqlite3.connect(self.cache_path) as connection:
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
cursor.execute("SELECT * FROM overlay_special_text WHERE rating_key = ? AND type = ?", (rating_key, data_type)) cursor.execute("SELECT * FROM overlay_special_text WHERE rating_key = ?", (rating_key, ))
row = cursor.fetchone() for row in cursor.fetchall():
if row: if row:
rating = row["text"] attrs[row["type"]] = row["text"]
return rating return attrs
def update_overlay_special_text(self, rating_key, data_type, text): def update_overlay_special_text(self, rating_key, data_type, text):
with sqlite3.connect(self.cache_path) as connection: with sqlite3.connect(self.cache_path) as connection:

View file

@ -8,12 +8,43 @@ logger = util.logger
portrait_dim = (1000, 1500) portrait_dim = (1000, 1500)
landscape_dim = (1920, 1080) landscape_dim = (1920, 1080)
rating_special_text = [f"text({a})" for a in ["audience_rating", "critic_rating", "user_rating"]] old_special_text2 = [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "0", "%", "#"]]
value_overlays = ["title", "show_title", "season_title", "original_title", "episode_count", "content_rating"] float_vars = ["audience_rating", "critic_rating", "user_rating"]
special_overlays = ["season_episode", "runtime", "originally_available"] int_vars = ["runtime", "season_number", "episode_number", "episode_count"]
special_text_overlays = [f"text({a})" for a in value_overlays + special_overlays] + rating_special_text date_vars = ["originally_available"]
old_special_text = [f"text({a}{s})" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["0", "%", "#"]] types_for_var = {
all_special_text = special_text_overlays + old_special_text "movie_show_season_episode_artist_album": ["user_rating", "title"],
"movie_show_episode_album": ["critic_rating", "originally_available"],
"movie_show_episode": ["audience_rating", "content_rating"],
"movie_show": ["original_title"],
"movie_episode": ["runtime"],
"season_episode": ["show_title", "season_number"],
"show_season": ["episode_count"],
"episode": ["season_title", "episode_number"]
}
var_mods = {
"title": [""],
"content_rating": [""],
"original_title": [""],
"show_title": [""],
"season_title": [""],
"user_rating": ["", "%", "#"],
"critic_rating": ["", "%", "#"],
"audience_rating": ["", "%", "#"],
"originally_available": ["", "["],
"runtime": ["", "H", "M"],
"season_number": ["", "W", "0", "00"],
"episode_number": ["", "W", "0", "00"],
"episode_count": ["", "W", "0", "00"],
}
vars_by_type = {
"movie": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "movie" in check],
"show": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "show" in check],
"season": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "season" in check],
"episode": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "episode" in check],
"artist": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "artist" in check],
"album": [f"{item}{m}" for check, sub in types_for_var.items() for item in sub for m in var_mods[item] if "album" in check],
}
def parse_cords(data, parent, required=False): def parse_cords(data, parent, required=False):
horizontal_align = util.parse("Overlay", "horizontal_align", data["horizontal_align"], parent=parent, horizontal_align = util.parse("Overlay", "horizontal_align", data["horizontal_align"], parent=parent,
@ -95,7 +126,7 @@ class Overlay:
self.font_color = None self.font_color = None
self.addon_offset = 0 self.addon_offset = 0
self.addon_position = None self.addon_position = None
self.text_overlay_format = None self.special_text = None
logger.debug("") logger.debug("")
logger.debug("Validating Method: overlay") logger.debug("Validating Method: overlay")
@ -223,6 +254,7 @@ class Overlay:
if not match: if not match:
raise Failed(f"Overlay Error: failed to parse overlay text name: {self.name}") raise Failed(f"Overlay Error: failed to parse overlay text name: {self.name}")
self.name = f"text({match.group(1)})" self.name = f"text({match.group(1)})"
text = f"{match.group(1)}"
self.font_name = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fonts", "Roboto-Medium.ttf") self.font_name = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fonts", "Roboto-Medium.ttf")
if "font_size" in self.data: if "font_size" in self.data:
self.font_size = util.parse("Overlay", "font_size", self.data["font_size"], datatype="int", parent="overlay", default=self.font_size) self.font_size = util.parse("Overlay", "font_size", self.data["font_size"], datatype="int", parent="overlay", default=self.font_size)
@ -249,52 +281,23 @@ class Overlay:
self.font_color = ImageColor.getcolor(self.data["font_color"], "RGBA") self.font_color = ImageColor.getcolor(self.data["font_color"], "RGBA")
except ValueError: except ValueError:
raise Failed(f"Overlay Error: overlay font_color: {self.data['font_color']} invalid") raise Failed(f"Overlay Error: overlay font_color: {self.data['font_color']} invalid")
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"]
if self.name in all_special_text:
if self.name.startswith("text(critic") and self.level == "season":
raise Failed("Overlay Error: builder_level season doesn't have critic_ratings")
elif self.name.startswith("text(audience") and self.level == "season":
raise Failed("Overlay Error: builder_level season doesn't have audience_ratings")
elif self.name in ["text(season_episode)", "text(show_title)"] and self.level not in ["season", "episode"]:
raise Failed(f"Overlay Error: {self.name[5:-1]} only works with builder_level season and episode")
elif self.name == "text(runtime)" and self.level not in ["movie", "episode"]:
raise Failed("Overlay Error: runtime only works with movies and builder_level: episode")
elif self.name == "text(season_title)" and self.level != "episode":
raise Failed("Overlay Error: season_title only works with builder_level: episode")
elif self.name == "text(original_title)" and self.level not in ["movie", "show"]:
raise Failed("Overlay Error: original_title only works with movies and shows")
elif self.name == "text(episode_count)" and self.level not in ["show", "season"]:
raise Failed("Overlay Error: episode_count only works with shows and builder_level: season")
elif self.name == ["text(content_rating)", "text(originally_available)"] and self.level == "season":
raise Failed(f"Overlay Error: {self.name[5:-1]} only works with movies, shows, and builder_level: episode")
elif self.name in old_special_text:
self.text_overlay_format = "<<value#>>" if self.name[-2] == "#" else f"<<value%>>{'' if self.name[-2] == '0' else '%'}"
self.name = f"{self.name[:-2]})"
elif "text_format" in self.data and self.data["text_format"]:
if self.name in rating_special_text and not any((f"<<value{m}>>" in self.data["text_format"] for m in ["", "#", "%"])):
raise Failed("Overlay Error: text_format must have the value variable")
elif self.name == "text(season_episode)" and self.level == "season" and not any((f"<<season{m}>>" in self.data["text_format"] for m in ["", "W", "0", "00"])):
raise Failed("Overlay Error: text_format must have the season variable")
elif self.name == "text(season_episode)" and self.level == "episode" and not any((f"<<{a}{m}>>" in self.data["text_format"] for a in ["season", "episode"] for m in ["", "W", "0", "00"])):
raise Failed("Overlay Error: text_format must have the season or episode variable")
elif self.name == "text(runtime)" and not any((f"<<value{m}>>" in self.data["text_format"] for m in ["", "M", "H"])):
raise Failed("Overlay Error: text_format must have the value variable")
elif self.name == "text(originally_available)":
match = re.search("<<value\\[(.+)]>>", self.data["text_format"])
if not match and "<<value>>" not in self.data["text_format"]:
raise Failed("Overlay Error: text_format must have the value variable")
if match:
try:
datetime.now().strftime(match.group(1))
except ValueError:
raise Failed("Overlay Error: text_format date format not valid")
elif self.name[5:-1] in value_overlays and "<<value>>" not in self.data["text_format"]:
raise Failed("Overlay Error: text_format must have the value variable")
self.text_overlay_format = self.data["text_format"]
elif self.name == "text(season_episode)":
self.text_overlay_format = "S<<season0>>" if self.level == "season" else "S<<season0>>E<<episode0>>"
else:
self.text_overlay_format = "<<value>>"
else: else:
box = self.image.size if self.image else None 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.portrait, self.portrait_box = self.get_backdrop(portrait_dim, box=box, text=self.name[5:-1])
@ -430,7 +433,7 @@ class Overlay:
output += f"{self.back_box[0]}{self.back_box[1]}{self.back_align}" output += f"{self.back_box[0]}{self.back_box[1]}{self.back_align}"
if self.addon_position is not None: if self.addon_position is not None:
output += f"{self.addon_position}{self.addon_offset}" 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.text_overlay_format]: 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]:
if value is not None: if value is not None:
output += f"{value}" output += f"{value}"
return output return output

View file

@ -120,17 +120,21 @@ class Overlays:
if self.config.Cache: if self.config.Cache:
for over_name in over_names: for over_name in over_names:
current_overlay = properties[over_name] current_overlay = properties[over_name]
if current_overlay.name in overlay.special_text_overlays: if current_overlay.name == "text(special_text)":
data_type = current_overlay.name[5:-1] for cache_key, cache_value in self.config.Cache.query_overlay_special_text(item.ratingKey).items():
actual = plex.attribute_translation[data_type] if data_type in plex.attribute_translation[data_type] else data_type actual = plex.attribute_translation[cache_key] if cache_key in plex.attribute_translation[cache_key] else cache_key
cache_value = self.config.Cache.query_overlay_special_text(item.ratingKey, data_type) if cache_value is None or not hasattr(item, actual) or getattr(item, actual) is None:
if cache_value is None or not hasattr(item, actual) or getattr(item, actual) is None: continue
continue if cache_key in overlay.float_vars:
if current_overlay.name in overlay.rating_special_text: cache_value = float(cache_value)
cache_value = float(cache_value) if cache_key in overlay.int_vars:
if getattr(item, actual) != cache_value: cache_value = int(cache_value)
overlay_change = True
if cache_key in overlay.date_vars:
if getattr(item, actual).strftime("%Y-%m-%d") != cache_value:
overlay_change = True
elif getattr(item, actual) != cache_value:
overlay_change = True
try: try:
poster, background, item_dir, name = self.library.find_item_assets(item) poster, background, item_dir, name = self.library.find_item_assets(item)
if not poster and self.library.assets_for_all: if not poster and self.library.assets_for_all:
@ -199,64 +203,71 @@ class Overlays:
def get_text(text_overlay): def get_text(text_overlay):
full_text = text_overlay.name[5:-1] full_text = text_overlay.name[5:-1]
if text_overlay.name in overlay.special_text_overlays: if full_text == "special_text":
if full_text == "season_episode" and text_overlay.level == "season": full_text = text_overlay.special_text
actual_attr = "seasonNumber" for format_var in overlay.vars_by_type[text_overlay.level]:
elif full_text == "show_title": if f"<<{format_var}" in full_text and format_var == "originally_available[":
actual_attr = "parentTitle" if text_overlay.level == "season" else "grandparentTitle" mod = re.search("<<originally_available\\[(.+)]>>", full_text).group(1)
elif full_text in plex.attribute_translation: format_var = "originally_available"
actual_attr = plex.attribute_translation[full_text] elif f"<<{format_var}>>" in full_text and format_var.endswith("00"):
else: mod = "00"
actual_attr = full_text format_var = format_var[:-2]
if not hasattr(item, actual_attr) or getattr(item, actual_attr) is None: elif f"<<{format_var}>>" in full_text and format_var.endswith(("%", "#", "H", "M", "0")):
raise Failed(f"Overlay Warning: No {full_text} found") mod = format_var[-1]
actual_value = getattr(item, actual_attr) format_var = format_var[:-1]
if self.config.Cache: elif f"<<{format_var}>>" in full_text:
self.config.Cache.update_overlay_special_text(item.ratingKey, full_text, actual_value) mod = ""
full_text = str(text_overlay.text_overlay_format)
if text_overlay.name in overlay.value_overlays + overlay.rating_special_text + ["text(originally_available)"] and "<<value>>" in full_text:
full_text = full_text.replace("<<value>>", actual_value)
if text_overlay.name in overlay.rating_special_text:
if "<<value%>>" in full_text:
full_text = full_text.replace("<<value%>>", f"{int(actual_value * 10)}%")
if "<<value0>>" in full_text:
full_text = full_text.replace("<<value0>>", f"{int(actual_value * 10)}")
if "<<value#>>" in full_text:
full_text = full_text.replace("<<value#>>", str(actual_value)[:-2] if str(actual_value).endswith(".0") else actual_value)
elif text_overlay.name == "text(originally_available)":
if "<<value>>" in full_text:
full_text = full_text.replace("<<value>>", actual_value.strftime("%Y-%m-%d"))
match = re.search("<<value\\[(.+)]>>", full_text)
if match:
full_text = re.sub("<<value\\[(.+)]>>", str(actual_value.strftime(match.group(1))), full_text)
elif text_overlay.name == "text(runtime)":
if "<<value>>" in full_text:
full_text = full_text.replace("<<value>>", actual_value / 60000)
if "<<valueH>>" in full_text:
full_text = full_text.replace("<<valueH>>", (actual_value / 60000) // 60)
if "<<valueM>>" in full_text:
full_text = full_text.replace("<<valueM>>", (actual_value / 60000) % 60)
elif text_overlay.name == "text(season_episode)":
if text_overlay.level == "season":
season = actual_value
episode = None
else: else:
season, episode = actual_value.upper()[1:].split("E") continue
for attr, attr_val in [("season", season), ("episode", episode)]: if format_var == "show_title":
if attr_val and f"<<{attr}>>" in full_text: actual_attr = "parentTitle" if text_overlay.level == "season" else "grandparentTitle"
full_text = full_text.replace(f"<<{attr}>>", attr_val) elif format_var in plex.attribute_translation:
if attr_val and f"<<{attr}W>>" in full_text: actual_attr = plex.attribute_translation[format_var]
full_text = full_text.replace(f"<<{attr}W>>", num2words(int(attr_val))) else:
if attr_val and f"<<{attr}0>>" in full_text: actual_attr = format_var
full_text = full_text.replace(f"<<{attr}0>>", f"{int(attr_val):02}") if not hasattr(item, actual_attr) or getattr(item, actual_attr) is None:
if attr_val and f"<<{attr}00>>" in full_text: logger.warning(f"Overlay Warning: No {full_text} found")
full_text = full_text.replace(f"<<{attr}00>>", f"{int(attr_val):03}") 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}"
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) return str(full_text)
for over_name in applied_names: for over_name in applied_names:
current_overlay = properties[over_name] current_overlay = properties[over_name]
if current_overlay.name.startswith("text"): if current_overlay.name.startswith("text"):
if current_overlay.name in overlay.special_text_overlays: if current_overlay.name == "text(special_text)":
image_box = current_overlay.image.size if current_overlay.image else None image_box = current_overlay.image.size if current_overlay.image else None
try: try:
overlay_image, addon_box = current_overlay.get_backdrop((canvas_width, canvas_height), box=image_box, text=get_text(current_overlay)) overlay_image, addon_box = current_overlay.get_backdrop((canvas_width, canvas_height), box=image_box, text=get_text(current_overlay))

View file

@ -148,9 +148,8 @@ attribute_translation = {
"writer": "writers", "writer": "writers",
"mood": "moods", "mood": "moods",
"style": "styles", "style": "styles",
"season_episode": "seasonEpisode", "episode_number": "episodeNumber",
"episode": "episodeNumber", "season_number": "seasonNumber",
"season": "seasonNumber",
"original_title": "originalTitle", "original_title": "originalTitle",
"runtime": "duration", "runtime": "duration",
"season_title": "parentTitle", "season_title": "parentTitle",