[103] overlay text backdrop and more filters

This commit is contained in:
meisnate12 2022-05-17 03:25:11 -04:00
parent 66c4fbc2a6
commit 1664a6002a
9 changed files with 203 additions and 133 deletions

View file

@ -1 +1 @@
1.16.5-develop102
1.16.5-develop103

View file

@ -30,14 +30,17 @@ String filters can take multiple values **only as a list**.
### Attribute
| String Filter | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:--------------------|:-----------------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
| `title` | Uses the title attribute to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `summary` | Uses the summary attribute to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `studio` | Uses the studio attribute to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `record_label` | Uses the record label attribute to match | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| `filepath` | Uses the item's filepath to match | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| `audio_track_title` | Uses the audio track titles to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ |
| String Filter | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:--------------------|:-----------------------------------------|:--------:|:-------------------:|:-------------------:|:--------:|:-------------------:|:-------------------:|:--------:|
| `title` | Uses the title attribute to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `summary` | Uses the summary attribute to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `studio` | Uses the studio attribute to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `record_label` | Uses the record label attribute to match | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| `folder` | Uses the item's folder to match | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| `filepath` | Uses the item's filepath to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; |
| `audio_track_title` | Uses the audio track titles to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; |
<sup>1</sup> Filters using the special `episodes`/`tracks` filters with the default percent.
## Tag Filters
@ -59,27 +62,28 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
### Attribute
| Tag Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:-----------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
| `actor` | Uses the actor tags to match | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `collection` | Uses the collection tags to match | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `content_rating` | Uses the content rating tags to match | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `network` | Uses the network tags to match | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `country` | Uses the country tags to match | &#9989; | &#10060; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; |
| `director` | Uses the director tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `genre` | Uses the genre tags to match | &#9989; | &#9989; | &#10060; | &#10060; | &#9989; | &#9989; | &#10060; |
| `label` | Uses the label tags to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#9989; | &#10060; |
| `producer` | Uses the actor tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `year` | Uses the year tag to match | &#9989; | &#9989; | &#9989; | &#9989; | &#10060; | &#9989; | &#9989; |
| `writer` | Uses the writer tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `resolution` | Uses the resolution tag to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `audio_language` | Uses the audio language tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `subtitle_language` | Uses the subtitle language tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `tmdb_genre`<sup>1</sup> | Uses the genre from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `tmdb_keyword`<sup>1</sup> | Uses the keyword from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `origin_country`<sup>1</sup> | Uses TMDb origin country [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to match<br>Example: `origin_country: us` | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| Tag Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:-----------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:-------------------:|:-------------------:|:--------:|:--------:|:--------:|:--------:|
| `actor` | Uses the actor tags to match | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `collection` | Uses the collection tags to match | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `content_rating` | Uses the content rating tags to match | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `network` | Uses the network tags to match | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `country` | Uses the country tags to match | &#9989; | &#10060; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; |
| `director` | Uses the director tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `genre` | Uses the genre tags to match | &#9989; | &#9989; | &#10060; | &#10060; | &#9989; | &#9989; | &#10060; |
| `label` | Uses the label tags to match | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `producer` | Uses the actor tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `year` | Uses the year tag to match | &#9989; | &#9989; | &#9989; | &#9989; | &#10060; | &#9989; | &#9989; |
| `writer` | Uses the writer tags to match | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `resolution` | Uses the resolution tag to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `audio_language` | Uses the audio language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `subtitle_language` | Uses the subtitle language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `tmdb_genre`<sup>2</sup> | Uses the genre from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `tmdb_keyword`<sup>2</sup> | Uses the keyword from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `origin_country`<sup>2</sup> | Uses TMDb origin country [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to match<br>Example: `origin_country: us` | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
<sup>1</sup> Also filters out missing movies/shows from being added to Radarr/Sonarr. These Values also cannot use the `count` modifiers.
<sup>1</sup> Filters using the special `episodes` filter with the default percent.
<sup>2</sup> Also filters out missing movies/shows from being added to Radarr/Sonarr. These Values also cannot use the `count` modifiers.
## Boolean Filters
@ -87,11 +91,13 @@ Boolean Filters have no modifiers.
### Attribute
| Boolean Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:--------------------|:------------------------------------------------------------|:-------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
| `has_collection` | Matches every item that has or does not have a collection | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `has_dolby_vision` | Matches every item that has or does not have a dolby vision | &#9989; | &#10060; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `has_overlay` | Matches every item that has or does not have an overlay | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| Boolean Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:--------------------|:------------------------------------------------------------|:-------:|:-------------------:|:-------------------:|:--------:|:--------:|:--------:|:--------:|
| `has_collection` | Matches every item that has or does not have a collection | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `has_dolby_vision` | Matches every item that has or does not have a dolby vision | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `has_overlay` | Matches every item that has or does not have an overlay | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#10060; |
<sup>1</sup> Filters using the special `episodes` filter with the default percent.
## Date Filters

View file

@ -54,25 +54,31 @@ Each overlay definition needs to specify what overlay to use. This can happen in
3. Using a dictionary for more overlay location options.
| Attribute | Description | Required |
|:--------------------|:----------------------------------------------------------------------------------------------------------------|:--------:|
| `name` | Name of the overlay. Each overlay name should be unique. | &#9989; |
| `file` | Local location of the Overlay Image. | &#10060; |
| `url` | URL of Overlay Image Online. | &#10060; |
| `git` | Location in the [Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) of the Overlay Image. | &#10060; |
| `repo` | Location in the [Custom Repo](../config/settings.md#custom-repo) of the Overlay Image. | &#10060; |
| `group` | Name of the Grouping for this overlay. **`weight` is required when using `group`** | &#10060; |
| `weight` | Weight of this overlay in its group. **`group` is required when using `weight`** | &#10060; |
| `horizontal_offset` | Horizontal Offset of this overlay. Can be a %. **`vertical_offset` is required when using `horizontal_offset`** | &#10060; |
| `horizontal_align` | Horizontal Alignment of the overlay. **Values:** `left`, `center`, `right` | &#10060; |
| `vertical_offset` | Vertical Offset of this overlay. Can be a %. **`horizontal_offset` is required when using `vertical_offset`** | &#10060; |
| `vertical_align` | Vertical Alignment of the overlay. **Values:** `top`, `center`, `bottom` | &#10060; |
| `font` | System Font Filename or path to font file for the Text Overlay | &#10060; |
| `font_size` | Font Size for the Text Overlay. **Value:** Integer greater than 0 | &#10060; |
| `font_color` | Font Color for the Text Overlay. **Value:** Color Hex Code. ex `#00FF00` | &#10060; |
| Attribute | Description | Required |
|:--------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|
| `name` | Name of the overlay. Each overlay name should be unique. | &#9989; |
| `file` | Local location of the Overlay Image. | &#10060; |
| `url` | URL of Overlay Image Online. | &#10060; |
| `git` | Location in the [Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) of the Overlay Image. | &#10060; |
| `repo` | Location in the [Custom Repo](../config/settings.md#custom-repo) of the Overlay Image. | &#10060; |
| `group` | Name of the Grouping for this overlay. Only one overlay with the highest weight per group will be applied.<br>**`weight` is required when using `group`**<br>**Values:** group name | &#10060; |
| `weight` | Weight of this overlay in its group.<br>**`group` is required when using `weight`**<br>**Values:** Integer | &#10060; |
| `horizontal_offset` | Horizontal Offset of this overlay. Can be a %.<br>**`vertical_offset` is required when using `horizontal_offset`**<br>**Value:** Integer 0 or greater or 1%-100% | &#10060; |
| `horizontal_align` | Horizontal Alignment of the overlay.<br>**Values:** `left`, `center`, `right` | &#10060; |
| `vertical_offset` | Vertical Offset of this overlay. Can be a %.<br>**`horizontal_offset` is required when using `vertical_offset`**<br>**Value:** Integer 0 or greater or 1%-100% | &#10060; |
| `vertical_align` | Vertical Alignment of the overlay.<br>**Values:** `top`, `center`, `bottom` | &#10060; |
| `font` | System Font Filename or path to font file for the Text Overlay.<br>**Value:** System Font Filename or path to font file | &#10060; |
| `font_size` | Font Size for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; |
| `font_color` | Font Color for the Text Overlay.<br>**Value:** Color Hex Code in format `#RGB`, `#RGBA`, `#RRGGBB` or `#RRGGBBAA`. | &#10060; |
| `back_color` | Backdrop Color for the Text Overlay.<br>**Value:** Color Hex Code in format `#RGB`, `#RGBA`, `#RRGGBB` or `#RRGGBBAA`. | &#10060; |
| `back_width` | Backdrop Width for the Text Overlay. If `back_width` is not specified the Backdrop Sizes to the text<br>**`back_height` is required when using `back_width`**<br>**Value:** Integer greater than 0 | &#10060; |
| `back_height` | Backdrop Height for the Text Overlay. If `back_height` is not specified the Backdrop Sizes to the text<br>**`back_width` is required when using `back_height`**<br>**Value:** Integer greater than 0 | &#10060; |
| `back_padding` | Backdrop Padding 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_width` | Backdrop Line Width for the Text Overlay.<br>**Value:** Integer greater than 0 | &#10060; |
* 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.
```yaml
overlays:

View file

@ -70,18 +70,18 @@ discover_status = {
"Ended": "ended", "Canceled": "canceled", "Pilot": "pilot"
}
filters_by_type = {
"movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays"],
"movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays", "filepath", "label", "audio_track_title"],
"movie_show_season_episode_album_track": ["year"],
"movie_show_episode_artist_track": ["filepath"],
"movie_show_season_episode_artist_album": ["has_overlay"],
"movie_show_season_episode": ["resolution", "audio_language", "subtitle_language", "has_dolby_vision"],
"movie_show_episode_album": ["release", "critic_rating", "history"],
"movie_show_episode_track": ["duration"],
"movie_show_artist_album": ["genre"],
"movie_show_episode": ["actor", "content_rating", "audience_rating"],
"movie_show_album": ["label"],
"movie_episode_track": ["audio_track_title"],
"movie_show": ["studio", "original_language", "has_overlay", "tmdb_vote_count", "tmdb_year", "tmdb_genre", "tmdb_title", "tmdb_keyword"],
"movie_episode": ["director", "producer", "writer", "resolution", "audio_language", "subtitle_language", "has_dolby_vision"],
"movie_show": ["studio", "original_language", "tmdb_vote_count", "tmdb_year", "tmdb_genre", "tmdb_title", "tmdb_keyword"],
"movie_episode": ["director", "producer", "writer"],
"movie_artist": ["country"],
"show_artist": ["folder"],
"show_season": ["episodes"],
"artist_album": ["tracks"],
"show": ["seasons", "tmdb_status", "tmdb_type", "origin_country", "network", "first_episode_aired", "last_episode_aired"],
@ -101,7 +101,7 @@ tmdb_filters = [
"original_language", "origin_country", "tmdb_vote_count", "tmdb_year", "tmdb_keyword", "tmdb_genre",
"first_episode_aired", "last_episode_aired", "tmdb_status", "tmdb_type", "tmdb_title"
]
string_filters = ["title", "summary", "studio", "record_label", "filepath", "audio_track_title", "tmdb_title"]
string_filters = ["title", "summary", "studio", "record_label", "folder", "filepath", "audio_track_title", "tmdb_title"]
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
tag_filters = [
"actor", "collection", "content_rating", "country", "director", "network", "genre", "label", "producer", "year", "origin_country",
@ -225,10 +225,12 @@ class CollectionBuilder:
logger.debug(f"Value: {data[methods['allowed_library_types']]}")
found_type = False
for library_type in util.get_list(self.data[methods["allowed_library_types"]], lower=True):
if library_type not in plex.library_types:
raise Failed(f"{self.Type} Error: {library_type} is invalid. Options: {', '.join(plex.library_types)}")
elif library_type == self.library.Plex.type:
if library_type == "true" or library_type == self.library.Plex.type:
found_type = True
elif library_type not in plex.library_types:
raise Failed(f"{self.Type} Error: {library_type} is invalid. Options: {', '.join(plex.library_types)}")
elif library_type == "false":
raise NotScheduled(f"Skipped because allowed_library_types is false")
if not found_type:
raise NotScheduled(f"Skipped because allowed_library_types {self.data[methods['allowed_library_types']]} doesn't match the library type: {self.library.Plex.type}")
@ -340,6 +342,7 @@ class CollectionBuilder:
self.schedule = ""
self.limit = 0
self.beginning_count = 0
self.default_percent = 50
self.minimum = self.library.minimum_items
self.tmdb_region = None
self.ignore_ids = [i for i in self.library.ignore_ids]
@ -1452,10 +1455,14 @@ class CollectionBuilder:
message = f"{self.Type} Error: {filter_final} is not a valid {self.collection_level} filter attribute"
elif filter_final is None:
message = f"{self.Type} Error: {filter_final} filter attribute is blank"
elif filter_attr in tmdb_filters:
self.tmdb_filters.append((filter_final, self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)))
else:
self.filters.append((filter_final, self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)))
final_data = self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)
if filter_attr in tmdb_filters:
self.tmdb_filters.append((filter_final, final_data))
elif self.collection_level in ["show", "season", "artist", "album"] and filter_attr in ["filepath", "audio_track_title", "resolution", "audio_language", "subtitle_language", "has_dolby_vision"]:
self.filters.append(("episodes" if self.collection_level in ["show", "season"] else "tracks", {filter_final: final_data, "percentage": self.default_percent}))
else:
self.filters.append((filter_final, final_data))
if message:
if validate:
raise Failed(message)
@ -1878,7 +1885,7 @@ class CollectionBuilder:
return util.get_list(data, upper=True)
elif attribute in ["original_language", "tmdb_keyword"]:
return util.get_list(data, lower=True)
elif attribute in ["filepath", "tmdb_genre"]:
elif attribute in ["tmdb_genre"]:
return util.get_list(data)
elif attribute == "history":
try:
@ -1965,14 +1972,14 @@ class CollectionBuilder:
return util.parse(self.Type, attribute, data, datatype="bool")
elif attribute in ["seasons", "episodes", "albums", "tracks"]:
if isinstance(data, dict) and data:
percentage = 60
percentage = self.default_percent
if "percentage" in data:
if data["percentage"] is None:
logger.warning(f"{self.Type} Warning: percentage filter attribute is blank using 60 as default")
logger.warning(f"{self.Type} Warning: percentage filter attribute is blank using {self.default_percent} as default")
else:
maybe = util.check_num(data["percentage"])
if maybe < 0 or maybe > 100:
logger.warning(f"{self.Type} Warning: percentage filter attribute must be a number 0-100 using 60 as default")
logger.warning(f"{self.Type} Warning: percentage filter attribute must be a number 0-100 using {self.default_percent} as default")
else:
percentage = maybe
final_filters = {"percentage": percentage}

View file

@ -1,4 +1,5 @@
import re, secrets, time, webbrowser
from json import JSONDecodeError
from modules import util
from modules.util import Failed, TimeoutExpired, YAML
@ -158,11 +159,14 @@ class MyAnimeList:
token = authorization["access_token"] if authorization else self.authorization["access_token"]
if self.config.trace_mode:
logger.debug(f"URL: {url}")
response = self.config.get_json(url, headers={"Authorization": f"Bearer {token}"})
if self.config.trace_mode:
logger.debug(f"Response: {response}")
if "error" in response: raise Failed(f"MyAnimeList Error: {response['error']}")
else: return response
try:
response = self.config.get_json(url, headers={"Authorization": f"Bearer {token}"})
if self.config.trace_mode:
logger.debug(f"Response: {response}")
if "error" in response: raise Failed(f"MyAnimeList Error: {response['error']}")
else: return response
except JSONDecodeError:
raise Failed(f"MyAnimeList Error: Connection Failed")
def _jiken_request(self, url, params=None):
data = self.config.get_json(f"{jiken_base_url}{url}", params=params)

View file

@ -41,30 +41,34 @@ class Overlays:
os.path.join(self.library.overlay_folder, old_overlay.title[:-8], f"{item.ratingKey}.png")
])
if self.library.remove_overlays:
remove_overlays = self.get_overlay_items()
if self.library.is_show:
remove_overlays.extend(self.get_overlay_items(libtype="episode"))
remove_overlays.extend(self.get_overlay_items(libtype="season"))
elif self.library.is_music:
remove_overlays.extend(self.get_overlay_items(libtype="album"))
logger.info("")
if remove_overlays:
logger.separator(f"Removing Overlays for the {self.library.name} Library")
for i, item in enumerate(remove_overlays, 1):
item_title = self.get_item_sort_title(item, atr="title")
logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item_title}")
self.remove_overlay(item, item_title, "Overlay", [
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png"),
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")
])
logger.exorcise()
else:
logger.separator(f"No Overlays to Remove for the {self.library.name} Library")
logger.info("")
else:
key_to_overlays = {}
properties = None
if not self.library.remove_overlays:
key_to_overlays, properties = self.compile_overlays()
ignore_list = [rk for rk in key_to_overlays]
remove_overlays = self.get_overlay_items(ignore=ignore_list)
if self.library.is_show:
remove_overlays.extend(self.get_overlay_items(libtype="episode", ignore=ignore_list))
remove_overlays.extend(self.get_overlay_items(libtype="season", ignore=ignore_list))
elif self.library.is_music:
remove_overlays.extend(self.get_overlay_items(libtype="album", ignore=ignore_list))
logger.info("")
if remove_overlays:
logger.separator(f"Removing Overlays for the {self.library.name} Library")
for i, item in enumerate(remove_overlays, 1):
item_title = self.get_item_sort_title(item, atr="title")
logger.ghost(f"Restoring: {i}/{len(remove_overlays)} {item_title}")
self.remove_overlay(item, item_title, "Overlay", [
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.png"),
os.path.join(self.library.overlay_backup, f"{item.ratingKey}.jpg")
])
logger.exorcise()
else:
logger.separator(f"No Overlays to Remove for the {self.library.name} Library")
logger.info("")
if not self.library.remove_overlays:
logger.info("")
logger.separator(f"Applying Overlays for the {self.library.name} Library")
logger.info("")
@ -164,7 +168,9 @@ class Overlays:
image_height = 1080 if isinstance(item, Episode) else 1500
new_poster = Image.open(poster.location if poster else has_original) \
.convert("RGBA").resize((image_width, image_height), Image.ANTIALIAS)
.convert("RGB").resize((image_width, image_height), Image.ANTIALIAS)
overlay_image = Image.new('RGBA', new_poster.size, (255, 255, 255, 0))
drawing = ImageDraw.Draw(overlay_image)
if blur_num > 0:
new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num))
for over_name in normal_overlays:
@ -173,7 +179,6 @@ class Overlays:
new_poster = new_poster.resize(overlay.image.size, Image.ANTIALIAS)
new_poster.paste(overlay.image, overlay.get_coordinates(image_width, image_height), overlay.image)
if text_names:
drawing = ImageDraw.Draw(new_poster)
for over_name in text_names:
overlay = properties[over_name]
text = over_name[5:-1]
@ -190,7 +195,27 @@ class Overlays:
self.config.Cache.update_overlay_ratings(item.ratingKey, rating_type, text)
if per:
text = f"{int(text * 10)}%"
drawing.text(overlay.get_coordinates(image_width, image_height, text=str(text)), str(text), font=overlay.font, fill=overlay.font_color)
x_cord, y_cord = overlay.get_coordinates(image_width, image_height, text=str(text))
_, _, width, height = overlay.get_text_size(str(text))
if overlay.back_color:
cords = (
x_cord - overlay.back_padding,
y_cord - overlay.back_padding,
x_cord + (overlay.back_width if overlay.back_width else width) + overlay.back_padding,
y_cord + (overlay.back_height if overlay.back_height else height) + overlay.back_padding
)
if overlay.back_width:
x_cord = int(x_cord + (overlay.back_width - width) / 2)
y_cord = int(y_cord + (overlay.back_height - height) / 2)
if overlay.back_radius:
drawing.rounded_rectangle(cords, fill=overlay.back_color, outline=overlay.back_line_color,
width=overlay.back_line_width, radius=overlay.back_radius)
else:
drawing.rectangle(cords, fill=overlay.back_color, outline=overlay.back_line_color,
width=overlay.back_line_width)
drawing.text((x_cord, y_cord), str(text), font=overlay.font, fill=overlay.font_color, anchor='lt')
new_poster.paste(overlay_image, (0, 0), overlay_image)
temp = os.path.join(self.library.overlay_folder, f"temp.png")
new_poster.save(temp, "PNG")
self.library.upload_poster(item, temp)

View file

@ -1169,7 +1169,7 @@ class Plex(Library):
for media in item.media:
for part in media.parts:
values.extend([a.extendedDisplayTitle for a in part.audioStreams() if a.extendedDisplayTitle])
elif filter_attr == "filepath":
elif filter_attr in ["filepath", "folder"]:
values = [loc for loc in item.locations]
else:
values = [getattr(item, filter_actual)]

View file

@ -840,8 +840,15 @@ class Overlay:
self.path = None
self.font = None
self.font_name = None
self.font_size = 12
self.font_size = 36
self.font_color = None
self.back_color = None
self.back_radius = None
self.back_line_width = None
self.back_line_color = None
self.back_padding = 0
self.back_height = None
self.back_width = None
logger.debug("")
logger.debug("Validating Method: overlay")
logger.debug(f"Value: {self.data}")
@ -855,16 +862,13 @@ class Overlay:
if "group" in self.data and self.data["group"]:
self.group = str(self.data["group"])
if "weight" in self.data and self.data["weight"] is not None:
pri = check_num(self.data["weight"])
if pri is None:
raise Failed(f"Overlay Error: overlay weight must be a number")
self.weight = pri
if "weight" in self.data:
self.weight = parse("Overlay", "weight", self.data["weight"], datatype="int", parent="overlay")
if ("group" in self.data or "weight" in self.data) and (self.weight is None or not self.group):
raise Failed(f"Overlay Error: overlay attribute's group and weight must be used together")
self.horizontal_align = parse("Overlay", "horizontal_align", self.data["horizontal_align"], options=["left", "center", "right"]) if "horizontal_align" in self.data else "left"
self.vertical_align = parse("Overlay", "vertical_align", self.data["vertical_align"], options=["top", "center", "bottom"]) if "vertical_align" in self.data else "top"
self.horizontal_align = parse("Overlay", "horizontal_align", self.data["horizontal_align"], parent="overlay", options=["left", "center", "right"]) if "horizontal_align" in self.data else "left"
self.vertical_align = parse("Overlay", "vertical_align", self.data["vertical_align"], parent="overlay", options=["top", "center", "bottom"]) if "vertical_align" in self.data else "top"
self.horizontal_offset = None
if "horizontal_offset" in self.data and self.data["horizontal_offset"] is not None:
@ -908,8 +912,8 @@ class Overlay:
if self.vertical_offset is None and self.vertical_align == "center":
self.vertical_offset = 0
if (self.horizontal_offset is not None or self.vertical_offset is not None) and (self.horizontal_offset is None or self.vertical_offset is None):
raise Failed(f"Overlay Error: overlay horizontal_offset and overlay vertical_offset must be used together")
if (self.horizontal_offset is None and self.vertical_offset is not None) or (self.vertical_offset is None and self.horizontal_offset is not None):
raise Failed(f"Overlay Error: overlay attribute's horizontal_offset and vertical_offset must be used together")
def get_and_save_image(image_url):
response = self.config.get(image_url)
@ -958,12 +962,8 @@ class Overlay:
self.name = f"text({match.group(1)})"
if os.path.exists("fonts/Roboto-Medium.ttf"):
self.font_name = "fonts/Roboto-Medium.ttf"
if "font_size" in self.data and self.data["font_size"] is not None:
font_size = check_num(self.data["font_size"])
if font_size is None or font_size < 1:
logger.error(f"Overlay Error: overlay font_size: {self.data['font_size']} invalid must be a greater than 0")
else:
self.font_size = font_size
if "font_size" in self.data:
self.font_size = parse("Overlay", "font_size", self.data["font_size"], datatype="int", parent="overlay", default=self.font_size)
if "font" in self.data and self.data["font"]:
font = str(self.data["font"])
if not os.path.exists(font):
@ -972,13 +972,27 @@ class Overlay:
raise Failed(f"Overlay Error: font: {font} not found. Options: {', '.join(fonts)}")
self.font_name = font
self.font = ImageFont.truetype(self.font_name, self.font_size)
if "font_color" in self.data and self.data["font_color"]:
try:
color_str = self.data["font_color"]
color_str = color_str if color_str.startswith("#") else f"#{color_str}"
self.font_color = ImageColor.getcolor(color_str, "RGB")
except ValueError:
logger.error(f"Overlay Error: overlay color: {self.data['color']} invalid")
def color(attr):
if attr in self.data and self.data[attr]:
try:
return ImageColor.getcolor(self.data[attr], "RGBA")
except ValueError:
raise Failed(f"Overlay Error: overlay {attr}: {self.data[attr]} invalid")
self.font_color = color("font_color")
self.back_color = color("back_color")
if "back_radius" in self.data:
self.back_radius = parse("Overlay", "back_radius", self.data["back_radius"], datatype="int", parent="overlay")
if "back_line_width" in self.data:
self.back_line_width = parse("Overlay", "back_line_width", self.data["back_line_width"], datatype="int", parent="overlay")
self.back_line_color = color("back_line_color")
if "back_padding" in self.data:
self.back_padding = parse("Overlay", "back_padding", self.data["back_padding"], datatype="int", parent="overlay", default=self.back_padding)
if "back_width" in self.data:
self.back_width = parse("Overlay", "back_width", self.data["back_width"], datatype="int", parent="overlay")
if "back_height" in self.data:
self.back_height = parse("Overlay", "back_height", self.data["back_height"], datatype="int", parent="overlay")
if (self.back_width and not self.back_height) or (self.back_height and not self.back_width):
raise Failed(f"Overlay Error: overlay attributes back_width and back_height must be used together")
else:
if "|" in self.name:
raise Failed(f"Overlay Error: Overlay Name: {self.name} cannot contain '|'")
@ -1007,18 +1021,27 @@ class Overlay:
output += f"{self.horizontal_align}{self.horizontal_offset}{self.vertical_offset}{self.vertical_align}"
if self.font_name:
output += f"{self.font_name}{self.font_size}"
if self.font_color:
output += str(self.font_color)
if self.back_width:
output += f"{self.back_width}{self.back_height}"
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
def has_coordinates(self):
return self.horizontal_offset is not None and self.vertical_offset is not None
def get_text_size(self, text):
return ImageDraw.Draw(Image.new("RGBA", (0, 0))).textbbox((0, 0), text, font=self.font, anchor='lt')
def get_coordinates(self, image_width, image_height, text=None):
if not self.has_coordinates():
return 0, 0
if text:
_, _, width, height = ImageDraw.Draw(Image.new("RGB", (0, 0))).textbbox((0, 0), text, font=self.font)
if self.back_width:
width = self.back_width
height = self.back_height
elif text:
_, _, width, height = self.get_text_size(text)
else:
width, height = self.image.size
@ -1031,7 +1054,5 @@ class Overlay:
else:
return value
x_cord = get_cord(self.horizontal_offset, image_width, width, self.horizontal_align)
y_cord = get_cord(self.vertical_offset, image_height, height, self.vertical_align)
return x_cord, y_cord
return get_cord(self.horizontal_offset, image_width, width, self.horizontal_align), \
get_cord(self.vertical_offset, image_height, height, self.vertical_align)

View file

@ -343,8 +343,9 @@ def run_config(config):
logger.info("")
logger.info(f"{'Title':<27} | Run Time |")
logger.info(f"{logger.separating_character * 27} | {logger.separating_character * 8} |")
for text, value in library_status[library.name].items():
logger.info(f"{text:<27} | {value:>8} |")
if library.name in library_status:
for text, value in library_status[library.name].items():
logger.info(f"{text:<27} | {value:>8} |")
logger.info("")
print_status(library.status)
if playlist_status: