mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-26 06:20:23 +00:00
[12] add .regex modifier for tags
This commit is contained in:
parent
9cf79d2a91
commit
e887bcbffb
8 changed files with 120 additions and 153 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.16.5-develop11
|
1.16.5-develop12
|
||||||
|
|
|
@ -7,6 +7,7 @@ libraries: # This is called out once within
|
||||||
- folder: config/Movies/ # This is a local directory on the system
|
- folder: config/Movies/ # This is a local directory on the system
|
||||||
- git: meisnate12/MovieCharts # This is a file within the GitHub Repository
|
- git: meisnate12/MovieCharts # This is a file within the GitHub Repository
|
||||||
overlay_path:
|
overlay_path:
|
||||||
|
- remove_overlays: false # Set this to true to remove all overlays
|
||||||
- file: config/Overlays.yml # This is a local file on the system
|
- file: config/Overlays.yml # This is a local file on the system
|
||||||
TV Shows:
|
TV Shows:
|
||||||
metadata_path:
|
metadata_path:
|
||||||
|
|
|
@ -143,7 +143,7 @@ libraries:
|
||||||
|
|
||||||
### Remove Overlays
|
### Remove Overlays
|
||||||
|
|
||||||
You can remove overlays from a library by adding `remove_overlays: true` to overlay_path
|
You can remove overlays from a library by adding `remove_overlays: true` to `overlay_path`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
libraries:
|
libraries:
|
||||||
|
@ -151,8 +151,8 @@ libraries:
|
||||||
metadata_path:
|
metadata_path:
|
||||||
- file: config/TV Shows.yml
|
- file: config/TV Shows.yml
|
||||||
overlay_path:
|
overlay_path:
|
||||||
|
- remove_overlays: true
|
||||||
- file: config/Overlays.yml
|
- file: config/Overlays.yml
|
||||||
- remove_overlays: ture
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* This will remove all overlays when run and not generate new ones.
|
* This will remove all overlays when run and not generate new ones.
|
||||||
|
|
|
@ -166,9 +166,10 @@ Tag search can take multiple values as a **list or a comma-separated string**.
|
||||||
### Tag Modifiers
|
### Tag Modifiers
|
||||||
|
|
||||||
| Tag Modifier | Description | Plex Web UI Display |
|
| Tag Modifier | Description | Plex Web UI Display |
|
||||||
|:-------------|:-----------------------------------------------------------------------|:-------------------:|
|
|:-------------|:------------------------------------------------------------------------|:-------------------:|
|
||||||
| No Modifier | Matches every item where the attribute matches the given string | `is` |
|
| No Modifier | Matches every item where the attribute matches the given string | `is` |
|
||||||
| `.not` | Matches every item where the attribute does not match the given string | `is not` |
|
| `.not` | Matches every item where the attribute does not match the given string | `is not` |
|
||||||
|
| `.regex` | Matches every item where one value of this attribute matches the regex. | `N/A` |
|
||||||
|
|
||||||
### Tag Attributes
|
### Tag Attributes
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
|
||||||
|:-------------|:------------------------------------------------------------------------------------------|
|
|:-------------|:------------------------------------------------------------------------------------------|
|
||||||
| No Modifier | Matches every item where the attribute matches the given string |
|
| No Modifier | Matches every item where the attribute matches the given string |
|
||||||
| `.not` | Matches every item where the attribute does not match the given string |
|
| `.not` | Matches every item where the attribute does not match the given string |
|
||||||
|
| `.regex` | Matches every item where one value of this attribute matches the regex. |
|
||||||
| `.count_gt` | Matches every item where the attribute count is greater then the given number |
|
| `.count_gt` | Matches every item where the attribute count is greater then the given number |
|
||||||
| `.count_gte` | Matches every item where the attribute count is greater then or equal to the given number |
|
| `.count_gte` | Matches every item where the attribute count is greater then or equal to the given number |
|
||||||
| `.count_lt` | Matches every item where the attribute count is less then the given number |
|
| `.count_lt` | Matches every item where the attribute count is less then the given number |
|
||||||
|
@ -59,7 +60,7 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
|
||||||
### Attribute
|
### Attribute
|
||||||
|
|
||||||
| Tag Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|
| Tag Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|
||||||
|:--------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
|
|:-----------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
|
||||||
| `actor` | Uses the actor tags to match | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `actor` | Uses the actor tags to match | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `collection` | Uses the collection tags to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `collection` | Uses the collection tags to match | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| `content_rating` | Uses the content rating tags to match | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `content_rating` | Uses the content rating tags to match | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
@ -67,8 +68,6 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
|
||||||
| `country` | Uses the country tags to match | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
| `country` | Uses the country tags to match | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||||
| `director` | Uses the director tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `director` | Uses the director tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `genre` | Uses the genre tags to match | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
|
| `genre` | Uses the genre tags to match | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
|
||||||
| `tmdb_genre`<sup>1</sup> | Uses the genre from TMDb to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| `tmdb_keyword`<sup>1</sup> | Uses the keyword from TMDb to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| `label` | Uses the label tags to match | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
| `label` | Uses the label tags to match | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||||
| `producer` | Uses the actor tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `producer` | Uses the actor tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `year` | Uses the year tag to match | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
|
| `year` | Uses the year tag to match | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
|
||||||
|
@ -76,10 +75,9 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
|
||||||
| `resolution` | Uses the resolution tag to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `resolution` | Uses the resolution tag to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `audio_language` | Uses the audio language tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `audio_language` | Uses the audio language tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `subtitle_language` | Uses the subtitle language tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| `subtitle_language` | Uses the subtitle language tags to match | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| `original_language`<sup>1</sup> | Uses TMDb original language [ISO 639-1 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to match<br>Example: `original_language: en, ko` | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
| `tmdb_genre`<sup>1</sup> | Uses the genre from TMDb to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
| `tmdb_keyword`<sup>1</sup> | Uses the keyword from TMDb to match | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
| `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` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
| `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` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
| `tmdb_status`<sup>1</sup> | Uses TMDb Status to match<br>**Values:** `returning`, `planned`, `production`, `ended`, `canceled`, `pilot` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| `tmdb_type`<sup>1</sup> | Uses TMDb Type to match<br>**Values:** `documentary`, `news`, `production`, `miniseries`, `reality`, `scripted`, `talk_show`, `video` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
|
|
||||||
<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> Also filters out missing movies/shows from being added to Radarr/Sonarr. These Values also cannot use the `count` modifiers.
|
||||||
|
|
||||||
|
@ -160,8 +158,13 @@ Special Filters each have their own set of rules for how they're used.
|
||||||
### Attribute
|
### Attribute
|
||||||
|
|
||||||
| Special Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|
| Special Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|
||||||
|:----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------:|:-------:|:--------:|:--------:|:--------:|:-------:|:--------:|
|
|:--------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:-------:|:--------:|:--------:|:--------:|:--------:|:--------:|
|
||||||
| `history` | Uses the release date attribute (originally available) to match dates throughout history<br>`day`: Match the Day and Month to Today's Date<br>`month`: Match the Month to Today's Date<br>`1-30`: Match the Day and Month to Today's Date or `1-30` days before Today's Date | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
|
| `history` | Uses the release date attribute (originally available) to match dates throughout history<br>`day`: Match the Day and Month to Today's Date<br>`month`: Match the Month to Today's Date<br>`1-30`: Match the Day and Month to Today's Date or `1-30` days before Today's Date | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
|
||||||
|
| `original_language`/`original_language.not`<sup>1</sup> | Uses TMDb original language [ISO 639-1 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to match<br>Example: `original_language: en, ko` | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
| `tmdb_status`/`tmdb_status.not`<sup>1</sup> | Uses TMDb Status to match<br>**Values:** `returning`, `planned`, `production`, `ended`, `canceled`, `pilot` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
| `tmdb_type`/`tmdb_type.not`<sup>1</sup> | Uses TMDb Type to match<br>**Values:** `documentary`, `news`, `production`, `miniseries`, `reality`, `scripted`, `talk_show`, `video` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
|
<sup>1</sup> Also filters out missing movies/shows from being added to Radarr/Sonarr.
|
||||||
|
|
||||||
## Collection Filter Examples
|
## Collection Filter Examples
|
||||||
|
|
||||||
|
|
|
@ -158,20 +158,23 @@ string_filters = ["title", "summary", "studio", "record_label", "filepath", "aud
|
||||||
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
|
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
|
||||||
tag_filters = [
|
tag_filters = [
|
||||||
"actor", "collection", "content_rating", "country", "director", "network", "genre", "label", "producer", "year", "origin_country",
|
"actor", "collection", "content_rating", "country", "director", "network", "genre", "label", "producer", "year", "origin_country",
|
||||||
"writer", "original_language", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre", "tmdb_status", "tmdb_type"
|
"writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre"
|
||||||
]
|
]
|
||||||
tag_modifiers = ["", ".not", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]
|
tag_modifiers = ["", ".not", ".regex", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]
|
||||||
boolean_filters = ["has_collection", "has_overlay", "has_dolby_vision"]
|
boolean_filters = ["has_collection", "has_overlay", "has_dolby_vision"]
|
||||||
date_filters = ["release", "added", "last_played", "first_episode_aired", "last_episode_aired"]
|
date_filters = ["release", "added", "last_played", "first_episode_aired", "last_episode_aired"]
|
||||||
date_modifiers = ["", ".not", ".before", ".after", ".regex"]
|
date_modifiers = ["", ".not", ".before", ".after", ".regex"]
|
||||||
number_filters = ["year", "tmdb_year", "critic_rating", "audience_rating", "user_rating", "tmdb_vote_count", "plays", "duration"]
|
number_filters = ["year", "tmdb_year", "critic_rating", "audience_rating", "user_rating", "tmdb_vote_count", "plays", "duration"]
|
||||||
number_modifiers = [".gt", ".gte", ".lt", ".lte"]
|
number_modifiers = [".gt", ".gte", ".lt", ".lte"]
|
||||||
special_filters = ["history"]
|
special_filters = ["history", "original_language", "original_language.not", "tmdb_status", "tmdb_status.not", "tmdb_type", "tmdb_type.not"]
|
||||||
all_filters = boolean_filters + special_filters + \
|
all_filters = boolean_filters + special_filters + \
|
||||||
[f"{f}{m}" for f in string_filters for m in string_modifiers] + \
|
[f"{f}{m}" for f in string_filters for m in string_modifiers] + \
|
||||||
[f"{f}{m}" for f in tag_filters for m in tag_modifiers] + \
|
[f"{f}{m}" for f in tag_filters for m in tag_modifiers] + \
|
||||||
[f"{f}{m}" for f in date_filters for m in date_modifiers] + \
|
[f"{f}{m}" for f in date_filters for m in date_modifiers] + \
|
||||||
[f"{f}{m}" for f in number_filters for m in number_modifiers]
|
[f"{f}{m}" for f in number_filters for m in number_modifiers]
|
||||||
|
date_attributes = plex.date_attributes + ["first_episode_aired", "last_episode_aired"]
|
||||||
|
year_attributes = plex.year_attributes + ["tmdb_year"]
|
||||||
|
number_attributes = plex.number_attributes + ["tmdb_vote_count"]
|
||||||
smart_invalid = ["collection_order", "collection_level"]
|
smart_invalid = ["collection_order", "collection_level"]
|
||||||
smart_only = ["collection_filtering"]
|
smart_only = ["collection_filtering"]
|
||||||
smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label"] + radarr_details + sonarr_details
|
smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label"] + radarr_details + sonarr_details
|
||||||
|
@ -1773,7 +1776,7 @@ class CollectionBuilder:
|
||||||
display_add += inside_display
|
display_add += inside_display
|
||||||
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
|
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
|
||||||
else:
|
else:
|
||||||
validation = self.validate_attribute(attr, modifier, final_attr, _data, validate, pairs=True)
|
validation = self.validate_attribute(attr, modifier, final_attr, _data, validate, plex_search=True)
|
||||||
if validation is not False and not validation:
|
if validation is not False and not validation:
|
||||||
continue
|
continue
|
||||||
elif attr in plex.date_attributes and modifier in ["", ".not"]:
|
elif attr in plex.date_attributes and modifier in ["", ".not"]:
|
||||||
|
@ -1786,7 +1789,7 @@ class CollectionBuilder:
|
||||||
bool_mod = "" if validation else "!"
|
bool_mod = "" if validation else "!"
|
||||||
bool_arg = "true" if validation else "false"
|
bool_arg = "true" if validation else "false"
|
||||||
results, display_add = build_url_arg(1, mod=bool_mod, arg_s=bool_arg, mod_s="is")
|
results, display_add = build_url_arg(1, mod=bool_mod, arg_s=bool_arg, mod_s="is")
|
||||||
elif (attr in plex.tag_attributes + plex.string_attributes + plex.year_attributes) and modifier in ["", ".is", ".isnot", ".not", ".begins", ".ends"]:
|
elif (attr in plex.tag_attributes + plex.string_attributes + plex.year_attributes) and modifier in ["", ".is", ".isnot", ".not", ".begins", ".ends", ".regex"]:
|
||||||
results = ""
|
results = ""
|
||||||
display_add = ""
|
display_add = ""
|
||||||
for og_value, result in validation:
|
for og_value, result in validation:
|
||||||
|
@ -1839,24 +1842,11 @@ class CollectionBuilder:
|
||||||
|
|
||||||
return type_key, filter_details, filter_url
|
return type_key, filter_details, filter_url
|
||||||
|
|
||||||
def validate_attribute(self, attribute, modifier, final, data, validate, pairs=False):
|
def validate_attribute(self, attribute, modifier, final, data, validate, plex_search=False):
|
||||||
def smart_pair(list_to_pair):
|
def smart_pair(list_to_pair):
|
||||||
return [(t, t) for t in list_to_pair] if pairs else list_to_pair
|
return [(t, t) for t in list_to_pair] if plex_search else list_to_pair
|
||||||
if modifier == ".regex":
|
if modifier == ".regex" and not plex_search:
|
||||||
regex_list = util.get_list(data, split=False)
|
return util.validate_regex(data, self.Type, validate=validate)
|
||||||
valid_regex = []
|
|
||||||
for reg in regex_list:
|
|
||||||
try:
|
|
||||||
re.compile(reg)
|
|
||||||
valid_regex.append(reg)
|
|
||||||
except re.error:
|
|
||||||
logger.stacktrace()
|
|
||||||
err = f"{self.Type} Error: Regular Expression Invalid: {reg}"
|
|
||||||
if validate:
|
|
||||||
raise Failed(err)
|
|
||||||
else:
|
|
||||||
logger.error(err)
|
|
||||||
return valid_regex
|
|
||||||
elif attribute in plex.string_attributes + string_filters and modifier in ["", ".not", ".is", ".isnot", ".begins", ".ends"]:
|
elif attribute in plex.string_attributes + string_filters and modifier in ["", ".not", ".is", ".isnot", ".begins", ".ends"]:
|
||||||
return smart_pair(util.get_list(data, split=False))
|
return smart_pair(util.get_list(data, split=False))
|
||||||
elif attribute == "origin_country":
|
elif attribute == "origin_country":
|
||||||
|
@ -1871,12 +1861,23 @@ class CollectionBuilder:
|
||||||
except Failed:
|
except Failed:
|
||||||
if str(data).lower() in ["day", "month"]:
|
if str(data).lower() in ["day", "month"]:
|
||||||
return data.lower()
|
return data.lower()
|
||||||
|
else:
|
||||||
raise Failed(f"{self.Type} Error: history attribute invalid: {data} must be a number between 1-30, day, or month")
|
raise Failed(f"{self.Type} Error: history attribute invalid: {data} must be a number between 1-30, day, or month")
|
||||||
elif attribute == "tmdb_type":
|
elif attribute == "tmdb_type":
|
||||||
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in discover_types.items()])
|
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in discover_types.items()])
|
||||||
elif attribute == "tmdb_status":
|
elif attribute == "tmdb_status":
|
||||||
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in discover_status.items()])
|
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in discover_status.items()])
|
||||||
elif attribute in plex.tag_attributes and modifier in ["", ".not"]:
|
elif attribute in plex.tag_attributes and modifier in [".regex"]:
|
||||||
|
_, names = self.library.get_search_choices(attribute, title=not plex_search, name_pairs=True)
|
||||||
|
valid_list = []
|
||||||
|
used = []
|
||||||
|
if plex_search and modifier == ".regex":
|
||||||
|
for reg in util.validate_regex(data, self.Type, validate=validate):
|
||||||
|
for name, key in names:
|
||||||
|
if name not in used and re.compile(reg).search(name):
|
||||||
|
valid_list.append((name, key) if plex_search else key)
|
||||||
|
return valid_list
|
||||||
|
elif attribute in plex.tag_attributes and modifier in ["", ".not", ".regex"]:
|
||||||
if attribute in plex.tmdb_attributes:
|
if attribute in plex.tmdb_attributes:
|
||||||
final_values = []
|
final_values = []
|
||||||
for value in util.get_list(data):
|
for value in util.get_list(data):
|
||||||
|
@ -1887,12 +1888,11 @@ class CollectionBuilder:
|
||||||
final_values.append(value)
|
final_values.append(value)
|
||||||
else:
|
else:
|
||||||
final_values = util.get_list(data)
|
final_values = util.get_list(data)
|
||||||
use_title = not pairs
|
search_choices, names = self.library.get_search_choices(attribute, title=not plex_search)
|
||||||
search_choices, names = self.library.get_search_choices(attribute, title=use_title)
|
|
||||||
valid_list = []
|
valid_list = []
|
||||||
for value in final_values:
|
for value in final_values:
|
||||||
if str(value).lower() in search_choices:
|
if str(value).lower() in search_choices:
|
||||||
if pairs:
|
if plex_search:
|
||||||
valid_list.append((value, search_choices[str(value).lower()]))
|
valid_list.append((value, search_choices[str(value).lower()]))
|
||||||
else:
|
else:
|
||||||
valid_list.append(search_choices[str(value).lower()])
|
valid_list.append(search_choices[str(value).lower()])
|
||||||
|
@ -1901,7 +1901,7 @@ class CollectionBuilder:
|
||||||
if attribute in ["actor", "director", "producer", "writer"]:
|
if attribute in ["actor", "director", "producer", "writer"]:
|
||||||
actor_id = self.library.get_actor_id(value)
|
actor_id = self.library.get_actor_id(value)
|
||||||
if actor_id:
|
if actor_id:
|
||||||
if pairs:
|
if plex_search:
|
||||||
valid_list.append((value, actor_id))
|
valid_list.append((value, actor_id))
|
||||||
else:
|
else:
|
||||||
valid_list.append(actor_id)
|
valid_list.append(actor_id)
|
||||||
|
@ -1914,18 +1914,18 @@ class CollectionBuilder:
|
||||||
else:
|
else:
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
return valid_list
|
return valid_list
|
||||||
elif attribute in plex.date_attributes and modifier in [".before", ".after"]:
|
elif attribute in date_attributes and modifier in [".before", ".after"]:
|
||||||
if data == "today":
|
if data == "today":
|
||||||
return datetime.strftime(datetime.now(), "%Y-%m-%d")
|
return datetime.strftime(datetime.now(), "%Y-%m-%d")
|
||||||
else:
|
else:
|
||||||
return util.validate_date(data, final, return_as="%Y-%m-%d")
|
return util.validate_date(data, final, return_as="%Y-%m-%d")
|
||||||
elif attribute in plex.year_attributes + ["tmdb_year"] and modifier in ["", ".not"]:
|
elif attribute in year_attributes and modifier in ["", ".not"]:
|
||||||
final_years = []
|
final_years = []
|
||||||
values = util.get_list(data)
|
values = util.get_list(data)
|
||||||
for value in values:
|
for value in values:
|
||||||
final_years.append(util.parse(self.Type, final, value, datatype="int"))
|
final_years.append(util.parse(self.Type, final, value, datatype="int"))
|
||||||
return smart_pair(final_years)
|
return smart_pair(final_years)
|
||||||
elif (attribute in plex.number_attributes + plex.date_attributes + plex.year_attributes + ["tmdb_year"] and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]) \
|
elif (attribute in number_attributes + date_attributes + year_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]) \
|
||||||
or (attribute in plex.tag_attributes and modifier in [".count_gt", ".count_gte", ".count_lt", ".count_lte"]):
|
or (attribute in plex.tag_attributes and modifier in [".count_gt", ".count_gte", ".count_lt", ".count_lte"]):
|
||||||
return util.parse(self.Type, final, data, datatype="int")
|
return util.parse(self.Type, final, data, datatype="int")
|
||||||
elif attribute in plex.float_attributes and modifier in [".gt", ".gte", ".lt", ".lte"]:
|
elif attribute in plex.float_attributes and modifier in [".gt", ".gte", ".lt", ".lte"]:
|
||||||
|
@ -1944,9 +1944,9 @@ class CollectionBuilder:
|
||||||
attribute = "radarr_add_missing" if self.library.is_movie else "sonarr_add_missing"
|
attribute = "radarr_add_missing" if self.library.is_movie else "sonarr_add_missing"
|
||||||
elif attribute in ["arr_tag", "arr_folder"]:
|
elif attribute in ["arr_tag", "arr_folder"]:
|
||||||
attribute = f"{'rad' if self.library.is_movie else 'son'}{attribute}"
|
attribute = f"{'rad' if self.library.is_movie else 'son'}{attribute}"
|
||||||
elif attribute in plex.date_attributes and modifier in [".gt", ".gte"]:
|
elif attribute in date_attributes and modifier in [".gt", ".gte"]:
|
||||||
modifier = ".after"
|
modifier = ".after"
|
||||||
elif attribute in plex.date_attributes and modifier in [".lt", ".lte"]:
|
elif attribute in date_attributes and modifier in [".lt", ".lte"]:
|
||||||
modifier = ".before"
|
modifier = ".before"
|
||||||
final = f"{attribute}{modifier}"
|
final = f"{attribute}{modifier}"
|
||||||
if text != final:
|
if text != final:
|
||||||
|
@ -2092,7 +2092,15 @@ class CollectionBuilder:
|
||||||
attrs = [c.iso_3166_1 for c in item.countries]
|
attrs = [c.iso_3166_1 for c in item.countries]
|
||||||
else:
|
else:
|
||||||
raise Failed
|
raise Failed
|
||||||
if (not list(set(filter_data) & set(attrs)) and modifier == "") \
|
if modifier == ".regex":
|
||||||
|
has_match = False
|
||||||
|
for reg in filter_data:
|
||||||
|
for name in attrs:
|
||||||
|
if re.compile(reg).search(name):
|
||||||
|
has_match = True
|
||||||
|
if has_match is False:
|
||||||
|
return False
|
||||||
|
elif (not list(set(filter_data) & set(attrs)) and modifier == "") \
|
||||||
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
||||||
return False
|
return False
|
||||||
elif filter_attr == "tmdb_title":
|
elif filter_attr == "tmdb_title":
|
||||||
|
@ -2227,11 +2235,19 @@ class CollectionBuilder:
|
||||||
attrs.extend([s.language for s in part.subtitleStreams()])
|
attrs.extend([s.language for s in part.subtitleStreams()])
|
||||||
elif filter_attr in ["content_rating", "year", "rating"]:
|
elif filter_attr in ["content_rating", "year", "rating"]:
|
||||||
attrs = [getattr(item, filter_actual)]
|
attrs = [getattr(item, filter_actual)]
|
||||||
elif filter_attr in ["actor", "country", "director", "genre", "label", "producer", "writer", "collection"]:
|
elif filter_attr in ["actor", "country", "director", "genre", "label", "producer", "writer", "collection", "network"]:
|
||||||
attrs = [attr.tag for attr in getattr(item, filter_actual)]
|
attrs = [attr.tag for attr in getattr(item, filter_actual)]
|
||||||
else:
|
else:
|
||||||
raise Failed(f"Filter Error: filter: {filter_final} not supported")
|
raise Failed(f"Filter Error: filter: {filter_final} not supported")
|
||||||
if (not list(set(filter_data) & set(attrs)) and modifier == "") \
|
if modifier == ".regex":
|
||||||
|
has_match = False
|
||||||
|
for reg in filter_data:
|
||||||
|
for name in attrs:
|
||||||
|
if re.compile(reg).search(name):
|
||||||
|
has_match = True
|
||||||
|
if has_match is False:
|
||||||
|
return False
|
||||||
|
elif (not list(set(filter_data) & set(attrs)) and modifier == "") \
|
||||||
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
or (list(set(filter_data) & set(attrs)) and modifier == ".not"):
|
||||||
return False
|
return False
|
||||||
logger.ghost(f"Filtering {display} {item.title}")
|
logger.ghost(f"Filtering {display} {item.title}")
|
||||||
|
|
101
modules/plex.py
101
modules/plex.py
|
@ -114,7 +114,7 @@ show_translation = {
|
||||||
}
|
}
|
||||||
modifier_translation = {
|
modifier_translation = {
|
||||||
"": "", ".not": "!", ".is": "%3D", ".isnot": "!%3D", ".gt": "%3E%3E", ".gte": "%3E", ".lt": "%3C%3C", ".lte": "%3C",
|
"": "", ".not": "!", ".is": "%3D", ".isnot": "!%3D", ".gt": "%3E%3E", ".gte": "%3E", ".lt": "%3C%3C", ".lte": "%3C",
|
||||||
".before": "%3C%3C", ".after": "%3E%3E", ".begins": "%3C", ".ends": "%3E"
|
".before": "%3C%3C", ".after": "%3E%3E", ".begins": "%3C", ".ends": "%3E", ".regex": ""
|
||||||
}
|
}
|
||||||
album_sorting_options = {"default": -1, "newest": 0, "oldest": 1, "name": 2}
|
album_sorting_options = {"default": -1, "newest": 0, "oldest": 1, "name": 2}
|
||||||
episode_sorting_options = {"default": -1, "oldest": 0, "newest": 1}
|
episode_sorting_options = {"default": -1, "oldest": 0, "newest": 1}
|
||||||
|
@ -152,84 +152,6 @@ item_advance_keys = {
|
||||||
"item_use_original_title": ("useOriginalTitle", use_original_title_options)
|
"item_use_original_title": ("useOriginalTitle", use_original_title_options)
|
||||||
}
|
}
|
||||||
new_plex_agents = ["tv.plex.agents.movie", "tv.plex.agents.series"]
|
new_plex_agents = ["tv.plex.agents.movie", "tv.plex.agents.series"]
|
||||||
music_searches = [
|
|
||||||
"artist_title", "artist_title.not", "artist_title.is", "artist_title.isnot", "artist_title.begins", "artist_title.ends",
|
|
||||||
"artist_user_rating.gt", "artist_user_rating.gte", "artist_user_rating.lt", "artist_user_rating.lte",
|
|
||||||
"artist_genre", "artist_genre.not",
|
|
||||||
"artist_collection", "artist_collection.not",
|
|
||||||
"artist_country", "artist_country.not",
|
|
||||||
"artist_mood", "artist_mood.not",
|
|
||||||
"artist_style", "artist_style.not",
|
|
||||||
"artist_added", "artist_added.not", "artist_added.before", "artist_added.after",
|
|
||||||
"artist_last_played", "artist_last_played.not", "artist_last_played.before", "artist_last_played.after",
|
|
||||||
"artist_unmatched",
|
|
||||||
"album_title", "album_title.not", "album_title.is", "album_title.isnot", "album_title.begins", "album_title.ends",
|
|
||||||
"album_year.gt", "album_year.gte", "album_year.lt", "album_year.lte",
|
|
||||||
"album_decade",
|
|
||||||
"album_genre", "album_genre.not",
|
|
||||||
"album_plays.gt", "album_plays.gte", "album_plays.lt", "album_plays.lte",
|
|
||||||
"album_last_played", "album_last_played.not", "album_last_played.before", "album_last_played.after",
|
|
||||||
"album_user_rating.gt", "album_user_rating.gte", "album_user_rating.lt", "album_user_rating.lte",
|
|
||||||
"album_critic_rating.gt", "album_critic_rating.gte", "album_critic_rating.lt", "album_critic_rating.lte",
|
|
||||||
"album_record_label", "album_record_label.not", "album_record_label.is", "album_record_label.isnot", "album_record_label.begins", "album_record_label.ends",
|
|
||||||
"album_mood", "album_mood.not",
|
|
||||||
"album_style", "album_style.not",
|
|
||||||
"album_format", "album_format.not",
|
|
||||||
"album_type", "album_type.not",
|
|
||||||
"album_collection", "album_collection.not",
|
|
||||||
"album_added", "album_added.not", "album_added.before", "album_added.after",
|
|
||||||
"album_released", "album_released.not", "album_released.before", "album_released.after",
|
|
||||||
"album_unmatched",
|
|
||||||
"album_source", "album_source.not",
|
|
||||||
"album_label", "album_label.not",
|
|
||||||
"track_mood", "track_mood.not",
|
|
||||||
"track_title", "track_title.not", "track_title.is", "track_title.isnot", "track_title.begins", "track_title.ends",
|
|
||||||
"track_plays.gt", "track_plays.gte", "track_plays.lt", "track_plays.lte",
|
|
||||||
"track_last_played", "track_last_played.not", "track_last_played.before", "track_last_played.after",
|
|
||||||
"track_skips.gt", "track_skips.gte", "track_skips.lt", "track_skips.lte",
|
|
||||||
"track_last_skipped", "track_last_skipped.not", "track_last_skipped.before", "track_last_skipped.after",
|
|
||||||
"track_user_rating.gt", "track_user_rating.gte", "track_user_rating.lt", "track_user_rating.lte",
|
|
||||||
"track_last_rated", "track_last_rated.not", "track_last_rated.before", "track_last_rated.after",
|
|
||||||
"track_added", "track_added.not", "track_added.before", "track_added.after",
|
|
||||||
"track_trash",
|
|
||||||
"track_source", "track_source.not"
|
|
||||||
]
|
|
||||||
searches = [
|
|
||||||
"title", "title.not", "title.is", "title.isnot", "title.begins", "title.ends",
|
|
||||||
"studio", "studio.not", "studio.is", "studio.isnot", "studio.begins", "studio.ends",
|
|
||||||
"actor", "actor.not",
|
|
||||||
"audio_language", "audio_language.not",
|
|
||||||
"collection", "collection.not",
|
|
||||||
"season_collection", "season_collection.not",
|
|
||||||
"episode_collection", "episode_collection.not",
|
|
||||||
"content_rating", "content_rating.not",
|
|
||||||
"country", "country.not",
|
|
||||||
"director", "director.not",
|
|
||||||
"genre", "genre.not",
|
|
||||||
"label", "label.not",
|
|
||||||
"network", "network.not",
|
|
||||||
"producer", "producer.not",
|
|
||||||
"subtitle_language", "subtitle_language.not",
|
|
||||||
"writer", "writer.not",
|
|
||||||
"decade", "resolution", "hdr", "unmatched", "duplicate", "unplayed", "progress", "trash",
|
|
||||||
"last_played", "last_played.not", "last_played.before", "last_played.after",
|
|
||||||
"added", "added.not", "added.before", "added.after",
|
|
||||||
"release", "release.not", "release.before", "release.after",
|
|
||||||
"duration.gt", "duration.gte", "duration.lt", "duration.lte",
|
|
||||||
"plays.gt", "plays.gte", "plays.lt", "plays.lte",
|
|
||||||
"user_rating.gt", "user_rating.gte", "user_rating.lt", "user_rating.lte",
|
|
||||||
"critic_rating.gt", "critic_rating.gte", "critic_rating.lt", "critic_rating.lte",
|
|
||||||
"audience_rating.gt", "audience_rating.gte", "audience_rating.lt", "audience_rating.lte",
|
|
||||||
"year", "year.not", "year.gt", "year.gte", "year.lt", "year.lte",
|
|
||||||
"unplayed_episodes", "episode_unplayed", "episode_duplicate", "episode_progress", "episode_unmatched", "show_unmatched",
|
|
||||||
"episode_title", "episode_title.not", "episode_title.is", "episode_title.isnot", "episode_title.begins", "episode_title.ends",
|
|
||||||
"episode_added", "episode_added.not", "episode_added.before", "episode_added.after",
|
|
||||||
"episode_air_date", "episode_air_date.not", "episode_air_date.before", "episode_air_date.after",
|
|
||||||
"episode_last_played", "episode_last_played.not", "episode_last_played.before", "episode_last_played.after",
|
|
||||||
"episode_plays.gt", "episode_plays.gte", "episode_plays.lt", "episode_plays.lte",
|
|
||||||
"episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt", "episode_user_rating.lte",
|
|
||||||
"episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt", "episode_year.lte"
|
|
||||||
] + music_searches
|
|
||||||
and_searches = [
|
and_searches = [
|
||||||
"title.and", "studio.and", "actor.and", "audio_language.and", "collection.and",
|
"title.and", "studio.and", "actor.and", "audio_language.and", "collection.and",
|
||||||
"content_rating.and", "country.and", "director.and", "genre.and", "label.and",
|
"content_rating.and", "country.and", "director.and", "genre.and", "label.and",
|
||||||
|
@ -260,6 +182,7 @@ show_only_searches = [
|
||||||
"unplayed_episodes", "episode_unplayed", "episode_duplicate", "episode_progress", "episode_unmatched", "show_unmatched",
|
"unplayed_episodes", "episode_unplayed", "episode_duplicate", "episode_progress", "episode_unmatched", "show_unmatched",
|
||||||
]
|
]
|
||||||
string_attributes = ["title", "studio", "episode_title", "artist_title", "album_title", "album_record_label", "track_title"]
|
string_attributes = ["title", "studio", "episode_title", "artist_title", "album_title", "album_record_label", "track_title"]
|
||||||
|
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends"]
|
||||||
float_attributes = [
|
float_attributes = [
|
||||||
"user_rating", "episode_user_rating", "critic_rating", "audience_rating", "duration",
|
"user_rating", "episode_user_rating", "critic_rating", "audience_rating", "duration",
|
||||||
"artist_user_rating", "album_user_rating", "album_critic_rating", "track_user_rating"
|
"artist_user_rating", "album_user_rating", "album_critic_rating", "track_user_rating"
|
||||||
|
@ -271,11 +194,13 @@ boolean_attributes = [
|
||||||
tmdb_attributes = ["actor", "director", "producer", "writer"]
|
tmdb_attributes = ["actor", "director", "producer", "writer"]
|
||||||
date_attributes = [
|
date_attributes = [
|
||||||
"added", "episode_added", "release", "episode_air_date", "last_played", "episode_last_played",
|
"added", "episode_added", "release", "episode_air_date", "last_played", "episode_last_played",
|
||||||
"first_episode_aired", "last_episode_aired", "artist_added", "artist_last_played", "album_last_played",
|
"artist_added", "artist_last_played", "album_last_played",
|
||||||
"album_added", "album_released", "track_last_played", "track_last_skipped", "track_last_rated", "track_added"
|
"album_added", "album_released", "track_last_played", "track_last_skipped", "track_last_rated", "track_added"
|
||||||
]
|
]
|
||||||
|
date_modifiers = ["", ".not", ".before", ".after"]
|
||||||
year_attributes = ["decade", "year", "episode_year", "album_year", "album_decade"]
|
year_attributes = ["decade", "year", "episode_year", "album_year", "album_decade"]
|
||||||
number_attributes = ["plays", "episode_plays", "tmdb_vote_count", "album_plays", "track_plays", "track_skips"] + year_attributes
|
number_attributes = ["plays", "episode_plays", "album_plays", "track_plays", "track_skips"] + year_attributes
|
||||||
|
number_modifiers = [".gt", ".gte", ".lt", ".lte"]
|
||||||
search_display = {"added": "Date Added", "release": "Release Date", "hdr": "HDR", "progress": "In Progress", "episode_progress": "Episode In Progress"}
|
search_display = {"added": "Date Added", "release": "Release Date", "hdr": "HDR", "progress": "In Progress", "episode_progress": "Episode In Progress"}
|
||||||
tag_attributes = [
|
tag_attributes = [
|
||||||
"actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label", "network",
|
"actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label", "network",
|
||||||
|
@ -283,6 +208,14 @@ tag_attributes = [
|
||||||
"artist_genre", "artist_collection", "artist_country", "artist_mood", "artist_style", "album_genre", "album_mood",
|
"artist_genre", "artist_collection", "artist_country", "artist_mood", "artist_style", "album_genre", "album_mood",
|
||||||
"album_style", "album_format", "album_type", "album_collection", "album_source", "album_label", "track_mood", "track_source"
|
"album_style", "album_format", "album_type", "album_collection", "album_source", "album_label", "track_mood", "track_source"
|
||||||
]
|
]
|
||||||
|
tag_modifiers = ["", ".not", ".regex"]
|
||||||
|
no_mods = ["resolution", "decade", "album_decade"]
|
||||||
|
searches = boolean_attributes + no_mods + \
|
||||||
|
[f"{f}{m}" for f in string_attributes for m in string_modifiers] + \
|
||||||
|
[f"{f}{m}" for f in tag_attributes + year_attributes for m in tag_modifiers if f not in no_mods] + \
|
||||||
|
[f"{f}{m}" for f in date_attributes for m in date_modifiers] + \
|
||||||
|
[f"{f}{m}" for f in number_attributes + float_attributes for m in number_modifiers if f not in no_mods]
|
||||||
|
music_searches = [a for a in searches if a.startswith(("artist", "album", "track"))]
|
||||||
movie_sorts = {
|
movie_sorts = {
|
||||||
"title.asc": "titleSort", "title.desc": "titleSort%3Adesc",
|
"title.asc": "titleSort", "title.desc": "titleSort%3Adesc",
|
||||||
"year.asc": "year", "year.desc": "year%3Adesc",
|
"year.asc": "year", "year.desc": "year%3Adesc",
|
||||||
|
@ -580,7 +513,7 @@ class Plex(Library):
|
||||||
return result.id
|
return result.id
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
|
||||||
def get_search_choices(self, search_name, title=True):
|
def get_search_choices(self, search_name, title=True, name_pairs=False):
|
||||||
final_search = search_translation[search_name] if search_name in search_translation else search_name
|
final_search = search_translation[search_name] if search_name in search_translation else search_name
|
||||||
final_search = show_translation[final_search] if self.is_show and final_search in show_translation else final_search
|
final_search = show_translation[final_search] if self.is_show and final_search in show_translation else final_search
|
||||||
try:
|
try:
|
||||||
|
@ -589,9 +522,7 @@ class Plex(Library):
|
||||||
use_title = title and final_search not in ["contentRating", "audioLanguage", "subtitleLanguage", "resolution"]
|
use_title = title and final_search not in ["contentRating", "audioLanguage", "subtitleLanguage", "resolution"]
|
||||||
for choice in self.Plex.listFilterChoices(final_search):
|
for choice in self.Plex.listFilterChoices(final_search):
|
||||||
if choice.title not in names:
|
if choice.title not in names:
|
||||||
names.append(choice.title)
|
names.append((choice.title, choice.key) if name_pairs else choice.title)
|
||||||
if choice.key not in names:
|
|
||||||
names.append(choice.key)
|
|
||||||
choices[choice.title] = choice.title if use_title else choice.key
|
choices[choice.title] = choice.title if use_title else choice.key
|
||||||
choices[choice.key] = choice.title if use_title else choice.key
|
choices[choice.key] = choice.title if use_title else choice.key
|
||||||
choices[choice.title.lower()] = choice.title if use_title else choice.key
|
choices[choice.title.lower()] = choice.title if use_title else choice.key
|
||||||
|
|
|
@ -143,6 +143,21 @@ def validate_date(date_text, method, return_as=None):
|
||||||
raise Failed(f"Collection Error: {method}: {date_text} must match pattern YYYY-MM-DD (e.g. 2020-12-25) or MM/DD/YYYY (e.g. 12/25/2020)")
|
raise Failed(f"Collection Error: {method}: {date_text} must match pattern YYYY-MM-DD (e.g. 2020-12-25) or MM/DD/YYYY (e.g. 12/25/2020)")
|
||||||
return datetime.strftime(date_obg, return_as) if return_as else date_obg
|
return datetime.strftime(date_obg, return_as) if return_as else date_obg
|
||||||
|
|
||||||
|
def validate_regex(data, col_type, validate=True):
|
||||||
|
regex_list = get_list(data, split=False)
|
||||||
|
valid_regex = []
|
||||||
|
for reg in regex_list:
|
||||||
|
try:
|
||||||
|
re.compile(reg)
|
||||||
|
valid_regex.append(reg)
|
||||||
|
except re.error:
|
||||||
|
err = f"{col_type} Error: Regular Expression Invalid: {reg}"
|
||||||
|
if validate:
|
||||||
|
raise Failed(err)
|
||||||
|
else:
|
||||||
|
logger.error(err)
|
||||||
|
return valid_regex
|
||||||
|
|
||||||
def logger_input(prompt, timeout=60):
|
def logger_input(prompt, timeout=60):
|
||||||
if windows: return windows_input(prompt, timeout)
|
if windows: return windows_input(prompt, timeout)
|
||||||
elif hasattr(signal, "SIGALRM"): return unix_input(prompt, timeout)
|
elif hasattr(signal, "SIGALRM"): return unix_input(prompt, timeout)
|
||||||
|
|
Loading…
Reference in a new issue