[25] more minor fixes

This commit is contained in:
meisnate12 2022-06-07 16:42:17 -04:00
parent 6d27ca508c
commit 64e67d4109
10 changed files with 190 additions and 91 deletions

View file

@ -1 +1 @@
1.17.0-develop24
1.17.0-develop25

View file

@ -24,6 +24,8 @@ plex:
| `empty_trash` | Runs Empty Trash on the Server after all Metadata Files are run | false | ❌ |
| `optimize` | Runs Optimize on the Server after all Metadata Files are run | false | ❌ |
* **Do Not Use the Plex Token found in Plex's Preferences.xml file**
* This script can be run on a remote Plex server, but be sure that the `url` provided is publicly addressable, and it's recommended to use `HTTPS`.
* If you need help finding your Plex authentication token, please see Plex's [support article](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/).

View file

@ -106,7 +106,7 @@ Specify the time of day that Plex Meta Manager will run.
</tr>
<tr>
<th>Default Value</th>
<td colspan="2"><code>03:00</code></td>
<td colspan="2"><code>05:00</code></td>
</tr>
<tr>
<th>Available Values</th>

View file

@ -16,6 +16,7 @@ These are the attributes which can be used within the Overlay File:
|:--------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------|
| [`templates`](templates) | contains definitions of templates that can be leveraged by multiple overlays |
| [`external_templates`](templates.md#external-templates) | contains [path types](../config/paths) that point to external templates that can be leveraged by multiple overlays |
| [`queues`](#overlay-queues) | contains the positional attributes of queues |
| [`overlays`](#overlays-attributes) | contains definitions of overlays you wish to add |
* `overlays` is required in order to run the Overlay File.
@ -66,29 +67,30 @@ overlays:
There are many attributes available when using overlays to edit how they work.
| 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 0%-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 0%-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; |
| 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`](#overlay-groups) | 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; |
| [`queue`](#overlay-queues) | Name of the Queue for this overlay. Define `queue` positions using the `queues` attribute at the top level of an Overlay File. Overlay with the highest weight is applied to the first position and so on.<br>**`weight` is required when using `queue`**<br>**Values:** queue name | &#10060; |
| `weight` | Weight of this overlay in its group or queue.<br>**`group` or `queue` is required when using `weight`**<br>**Values:** Integer 0 or greater | &#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 0%-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 0%-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.
@ -108,7 +110,7 @@ overlays:
imdb_chart: top_movies
overlay:
name: IMDB-Top-250
repo: PMM/overlays/images/IMDB-Top-250
git: PMM/overlays/images/IMDB-Top-250
horizontal_offset: 0
horizontal_align: right
vertical_offset: 0
@ -134,7 +136,6 @@ overlays:
![](blur.png)
### Text Overlay
You can add text as an overlay using the special `text()` overlay name. Anything inside the parentheses will be added as an overlay onto the image. Ex `text(4K)` adds `4K` to the image.
@ -173,6 +174,89 @@ overlays:
back_height: 105
```
### Overlay Groups
Overlay groups are defined by the name given to the `group` attribute. Only one overlay with the highest weight per group will be applied.
This is an example where the Multi-Audio overlay will be applied over the Dual-Audio overlay for every item found by both.
```yaml
overlays:
Dual-Audio:
overlay:
name: Dual-Audio
git: PMM/overlays/images/Dual-Audio
group: audio_language
weight: 10
horizontal_offset: 0
horizontal_align: center
vertical_offset: 15
vertical_align: bottom
plex_all: true
filters:
audio_language.count_gt: 1
Multi-Audio:
overlay:
name: Multi-Audio
git: PMM/overlays/images/Multi-Audio
group: audio_language
weight: 20
horizontal_offset: 0
horizontal_align: center
vertical_offset: 15
vertical_align: bottom
plex_all: true
filters:
audio_language.count_gt: 2
```
### Overlay Queues
Overlay queues are defined by the name given to the `queue` attribute. The overlay with the highest weight is put into the first queue position, then the second highest is placed in the second queue position and so on.
You can define the queue positions by using the `queues` attribute at the top level of an Overlay File. You can define as many positions as you want.
```yaml
queues:
custom_queue_name:
- horizontal_offset: 300 # This is the first position
horizontal_align: center
vertical_offset: 1375
vertical_align: top
- horizontal_offset: 300 # This is the second position
horizontal_align: center
vertical_offset: 1250
vertical_align: top
overlays:
IMDb:
imdb_chart: popular_movies
overlay:
name: text(IMDb Popular)
queue: custom_queue_name
weight: 20
font: fonts/Inter-Medium.ttf
font_size: 65
font_color: "#FFFFFF"
back_color: "#00000099"
back_radius: 30
back_width: 380
back_height: 105
TMDb:
tmdb_popular: 100
overlay:
name: text(TMDb Popular)
queue: custom_queue_name
weight: 10
font: fonts/Inter-Medium.ttf
font_size: 65
font_color: "#FFFFFF"
back_color: "#00000099"
back_radius: 30
back_width: 400
back_height: 105
```
## Suppress Overlays
You can add `suppress_overlays` to an overlay definition and give it a list or comma separated string of overlay names you want suppressed from this item if this overlay is attached to the item.

View file

@ -2293,7 +2293,7 @@ class CollectionBuilder:
tmdb_paths = []
tvdb_paths = []
for item in self.items:
if "item_assets" in self.item_details and self.library.asset_directory and "Overlay" not in [la.tag for la in item.labels]:
if "item_assets" in self.item_details and self.library.asset_directory and "Overlay" not in [la.tag for la in self.library.item_labels(item)]:
self.library.find_and_upload_assets(item)
self.library.edit_tags("label", item, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags)
path = os.path.dirname(str(item.locations[0])) if self.library.is_movie else str(item.locations[0])

View file

@ -79,7 +79,7 @@ class Operations:
logger.error(e)
continue
logger.ghost(f"Processing: {i}/{len(items)} {item.title}")
current_labels = [la.tag for la in item.labels] if self.library.assets_for_all or self.library.mass_imdb_parental_labels else []
current_labels = [la.tag for la in self.library.item_labels(item)] if self.library.assets_for_all or self.library.mass_imdb_parental_labels else []
if self.library.assets_for_all and self.library.asset_directory and "Overlay" not in current_labels:
self.library.find_and_upload_assets(item)

View file

@ -86,7 +86,7 @@ class Overlays:
image, image_compare, overlay_compare = self.config.Cache.query_image_map(item.ratingKey, f"{self.library.image_table_name}_overlays")
overlay_compare = [] if overlay_compare is None else util.get_list(overlay_compare, split="|")
has_overlay = any([item_tag.tag.lower() == "overlay" for item_tag in item.labels])
has_overlay = any([item_tag.tag.lower() == "overlay" for item_tag in self.library.item_labels(item)])
compare_names = {properties[ov].get_overlay_compare(): ov for ov in over_names}
blur_num = 0

View file

@ -529,6 +529,10 @@ class Plex(Library):
def collection_order_query(self, collection, data):
collection.sortUpdate(sort=data)
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
def item_labels(self, item):
return item.labels
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
def reload(self, item, force=False):
is_full = False
@ -640,9 +644,10 @@ class Plex(Library):
def scan_user(server, username):
try:
for playlist in server.playlists():
if playlist.title not in playlists:
playlists[playlist.title] = []
playlists[playlist.title].append(username)
if isinstance(playlist, Playlist):
if playlist.title not in playlists:
playlists[playlist.title] = []
playlists[playlist.title].append(username)
except requests.exceptions.ConnectionError:
pass
scan_user(self.PlexServer, self.account.title)
@ -1244,7 +1249,7 @@ class Plex(Library):
if filter_attr == "has_collection":
filter_check = len(item.collections) > 0
elif filter_attr == "has_overlay":
for label in item.labels:
for label in self.item_labels(item):
if label.tag.lower().endswith(" overlay") or label.tag.lower() == "overlay":
filter_check = True
break

View file

@ -945,7 +945,7 @@ class Overlay:
self.horizontal_align, self.horizontal_offset, self.vertical_align, self.vertical_offset = parse_cords(self.data, "overlay")
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 must be used together")
raise Failed(f"Overlay Error: overlay attribute's horizontal_offset and vertical_offset must be used together")
def color(attr):
if attr in self.data and self.data[attr]:
@ -966,7 +966,7 @@ class Overlay:
elif back_width >= 0 and back_height >= 0:
self.back_box = (back_width, back_height)
self.has_back = True if self.back_color or self.back_line_color else False
if self.has_back and not self.has_coordinates():
if self.has_back and not self.has_coordinates() and not self.queue:
raise Failed(f"Overlay Error: horizontal_offset and vertical_offset are required when using a backdrop")
def get_and_save_image(image_url):

View file

@ -1,4 +1,5 @@
import argparse, os, sys, time, traceback, uuid
import argparse, os, sys, time, uuid
from concurrent.futures import ProcessPoolExecutor
from datetime import datetime
try:
@ -18,7 +19,7 @@ parser = argparse.ArgumentParser()
parser.add_argument("-db", "--debug", dest="debug", help=argparse.SUPPRESS, action="store_true", default=False)
parser.add_argument("-tr", "--trace", dest="trace", help=argparse.SUPPRESS, action="store_true", default=False)
parser.add_argument("-c", "--config", dest="config", help="Run with desired *.yml file", type=str)
parser.add_argument("-t", "--time", "--times", dest="times", help="Times to update each day use format HH:MM (Default: 03:00) (comma-separated list)", default="05:00", type=str)
parser.add_argument("-t", "--time", "--times", dest="times", help="Times to update each day use format HH:MM (Default: 05:00) (comma-separated list)", default="05:00", type=str)
parser.add_argument("-re", "--resume", dest="resume", help="Resume collection run from a specific collection", type=str)
parser.add_argument("-r", "--run", dest="run", help="Run without the scheduler", action="store_true", default=False)
parser.add_argument("-is", "--ignore-schedules", dest="ignore_schedules", help="Run ignoring collection schedules", action="store_true", default=False)
@ -115,8 +116,10 @@ from modules.config import ConfigFile
from modules.util import Failed, NotScheduled, Deleted
def my_except_hook(exctype, value, tb):
for _line in traceback.format_exception(etype=exctype, value=value, tb=tb):
logger.critical(_line)
if issubclass(exctype, KeyboardInterrupt):
sys.__excepthook__(exctype, value, tb)
else:
logger.critical("Uncaught Exception", exc_info=(exctype, value, tb))
sys.excepthook = my_except_hook
@ -144,6 +147,10 @@ if not uuid_num:
plexapi.BASE_HEADERS["X-Plex-Client-Identifier"] = str(uuid_num)
def process(attrs):
with ProcessPoolExecutor(max_workers=1) as executor:
executor.submit(start, *[attrs])
def start(attrs):
logger.add_main_handler()
logger.separator()
@ -845,54 +852,55 @@ def run_playlists(config):
logger.remove_playlist_handler(playlist_log_name)
return status, stats
try:
if run or test or collections or libraries or metadata_files or resume:
start({
"config_file": config_file,
"test": test,
"delete": delete,
"ignore_schedules": ignore_schedules,
"collections": collections,
"libraries": libraries,
"metadata_files": metadata_files,
"library_first": library_first,
"resume": resume,
"trace": trace
})
else:
times_to_run = util.get_list(times)
valid_times = []
for time_to_run in times_to_run:
try:
valid_times.append(datetime.strftime(datetime.strptime(time_to_run, "%H:%M"), "%H:%M"))
except ValueError:
if time_to_run:
raise Failed(f"Argument Error: time argument invalid: {time_to_run} must be in the HH:MM format between 00:00-23:59")
else:
raise Failed(f"Argument Error: blank time argument")
for time_to_run in valid_times:
schedule.every().day.at(time_to_run).do(start, {"config_file": config_file, "time": time_to_run, "delete": delete, "library_first": library_first, "trace": trace})
while True:
schedule.run_pending()
if not no_countdown:
current_time = datetime.now().strftime("%H:%M")
seconds = None
og_time_str = ""
for time_to_run in valid_times:
new_seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current_time, "%H:%M")).total_seconds()
if new_seconds < 0:
new_seconds += 86400
if (seconds is None or new_seconds < seconds) and new_seconds > 0:
seconds = new_seconds
og_time_str = time_to_run
if seconds is not None:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else ""
time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}"
logger.ghost(f"Current Time: {current_time} | {time_str} until the next run at {og_time_str} | Runs: {', '.join(times_to_run)}")
else:
logger.error(f"Time Error: {valid_times}")
time.sleep(60)
except KeyboardInterrupt:
logger.separator("Exiting Plex Meta Manager")
if __name__ == "__main__":
try:
if run or test or collections or libraries or metadata_files or resume:
process({
"config_file": config_file,
"test": test,
"delete": delete,
"ignore_schedules": ignore_schedules,
"collections": collections,
"libraries": libraries,
"metadata_files": metadata_files,
"library_first": library_first,
"resume": resume,
"trace": trace
})
else:
times_to_run = util.get_list(times)
valid_times = []
for time_to_run in times_to_run:
try:
valid_times.append(datetime.strftime(datetime.strptime(time_to_run, "%H:%M"), "%H:%M"))
except ValueError:
if time_to_run:
raise Failed(f"Argument Error: time argument invalid: {time_to_run} must be in the HH:MM format between 00:00-23:59")
else:
raise Failed(f"Argument Error: blank time argument")
for time_to_run in valid_times:
schedule.every().day.at(time_to_run).do(process, {"config_file": config_file, "time": time_to_run, "delete": delete, "library_first": library_first, "trace": trace})
while True:
schedule.run_pending()
if not no_countdown:
current_time = datetime.now().strftime("%H:%M")
seconds = None
og_time_str = ""
for time_to_run in valid_times:
new_seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current_time, "%H:%M")).total_seconds()
if new_seconds < 0:
new_seconds += 86400
if (seconds is None or new_seconds < seconds) and new_seconds > 0:
seconds = new_seconds
og_time_str = time_to_run
if seconds is not None:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else ""
time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}"
logger.ghost(f"Current Time: {current_time} | {time_str} until the next run at {og_time_str} | Runs: {', '.join(times_to_run)}")
else:
logger.error(f"Time Error: {valid_times}")
time.sleep(60)
except KeyboardInterrupt:
logger.separator("Exiting Plex Meta Manager")