[41] add video_codec, video_profile, audio_codec, audio_profile, channels, height, width, and aspect filters

This commit is contained in:
meisnate12 2022-06-15 23:20:40 -04:00
parent 0e98db0644
commit c7ccae1253
5 changed files with 72 additions and 28 deletions

View file

@ -1 +1 @@
1.17.0-develop40 1.17.0-develop41

View file

@ -78,6 +78,10 @@ Tag filters can take multiple values as a **list or a comma-separated string**.
| `resolution` | Uses the resolution tag to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; | | `resolution` | Uses the resolution tag to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `audio_language` | Uses the audio language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; | | `audio_language` | Uses the audio language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `subtitle_language` | Uses the subtitle language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; | | `subtitle_language` | Uses the subtitle language tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `video_codec` | Uses the video codec tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `video_profile` | Uses the video profile tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `audio_codec` | Uses the audio codec tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `audio_profile` | Uses the audio profile tags to match | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `tmdb_genre`<sup>2</sup> | Uses the genre from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; | | `tmdb_genre`<sup>2</sup> | Uses the genre from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `tmdb_keyword`<sup>2</sup> | Uses the keyword from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; | | `tmdb_keyword`<sup>2</sup> | Uses the keyword from TMDb to match | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `origin_country`<sup>2</sup> | Uses TMDb origin country [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to match<br>Example: `origin_country: us` | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; | | `origin_country`<sup>2</sup> | Uses TMDb origin country [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to match<br>Example: `origin_country: us` | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
@ -138,6 +142,8 @@ Number filters can **NOT** take multiple values.
| Number Modifier | Description | Format | | Number Modifier | Description | Format |
|:----------------|:-------------------------------------------------------------------------------------------|:-------------------------------------------------:| |:----------------|:-------------------------------------------------------------------------------------------|:-------------------------------------------------:|
| No Modifier | Matches every item where the number attribute is equal to the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` |
| `.not` | Matches every item where the number attribute is not equal to the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` |
| `.gt` | Matches every item where the number attribute is greater than the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` | | `.gt` | Matches every item where the number attribute is greater than the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` |
| `.gte` | Matches every item where the number attribute is greater than or equal to the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` | | `.gte` | Matches every item where the number attribute is greater than or equal to the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` |
| `.lt` | Matches every item where the number attribute is less than the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` | | `.lt` | Matches every item where the number attribute is less than the given number | **Format:** number<br>e.g. `30`, `1995`, or `7.5` |
@ -145,18 +151,24 @@ Number filters can **NOT** take multiple values.
### Attribute ### Attribute
| Number Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track | | Number Filters | Description | Movies | Shows | Seasons | Episodes | Artists | Albums | Track |
|:------------------------------|:---------------------------------------------------------------------|:-------:|:-------:|:--------:|:--------:|:--------:|:--------:|:--------:| |:------------------------------|:---------------------------------------------------------------------|:-------:|:-------------------:|:-------------------:|:--------:|:--------:|:--------:|:--------:|
| `year` | Uses the year attribute to match<br>minimum: `1` | &#9989; | &#9989; | &#9989; | &#9989; | &#10060; | &#9989; | &#9989; | | `year` | Uses the year attribute to match<br>minimum: `1` | &#9989; | &#9989; | &#9989; | &#9989; | &#10060; | &#9989; | &#9989; |
| `tmdb_year`<sup>1</sup> | Uses the year on TMDb to match<br>minimum: `1` | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; | | `tmdb_year`<sup>2</sup> | Uses the year on TMDb to match<br>minimum: `1` | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `critic_rating` | Uses the critic rating attribute to match<br>`0.0` - `10.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#9989; | &#10060; | | `critic_rating` | Uses the critic rating attribute to match<br>`0.0` - `10.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#9989; | &#10060; |
| `audience_rating` | Uses the audience rating attribute to match<br> `0.0` - `10.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | | `audience_rating` | Uses the audience rating attribute to match<br> `0.0` - `10.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
| `user_rating` | Uses the user rating attribute to match<br>`0.0` - `10.0` | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | | `user_rating` | Uses the user rating attribute to match<br>`0.0` - `10.0` | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `tmdb_vote_count`<sup>1</sup> | Uses the tmdb vote count to match<br>minimum: `1` | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; | | `tmdb_vote_count`<sup>2</sup> | Uses the tmdb vote count to match<br>minimum: `1` | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; | &#10060; | &#10060; |
| `plays` | Uses the plays attribute to match<br>minimum: `1` | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | | `plays` | Uses the plays attribute to match<br>minimum: `1` | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; | &#9989; |
| `duration` | Uses the duration attribute to match using minutes<br>minimum: `0.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#9989; | | `duration` | Uses the duration attribute to match using minutes<br>minimum: `0.0` | &#9989; | &#9989; | &#10060; | &#9989; | &#10060; | &#10060; | &#9989; |
| `channels` | Uses the audio channels attribute to match<br>minimum: `0` | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `height` | Uses the height attribute to match<br>minimum: `0` | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `width` | Uses the width attribute to match<br>minimum: `0` | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
| `aspect` | Uses the aspect attribute to match<br>minimum: `0.0` | &#9989; | &#9989;<sup>1</sup> | &#9989;<sup>1</sup> | &#9989; | &#10060; | &#10060; | &#10060; |
<sup>1</sup> Also filters out missing movies/shows from being added to Radarr/Sonarr. <sup>1</sup> Filters using the special `episodes` [filter](#special-filters) with the [default percent](details/setting).
<sup>2</sup> Also filters out missing movies/shows from being added to Radarr/Sonarr.
## Special Filters ## Special Filters

View file

@ -70,11 +70,15 @@ discover_status = {
"Returning Series": "returning", "Planned": "planned", "In Production": "production", "Returning Series": "returning", "Planned": "planned", "In Production": "production",
"Ended": "ended", "Canceled": "canceled", "Pilot": "pilot" "Ended": "ended", "Canceled": "canceled", "Pilot": "pilot"
} }
sub_filters = [
"filepath", "audio_track_title", "resolution", "audio_language", "subtitle_language", "has_dolby_vision",
"channels", "height", "width", "aspect", "audio_codec", "audio_profile", "video_codec", "video_profile"
]
filters_by_type = { filters_by_type = {
"movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays", "filepath", "label", "audio_track_title"], "movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays", "filepath", "label", "audio_track_title"],
"movie_show_season_episode_album_track": ["year"], "movie_show_season_episode_album_track": ["year"],
"movie_show_season_episode_artist_album": ["has_overlay"], "movie_show_season_episode_artist_album": ["has_overlay"],
"movie_show_season_episode": ["resolution", "audio_language", "subtitle_language", "has_dolby_vision"], "movie_show_season_episode": ["resolution", "audio_language", "subtitle_language", "has_dolby_vision", "channels", "height", "width", "aspect", "audio_codec", "audio_profile", "video_codec", "video_profile"],
"movie_show_episode_album": ["release", "critic_rating", "history"], "movie_show_episode_album": ["release", "critic_rating", "history"],
"movie_show_episode_track": ["duration"], "movie_show_episode_track": ["duration"],
"movie_show_artist_album": ["genre"], "movie_show_artist_album": ["genre"],
@ -105,15 +109,18 @@ tmdb_filters = [
string_filters = ["title", "summary", "studio", "record_label", "folder", "filepath", "audio_track_title", "tmdb_title"] string_filters = ["title", "summary", "studio", "record_label", "folder", "filepath", "audio_track_title", "tmdb_title"]
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"] 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",
"writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre" "origin_country", "writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre",
"audio_codec", "audio_profile", "video_codec", "video_profile"
] ]
tag_modifiers = ["", ".not", ".regex", ".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 = [
number_modifiers = [".gt", ".gte", ".lt", ".lte"] "year", "tmdb_year", "critic_rating", "audience_rating", "user_rating", "tmdb_vote_count", "plays", "duration",
"channels", "height", "width", "aspect"]
number_modifiers = ["", ".not", ".gt", ".gte", ".lt", ".lte"]
special_filters = [ special_filters = [
"history", "episodes", "seasons", "albums", "tracks", "original_language", "original_language.not", "history", "episodes", "seasons", "albums", "tracks", "original_language", "original_language.not",
"tmdb_status", "tmdb_status.not", "tmdb_type", "tmdb_type.not" "tmdb_status", "tmdb_status.not", "tmdb_type", "tmdb_type.not"
@ -125,7 +132,10 @@ all_filters = boolean_filters + special_filters + \
[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"] date_attributes = plex.date_attributes + ["first_episode_aired", "last_episode_aired"]
year_attributes = plex.year_attributes + ["tmdb_year"] year_attributes = plex.year_attributes + ["tmdb_year"]
number_attributes = plex.number_attributes + ["tmdb_vote_count"] number_attributes = plex.number_attributes + ["channels", "height", "width"]
tag_attributes = plex.tag_attributes + ["audio_codec", "audio_profile", "video_codec", "video_profile"]
float_attributes = plex.float_attributes + ["aspect"]
boolean_attributes = plex.boolean_attributes + boolean_filters
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_report", "smart_label"] + radarr_details + sonarr_details smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_report", "smart_label"] + radarr_details + sonarr_details
@ -1437,7 +1447,7 @@ class CollectionBuilder:
final_data = self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate) final_data = self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)
if filter_attr in tmdb_filters: if filter_attr in tmdb_filters:
self.tmdb_filters.append((filter_final, final_data)) self.tmdb_filters.append((filter_final, final_data))
elif self.collection_level in ["show", "season", "artist", "album"] and filter_attr in ["filepath", "audio_track_title", "resolution", "audio_language", "subtitle_language", "has_dolby_vision"]: elif self.collection_level in ["show", "season", "artist", "album"] and filter_attr in sub_filters:
self.filters.append(("episodes" if self.collection_level in ["show", "season"] else "tracks", {filter_final: final_data, "percentage": self.default_percent})) self.filters.append(("episodes" if self.collection_level in ["show", "season"] else "tracks", {filter_final: final_data, "percentage": self.default_percent}))
else: else:
self.filters.append((filter_final, final_data)) self.filters.append((filter_final, final_data))
@ -1852,7 +1862,7 @@ class CollectionBuilder:
def validate_attribute(self, attribute, modifier, final, data, validate, plex_search=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 plex_search else list_to_pair return [(t, t) for t in list_to_pair] if plex_search else list_to_pair
if attribute in plex.tag_attributes and modifier in [".regex"]: if attribute in tag_attributes and modifier in [".regex"]:
_, names = self.library.get_search_choices(attribute, title=not plex_search, name_pairs=True) _, names = self.library.get_search_choices(attribute, title=not plex_search, name_pairs=True)
valid_list = [] valid_list = []
used = [] used = []
@ -1892,7 +1902,7 @@ class CollectionBuilder:
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 tag_attributes and modifier in ["", ".not"]:
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):
@ -1948,11 +1958,11 @@ class CollectionBuilder:
search_data = util.parse(self.Type, final, data, datatype="int", minimum=0) search_data = util.parse(self.Type, final, data, datatype="int", minimum=0)
return f"{search_data}{search_mod}" if plex_search else search_data return f"{search_data}{search_mod}" if plex_search else search_data
elif (attribute in number_attributes + year_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]) \ elif (attribute in number_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 tag_attributes and modifier in [".count_gt", ".count_gte", ".count_lt", ".count_lte"]):
return util.parse(self.Type, final, data, datatype="int", minimum=0) return util.parse(self.Type, final, data, datatype="int", minimum=0)
elif attribute in plex.float_attributes and modifier in [".gt", ".gte", ".lt", ".lte"]: elif attribute in float_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
return util.parse(self.Type, final, data, datatype="float", minimum=0, maximum=None if attribute == "duration" else 10) return util.parse(self.Type, final, data, datatype="float", minimum=0, maximum=None if attribute == "duration" else 10)
elif attribute in plex.boolean_attributes + boolean_filters: elif attribute in boolean_attributes:
return util.parse(self.Type, attribute, data, datatype="bool") return util.parse(self.Type, attribute, data, datatype="bool")
elif attribute in ["seasons", "episodes", "albums", "tracks"]: elif attribute in ["seasons", "episodes", "albums", "tracks"]:
if isinstance(data, dict) and data: if isinstance(data, dict) and data:

View file

@ -120,6 +120,13 @@ modifier_translation = {
".before": "%3C%3C", ".after": "%3E%3E", ".begins": "%3C", ".ends": "%3E", ".regex": "" ".before": "%3C%3C", ".after": "%3E%3E", ".begins": "%3C", ".ends": "%3E", ".regex": ""
} }
attribute_translation = { attribute_translation = {
"aspect": "aspectRatio",
"channels": "audioChannels",
"audio_codec": "audioCodec",
"audio_profile ": "audioProfile",
"video_codec": "videoCodec",
"video_profile": "videoProfile",
"resolution": "videoResolution",
"record_label": "studio", "record_label": "studio",
"actor": "actors", "actor": "actors",
"audience_rating": "audienceRating", "audience_rating": "audienceRating",
@ -1257,6 +1264,11 @@ class Plex(Library):
for media in item.media: for media in item.media:
for part in media.parts: for part in media.parts:
values.extend([a.extendedDisplayTitle for a in part.audioStreams() if a.extendedDisplayTitle]) values.extend([a.extendedDisplayTitle for a in part.audioStreams() if a.extendedDisplayTitle])
elif filter_attr in ["audio_codec", "audio_profile", "video_codec", "video_profile"]:
for media in item.media:
attr = getattr(media, filter_actual)
if attr and attr not in values:
values.append(attr)
elif filter_attr in ["filepath", "folder"]: elif filter_attr in ["filepath", "folder"]:
values = [loc for loc in item.locations] values = [loc for loc in item.locations]
else: else:
@ -1322,12 +1334,20 @@ class Plex(Library):
failures += 1 failures += 1
if failures > failure_threshold: if failures > failure_threshold:
return False return False
elif modifier in [".gt", ".gte", ".lt", ".lte", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]: elif filter_attr in builder.number_filters or modifier in [".gt", ".gte", ".lt", ".lte", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]:
divider = 60000 if filter_attr == "duration" else 1 divider = 60000 if filter_attr == "duration" else 1
test_number = [] test_number = []
if filter_attr == "resolution": if filter_attr in ["resolution", "audio_codec", "audio_profile", "video_codec", "video_profile"]:
for media in item.media: for media in item.media:
test_number.append(media.videoResolution) attr = getattr(media, filter_actual)
if attr and attr not in test_number:
test_number.append(attr)
elif filter_attr in ["channels", "height", "width", "aspect"]:
test_number = 0
for media in item.media:
attr = getattr(media, filter_actual)
if attr and attr > test_number:
test_number = attr
elif filter_attr == "audio_language": elif filter_attr == "audio_language":
for media in item.media: for media in item.media:
for part in media.parts: for part in media.parts:

View file

@ -488,7 +488,9 @@ def is_date_filter(value, modifier, data, final, current_time):
return False return False
def is_number_filter(value, modifier, data): def is_number_filter(value, modifier, data):
return value is None or (modifier == ".gt" and value <= data) \ return value is None or (modifier == "" and value == data) \
or (modifier == ".not" and value != data) \
or (modifier == ".gt" and value <= data) \
or (modifier == ".gte" and value < data) \ or (modifier == ".gte" and value < data) \
or (modifier == ".lt" and value >= data) \ or (modifier == ".lt" and value >= data) \
or (modifier == ".lte" and value > data) or (modifier == ".lte" and value > data)