From 1c502a2fedf3d06ec8f2f1515ef5d2b2cf3f080d Mon Sep 17 00:00:00 2001 From: vasilky3 Date: Sun, 24 Sep 2023 10:56:17 +0300 Subject: [PATCH] add smart show filter Main idea is search lots of torrent using title only. Filter this data using show_year, season_year, episode_year, season, episode, absolute_episode_number,season_name. More over filter looks for ranges in names e.g.: filter for ep 132 will return torrent 'Naruto [1-524]'. Same logic for years and seasons. --- resources/language/English/strings.po | 4 ++ resources/language/messages.pot | 4 ++ resources/settings.xml | 1 + src/client.py | 3 ++ src/filter.py | 66 +++++++++++++++++++++++++++ src/jackett.py | 29 +++++++++--- src/utils.py | 8 ++++ 7 files changed, 108 insertions(+), 7 deletions(-) diff --git a/resources/language/English/strings.po b/resources/language/English/strings.po index 038f935..e7e8077 100644 --- a/resources/language/English/strings.po +++ b/resources/language/English/strings.po @@ -90,6 +90,10 @@ msgctxt "#32058" msgid "When looking for an episode, do a secondary search for season" msgstr "" +msgctxt "#32059" +msgid "Use smart filter for shows. Slower but mach better results" +msgstr "" + msgctxt "#32100" msgid "Filter Keywords" msgstr "" diff --git a/resources/language/messages.pot b/resources/language/messages.pot index ee48a23..b32b0e5 100644 --- a/resources/language/messages.pot +++ b/resources/language/messages.pot @@ -91,6 +91,10 @@ msgctxt "#32058" msgid "When looking for an episode, do a secondary search for season" msgstr "" +msgctxt "#32059" +msgid "Use smart filter for shows. Slower but mach better results" +msgstr "" + msgctxt "#32100" msgid "Filter Keywords" msgstr "" diff --git a/resources/settings.xml b/resources/settings.xml index d8f85df..6a58943 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -16,6 +16,7 @@ + diff --git a/src/client.py b/src/client.py index ac6d823..a20423a 100644 --- a/src/client.py +++ b/src/client.py @@ -172,6 +172,9 @@ def _filter_season(self, results, season): if s_re.search(result['name']) and not ep_re.search(result['name']) ] + def search_title(self, title, imdb_id): + return self.search_shows(title, imdb_id=imdb_id) + def search_season(self, title, season, imdb_id): return self.search_shows(title, season=season, imdb_id=imdb_id) diff --git a/src/filter.py b/src/filter.py index 649c6a1..9bc4940 100644 --- a/src/filter.py +++ b/src/filter.py @@ -1,6 +1,7 @@ # coding=utf-8 from logger import log from utils import get_setting, UNKNOWN +import re # @@ -126,3 +127,68 @@ def release_type(results): for result in results if get_setting('include_release_' + result["release_type"], bool) ] + + +def tv_season_episode(results, season, season_name, episode, global_ep, ep_year, season_year=0, start_year=0): + # Function for sorting large amount of not accurate torrents. Checks year season and episodes. + # The filtering shouldn't be strict. I'm trying not to lose suitable torrents. + filtered = [] + for res in results: + name = res["name"].lower() + # Remove resolution + name = re.sub(r"\d+p", '', name) + log.debug(f"torrent: {name}") + + if season_name and season_name in name: + filtered.append(res) + continue + + year_pattern = r"(?P(?:19|20)\d{2})(?:\s*-\s*(?P[12]\d{3}))?" + y = re.search(year_pattern, name) + if not y: + log.debug("No year") + continue + y_from = int(y.group("from") or "99999") + y_to = int(y.group("to") or "-1") + if (start_year != y_from and ep_year != y_from and season_year != y_from and + (y_from > season_year or season_year > y_to)): + log.debug(f"No suitable year: {ep_year or 'none'} || {season_year or 'none'} || {start_year or 'none'}") + continue + # Remove the year from the text + name_no_year = re.sub(year_pattern, '', name) + + if f"s{season}e{episode}" in name_no_year: + filtered.append(res) + continue + + season_pattern = r"\W(?Ps|season|сезон)[\s\(\[\{]*(?P\d+)(?:\s*-\s*(?P\d+))?" + s = re.search(season_pattern, name_no_year) + if s: + s_from = int(s.group("from") or "99999") + s_to = int(s.group("to") or "-1") + s_flag = s.group("s_flag") + if season == s_from or (s_from <= season <= s_to): # season is suitable + filtered.append(res) + continue + elif s_flag and s_from != 1: # season is marked but not suitable. If season is first need check episodes + log.debug(f"No suitable season: {season or 'none'}") + continue + # Remove the season from the text + else: + log.debug("No season found") + + if not global_ep: + continue + episode_pattern = r"(?:e?(?P\d+)(?:\s*-\s*e?(?P\d+)))|(?P\d+)(?:\s*\+\s*\d*)?(?:\s*(из|of)\s*(?P\d+))" + e = re.search(episode_pattern, name_no_year) + while e: + e_from = int(e.group("from") or "0") + e_to = int(e.group("to") or "0") + e_last = int(e.group("last") or "0") + if (e_from <= global_ep <= e_to) or global_ep <= e_last: + filtered.append(res) + break + name_no_year = re.sub(episode_pattern, '', name_no_year, 1) + e = re.search(episode_pattern, name_no_year) + + return filtered diff --git a/src/jackett.py b/src/jackett.py index 85bda08..662fcbd 100644 --- a/src/jackett.py +++ b/src/jackett.py @@ -54,6 +54,7 @@ def validate_client(): def search(payload, method="general"): + log.info(f"got req from elementum:{payload}") payload = parse_payload(method, payload) log.debug(f"Searching with payload ({method}): f{payload}") @@ -71,9 +72,9 @@ def search(payload, method="general"): log.debug(f"All results: {results}") log.info(f"Jackett returned {len(results)} results in {request_time} seconds") - except Exception as exc: + except Exception: utils.notify(utils.translation(32703)) - log.error(f"Got exeption: {traceback.format_exc()}") + log.error(f"Got exception: {traceback.format_exc()}") finally: p_dialog.close() del p_dialog @@ -111,10 +112,12 @@ def parse_payload(method, payload): log.info(f"Could not determine search title, falling back to normal title: {payload['title']}") payload["search_title"] = payload["title"] + payload['season_name'] = utils.check_season_name(payload["search_title"], payload.get('season_name', "")) + return payload -def filter_results(method, results): +def filter_results(method, results, season, season_name, episode, global_ep, ep_year, season_year=0, start_year=0): log.debug(f"results before filtered: {results}") if get_setting('filter_keywords_enabled', bool): @@ -142,9 +145,15 @@ def filter_results(method, results): results = filter.seed(results) log.debug(f"filtering no seeds results: {results}") + if method == "episode" and get_setting("use_smart_show_filter", bool): + log.info(f"smart-filtering show torrents {len(results)}") + results = filter.tv_season_episode(results, season, season_name, episode, global_ep, ep_year, season_year, + start_year) + log.debug(f"smart-filtering show torrents results: {results}") + # todo maybe rating and codec - log.debug(f"Results resulted in {len(results)} results: {results}") + log.debug(f"Resulted in {len(results)} results: {results}") return results @@ -182,9 +191,13 @@ def search_jackett(p_dialog, payload, method): elif method == 'season': res = jackett.search_season(payload["search_title"], payload["season"], payload["imdb_id"]) elif method == 'episode': - res = jackett.search_episode(payload["search_title"], payload["season"], payload["episode"], payload["imdb_id"]) + if get_setting("use_smart_show_filter", bool): + res = jackett.search_title(payload["search_title"], payload["imdb_id"]) + else: + res = jackett.search_episode(payload["search_title"], payload["season"], payload["episode"], + payload["imdb_id"]) elif method == 'anime': - log.warning("jackett provider does not yet support anime search") + log.warn("jackett provider does not yet support anime search") res = [] log.info(f"anime payload={payload}") # client.search_query(payload["search_title"], payload["season"], payload["episode"], payload["imdb_id"]) @@ -193,7 +206,9 @@ def search_jackett(p_dialog, payload, method): log.debug(f"{method} search returned {len(res)} results") p_dialog.update(25, message=utils.translation(32750)) - res = filter_results(method, res) + res = filter_results(method, res, payload.get('season', None), payload.get('season_name', ""), + payload.get('episode', None), payload.get('absolute_number', None), payload.get('year', None), + payload.get('season_year', None), payload.get('show_year', None)) res = jackett.async_magnet_resolve(res) diff --git a/src/utils.py b/src/utils.py index d946217..ee08355 100644 --- a/src/utils.py +++ b/src/utils.py @@ -116,3 +116,11 @@ def get_provider_color(provider_name): colors[i] = f'{colors[i]:02x}' return "FF" + "".join(colors).upper() + + +def check_season_name(title, season_name=""): + # make sure season name is unique. Not eq to movie title or "season". It saves from false-positive filtering. + season_name = season_name.lower() + if season_name in title or "season" in season_name: + return "" + return season_name