From c25ee45e94b26c9bd59d49b41317c82ce51c7565 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 25 Feb 2021 16:59:56 -0500 Subject: [PATCH] #66 filter by duration and filter validation --- modules/builder.py | 45 ++++++++++++++++++------------------- modules/plex.py | 26 +++++++--------------- modules/util.py | 55 +++++++++++++++++++++++++++++++--------------- 3 files changed, 68 insertions(+), 58 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 99343c67..9f8c0e6e 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -22,6 +22,8 @@ class CollectionBuilder: self.posters = [] self.backgrounds = [] self.schedule = None + current_time = datetime.now() + current_year = current_time.year if "template" in data: if not self.library.templates: @@ -113,7 +115,6 @@ class CollectionBuilder: skip_collection = False else: schedule_list = util.get_list(data["schedule"]) - current_time = datetime.now() next_month = current_time.replace(day=28) + timedelta(days=4) last_day = next_month - timedelta(days=next_month.day) for schedule in schedule_list: @@ -316,10 +317,24 @@ class CollectionBuilder: logger.warning(f"Collection Warning: {f} filter will run as {filter_method}") else: filter_method = f - if filter_method in util.movie_only_filters and self.library.is_show: raise Failed(f"Collection Error: {filter_method} filter only works for movie libraries") - elif data[m][f] is None: raise Failed(f"Collection Error: {filter_method} filter is blank") - elif filter_method in util.all_filters: self.filters.append((filter_method, data[m][f])) - else: raise Failed(f"Collection Error: {filter_method} filter not supported") + if filter_method in util.movie_only_filters and self.library.is_show: + raise Failed(f"Collection Error: {filter_method} filter only works for movie libraries") + elif data[m][f] is None: + raise Failed(f"Collection Error: {filter_method} filter is blank") + elif filter_method == "year": + self.filters.append((filter_method, util.get_year_list(data[m][f], f"{filter_method} filter"))) + elif filter_method in ["max_age", "duration.gte", "duration.lte"]: + self.filters.append((filter_method, util.check_number(data[m][f], f"{filter_method} filter", minimum=1))) + elif filter_method in ["year.gte", "year.lte"]: + self.filters.append((filter_method, util.check_number(data[m][f], f"{filter_method} filter", minimum=1800, maximum=current_year))) + elif filter_method in ["rating.gte", "rating.lte"]: + self.filters.append((filter_method, util.check_number(data[m][f], f"{filter_method} filter", number_type="float", minimum=0.1, maximum=10))) + elif filter_method in ["originally_available.gte", "originally_available.lte"]: + self.filters.append((filter_method, util.check_date(data[m][f], f"{filter_method} filter"))) + elif filter_method in util.all_filters: + self.filters.append((filter_method, data[m][f])) + else: + raise Failed(f"Collection Error: {filter_method} filter not supported") elif method_name == "plex_collectionless": new_dictionary = {} prefix_list = [] @@ -396,24 +411,11 @@ class CollectionBuilder: if attr_data is True: new_dictionary[attr] = attr_data elif attr in ["primary_release_date.gte", "primary_release_date.lte", "release_date.gte", "release_date.lte", "air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte"]: - if re.compile("[0-1]?[0-9][/-][0-3]?[0-9][/-][1-2][890][0-9][0-9]").match(str(attr_data)): - the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-") - new_dictionary[attr] = f"{the_date[2]}-{the_date[0]}-{the_date[1]}" - elif re.compile("[1-2][890][0-9][0-9][/-][0-1]?[0-9][/-][0-3]?[0-9]").match(str(attr_data)): - the_date = str(attr_data).split("/") if "/" in str(attr_data) else str(attr_data).split("-") - new_dictionary[attr] = f"{the_date[0]}-{the_date[1]}-{the_date[2]}" - else: - raise Failed(f"Collection Error: {m} attribute {attr}: {attr_data} must match pattern MM/DD/YYYY e.g. 12/25/2020") + new_dictionary[attr] = util.check_date(attr_data, f"{m} attribute {attr}", return_string=True) elif attr in ["primary_release_year", "year", "first_air_date_year"]: - if isinstance(attr_data, int) and 1800 < attr_data < 2200: - new_dictionary[attr] = attr_data - else: - raise Failed(f"Collection Error: {m} attribute {attr}: must be a valid year e.g. 1990") + new_dictionary[attr] = util.check_number(attr_data, f"{m} attribute {attr}", minimum=1800, maximum=current_year + 1) elif attr in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]: - if (isinstance(attr_data, int) or isinstance(attr_data, float)) and 0 < attr_data: - new_dictionary[attr] = attr_data - else: - raise Failed(f"Collection Error: {m} attribute {attr}: must be a valid number greater then 0") + new_dictionary[attr] = util.check_number(attr_data, f"{m} attribute {attr}", minimum=1) elif attr in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]: new_dictionary[attr] = attr_data else: @@ -448,7 +450,6 @@ class CollectionBuilder: elif data[m]["sort_by"] not in util.mal_season_sort: logger.warning(f"Collection Warning: mal_season sort_by attribute {data[m]['sort_by']} invalid must be either 'members' or 'score' using members as default") else: new_dictionary["sort_by"] = util.mal_season_sort[data[m]["sort_by"]] - current_time = datetime.now() if current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter" elif current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring" elif current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer" diff --git a/modules/plex.py b/modules/plex.py index 2b8c3673..47dc9c62 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -139,8 +139,7 @@ class PlexAPI: method = util.filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else util.filter_alias[f[0]] if method == "max_age": threshold_date = datetime.now() - timedelta(days=f[1]) - attr = getattr(current, "originallyAvailableAt") - if attr is None or attr < threshold_date: + if current.originallyAvailableAt is None or current.originallyAvailableAt < threshold_date: match = False break elif method == "original_language": @@ -160,17 +159,12 @@ class PlexAPI: match = False break elif modifier in [".gte", ".lte"]: - if method == "originallyAvailableAt": - threshold_date = datetime.strptime(f[1], "%m/%d/%y") - attr = getattr(current, "originallyAvailableAt") - if (modifier == ".lte" and attr > threshold_date) or (modifier == ".gte" and attr < threshold_date): - match = False - break - elif method in ["year", "rating"]: - attr = getattr(current, method) - if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]): - match = False - break + attr = getattr(current, method) + if method == "duration": + attr = attr / 60000 + if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]): + match = False + break else: terms = util.get_list(f[1]) attrs = [] @@ -214,11 +208,7 @@ class PlexAPI: logger.info("") year = None if "year" in self.metadata[m]: - now = datetime.now() - if self.metadata[m]["year"] is None: logger.error("Metadata Error: year attribute is blank") - elif not isinstance(self.metadata[m]["year"], int): logger.error("Metadata Error: year attribute must be an integer") - elif self.metadata[m]["year"] not in range(1800, now.year + 2): logger.error(f"Metadata Error: year attribute must be between 1800-{now.year + 1}") - else: year = self.metadata[m]["year"] + year = util.check_number(self.metadata[m]["year"], "year", minimum=1800, maximum=datetime.now().year + 1) title = m if "title" in self.metadata[m]: diff --git a/modules/util.py b/modules/util.py index d8793246..1236a2e8 100644 --- a/modules/util.py +++ b/modules/util.py @@ -1,4 +1,5 @@ -import datetime, logging, re, signal, sys, time, traceback +import logging, re, signal, sys, time, traceback +from datetime import datetime try: import msvcrt @@ -369,6 +370,7 @@ all_filters = [ "genre", "genre.not", "max_age", "originally_available.gte", "originally_available.lte", + "duration.gte", "duration.lte", "original_language", "original_language.not", "rating.gte", "rating.lte", "studio", "studio.not", @@ -381,6 +383,7 @@ movie_only_filters = [ "audio_language", "audio_language.not", "country", "country.not", "director", "director.not", + "duration.gte", "duration.lte", "original_language", "original_language.not", "subtitle_language", "subtitle_language.not", "video_resolution", "video_resolution.not", @@ -511,33 +514,49 @@ def get_int_list(data, id_type): def get_year_list(data, method): values = get_list(data) final_years = [] - current_year = datetime.datetime.now().year + current_year = datetime.now().year for value in values: try: if "-" in value: year_range = re.search("(\\d{4})-(\\d{4}|NOW)", str(value)) - start = year_range.group(1) - end = year_range.group(2) - if end == "NOW": - end = current_year - if int(start) < 1800 or int(start) > current_year: logger.error(f"Collection Error: Skipping {method} starting year {start} must be between 1800 and {current_year}") - elif int(end) < 1800 or int(end) > current_year: logger.error(f"Collection Error: Skipping {method} ending year {end} must be between 1800 and {current_year}") - elif int(start) > int(end): logger.error(f"Collection Error: Skipping {method} starting year {start} cannot be greater then ending year {end}") + start = check_year(year_range.group(1), current_year, method) + end = current_year if year_range.group(2) == "NOW" else check_year(year_range.group(2), current_year, method) + if int(start) > int(end): + raise Failed(f"Collection Error: {method} starting year: {start} cannot be greater then ending year {end}") else: for i in range(int(start), int(end) + 1): - final_years.append(i) + final_years.append(int(i)) else: - year = re.search("(\\d+)", str(value)).group(1) - if int(year) < 1800 or int(year) > current_year: - logger.error(f"Collection Error: Skipping {method} year {year} must be between 1800 and {current_year}") - else: - if len(str(year)) != len(str(value)): - logger.warning(f"Collection Warning: {value} can be replaced with {year}") - final_years.append(year) + final_years.append(check_year(value, current_year, method)) except AttributeError: - logger.error(f"Collection Error: Skipping {method} failed to parse year from {value}") + raise Failed(f"Collection Error: {method} failed to parse year from {value}") return final_years +def check_year(year, current_year, method): + return check_number(year, method, minimum=1800, maximum=current_year) + +def check_number(value, method, number_type="int", minimum=None, maximum=None): + if number_type == "int": + try: num_value = int(str(value)) + except ValueError: raise Failed(f"Collection Error: {method}: {value} must be an integer") + elif number_type == "float": + try: num_value = float(str(value)) + except ValueError: raise Failed(f"Collection Error: {method}: {value} must be a number") + else: raise Failed(f"Number Type: {number_type} invalid") + if minimum is not None and maximum is not None and (num_value < minimum or num_value > maximum): + raise Failed(f"Collection Error: {method}: {num_value} must be between {minimum} and {maximum}") + elif minimum is not None and num_value < minimum: + raise Failed(f"Collection Error: {method}: {num_value} is less then {minimum}") + elif maximum is not None and num_value > maximum: + raise Failed(f"Collection Error: {method}: {num_value} is greater then {maximum}") + else: + return num_value + +def check_date(date_text, method, return_string=False): + try: date_obg = datetime.strptime(str(date_text), "%m/%d/%Y") + except ValueError: raise Failed(f"Collection Error: {method}: {date_text} must match pattern MM/DD/YYYY e.g. 12/25/2020") + return str(date_text) if return_string else date_obg + def logger_input(prompt, timeout=60): if windows: return windows_input(prompt, timeout) elif hasattr(signal, "SIGALRM"): return unix_input(prompt, timeout)