mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-22 12:33:10 +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
|
||||
- git: meisnate12/MovieCharts # This is a file within the GitHub Repository
|
||||
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
|
||||
TV Shows:
|
||||
metadata_path:
|
||||
|
|
|
@ -143,7 +143,7 @@ libraries:
|
|||
|
||||
### 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
|
||||
libraries:
|
||||
|
@ -151,8 +151,8 @@ libraries:
|
|||
metadata_path:
|
||||
- file: config/TV Shows.yml
|
||||
overlay_path:
|
||||
- remove_overlays: true
|
||||
- file: config/Overlays.yml
|
||||
- remove_overlays: ture
|
||||
```
|
||||
|
||||
* 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 Modifier | Description | Plex Web UI Display |
|
||||
|:-------------|:-----------------------------------------------------------------------|:-------------------:|
|
||||
|:-------------|:------------------------------------------------------------------------|:-------------------:|
|
||||
| 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` |
|
||||
| `.regex` | Matches every item where one value of this attribute matches the regex. | `N/A` |
|
||||
|
||||
### 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 |
|
||||
| `.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_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 |
|
||||
|
@ -59,7 +60,7 @@ 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 | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| `collection` | Uses the collection 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 | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| `director` | Uses the director 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 | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||
| `producer` | Uses the actor tags 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 | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| `audio_language` | Uses the audio 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` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| `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.
|
||||
|
||||
|
@ -160,8 +158,13 @@ Special Filters each have their own set of rules for how they're used.
|
|||
### Attribute
|
||||
|
||||
| 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 | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
|
||||
| `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
|
||||
|
||||
|
|
|
@ -158,20 +158,23 @@ string_filters = ["title", "summary", "studio", "record_label", "filepath", "aud
|
|||
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
|
||||
tag_filters = [
|
||||
"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"]
|
||||
date_filters = ["release", "added", "last_played", "first_episode_aired", "last_episode_aired"]
|
||||
date_modifiers = ["", ".not", ".before", ".after", ".regex"]
|
||||
number_filters = ["year", "tmdb_year", "critic_rating", "audience_rating", "user_rating", "tmdb_vote_count", "plays", "duration"]
|
||||
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 + \
|
||||
[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 date_filters for m in date_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_only = ["collection_filtering"]
|
||||
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
|
||||
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
|
||||
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:
|
||||
continue
|
||||
elif attr in plex.date_attributes and modifier in ["", ".not"]:
|
||||
|
@ -1786,7 +1789,7 @@ class CollectionBuilder:
|
|||
bool_mod = "" if validation else "!"
|
||||
bool_arg = "true" if validation else "false"
|
||||
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 = ""
|
||||
display_add = ""
|
||||
for og_value, result in validation:
|
||||
|
@ -1839,24 +1842,11 @@ class CollectionBuilder:
|
|||
|
||||
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):
|
||||
return [(t, t) for t in list_to_pair] if pairs else list_to_pair
|
||||
if modifier == ".regex":
|
||||
regex_list = util.get_list(data, split=False)
|
||||
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
|
||||
return [(t, t) for t in list_to_pair] if plex_search else list_to_pair
|
||||
if modifier == ".regex" and not plex_search:
|
||||
return util.validate_regex(data, self.Type, validate=validate)
|
||||
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))
|
||||
elif attribute == "origin_country":
|
||||
|
@ -1871,12 +1861,23 @@ class CollectionBuilder:
|
|||
except Failed:
|
||||
if str(data).lower() in ["day", "month"]:
|
||||
return data.lower()
|
||||
else:
|
||||
raise Failed(f"{self.Type} Error: history attribute invalid: {data} must be a number between 1-30, day, or month")
|
||||
elif attribute == "tmdb_type":
|
||||
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in discover_types.items()])
|
||||
elif attribute == "tmdb_status":
|
||||
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:
|
||||
final_values = []
|
||||
for value in util.get_list(data):
|
||||
|
@ -1887,12 +1888,11 @@ class CollectionBuilder:
|
|||
final_values.append(value)
|
||||
else:
|
||||
final_values = util.get_list(data)
|
||||
use_title = not pairs
|
||||
search_choices, names = self.library.get_search_choices(attribute, title=use_title)
|
||||
search_choices, names = self.library.get_search_choices(attribute, title=not plex_search)
|
||||
valid_list = []
|
||||
for value in final_values:
|
||||
if str(value).lower() in search_choices:
|
||||
if pairs:
|
||||
if plex_search:
|
||||
valid_list.append((value, search_choices[str(value).lower()]))
|
||||
else:
|
||||
valid_list.append(search_choices[str(value).lower()])
|
||||
|
@ -1901,7 +1901,7 @@ class CollectionBuilder:
|
|||
if attribute in ["actor", "director", "producer", "writer"]:
|
||||
actor_id = self.library.get_actor_id(value)
|
||||
if actor_id:
|
||||
if pairs:
|
||||
if plex_search:
|
||||
valid_list.append((value, actor_id))
|
||||
else:
|
||||
valid_list.append(actor_id)
|
||||
|
@ -1914,18 +1914,18 @@ class CollectionBuilder:
|
|||
else:
|
||||
logger.error(error)
|
||||
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":
|
||||
return datetime.strftime(datetime.now(), "%Y-%m-%d")
|
||||
else:
|
||||
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 = []
|
||||
values = util.get_list(data)
|
||||
for value in values:
|
||||
final_years.append(util.parse(self.Type, final, value, datatype="int"))
|
||||
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"]):
|
||||
return util.parse(self.Type, final, data, datatype="int")
|
||||
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"
|
||||
elif attribute in ["arr_tag", "arr_folder"]:
|
||||
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"
|
||||
elif attribute in plex.date_attributes and modifier in [".lt", ".lte"]:
|
||||
elif attribute in date_attributes and modifier in [".lt", ".lte"]:
|
||||
modifier = ".before"
|
||||
final = f"{attribute}{modifier}"
|
||||
if text != final:
|
||||
|
@ -2092,7 +2092,15 @@ class CollectionBuilder:
|
|||
attrs = [c.iso_3166_1 for c in item.countries]
|
||||
else:
|
||||
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"):
|
||||
return False
|
||||
elif filter_attr == "tmdb_title":
|
||||
|
@ -2227,11 +2235,19 @@ class CollectionBuilder:
|
|||
attrs.extend([s.language for s in part.subtitleStreams()])
|
||||
elif filter_attr in ["content_rating", "year", "rating"]:
|
||||
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)]
|
||||
else:
|
||||
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"):
|
||||
return False
|
||||
logger.ghost(f"Filtering {display} {item.title}")
|
||||
|
|
101
modules/plex.py
101
modules/plex.py
|
@ -114,7 +114,7 @@ show_translation = {
|
|||
}
|
||||
modifier_translation = {
|
||||
"": "", ".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}
|
||||
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)
|
||||
}
|
||||
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 = [
|
||||
"title.and", "studio.and", "actor.and", "audio_language.and", "collection.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",
|
||||
]
|
||||
string_attributes = ["title", "studio", "episode_title", "artist_title", "album_title", "album_record_label", "track_title"]
|
||||
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends"]
|
||||
float_attributes = [
|
||||
"user_rating", "episode_user_rating", "critic_rating", "audience_rating", "duration",
|
||||
"artist_user_rating", "album_user_rating", "album_critic_rating", "track_user_rating"
|
||||
|
@ -271,11 +194,13 @@ boolean_attributes = [
|
|||
tmdb_attributes = ["actor", "director", "producer", "writer"]
|
||||
date_attributes = [
|
||||
"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"
|
||||
]
|
||||
date_modifiers = ["", ".not", ".before", ".after"]
|
||||
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"}
|
||||
tag_attributes = [
|
||||
"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",
|
||||
"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 = {
|
||||
"title.asc": "titleSort", "title.desc": "titleSort%3Adesc",
|
||||
"year.asc": "year", "year.desc": "year%3Adesc",
|
||||
|
@ -580,7 +513,7 @@ class Plex(Library):
|
|||
return result.id
|
||||
|
||||
@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 = show_translation[final_search] if self.is_show and final_search in show_translation else final_search
|
||||
try:
|
||||
|
@ -589,9 +522,7 @@ class Plex(Library):
|
|||
use_title = title and final_search not in ["contentRating", "audioLanguage", "subtitleLanguage", "resolution"]
|
||||
for choice in self.Plex.listFilterChoices(final_search):
|
||||
if choice.title not in names:
|
||||
names.append(choice.title)
|
||||
if choice.key not in names:
|
||||
names.append(choice.key)
|
||||
names.append((choice.title, choice.key) if name_pairs else choice.title)
|
||||
choices[choice.title] = 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
|
||||
|
|
|
@ -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)")
|
||||
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):
|
||||
if windows: return windows_input(prompt, timeout)
|
||||
elif hasattr(signal, "SIGALRM"): return unix_input(prompt, timeout)
|
||||
|
|
Loading…
Reference in a new issue