From dfc18b8d54e5c2453da6f60c51416b5ad16476c9 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 10:59:39 +0000 Subject: [PATCH 01/23] Small code change for parsing air-by-date shows to get season/episode info --- sickbeard/name_parser/parser.py | 1289 ++++++++++++++++--------------- 1 file changed, 646 insertions(+), 643 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 7fd5dfca..95dc14c7 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -1,644 +1,647 @@ -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -from __future__ import with_statement - -import os -import time -import re -import datetime -import os.path -import regexes -import sickbeard - -from sickbeard import logger, helpers, scene_numbering, common, exceptions, scene_exceptions, encodingKludge as ek, db -from dateutil import parser - - -class NameParser(object): - ALL_REGEX = 0 - NORMAL_REGEX = 1 - SPORTS_REGEX = 2 - ANIME_REGEX = 3 - - def __init__(self, file_name=True, showObj=None, tryIndexers=False, convert=False, - naming_pattern=False): - - self.file_name = file_name - self.showObj = showObj - self.tryIndexers = tryIndexers - self.convert = convert - self.naming_pattern = naming_pattern - - if self.showObj and not self.showObj.is_anime and not self.showObj.is_sports: - self._compile_regexes(self.NORMAL_REGEX) - elif self.showObj and self.showObj.is_anime: - self._compile_regexes(self.ANIME_REGEX) - elif self.showObj and self.showObj.is_sports: - self._compile_regexes(self.SPORTS_REGEX) - else: - self._compile_regexes(self.ALL_REGEX) - - def clean_series_name(self, series_name): - """Cleans up series name by removing any . and _ - characters, along with any trailing hyphens. - - Is basically equivalent to replacing all _ and . with a - space, but handles decimal numbers in string, for example: - - >>> cleanRegexedSeriesName("an.example.1.0.test") - 'an example 1.0 test' - >>> cleanRegexedSeriesName("an_example_1.0_test") - 'an example 1.0 test' - - Stolen from dbr's tvnamer - """ - - series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name) - series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot - series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name) - series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name) - series_name = series_name.replace("_", " ") - series_name = re.sub("-$", "", series_name) - series_name = re.sub("^\[.*\]", "", series_name) - return series_name.strip() - - def _compile_regexes(self, regexMode): - if regexMode == self.SPORTS_REGEX: - logger.log(u"Using SPORTS regexs", logger.DEBUG) - uncompiled_regex = [regexes.sports_regexs] - elif regexMode == self.ANIME_REGEX: - logger.log(u"Using ANIME regexs", logger.DEBUG) - uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes] - elif regexMode == self.NORMAL_REGEX: - logger.log(u"Using NORMAL regexs", logger.DEBUG) - uncompiled_regex = [regexes.normal_regexes] - else: - logger.log(u"Using ALL regexes", logger.DEBUG) - uncompiled_regex = [regexes.normal_regexes, regexes.sports_regexs, regexes.anime_regexes] - - self.compiled_regexes = [] - for regexItem in uncompiled_regex: - for cur_pattern_num, (cur_pattern_name, cur_pattern) in enumerate(regexItem): - try: - cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE) - except re.error, errormsg: - logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern)) - else: - self.compiled_regexes.append((cur_pattern_num, cur_pattern_name, cur_regex)) - - def _parse_string(self, name): - if not name: - return - - matches = [] - bestResult = None - - for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes: - match = cur_regex.match(name) - - if not match: - continue - - result = ParseResult(name) - result.which_regex = [cur_regex_name] - result.score = 0 - cur_regex_num - - named_groups = match.groupdict().keys() - - if 'series_name' in named_groups: - result.series_name = match.group('series_name') - if result.series_name: - result.series_name = self.clean_series_name(result.series_name) - result.score += 1 - - if 'season_num' in named_groups: - tmp_season = int(match.group('season_num')) - if cur_regex_name == 'bare' and tmp_season in (19, 20): - continue - result.season_number = tmp_season - result.score += 1 - - if 'ep_num' in named_groups: - ep_num = self._convert_number(match.group('ep_num')) - if 'extra_ep_num' in named_groups and match.group('extra_ep_num'): - result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1) - result.score += 1 - else: - result.episode_numbers = [ep_num] - result.score += 1 - - if 'ep_ab_num' in named_groups: - ep_ab_num = self._convert_number(match.group('ep_ab_num')) - if 'extra_ab_ep_num' in named_groups and match.group('extra_ab_ep_num'): - result.ab_episode_numbers = range(ep_ab_num, - self._convert_number(match.group('extra_ab_ep_num')) + 1) - result.score += 1 - else: - result.ab_episode_numbers = [ep_ab_num] - result.score += 1 - - if 'sports_event_id' in named_groups: - sports_event_id = match.group('sports_event_id') - if sports_event_id: - result.sports_event_id = int(match.group('sports_event_id')) - result.score += 1 - - if 'sports_event_name' in named_groups: - result.sports_event_name = match.group('sports_event_name') - if result.sports_event_name: - result.sports_event_name = self.clean_series_name(result.sports_event_name) - result.score += 1 - - if 'sports_air_date' in named_groups: - sports_air_date = match.group('sports_air_date') - try: - result.sports_air_date = parser.parse(sports_air_date, fuzzy=True).date() - result.score += 1 - except: - continue - - if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups: - year = int(match.group('air_year')) - month = int(match.group('air_month')) - day = int(match.group('air_day')) - - try: - dtStr = '%s-%s-%s' % (year, month, day) - result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date() - result.score += 1 - except: - continue - - if 'extra_info' in named_groups: - tmp_extra_info = match.group('extra_info') - - # Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season - if tmp_extra_info and cur_regex_name == 'season_only' and re.search( - r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I): - continue - result.extra_info = tmp_extra_info - result.score += 1 - - if 'release_group' in named_groups: - result.release_group = match.group('release_group') - result.score += 1 - - if 'version' in named_groups: - # assigns version to anime file if detected using anime regex. Non-anime regex receives -1 - version = match.group('version') - if version: - result.version = version - else: - result.version = 1 - else: - result.version = -1 - - - matches.append(result) - - if len(matches): - # pick best match with highest score based on placement - bestResult = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score) - - show = None - if not self.naming_pattern: - # try and create a show object for this result - show = helpers.get_show(bestResult.series_name, self.tryIndexers) - - # confirm passed in show object indexer id matches result show object indexer id - if show: - if self.showObj and show.indexerid != self.showObj.indexerid: - show = None - bestResult.show = show - elif not show and self.showObj: - bestResult.show = self.showObj - - # if this is a naming pattern test or result doesn't have a show object then return best result - if not bestResult.show or self.naming_pattern: - return bestResult - - # get quality - bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) - - new_episode_numbers = [] - new_season_numbers = [] - new_absolute_numbers = [] - - # if we have an air-by-date show then get the real season/episode numbers - if bestResult.is_air_by_date or bestResult.is_sports: - airdate = bestResult.air_date.toordinal() if bestResult.air_date else bestResult.sports_air_date.toordinal() - - myDB = db.DBConnection() - sql_result = myDB.select( - "SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?", - [bestResult.show.indexerid, bestResult.show.indexer, airdate]) - - if sql_result: - season_number = int(sql_result[0][0]) - episode_numbers = [int(sql_result[0][1])] - - for epNo in episode_numbers: - s = season_number - e = epNo - - if self.convert: - (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, - bestResult.show.indexer, - season_number, - epNo) - new_episode_numbers.append(e) - new_season_numbers.append(s) - - elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers): - scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1] - for epAbsNo in bestResult.ab_episode_numbers: - a = epAbsNo - - if self.convert: - a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid, - bestResult.show.indexer, epAbsNo, - True, scene_season) - - (s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a]) - - new_absolute_numbers.append(a) - new_episode_numbers.extend(e) - new_season_numbers.append(s) - - elif bestResult.season_number and len(bestResult.episode_numbers): - for epNo in bestResult.episode_numbers: - s = bestResult.season_number - e = epNo - - if self.convert: - (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, - bestResult.show.indexer, - bestResult.season_number, - epNo) - if bestResult.show.is_anime: - a = helpers.get_absolute_number_from_season_and_episode(bestResult.show, s, e) - if a: - new_absolute_numbers.append(a) - - new_episode_numbers.append(e) - new_season_numbers.append(s) - - # need to do a quick sanity check heregex. It's possible that we now have episodes - # from more than one season (by tvdb numbering), and this is just too much - # for sickbeard, so we'd need to flag it. - new_season_numbers = list(set(new_season_numbers)) # remove duplicates - if len(new_season_numbers) > 1: - raise InvalidNameException("Scene numbering results episodes from " - "seasons %s, (i.e. more than one) and " - "sickrage does not support this. " - "Sorry." % (str(new_season_numbers))) - - # I guess it's possible that we'd have duplicate episodes too, so lets - # eliminate them - new_episode_numbers = list(set(new_episode_numbers)) - new_episode_numbers.sort() - - # maybe even duplicate absolute numbers so why not do them as well - new_absolute_numbers = list(set(new_absolute_numbers)) - new_absolute_numbers.sort() - - if len(new_absolute_numbers): - bestResult.ab_episode_numbers = new_absolute_numbers - - if len(new_season_numbers) and len(new_episode_numbers): - bestResult.episode_numbers = new_episode_numbers - bestResult.season_number = new_season_numbers[0] - - if self.convert: - logger.log( - u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8', - 'xmlcharrefreplace'), - logger.DEBUG) - - # CPU sleep - time.sleep(0.02) - - return bestResult - - def _combine_results(self, first, second, attr): - # if the first doesn't exist then return the second or nothing - if not first: - if not second: - return None - else: - return getattr(second, attr) - - # if the second doesn't exist then return the first - if not second: - return getattr(first, attr) - - a = getattr(first, attr) - b = getattr(second, attr) - - # if a is good use it - if a != None or (type(a) == list and len(a)): - return a - # if not use b (if b isn't set it'll just be default) - else: - return b - - def _unicodify(self, obj, encoding="utf-8"): - if isinstance(obj, basestring): - if not isinstance(obj, unicode): - obj = unicode(obj, encoding, 'replace') - return obj - - def _convert_number(self, org_number): - """ - Convert org_number into an integer - org_number: integer or representation of a number: string or unicode - Try force converting to int first, on error try converting from Roman numerals - returns integer or 0 - """ - - try: - # try forcing to int - if org_number: - number = int(org_number) - else: - number = 0 - - except: - # on error try converting from Roman numerals - roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), - ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), - ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) - ) - - roman_numeral = str(org_number).upper() - number = 0 - index = 0 - - for numeral, integer in roman_to_int_map: - while roman_numeral[index:index + len(numeral)] == numeral: - number += integer - index += len(numeral) - - return number - - def parse(self, name, cache_result=True): - name = self._unicodify(name) - - if self.naming_pattern: - cache_result = False - - cached = name_parser_cache.get(name) - if cached: - return cached - - # break it into parts if there are any (dirname, file name, extension) - dir_name, file_name = ek.ek(os.path.split, name) - - if self.file_name: - base_file_name = helpers.remove_extension(file_name) - else: - base_file_name = file_name - - # set up a result to use - final_result = ParseResult(name) - - # try parsing the file name - file_name_result = self._parse_string(base_file_name) - - # use only the direct parent dir - dir_name = os.path.basename(dir_name) - - # parse the dirname for extra info if needed - dir_name_result = self._parse_string(dir_name) - - # build the ParseResult object - final_result.air_date = self._combine_results(file_name_result, dir_name_result, 'air_date') - - # anime absolute numbers - final_result.ab_episode_numbers = self._combine_results(file_name_result, dir_name_result, 'ab_episode_numbers') - - # sports - final_result.sports_event_id = self._combine_results(file_name_result, dir_name_result, 'sports_event_id') - final_result.sports_event_name = self._combine_results(file_name_result, dir_name_result, 'sports_event_name') - final_result.sports_air_date = self._combine_results(file_name_result, dir_name_result, 'sports_air_date') - - if not final_result.air_date and not final_result.sports_air_date: - final_result.season_number = self._combine_results(file_name_result, dir_name_result, 'season_number') - final_result.episode_numbers = self._combine_results(file_name_result, dir_name_result, 'episode_numbers') - - # if the dirname has a release group/show name I believe it over the filename - final_result.series_name = self._combine_results(dir_name_result, file_name_result, 'series_name') - final_result.extra_info = self._combine_results(dir_name_result, file_name_result, 'extra_info') - final_result.release_group = self._combine_results(dir_name_result, file_name_result, 'release_group') - final_result.version = self._combine_results(dir_name_result, file_name_result, 'version') - - final_result.which_regex = [] - if final_result == file_name_result: - final_result.which_regex = file_name_result.which_regex - elif final_result == dir_name_result: - final_result.which_regex = dir_name_result.which_regex - else: - if file_name_result: - final_result.which_regex += file_name_result.which_regex - if dir_name_result: - final_result.which_regex += dir_name_result.which_regex - - final_result.show = self._combine_results(file_name_result, dir_name_result, 'show') - final_result.quality = self._combine_results(file_name_result, dir_name_result, 'quality') - - if not final_result.show: - raise InvalidShowException( - "Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) - - # if there's no useful info in it then raise an exception - if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and final_result.sports_air_date == None and not final_result.ab_episode_numbers and not final_result.series_name: - raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) - - if cache_result: - name_parser_cache.add(name, final_result) - - logger.log(u"Parsed " + name + " into " + str(final_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG) - return final_result - - -class ParseResult(object): - def __init__(self, - original_name, - series_name=None, - sports_event_id=None, - sports_event_name=None, - sports_air_date=None, - season_number=None, - episode_numbers=None, - extra_info=None, - release_group=None, - air_date=None, - ab_episode_numbers=None, - show=None, - score=None, - quality=None, - version=None - ): - - self.original_name = original_name - - self.series_name = series_name - self.season_number = season_number - if not episode_numbers: - self.episode_numbers = [] - else: - self.episode_numbers = episode_numbers - - if not ab_episode_numbers: - self.ab_episode_numbers = [] - else: - self.ab_episode_numbers = ab_episode_numbers - - if not quality: - self.quality = common.Quality.UNKNOWN - else: - self.quality = quality - - self.extra_info = extra_info - self.release_group = release_group - - self.air_date = air_date - - self.sports_event_id = sports_event_id - self.sports_event_name = sports_event_name - self.sports_air_date = sports_air_date - - self.which_regex = [] - self.show = show - self.score = score - - self.version = version - - def __eq__(self, other): - if not other: - return False - - if self.series_name != other.series_name: - return False - if self.season_number != other.season_number: - return False - if self.episode_numbers != other.episode_numbers: - return False - if self.extra_info != other.extra_info: - return False - if self.release_group != other.release_group: - return False - if self.air_date != other.air_date: - return False - if self.sports_event_id != other.sports_event_id: - return False - if self.sports_event_name != other.sports_event_name: - return False - if self.sports_air_date != other.sports_air_date: - return False - if self.ab_episode_numbers != other.ab_episode_numbers: - return False - if self.show != other.show: - return False - if self.score != other.score: - return False - if self.quality != other.quality: - return False - if self.version != other.version: - return False - - return True - - def __str__(self): - if self.series_name != None: - to_return = self.series_name + u' - ' - else: - to_return = u'' - if self.season_number != None: - to_return += 'S' + str(self.season_number) - if self.episode_numbers and len(self.episode_numbers): - for e in self.episode_numbers: - to_return += 'E' + str(e) - - if self.is_air_by_date: - to_return += str(self.air_date) - if self.is_sports: - to_return += str(self.sports_event_name) - to_return += str(self.sports_event_id) - to_return += str(self.sports_air_date) - if self.ab_episode_numbers: - to_return += ' [ABS: ' + str(self.ab_episode_numbers) + ']' - if self.version: - to_return += ' [ANIME VER: ' + str(self.version) + ']' - - if self.release_group: - to_return += ' [GROUP: ' + self.release_group + ']' - - to_return += ' [ABD: ' + str(self.is_air_by_date) + ']' - to_return += ' [SPORTS: ' + str(self.is_sports) + ']' - to_return += ' [ANIME: ' + str(self.is_anime) + ']' - to_return += ' [whichReg: ' + str(self.which_regex) + ']' - - return to_return.encode('utf-8') - - @property - def is_air_by_date(self): - if self.season_number == None and len(self.episode_numbers) == 0 and self.air_date: - return True - return False - - @property - def is_sports(self): - if self.season_number == None and len(self.episode_numbers) == 0 and self.sports_air_date: - return True - return False - - @property - def is_anime(self): - if len(self.ab_episode_numbers): - return True - return False - - -class NameParserCache(object): - _previous_parsed = {} - _cache_size = 100 - - def add(self, name, parse_result): - self._previous_parsed[name] = parse_result - _current_cache_size = len(self._previous_parsed) - if _current_cache_size > self._cache_size: - for i in range(_current_cache_size - self._cache_size): - del self._previous_parsed[self._previous_parsed.keys()[0]] - - def get(self, name): - if name in self._previous_parsed: - logger.log("Using cached parse result for: " + name, logger.DEBUG) - return self._previous_parsed[name] - - -name_parser_cache = NameParserCache() - - -class InvalidNameException(Exception): - "The given release name is not valid" - - -class InvalidShowException(Exception): +# Author: Nic Wolfe +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see . + +from __future__ import with_statement + +import os +import time +import re +import datetime +import os.path +import regexes +import sickbeard + +from sickbeard import logger, helpers, scene_numbering, common, exceptions, scene_exceptions, encodingKludge as ek, db +from dateutil import parser + + +class NameParser(object): + ALL_REGEX = 0 + NORMAL_REGEX = 1 + SPORTS_REGEX = 2 + ANIME_REGEX = 3 + + def __init__(self, file_name=True, showObj=None, tryIndexers=False, convert=False, + naming_pattern=False): + + self.file_name = file_name + self.showObj = showObj + self.tryIndexers = tryIndexers + self.convert = convert + self.naming_pattern = naming_pattern + + if self.showObj and not self.showObj.is_anime and not self.showObj.is_sports: + self._compile_regexes(self.NORMAL_REGEX) + elif self.showObj and self.showObj.is_anime: + self._compile_regexes(self.ANIME_REGEX) + elif self.showObj and self.showObj.is_sports: + self._compile_regexes(self.SPORTS_REGEX) + else: + self._compile_regexes(self.ALL_REGEX) + + def clean_series_name(self, series_name): + """Cleans up series name by removing any . and _ + characters, along with any trailing hyphens. + + Is basically equivalent to replacing all _ and . with a + space, but handles decimal numbers in string, for example: + + >>> cleanRegexedSeriesName("an.example.1.0.test") + 'an example 1.0 test' + >>> cleanRegexedSeriesName("an_example_1.0_test") + 'an example 1.0 test' + + Stolen from dbr's tvnamer + """ + + series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name) + series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot + series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name) + series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name) + series_name = series_name.replace("_", " ") + series_name = re.sub("-$", "", series_name) + series_name = re.sub("^\[.*\]", "", series_name) + return series_name.strip() + + def _compile_regexes(self, regexMode): + if regexMode == self.SPORTS_REGEX: + logger.log(u"Using SPORTS regexs", logger.DEBUG) + uncompiled_regex = [regexes.sports_regexs] + elif regexMode == self.ANIME_REGEX: + logger.log(u"Using ANIME regexs", logger.DEBUG) + uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes] + elif regexMode == self.NORMAL_REGEX: + logger.log(u"Using NORMAL regexs", logger.DEBUG) + uncompiled_regex = [regexes.normal_regexes] + else: + logger.log(u"Using ALL regexes", logger.DEBUG) + uncompiled_regex = [regexes.normal_regexes, regexes.sports_regexs, regexes.anime_regexes] + + self.compiled_regexes = [] + for regexItem in uncompiled_regex: + for cur_pattern_num, (cur_pattern_name, cur_pattern) in enumerate(regexItem): + try: + cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE) + except re.error, errormsg: + logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern)) + else: + self.compiled_regexes.append((cur_pattern_num, cur_pattern_name, cur_regex)) + + def _parse_string(self, name): + if not name: + return + + matches = [] + bestResult = None + + for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes: + match = cur_regex.match(name) + + if not match: + continue + + result = ParseResult(name) + result.which_regex = [cur_regex_name] + result.score = 0 - cur_regex_num + + named_groups = match.groupdict().keys() + + if 'series_name' in named_groups: + result.series_name = match.group('series_name') + if result.series_name: + result.series_name = self.clean_series_name(result.series_name) + result.score += 1 + + if 'season_num' in named_groups: + tmp_season = int(match.group('season_num')) + if cur_regex_name == 'bare' and tmp_season in (19, 20): + continue + result.season_number = tmp_season + result.score += 1 + + if 'ep_num' in named_groups: + ep_num = self._convert_number(match.group('ep_num')) + if 'extra_ep_num' in named_groups and match.group('extra_ep_num'): + result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1) + result.score += 1 + else: + result.episode_numbers = [ep_num] + result.score += 1 + + if 'ep_ab_num' in named_groups: + ep_ab_num = self._convert_number(match.group('ep_ab_num')) + if 'extra_ab_ep_num' in named_groups and match.group('extra_ab_ep_num'): + result.ab_episode_numbers = range(ep_ab_num, + self._convert_number(match.group('extra_ab_ep_num')) + 1) + result.score += 1 + else: + result.ab_episode_numbers = [ep_ab_num] + result.score += 1 + + if 'sports_event_id' in named_groups: + sports_event_id = match.group('sports_event_id') + if sports_event_id: + result.sports_event_id = int(match.group('sports_event_id')) + result.score += 1 + + if 'sports_event_name' in named_groups: + result.sports_event_name = match.group('sports_event_name') + if result.sports_event_name: + result.sports_event_name = self.clean_series_name(result.sports_event_name) + result.score += 1 + + if 'sports_air_date' in named_groups: + sports_air_date = match.group('sports_air_date') + try: + result.sports_air_date = parser.parse(sports_air_date, fuzzy=True).date() + result.score += 1 + except: + continue + + if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups: + year = int(match.group('air_year')) + month = int(match.group('air_month')) + day = int(match.group('air_day')) + + try: + dtStr = '%s-%s-%s' % (year, month, day) + result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date() + result.score += 1 + except: + continue + + if 'extra_info' in named_groups: + tmp_extra_info = match.group('extra_info') + + # Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season + if tmp_extra_info and cur_regex_name == 'season_only' and re.search( + r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I): + continue + result.extra_info = tmp_extra_info + result.score += 1 + + if 'release_group' in named_groups: + result.release_group = match.group('release_group') + result.score += 1 + + if 'version' in named_groups: + # assigns version to anime file if detected using anime regex. Non-anime regex receives -1 + version = match.group('version') + if version: + result.version = version + else: + result.version = 1 + else: + result.version = -1 + + + matches.append(result) + + if len(matches): + # pick best match with highest score based on placement + bestResult = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score) + + show = None + if not self.naming_pattern: + # try and create a show object for this result + show = helpers.get_show(bestResult.series_name, self.tryIndexers) + + # confirm passed in show object indexer id matches result show object indexer id + if show: + if self.showObj and show.indexerid != self.showObj.indexerid: + show = None + bestResult.show = show + elif not show and self.showObj: + bestResult.show = self.showObj + + # if this is a naming pattern test or result doesn't have a show object then return best result + if not bestResult.show or self.naming_pattern: + return bestResult + + # get quality + bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) + + new_episode_numbers = [] + new_season_numbers = [] + new_absolute_numbers = [] + + # if we have an air-by-date show then get the real season/episode numbers + if bestResult.is_air_by_date or bestResult.is_sports: + try: + airdate = bestResult.air_date.toordinal() + except: + airdate = bestResult.sports_air_date.toordinal() + + myDB = db.DBConnection() + sql_result = myDB.select( + "SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?", + [bestResult.show.indexerid, bestResult.show.indexer, airdate]) + + if sql_result: + season_number = int(sql_result[0][0]) + episode_numbers = [int(sql_result[0][1])] + + for epNo in episode_numbers: + s = season_number + e = epNo + + if self.convert: + (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, + bestResult.show.indexer, + season_number, + epNo) + new_episode_numbers.append(e) + new_season_numbers.append(s) + + elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers): + scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1] + for epAbsNo in bestResult.ab_episode_numbers: + a = epAbsNo + + if self.convert: + a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid, + bestResult.show.indexer, epAbsNo, + True, scene_season) + + (s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a]) + + new_absolute_numbers.append(a) + new_episode_numbers.extend(e) + new_season_numbers.append(s) + + elif bestResult.season_number and len(bestResult.episode_numbers): + for epNo in bestResult.episode_numbers: + s = bestResult.season_number + e = epNo + + if self.convert: + (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, + bestResult.show.indexer, + bestResult.season_number, + epNo) + if bestResult.show.is_anime: + a = helpers.get_absolute_number_from_season_and_episode(bestResult.show, s, e) + if a: + new_absolute_numbers.append(a) + + new_episode_numbers.append(e) + new_season_numbers.append(s) + + # need to do a quick sanity check heregex. It's possible that we now have episodes + # from more than one season (by tvdb numbering), and this is just too much + # for sickbeard, so we'd need to flag it. + new_season_numbers = list(set(new_season_numbers)) # remove duplicates + if len(new_season_numbers) > 1: + raise InvalidNameException("Scene numbering results episodes from " + "seasons %s, (i.e. more than one) and " + "sickrage does not support this. " + "Sorry." % (str(new_season_numbers))) + + # I guess it's possible that we'd have duplicate episodes too, so lets + # eliminate them + new_episode_numbers = list(set(new_episode_numbers)) + new_episode_numbers.sort() + + # maybe even duplicate absolute numbers so why not do them as well + new_absolute_numbers = list(set(new_absolute_numbers)) + new_absolute_numbers.sort() + + if len(new_absolute_numbers): + bestResult.ab_episode_numbers = new_absolute_numbers + + if len(new_season_numbers) and len(new_episode_numbers): + bestResult.episode_numbers = new_episode_numbers + bestResult.season_number = new_season_numbers[0] + + if self.convert: + logger.log( + u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8', + 'xmlcharrefreplace'), + logger.DEBUG) + + # CPU sleep + time.sleep(0.02) + + return bestResult + + def _combine_results(self, first, second, attr): + # if the first doesn't exist then return the second or nothing + if not first: + if not second: + return None + else: + return getattr(second, attr) + + # if the second doesn't exist then return the first + if not second: + return getattr(first, attr) + + a = getattr(first, attr) + b = getattr(second, attr) + + # if a is good use it + if a != None or (type(a) == list and len(a)): + return a + # if not use b (if b isn't set it'll just be default) + else: + return b + + def _unicodify(self, obj, encoding="utf-8"): + if isinstance(obj, basestring): + if not isinstance(obj, unicode): + obj = unicode(obj, encoding, 'replace') + return obj + + def _convert_number(self, org_number): + """ + Convert org_number into an integer + org_number: integer or representation of a number: string or unicode + Try force converting to int first, on error try converting from Roman numerals + returns integer or 0 + """ + + try: + # try forcing to int + if org_number: + number = int(org_number) + else: + number = 0 + + except: + # on error try converting from Roman numerals + roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), + ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), + ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) + ) + + roman_numeral = str(org_number).upper() + number = 0 + index = 0 + + for numeral, integer in roman_to_int_map: + while roman_numeral[index:index + len(numeral)] == numeral: + number += integer + index += len(numeral) + + return number + + def parse(self, name, cache_result=True): + name = self._unicodify(name) + + if self.naming_pattern: + cache_result = False + + cached = name_parser_cache.get(name) + if cached: + return cached + + # break it into parts if there are any (dirname, file name, extension) + dir_name, file_name = ek.ek(os.path.split, name) + + if self.file_name: + base_file_name = helpers.remove_extension(file_name) + else: + base_file_name = file_name + + # set up a result to use + final_result = ParseResult(name) + + # try parsing the file name + file_name_result = self._parse_string(base_file_name) + + # use only the direct parent dir + dir_name = os.path.basename(dir_name) + + # parse the dirname for extra info if needed + dir_name_result = self._parse_string(dir_name) + + # build the ParseResult object + final_result.air_date = self._combine_results(file_name_result, dir_name_result, 'air_date') + + # anime absolute numbers + final_result.ab_episode_numbers = self._combine_results(file_name_result, dir_name_result, 'ab_episode_numbers') + + # sports + final_result.sports_event_id = self._combine_results(file_name_result, dir_name_result, 'sports_event_id') + final_result.sports_event_name = self._combine_results(file_name_result, dir_name_result, 'sports_event_name') + final_result.sports_air_date = self._combine_results(file_name_result, dir_name_result, 'sports_air_date') + + if not final_result.air_date and not final_result.sports_air_date: + final_result.season_number = self._combine_results(file_name_result, dir_name_result, 'season_number') + final_result.episode_numbers = self._combine_results(file_name_result, dir_name_result, 'episode_numbers') + + # if the dirname has a release group/show name I believe it over the filename + final_result.series_name = self._combine_results(dir_name_result, file_name_result, 'series_name') + final_result.extra_info = self._combine_results(dir_name_result, file_name_result, 'extra_info') + final_result.release_group = self._combine_results(dir_name_result, file_name_result, 'release_group') + final_result.version = self._combine_results(dir_name_result, file_name_result, 'version') + + final_result.which_regex = [] + if final_result == file_name_result: + final_result.which_regex = file_name_result.which_regex + elif final_result == dir_name_result: + final_result.which_regex = dir_name_result.which_regex + else: + if file_name_result: + final_result.which_regex += file_name_result.which_regex + if dir_name_result: + final_result.which_regex += dir_name_result.which_regex + + final_result.show = self._combine_results(file_name_result, dir_name_result, 'show') + final_result.quality = self._combine_results(file_name_result, dir_name_result, 'quality') + + if not final_result.show: + raise InvalidShowException( + "Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) + + # if there's no useful info in it then raise an exception + if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and final_result.sports_air_date == None and not final_result.ab_episode_numbers and not final_result.series_name: + raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) + + if cache_result: + name_parser_cache.add(name, final_result) + + logger.log(u"Parsed " + name + " into " + str(final_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG) + return final_result + + +class ParseResult(object): + def __init__(self, + original_name, + series_name=None, + sports_event_id=None, + sports_event_name=None, + sports_air_date=None, + season_number=None, + episode_numbers=None, + extra_info=None, + release_group=None, + air_date=None, + ab_episode_numbers=None, + show=None, + score=None, + quality=None, + version=None + ): + + self.original_name = original_name + + self.series_name = series_name + self.season_number = season_number + if not episode_numbers: + self.episode_numbers = [] + else: + self.episode_numbers = episode_numbers + + if not ab_episode_numbers: + self.ab_episode_numbers = [] + else: + self.ab_episode_numbers = ab_episode_numbers + + if not quality: + self.quality = common.Quality.UNKNOWN + else: + self.quality = quality + + self.extra_info = extra_info + self.release_group = release_group + + self.air_date = air_date + + self.sports_event_id = sports_event_id + self.sports_event_name = sports_event_name + self.sports_air_date = sports_air_date + + self.which_regex = [] + self.show = show + self.score = score + + self.version = version + + def __eq__(self, other): + if not other: + return False + + if self.series_name != other.series_name: + return False + if self.season_number != other.season_number: + return False + if self.episode_numbers != other.episode_numbers: + return False + if self.extra_info != other.extra_info: + return False + if self.release_group != other.release_group: + return False + if self.air_date != other.air_date: + return False + if self.sports_event_id != other.sports_event_id: + return False + if self.sports_event_name != other.sports_event_name: + return False + if self.sports_air_date != other.sports_air_date: + return False + if self.ab_episode_numbers != other.ab_episode_numbers: + return False + if self.show != other.show: + return False + if self.score != other.score: + return False + if self.quality != other.quality: + return False + if self.version != other.version: + return False + + return True + + def __str__(self): + if self.series_name != None: + to_return = self.series_name + u' - ' + else: + to_return = u'' + if self.season_number != None: + to_return += 'S' + str(self.season_number) + if self.episode_numbers and len(self.episode_numbers): + for e in self.episode_numbers: + to_return += 'E' + str(e) + + if self.is_air_by_date: + to_return += str(self.air_date) + if self.is_sports: + to_return += str(self.sports_event_name) + to_return += str(self.sports_event_id) + to_return += str(self.sports_air_date) + if self.ab_episode_numbers: + to_return += ' [ABS: ' + str(self.ab_episode_numbers) + ']' + if self.version: + to_return += ' [ANIME VER: ' + str(self.version) + ']' + + if self.release_group: + to_return += ' [GROUP: ' + self.release_group + ']' + + to_return += ' [ABD: ' + str(self.is_air_by_date) + ']' + to_return += ' [SPORTS: ' + str(self.is_sports) + ']' + to_return += ' [ANIME: ' + str(self.is_anime) + ']' + to_return += ' [whichReg: ' + str(self.which_regex) + ']' + + return to_return.encode('utf-8') + + @property + def is_air_by_date(self): + if self.season_number == None and len(self.episode_numbers) == 0 and self.air_date: + return True + return False + + @property + def is_sports(self): + if self.season_number == None and len(self.episode_numbers) == 0 and self.sports_air_date: + return True + return False + + @property + def is_anime(self): + if len(self.ab_episode_numbers): + return True + return False + + +class NameParserCache(object): + _previous_parsed = {} + _cache_size = 100 + + def add(self, name, parse_result): + self._previous_parsed[name] = parse_result + _current_cache_size = len(self._previous_parsed) + if _current_cache_size > self._cache_size: + for i in range(_current_cache_size - self._cache_size): + del self._previous_parsed[self._previous_parsed.keys()[0]] + + def get(self, name): + if name in self._previous_parsed: + logger.log("Using cached parse result for: " + name, logger.DEBUG) + return self._previous_parsed[name] + + +name_parser_cache = NameParserCache() + + +class InvalidNameException(Exception): + "The given release name is not valid" + + +class InvalidShowException(Exception): "The given show name is not valid" \ No newline at end of file From 1ee64ced4d57e8d0d8add6ce67ee28e80ff3595c Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:14:51 +0000 Subject: [PATCH 02/23] If we fail to find season/episode info for air-by-date shows via DB query we attempt via indexerAPI calls --- sickbeard/name_parser/parser.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 95dc14c7..09f1f6da 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -255,6 +255,26 @@ class NameParser(object): if sql_result: season_number = int(sql_result[0][0]) episode_numbers = [int(sql_result[0][1])] + else: + try: + lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() + + if bestResult.show.lang: + lINDEXER_API_PARMS['language'] = bestResult.show.lang + + t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS) + + if bestResult.is_air_by_date: + epObj = t[bestResult.show.indexerid].airedOn(parse_result.air_date)[0] + else: + epObj = t[bestResult.show.indexerid].airedOn(parse_result.sports_air_date)[0] + + season_number = int(epObj["seasonnumber"]) + episode_numbers = [int(epObj["episodenumber"])] + except sickbeard.indexer_episodenotfound: + logger.log(u"Unable to find episode with date " + str(parse_result.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING) + except sickbeard.indexer_error, e: + logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING for epNo in episode_numbers: s = season_number From 17906539ae037c04b9840114ed07669f6201791d Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:16:55 +0000 Subject: [PATCH 03/23] Fixed improper line indentation --- sickbeard/name_parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 09f1f6da..47581e18 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -256,7 +256,7 @@ class NameParser(object): season_number = int(sql_result[0][0]) episode_numbers = [int(sql_result[0][1])] else: - try: + try: lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() if bestResult.show.lang: From f5725f44022d87d17d1a2c15434d8855d444127c Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:20:24 +0000 Subject: [PATCH 04/23] Minor code correction. --- sickbeard/name_parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 47581e18..e511b2af 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -236,6 +236,8 @@ class NameParser(object): # get quality bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) + season_number = None + episode_numbers = [] new_episode_numbers = [] new_season_numbers = [] new_absolute_numbers = [] From 6a5567d04096f671a5e0c66a7259b707940f6906 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:22:56 +0000 Subject: [PATCH 05/23] Another minor code fix --- sickbeard/name_parser/parser.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index e511b2af..099c5b73 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -278,17 +278,17 @@ class NameParser(object): except sickbeard.indexer_error, e: logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING - for epNo in episode_numbers: - s = season_number - e = epNo + for epNo in episode_numbers: + s = season_number + e = epNo - if self.convert: - (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, - bestResult.show.indexer, - season_number, - epNo) - new_episode_numbers.append(e) - new_season_numbers.append(s) + if self.convert: + (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, + bestResult.show.indexer, + season_number, + epNo) + new_episode_numbers.append(e) + new_season_numbers.append(s) elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers): scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1] From 3ae4c13104e489a0d68bdffa38de1be917a732c0 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:28:22 +0000 Subject: [PATCH 06/23] Code fix --- sickbeard/name_parser/parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 099c5b73..cab8953a 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -236,8 +236,6 @@ class NameParser(object): # get quality bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) - season_number = None - episode_numbers = [] new_episode_numbers = [] new_season_numbers = [] new_absolute_numbers = [] @@ -275,8 +273,10 @@ class NameParser(object): episode_numbers = [int(epObj["episodenumber"])] except sickbeard.indexer_episodenotfound: logger.log(u"Unable to find episode with date " + str(parse_result.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING) + episode_numbers = [] except sickbeard.indexer_error, e: logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING + episode_numbers = [] for epNo in episode_numbers: s = season_number From f1c45a596dd22878ef06b5f5f0698ed0f160a9cf Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:30:36 +0000 Subject: [PATCH 07/23] Code typo fixed --- sickbeard/name_parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index cab8953a..d07e6860 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -275,7 +275,7 @@ class NameParser(object): logger.log(u"Unable to find episode with date " + str(parse_result.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING) episode_numbers = [] except sickbeard.indexer_error, e: - logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING + logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING) episode_numbers = [] for epNo in episode_numbers: From e98418ba8d707f9db57ac7f9b13cf9d1db1daf02 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 11:36:14 +0000 Subject: [PATCH 08/23] Small code change --- sickbeard/name_parser/parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index d07e6860..baa70d77 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -255,7 +255,8 @@ class NameParser(object): if sql_result: season_number = int(sql_result[0][0]) episode_numbers = [int(sql_result[0][1])] - else: + + if not season_number or not len(episode_numbers): try: lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() From 50b91f9d7ceae7b3ba0b7b8cdd609235e54ccc1d Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 17:17:44 +0000 Subject: [PATCH 09/23] Code correction --- sickbeard/versionChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index 312d5d2b..370f31e3 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -758,4 +758,4 @@ class SourceUpdateManager(UpdateManager): def list_remote_branches(self): gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) - return [x.name for x in gh.branches() if x] \ No newline at end of file + return [x.name for x in gh.branches()] \ No newline at end of file From 9714fc3299774e013fec3c74c12b701d653ecd61 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sat, 9 Aug 2014 17:28:15 +0000 Subject: [PATCH 10/23] Code fix for attribute error --- sickbeard/versionChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index 370f31e3..6ae17729 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -758,4 +758,4 @@ class SourceUpdateManager(UpdateManager): def list_remote_branches(self): gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) - return [x.name for x in gh.branches()] \ No newline at end of file + return [x.name for x in gh.branches() if x and 'name' in x] \ No newline at end of file From 16ae4c1db7f7f6b7ef684b8b3f2221ed5fa6662b Mon Sep 17 00:00:00 2001 From: David Waldhans Date: Sat, 9 Aug 2014 19:00:53 +0200 Subject: [PATCH 11/23] Fixed search pattern for checking ignored and required words in release names --- sickbeard/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/search.py b/sickbeard/search.py index 9fbcf42c..beb32838 100644 --- a/sickbeard/search.py +++ b/sickbeard/search.py @@ -177,7 +177,7 @@ def filter_release_name(name, filter_words): Returns: False if the release name is OK, True if it contains one of the filter_words """ if filter_words: - filters = [re.compile('(^|[\W_])%s($|[\W_])' % filter.strip(), re.I) for filter in filter_words.split(',')] + filters = [re.compile('.*%s.*' % filter.strip(), re.I) for filter in filter_words.split(',')] for regfilter in filters: if regfilter.search(name): logger.log(u"" + name + " contains pattern: " + regfilter.pattern, logger.DEBUG) From e19f1ee53e72c69a755cafbe70917cc7d83142a5 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 11 Aug 2014 07:59:17 +0800 Subject: [PATCH 12/23] Fix for HDbits tvcache issue --- sickbeard/providers/hdbits.py | 45 ++++------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py index 38a20ec2..66e379e3 100644 --- a/sickbeard/providers/hdbits.py +++ b/sickbeard/providers/hdbits.py @@ -207,50 +207,13 @@ class HDBitsCache(tvcache.TVCache): # only poll HDBits every 15 minutes max self.minTime = 15 - def updateCache(self): - - # delete anything older then 7 days - self._clearCache() - - if not self.shouldUpdate(): - return - - if self._checkAuth(None): - - parsedJSON = self._getRSSData() - if not parsedJSON: - logger.log(u"Error trying to load " + self.provider.name + " JSON feed", logger.ERROR) - return [] - - # mark updated - self.setLastUpdate() - - if self._checkAuth(parsedJSON): - if parsedJSON and 'data' in parsedJSON: - items = parsedJSON['data'] - else: - logger.log(u"Resulting JSON from " + self.provider.name + " isn't correct, not parsing it", - logger.ERROR) - return [] - - cl = [] - for item in items: - ci = self._parseItem(item) - if ci is not None: - cl.append(ci) - - if len(cl) > 0: - myDB = self._getDB() - myDB.mass_action(cl) - else: - raise exceptions.AuthException( - "Your authentication info for " + self.provider.name + " is incorrect, check your config") + def _getDailyData(self): + parsedJSON = self.provider.getURL(self.provider.rss_url, post_data=self.provider._make_post_data_JSON(), json=True) + if parsedJSON and 'data' in parsedJSON: + return parsedJSON['data'] else: return [] - def _getRSSData(self): - return self.provider.getURL(self.provider.rss_url, post_data=self.provider._make_post_data_JSON(), json=True) - def _checkAuth(self, data): return self.provider._checkAuthFromData(data) From 66f962f89b2d4f4dbbdc721bcbcdf1728a8f7893 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 11 Aug 2014 08:10:24 +0800 Subject: [PATCH 13/23] Fix for tpb ABD shows --- sickbeard/providers/thepiratebay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py index 5c69e5cb..dd7d841d 100644 --- a/sickbeard/providers/thepiratebay.py +++ b/sickbeard/providers/thepiratebay.py @@ -196,7 +196,7 @@ class ThePirateBayProvider(generic.TorrentProvider): if self.show.air_by_date: for show_name in set(allPossibleShowNames(self.show)): ep_string = sanitizeSceneName(show_name) + ' ' + \ - str(ep_obj.airdate).replace('-', '|') + str(ep_obj.airdate).replace('-', ' ') search_string['Episode'].append(ep_string) elif self.show.sports: for show_name in set(allPossibleShowNames(self.show)): From d9f073bbd07ab2831546a4451aa12646e97ff5ed Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 11 Aug 2014 18:19:48 +0800 Subject: [PATCH 14/23] Fixes resetting of auto postprocessing timer config --- sickbeard/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index ef2d28bf..726defcc 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -639,7 +639,7 @@ def initialize(consoleLogging=True): USENET_RETENTION = check_setting_int(CFG, 'General', 'usenet_retention', 500) - AUTOPOSTPROCESSER_FREQUENCY = check_setting_int(CFG, 'General', 'dailysearch_frequency', + AUTOPOSTPROCESSER_FREQUENCY = check_setting_int(CFG, 'General', 'autopostprocesser_frequency', DEFAULT_AUTOPOSTPROCESSER_FREQUENCY) if AUTOPOSTPROCESSER_FREQUENCY < MIN_AUTOPOSTPROCESSER_FREQUENCY: AUTOPOSTPROCESSER_FREQUENCY = MIN_AUTOPOSTPROCESSER_FREQUENCY From 72b0b04045e29269de11fc4b663066807e6b29e2 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 11 Aug 2014 18:29:28 +0800 Subject: [PATCH 15/23] Halt post processing if lftp temporary files are detected --- sickbeard/helpers.py | 7 +++---- sickbeard/processTV.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index fd966bf7..d95ce049 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -136,10 +136,9 @@ def replaceExtension(filename, newExt): return sepFile[0] + "." + newExt -def isBtsyncFile(filename): - sepFile = filename.rpartition(".") - - if sepFile[2].lower() == '!sync': +def isSyncFile(filename): + extension = filename.rpartition(".")[2].lower() + if extension == '!sync' or extension == 'lftp-pget-status': return True else: return False diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index a02c09f2..53f4b5f8 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -138,11 +138,11 @@ def processDir(dirName, nzbName=None, process_method=None, force=False, is_prior path, dirs, files = get_path_dir_files(dirName, nzbName, type) - btsyncFiles = filter(helpers.isBtsyncFile, files) + SyncFiles = filter(helpers.isSyncFile, files) - # Don't post process if files are still being synced from btsync - if btsyncFiles: - returnStr += logHelper(u"Found .!sync files, skipping post processing", logger.ERROR) + # Don't post process if files are still being synced + if SyncFiles: + returnStr += logHelper(u"Found temporary sync files, skipping post processing", logger.ERROR) return returnStr returnStr += logHelper(u"PostProcessing Path: " + path, logger.DEBUG) @@ -186,11 +186,11 @@ def processDir(dirName, nzbName=None, process_method=None, force=False, is_prior for processPath, processDir, fileList in ek.ek(os.walk, ek.ek(os.path.join, path, dir), topdown=False): - btsyncFiles = filter(helpers.isBtsyncFile, fileList) + SyncFiles = filter(helpers.isSyncFile, fileList) - # Don't post process if files are still being synced from btsync - if btsyncFiles: - returnStr += logHelper(u"Found .!sync files, skipping post processing", logger.ERROR) + # Don't post process if files are still being synced + if SyncFiles: + returnStr += logHelper(u"Found temporary sync files, skipping post processing", logger.ERROR) return returnStr rarFiles = filter(helpers.isRarFile, fileList) From f4c0893d3227954d28b80015484e52e35130332f Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 11 Aug 2014 19:29:35 +0800 Subject: [PATCH 16/23] Fixes for trakt settings not saving --- gui/slick/interfaces/default/config_notifications.tmpl | 6 +++--- sickbeard/__init__.py | 4 +++- sickbeard/webserve.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl index cec0087e..d8d26051 100644 --- a/gui/slick/interfaces/default/config_notifications.tmpl +++ b/gui/slick/interfaces/default/config_notifications.tmpl @@ -1224,9 +1224,9 @@ + + <% + if config['nzbget_priority'] == -100: + prio_verylow = 'selected="selected"' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == -50: + prio_verylow = '' + prio_low = 'selected="selected"' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 0: + prio_verylow = '' + prio_low = '' + prio_normal = 'selected="selected"' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 50: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = 'selected="selected"' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 100: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = 'selected="selected"' + prio_force = '' + elif config['nzbget_priority'] == 900: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = 'selected="selected"' + else: + prio_verylow = '' + prio_low = '' + prio_normal = 'selected="selected"' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + %> + +
+ + +
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index ef2d28bf..7c787f82 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -240,6 +240,7 @@ NZBGET_PASSWORD = None NZBGET_CATEGORY = None NZBGET_HOST = None NZBGET_USE_HTTPS = False +NZBGET_PRIORITY = 100 TORRENT_USERNAME = None TORRENT_PASSWORD = None @@ -446,7 +447,7 @@ def initialize(consoleLogging=True): global BRANCH, ACTUAL_LOG_DIR, LOG_DIR, WEB_PORT, WEB_LOG, ENCRYPTION_VERSION, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, USE_API, API_KEY, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \ HANDLE_REVERSE_PROXY, USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, CHECK_PROPERS_INTERVAL, ALLOW_HIGH_PRIORITY, TORRENT_METHOD, \ SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \ - NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, NZBGET_USE_HTTPS, backlogSearchScheduler, \ + NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_HOST, NZBGET_USE_HTTPS, backlogSearchScheduler, \ TORRENT_USERNAME, TORRENT_PASSWORD, TORRENT_HOST, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, TORRENT_HIGH_BANDWIDTH, TORRENT_LABEL, TORRENT_VERIFY_CERT, \ USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \ XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, BACKLOG_FREQUENCY, \ @@ -691,6 +692,7 @@ def initialize(consoleLogging=True): NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', 'tv') NZBGET_HOST = check_setting_str(CFG, 'NZBget', 'nzbget_host', '') NZBGET_USE_HTTPS = bool(check_setting_int(CFG, 'NZBget', 'nzbget_use_https', 0)) + NZBGET_PRIORITY = check_setting_int(CFG, 'NZBget', 'nzbget_priority', 100) TORRENT_USERNAME = check_setting_str(CFG, 'TORRENT', 'torrent_username', '') TORRENT_PASSWORD = check_setting_str(CFG, 'TORRENT', 'torrent_password', '') @@ -1498,6 +1500,7 @@ def save_config(): new_config['NZBget']['nzbget_category'] = NZBGET_CATEGORY new_config['NZBget']['nzbget_host'] = NZBGET_HOST new_config['NZBget']['nzbget_use_https'] = int(NZBGET_USE_HTTPS) + new_config['NZBget']['nzbget_priority'] = NZBGET_PRIORITY new_config['TORRENT'] = {} new_config['TORRENT']['torrent_username'] = TORRENT_USERNAME diff --git a/sickbeard/nzbget.py b/sickbeard/nzbget.py index acdba6c7..469e4e4d 100644 --- a/sickbeard/nzbget.py +++ b/sickbeard/nzbget.py @@ -82,7 +82,7 @@ def sendNZB(nzb, proper=False): dupekey += "-" + str(curEp.season) + "." + str(curEp.episode) if datetime.date.today() - curEp.airdate <= datetime.timedelta(days=7): addToTop = True - nzbgetprio = 100 + nzbgetprio = sickbeard.NZBGET_PRIORITY if nzb.quality != Quality.UNKNOWN: dupescore = nzb.quality * 100 diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 2e42a247..03b855af 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1601,7 +1601,7 @@ class ConfigSearch(MainHandler): def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None, sab_apikey=None, sab_category=None, sab_host=None, nzbget_username=None, nzbget_password=None, - nzbget_category=None, nzbget_host=None, nzbget_use_https=None, dailysearch_frequency=None, + nzbget_category=None, nzbget_priority=100, nzbget_host=None, nzbget_use_https=None, dailysearch_frequency=None, nzb_method=None, torrent_method=None, usenet_retention=None, backlog_frequency=None, download_propers=None, check_propers_interval=None, allow_high_priority=None, backlog_startup=None, dailysearch_startup=None, @@ -1648,6 +1648,7 @@ class ConfigSearch(MainHandler): sickbeard.NZBGET_CATEGORY = nzbget_category sickbeard.NZBGET_HOST = config.clean_host(nzbget_host) sickbeard.NZBGET_USE_HTTPS = config.checkbox_to_value(nzbget_use_https) + sickbeard.NZBGET_PRIORITY = int(nzbget_priority) sickbeard.TORRENT_USERNAME = torrent_username sickbeard.TORRENT_PASSWORD = torrent_password From 37e35754c4d6d83d1d79aa3d74e677e8227e539f Mon Sep 17 00:00:00 2001 From: Brian Hartvigsen Date: Mon, 11 Aug 2014 17:38:37 -0600 Subject: [PATCH 21/23] Add fixing name_parser/parse.py too... --- sickbeard/name_parser/parser.py | 1338 +++++++++++++++---------------- 1 file changed, 669 insertions(+), 669 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index baa70d77..735f2ad4 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -1,670 +1,670 @@ -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -from __future__ import with_statement - -import os -import time -import re -import datetime -import os.path -import regexes -import sickbeard - -from sickbeard import logger, helpers, scene_numbering, common, exceptions, scene_exceptions, encodingKludge as ek, db -from dateutil import parser - - -class NameParser(object): - ALL_REGEX = 0 - NORMAL_REGEX = 1 - SPORTS_REGEX = 2 - ANIME_REGEX = 3 - - def __init__(self, file_name=True, showObj=None, tryIndexers=False, convert=False, - naming_pattern=False): - - self.file_name = file_name - self.showObj = showObj - self.tryIndexers = tryIndexers - self.convert = convert - self.naming_pattern = naming_pattern - - if self.showObj and not self.showObj.is_anime and not self.showObj.is_sports: - self._compile_regexes(self.NORMAL_REGEX) - elif self.showObj and self.showObj.is_anime: - self._compile_regexes(self.ANIME_REGEX) - elif self.showObj and self.showObj.is_sports: - self._compile_regexes(self.SPORTS_REGEX) - else: - self._compile_regexes(self.ALL_REGEX) - - def clean_series_name(self, series_name): - """Cleans up series name by removing any . and _ - characters, along with any trailing hyphens. - - Is basically equivalent to replacing all _ and . with a - space, but handles decimal numbers in string, for example: - - >>> cleanRegexedSeriesName("an.example.1.0.test") - 'an example 1.0 test' - >>> cleanRegexedSeriesName("an_example_1.0_test") - 'an example 1.0 test' - - Stolen from dbr's tvnamer - """ - - series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name) - series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot - series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name) - series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name) - series_name = series_name.replace("_", " ") - series_name = re.sub("-$", "", series_name) - series_name = re.sub("^\[.*\]", "", series_name) - return series_name.strip() - - def _compile_regexes(self, regexMode): - if regexMode == self.SPORTS_REGEX: - logger.log(u"Using SPORTS regexs", logger.DEBUG) - uncompiled_regex = [regexes.sports_regexs] - elif regexMode == self.ANIME_REGEX: - logger.log(u"Using ANIME regexs", logger.DEBUG) - uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes] - elif regexMode == self.NORMAL_REGEX: - logger.log(u"Using NORMAL regexs", logger.DEBUG) - uncompiled_regex = [regexes.normal_regexes] - else: - logger.log(u"Using ALL regexes", logger.DEBUG) - uncompiled_regex = [regexes.normal_regexes, regexes.sports_regexs, regexes.anime_regexes] - - self.compiled_regexes = [] - for regexItem in uncompiled_regex: - for cur_pattern_num, (cur_pattern_name, cur_pattern) in enumerate(regexItem): - try: - cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE) - except re.error, errormsg: - logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern)) - else: - self.compiled_regexes.append((cur_pattern_num, cur_pattern_name, cur_regex)) - - def _parse_string(self, name): - if not name: - return - - matches = [] - bestResult = None - - for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes: - match = cur_regex.match(name) - - if not match: - continue - - result = ParseResult(name) - result.which_regex = [cur_regex_name] - result.score = 0 - cur_regex_num - - named_groups = match.groupdict().keys() - - if 'series_name' in named_groups: - result.series_name = match.group('series_name') - if result.series_name: - result.series_name = self.clean_series_name(result.series_name) - result.score += 1 - - if 'season_num' in named_groups: - tmp_season = int(match.group('season_num')) - if cur_regex_name == 'bare' and tmp_season in (19, 20): - continue - result.season_number = tmp_season - result.score += 1 - - if 'ep_num' in named_groups: - ep_num = self._convert_number(match.group('ep_num')) - if 'extra_ep_num' in named_groups and match.group('extra_ep_num'): - result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1) - result.score += 1 - else: - result.episode_numbers = [ep_num] - result.score += 1 - - if 'ep_ab_num' in named_groups: - ep_ab_num = self._convert_number(match.group('ep_ab_num')) - if 'extra_ab_ep_num' in named_groups and match.group('extra_ab_ep_num'): - result.ab_episode_numbers = range(ep_ab_num, - self._convert_number(match.group('extra_ab_ep_num')) + 1) - result.score += 1 - else: - result.ab_episode_numbers = [ep_ab_num] - result.score += 1 - - if 'sports_event_id' in named_groups: - sports_event_id = match.group('sports_event_id') - if sports_event_id: - result.sports_event_id = int(match.group('sports_event_id')) - result.score += 1 - - if 'sports_event_name' in named_groups: - result.sports_event_name = match.group('sports_event_name') - if result.sports_event_name: - result.sports_event_name = self.clean_series_name(result.sports_event_name) - result.score += 1 - - if 'sports_air_date' in named_groups: - sports_air_date = match.group('sports_air_date') - try: - result.sports_air_date = parser.parse(sports_air_date, fuzzy=True).date() - result.score += 1 - except: - continue - - if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups: - year = int(match.group('air_year')) - month = int(match.group('air_month')) - day = int(match.group('air_day')) - - try: - dtStr = '%s-%s-%s' % (year, month, day) - result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date() - result.score += 1 - except: - continue - - if 'extra_info' in named_groups: - tmp_extra_info = match.group('extra_info') - - # Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season - if tmp_extra_info and cur_regex_name == 'season_only' and re.search( - r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I): - continue - result.extra_info = tmp_extra_info - result.score += 1 - - if 'release_group' in named_groups: - result.release_group = match.group('release_group') - result.score += 1 - - if 'version' in named_groups: - # assigns version to anime file if detected using anime regex. Non-anime regex receives -1 - version = match.group('version') - if version: - result.version = version - else: - result.version = 1 - else: - result.version = -1 - - - matches.append(result) - - if len(matches): - # pick best match with highest score based on placement - bestResult = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score) - - show = None - if not self.naming_pattern: - # try and create a show object for this result - show = helpers.get_show(bestResult.series_name, self.tryIndexers) - - # confirm passed in show object indexer id matches result show object indexer id - if show: - if self.showObj and show.indexerid != self.showObj.indexerid: - show = None - bestResult.show = show - elif not show and self.showObj: - bestResult.show = self.showObj - - # if this is a naming pattern test or result doesn't have a show object then return best result - if not bestResult.show or self.naming_pattern: - return bestResult - - # get quality - bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) - - new_episode_numbers = [] - new_season_numbers = [] - new_absolute_numbers = [] - - # if we have an air-by-date show then get the real season/episode numbers - if bestResult.is_air_by_date or bestResult.is_sports: - try: - airdate = bestResult.air_date.toordinal() - except: - airdate = bestResult.sports_air_date.toordinal() - - myDB = db.DBConnection() - sql_result = myDB.select( - "SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?", - [bestResult.show.indexerid, bestResult.show.indexer, airdate]) - - if sql_result: - season_number = int(sql_result[0][0]) - episode_numbers = [int(sql_result[0][1])] - - if not season_number or not len(episode_numbers): - try: - lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() - - if bestResult.show.lang: - lINDEXER_API_PARMS['language'] = bestResult.show.lang - - t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS) - - if bestResult.is_air_by_date: - epObj = t[bestResult.show.indexerid].airedOn(parse_result.air_date)[0] - else: - epObj = t[bestResult.show.indexerid].airedOn(parse_result.sports_air_date)[0] - - season_number = int(epObj["seasonnumber"]) - episode_numbers = [int(epObj["episodenumber"])] - except sickbeard.indexer_episodenotfound: - logger.log(u"Unable to find episode with date " + str(parse_result.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING) - episode_numbers = [] - except sickbeard.indexer_error, e: - logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING) - episode_numbers = [] - - for epNo in episode_numbers: - s = season_number - e = epNo - - if self.convert: - (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, - bestResult.show.indexer, - season_number, - epNo) - new_episode_numbers.append(e) - new_season_numbers.append(s) - - elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers): - scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1] - for epAbsNo in bestResult.ab_episode_numbers: - a = epAbsNo - - if self.convert: - a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid, - bestResult.show.indexer, epAbsNo, - True, scene_season) - - (s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a]) - - new_absolute_numbers.append(a) - new_episode_numbers.extend(e) - new_season_numbers.append(s) - - elif bestResult.season_number and len(bestResult.episode_numbers): - for epNo in bestResult.episode_numbers: - s = bestResult.season_number - e = epNo - - if self.convert: - (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, - bestResult.show.indexer, - bestResult.season_number, - epNo) - if bestResult.show.is_anime: - a = helpers.get_absolute_number_from_season_and_episode(bestResult.show, s, e) - if a: - new_absolute_numbers.append(a) - - new_episode_numbers.append(e) - new_season_numbers.append(s) - - # need to do a quick sanity check heregex. It's possible that we now have episodes - # from more than one season (by tvdb numbering), and this is just too much - # for sickbeard, so we'd need to flag it. - new_season_numbers = list(set(new_season_numbers)) # remove duplicates - if len(new_season_numbers) > 1: - raise InvalidNameException("Scene numbering results episodes from " - "seasons %s, (i.e. more than one) and " - "sickrage does not support this. " - "Sorry." % (str(new_season_numbers))) - - # I guess it's possible that we'd have duplicate episodes too, so lets - # eliminate them - new_episode_numbers = list(set(new_episode_numbers)) - new_episode_numbers.sort() - - # maybe even duplicate absolute numbers so why not do them as well - new_absolute_numbers = list(set(new_absolute_numbers)) - new_absolute_numbers.sort() - - if len(new_absolute_numbers): - bestResult.ab_episode_numbers = new_absolute_numbers - - if len(new_season_numbers) and len(new_episode_numbers): - bestResult.episode_numbers = new_episode_numbers - bestResult.season_number = new_season_numbers[0] - - if self.convert: - logger.log( - u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8', - 'xmlcharrefreplace'), - logger.DEBUG) - - # CPU sleep - time.sleep(0.02) - - return bestResult - - def _combine_results(self, first, second, attr): - # if the first doesn't exist then return the second or nothing - if not first: - if not second: - return None - else: - return getattr(second, attr) - - # if the second doesn't exist then return the first - if not second: - return getattr(first, attr) - - a = getattr(first, attr) - b = getattr(second, attr) - - # if a is good use it - if a != None or (type(a) == list and len(a)): - return a - # if not use b (if b isn't set it'll just be default) - else: - return b - - def _unicodify(self, obj, encoding="utf-8"): - if isinstance(obj, basestring): - if not isinstance(obj, unicode): - obj = unicode(obj, encoding, 'replace') - return obj - - def _convert_number(self, org_number): - """ - Convert org_number into an integer - org_number: integer or representation of a number: string or unicode - Try force converting to int first, on error try converting from Roman numerals - returns integer or 0 - """ - - try: - # try forcing to int - if org_number: - number = int(org_number) - else: - number = 0 - - except: - # on error try converting from Roman numerals - roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), - ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), - ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) - ) - - roman_numeral = str(org_number).upper() - number = 0 - index = 0 - - for numeral, integer in roman_to_int_map: - while roman_numeral[index:index + len(numeral)] == numeral: - number += integer - index += len(numeral) - - return number - - def parse(self, name, cache_result=True): - name = self._unicodify(name) - - if self.naming_pattern: - cache_result = False - - cached = name_parser_cache.get(name) - if cached: - return cached - - # break it into parts if there are any (dirname, file name, extension) - dir_name, file_name = ek.ek(os.path.split, name) - - if self.file_name: - base_file_name = helpers.remove_extension(file_name) - else: - base_file_name = file_name - - # set up a result to use - final_result = ParseResult(name) - - # try parsing the file name - file_name_result = self._parse_string(base_file_name) - - # use only the direct parent dir - dir_name = os.path.basename(dir_name) - - # parse the dirname for extra info if needed - dir_name_result = self._parse_string(dir_name) - - # build the ParseResult object - final_result.air_date = self._combine_results(file_name_result, dir_name_result, 'air_date') - - # anime absolute numbers - final_result.ab_episode_numbers = self._combine_results(file_name_result, dir_name_result, 'ab_episode_numbers') - - # sports - final_result.sports_event_id = self._combine_results(file_name_result, dir_name_result, 'sports_event_id') - final_result.sports_event_name = self._combine_results(file_name_result, dir_name_result, 'sports_event_name') - final_result.sports_air_date = self._combine_results(file_name_result, dir_name_result, 'sports_air_date') - - if not final_result.air_date and not final_result.sports_air_date: - final_result.season_number = self._combine_results(file_name_result, dir_name_result, 'season_number') - final_result.episode_numbers = self._combine_results(file_name_result, dir_name_result, 'episode_numbers') - - # if the dirname has a release group/show name I believe it over the filename - final_result.series_name = self._combine_results(dir_name_result, file_name_result, 'series_name') - final_result.extra_info = self._combine_results(dir_name_result, file_name_result, 'extra_info') - final_result.release_group = self._combine_results(dir_name_result, file_name_result, 'release_group') - final_result.version = self._combine_results(dir_name_result, file_name_result, 'version') - - final_result.which_regex = [] - if final_result == file_name_result: - final_result.which_regex = file_name_result.which_regex - elif final_result == dir_name_result: - final_result.which_regex = dir_name_result.which_regex - else: - if file_name_result: - final_result.which_regex += file_name_result.which_regex - if dir_name_result: - final_result.which_regex += dir_name_result.which_regex - - final_result.show = self._combine_results(file_name_result, dir_name_result, 'show') - final_result.quality = self._combine_results(file_name_result, dir_name_result, 'quality') - - if not final_result.show: - raise InvalidShowException( - "Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) - - # if there's no useful info in it then raise an exception - if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and final_result.sports_air_date == None and not final_result.ab_episode_numbers and not final_result.series_name: - raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) - - if cache_result: - name_parser_cache.add(name, final_result) - - logger.log(u"Parsed " + name + " into " + str(final_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG) - return final_result - - -class ParseResult(object): - def __init__(self, - original_name, - series_name=None, - sports_event_id=None, - sports_event_name=None, - sports_air_date=None, - season_number=None, - episode_numbers=None, - extra_info=None, - release_group=None, - air_date=None, - ab_episode_numbers=None, - show=None, - score=None, - quality=None, - version=None - ): - - self.original_name = original_name - - self.series_name = series_name - self.season_number = season_number - if not episode_numbers: - self.episode_numbers = [] - else: - self.episode_numbers = episode_numbers - - if not ab_episode_numbers: - self.ab_episode_numbers = [] - else: - self.ab_episode_numbers = ab_episode_numbers - - if not quality: - self.quality = common.Quality.UNKNOWN - else: - self.quality = quality - - self.extra_info = extra_info - self.release_group = release_group - - self.air_date = air_date - - self.sports_event_id = sports_event_id - self.sports_event_name = sports_event_name - self.sports_air_date = sports_air_date - - self.which_regex = [] - self.show = show - self.score = score - - self.version = version - - def __eq__(self, other): - if not other: - return False - - if self.series_name != other.series_name: - return False - if self.season_number != other.season_number: - return False - if self.episode_numbers != other.episode_numbers: - return False - if self.extra_info != other.extra_info: - return False - if self.release_group != other.release_group: - return False - if self.air_date != other.air_date: - return False - if self.sports_event_id != other.sports_event_id: - return False - if self.sports_event_name != other.sports_event_name: - return False - if self.sports_air_date != other.sports_air_date: - return False - if self.ab_episode_numbers != other.ab_episode_numbers: - return False - if self.show != other.show: - return False - if self.score != other.score: - return False - if self.quality != other.quality: - return False - if self.version != other.version: - return False - - return True - - def __str__(self): - if self.series_name != None: - to_return = self.series_name + u' - ' - else: - to_return = u'' - if self.season_number != None: - to_return += 'S' + str(self.season_number) - if self.episode_numbers and len(self.episode_numbers): - for e in self.episode_numbers: - to_return += 'E' + str(e) - - if self.is_air_by_date: - to_return += str(self.air_date) - if self.is_sports: - to_return += str(self.sports_event_name) - to_return += str(self.sports_event_id) - to_return += str(self.sports_air_date) - if self.ab_episode_numbers: - to_return += ' [ABS: ' + str(self.ab_episode_numbers) + ']' - if self.version: - to_return += ' [ANIME VER: ' + str(self.version) + ']' - - if self.release_group: - to_return += ' [GROUP: ' + self.release_group + ']' - - to_return += ' [ABD: ' + str(self.is_air_by_date) + ']' - to_return += ' [SPORTS: ' + str(self.is_sports) + ']' - to_return += ' [ANIME: ' + str(self.is_anime) + ']' - to_return += ' [whichReg: ' + str(self.which_regex) + ']' - - return to_return.encode('utf-8') - - @property - def is_air_by_date(self): - if self.season_number == None and len(self.episode_numbers) == 0 and self.air_date: - return True - return False - - @property - def is_sports(self): - if self.season_number == None and len(self.episode_numbers) == 0 and self.sports_air_date: - return True - return False - - @property - def is_anime(self): - if len(self.ab_episode_numbers): - return True - return False - - -class NameParserCache(object): - _previous_parsed = {} - _cache_size = 100 - - def add(self, name, parse_result): - self._previous_parsed[name] = parse_result - _current_cache_size = len(self._previous_parsed) - if _current_cache_size > self._cache_size: - for i in range(_current_cache_size - self._cache_size): - del self._previous_parsed[self._previous_parsed.keys()[0]] - - def get(self, name): - if name in self._previous_parsed: - logger.log("Using cached parse result for: " + name, logger.DEBUG) - return self._previous_parsed[name] - - -name_parser_cache = NameParserCache() - - -class InvalidNameException(Exception): - "The given release name is not valid" - - -class InvalidShowException(Exception): +# Author: Nic Wolfe +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see . + +from __future__ import with_statement + +import os +import time +import re +import datetime +import os.path +import regexes +import sickbeard + +from sickbeard import logger, helpers, scene_numbering, common, exceptions, scene_exceptions, encodingKludge as ek, db +from dateutil import parser + + +class NameParser(object): + ALL_REGEX = 0 + NORMAL_REGEX = 1 + SPORTS_REGEX = 2 + ANIME_REGEX = 3 + + def __init__(self, file_name=True, showObj=None, tryIndexers=False, convert=False, + naming_pattern=False): + + self.file_name = file_name + self.showObj = showObj + self.tryIndexers = tryIndexers + self.convert = convert + self.naming_pattern = naming_pattern + + if self.showObj and not self.showObj.is_anime and not self.showObj.is_sports: + self._compile_regexes(self.NORMAL_REGEX) + elif self.showObj and self.showObj.is_anime: + self._compile_regexes(self.ANIME_REGEX) + elif self.showObj and self.showObj.is_sports: + self._compile_regexes(self.SPORTS_REGEX) + else: + self._compile_regexes(self.ALL_REGEX) + + def clean_series_name(self, series_name): + """Cleans up series name by removing any . and _ + characters, along with any trailing hyphens. + + Is basically equivalent to replacing all _ and . with a + space, but handles decimal numbers in string, for example: + + >>> cleanRegexedSeriesName("an.example.1.0.test") + 'an example 1.0 test' + >>> cleanRegexedSeriesName("an_example_1.0_test") + 'an example 1.0 test' + + Stolen from dbr's tvnamer + """ + + series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name) + series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot + series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name) + series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name) + series_name = series_name.replace("_", " ") + series_name = re.sub("-$", "", series_name) + series_name = re.sub("^\[.*\]", "", series_name) + return series_name.strip() + + def _compile_regexes(self, regexMode): + if regexMode == self.SPORTS_REGEX: + logger.log(u"Using SPORTS regexs", logger.DEBUG) + uncompiled_regex = [regexes.sports_regexs] + elif regexMode == self.ANIME_REGEX: + logger.log(u"Using ANIME regexs", logger.DEBUG) + uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes] + elif regexMode == self.NORMAL_REGEX: + logger.log(u"Using NORMAL regexs", logger.DEBUG) + uncompiled_regex = [regexes.normal_regexes] + else: + logger.log(u"Using ALL regexes", logger.DEBUG) + uncompiled_regex = [regexes.normal_regexes, regexes.sports_regexs, regexes.anime_regexes] + + self.compiled_regexes = [] + for regexItem in uncompiled_regex: + for cur_pattern_num, (cur_pattern_name, cur_pattern) in enumerate(regexItem): + try: + cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE) + except re.error, errormsg: + logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern)) + else: + self.compiled_regexes.append((cur_pattern_num, cur_pattern_name, cur_regex)) + + def _parse_string(self, name): + if not name: + return + + matches = [] + bestResult = None + + for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes: + match = cur_regex.match(name) + + if not match: + continue + + result = ParseResult(name) + result.which_regex = [cur_regex_name] + result.score = 0 - cur_regex_num + + named_groups = match.groupdict().keys() + + if 'series_name' in named_groups: + result.series_name = match.group('series_name') + if result.series_name: + result.series_name = self.clean_series_name(result.series_name) + result.score += 1 + + if 'season_num' in named_groups: + tmp_season = int(match.group('season_num')) + if cur_regex_name == 'bare' and tmp_season in (19, 20): + continue + result.season_number = tmp_season + result.score += 1 + + if 'ep_num' in named_groups: + ep_num = self._convert_number(match.group('ep_num')) + if 'extra_ep_num' in named_groups and match.group('extra_ep_num'): + result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1) + result.score += 1 + else: + result.episode_numbers = [ep_num] + result.score += 1 + + if 'ep_ab_num' in named_groups: + ep_ab_num = self._convert_number(match.group('ep_ab_num')) + if 'extra_ab_ep_num' in named_groups and match.group('extra_ab_ep_num'): + result.ab_episode_numbers = range(ep_ab_num, + self._convert_number(match.group('extra_ab_ep_num')) + 1) + result.score += 1 + else: + result.ab_episode_numbers = [ep_ab_num] + result.score += 1 + + if 'sports_event_id' in named_groups: + sports_event_id = match.group('sports_event_id') + if sports_event_id: + result.sports_event_id = int(match.group('sports_event_id')) + result.score += 1 + + if 'sports_event_name' in named_groups: + result.sports_event_name = match.group('sports_event_name') + if result.sports_event_name: + result.sports_event_name = self.clean_series_name(result.sports_event_name) + result.score += 1 + + if 'sports_air_date' in named_groups: + sports_air_date = match.group('sports_air_date') + try: + result.sports_air_date = parser.parse(sports_air_date, fuzzy=True).date() + result.score += 1 + except: + continue + + if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups: + year = int(match.group('air_year')) + month = int(match.group('air_month')) + day = int(match.group('air_day')) + + try: + dtStr = '%s-%s-%s' % (year, month, day) + result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date() + result.score += 1 + except: + continue + + if 'extra_info' in named_groups: + tmp_extra_info = match.group('extra_info') + + # Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season + if tmp_extra_info and cur_regex_name == 'season_only' and re.search( + r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I): + continue + result.extra_info = tmp_extra_info + result.score += 1 + + if 'release_group' in named_groups: + result.release_group = match.group('release_group') + result.score += 1 + + if 'version' in named_groups: + # assigns version to anime file if detected using anime regex. Non-anime regex receives -1 + version = match.group('version') + if version: + result.version = version + else: + result.version = 1 + else: + result.version = -1 + + + matches.append(result) + + if len(matches): + # pick best match with highest score based on placement + bestResult = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score) + + show = None + if not self.naming_pattern: + # try and create a show object for this result + show = helpers.get_show(bestResult.series_name, self.tryIndexers) + + # confirm passed in show object indexer id matches result show object indexer id + if show: + if self.showObj and show.indexerid != self.showObj.indexerid: + show = None + bestResult.show = show + elif not show and self.showObj: + bestResult.show = self.showObj + + # if this is a naming pattern test or result doesn't have a show object then return best result + if not bestResult.show or self.naming_pattern: + return bestResult + + # get quality + bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) + + new_episode_numbers = [] + new_season_numbers = [] + new_absolute_numbers = [] + + # if we have an air-by-date show then get the real season/episode numbers + if bestResult.is_air_by_date or bestResult.is_sports: + try: + airdate = bestResult.air_date.toordinal() + except: + airdate = bestResult.sports_air_date.toordinal() + + myDB = db.DBConnection() + sql_result = myDB.select( + "SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?", + [bestResult.show.indexerid, bestResult.show.indexer, airdate]) + + if sql_result: + season_number = int(sql_result[0][0]) + episode_numbers = [int(sql_result[0][1])] + + if not season_number or not len(episode_numbers): + try: + lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() + + if bestResult.show.lang: + lINDEXER_API_PARMS['language'] = bestResult.show.lang + + t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS) + + if bestResult.is_air_by_date: + epObj = t[bestResult.show.indexerid].airedOn(parse_result.air_date)[0] + else: + epObj = t[bestResult.show.indexerid].airedOn(parse_result.sports_air_date)[0] + + season_number = int(epObj["seasonnumber"]) + episode_numbers = [int(epObj["episodenumber"])] + except sickbeard.indexer_episodenotfound: + logger.log(u"Unable to find episode with date " + str(parse_result.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING) + episode_numbers = [] + except sickbeard.indexer_error, e: + logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING) + episode_numbers = [] + + for epNo in episode_numbers: + s = season_number + e = epNo + + if self.convert: + (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, + bestResult.show.indexer, + season_number, + epNo) + new_episode_numbers.append(e) + new_season_numbers.append(s) + + elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers): + scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1] + for epAbsNo in bestResult.ab_episode_numbers: + a = epAbsNo + + if self.convert: + a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid, + bestResult.show.indexer, epAbsNo, + True, scene_season) + + (s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a]) + + new_absolute_numbers.append(a) + new_episode_numbers.extend(e) + new_season_numbers.append(s) + + elif bestResult.season_number and len(bestResult.episode_numbers): + for epNo in bestResult.episode_numbers: + s = bestResult.season_number + e = epNo + + if self.convert: + (s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid, + bestResult.show.indexer, + bestResult.season_number, + epNo) + if bestResult.show.is_anime: + a = helpers.get_absolute_number_from_season_and_episode(bestResult.show, s, e) + if a: + new_absolute_numbers.append(a) + + new_episode_numbers.append(e) + new_season_numbers.append(s) + + # need to do a quick sanity check heregex. It's possible that we now have episodes + # from more than one season (by tvdb numbering), and this is just too much + # for sickbeard, so we'd need to flag it. + new_season_numbers = list(set(new_season_numbers)) # remove duplicates + if len(new_season_numbers) > 1: + raise InvalidNameException("Scene numbering results episodes from " + "seasons %s, (i.e. more than one) and " + "sickrage does not support this. " + "Sorry." % (str(new_season_numbers))) + + # I guess it's possible that we'd have duplicate episodes too, so lets + # eliminate them + new_episode_numbers = list(set(new_episode_numbers)) + new_episode_numbers.sort() + + # maybe even duplicate absolute numbers so why not do them as well + new_absolute_numbers = list(set(new_absolute_numbers)) + new_absolute_numbers.sort() + + if len(new_absolute_numbers): + bestResult.ab_episode_numbers = new_absolute_numbers + + if len(new_season_numbers) and len(new_episode_numbers): + bestResult.episode_numbers = new_episode_numbers + bestResult.season_number = new_season_numbers[0] + + if self.convert: + logger.log( + u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8', + 'xmlcharrefreplace'), + logger.DEBUG) + + # CPU sleep + time.sleep(0.02) + + return bestResult + + def _combine_results(self, first, second, attr): + # if the first doesn't exist then return the second or nothing + if not first: + if not second: + return None + else: + return getattr(second, attr) + + # if the second doesn't exist then return the first + if not second: + return getattr(first, attr) + + a = getattr(first, attr) + b = getattr(second, attr) + + # if a is good use it + if a != None or (type(a) == list and len(a)): + return a + # if not use b (if b isn't set it'll just be default) + else: + return b + + def _unicodify(self, obj, encoding="utf-8"): + if isinstance(obj, basestring): + if not isinstance(obj, unicode): + obj = unicode(obj, encoding, 'replace') + return obj + + def _convert_number(self, org_number): + """ + Convert org_number into an integer + org_number: integer or representation of a number: string or unicode + Try force converting to int first, on error try converting from Roman numerals + returns integer or 0 + """ + + try: + # try forcing to int + if org_number: + number = int(org_number) + else: + number = 0 + + except: + # on error try converting from Roman numerals + roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), + ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), + ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) + ) + + roman_numeral = str(org_number).upper() + number = 0 + index = 0 + + for numeral, integer in roman_to_int_map: + while roman_numeral[index:index + len(numeral)] == numeral: + number += integer + index += len(numeral) + + return number + + def parse(self, name, cache_result=True): + name = self._unicodify(name) + + if self.naming_pattern: + cache_result = False + + cached = name_parser_cache.get(name) + if cached: + return cached + + # break it into parts if there are any (dirname, file name, extension) + dir_name, file_name = ek.ek(os.path.split, name) + + if self.file_name: + base_file_name = helpers.remove_extension(file_name) + else: + base_file_name = file_name + + # set up a result to use + final_result = ParseResult(name) + + # try parsing the file name + file_name_result = self._parse_string(base_file_name) + + # use only the direct parent dir + dir_name = os.path.basename(dir_name) + + # parse the dirname for extra info if needed + dir_name_result = self._parse_string(dir_name) + + # build the ParseResult object + final_result.air_date = self._combine_results(file_name_result, dir_name_result, 'air_date') + + # anime absolute numbers + final_result.ab_episode_numbers = self._combine_results(file_name_result, dir_name_result, 'ab_episode_numbers') + + # sports + final_result.sports_event_id = self._combine_results(file_name_result, dir_name_result, 'sports_event_id') + final_result.sports_event_name = self._combine_results(file_name_result, dir_name_result, 'sports_event_name') + final_result.sports_air_date = self._combine_results(file_name_result, dir_name_result, 'sports_air_date') + + if not final_result.air_date and not final_result.sports_air_date: + final_result.season_number = self._combine_results(file_name_result, dir_name_result, 'season_number') + final_result.episode_numbers = self._combine_results(file_name_result, dir_name_result, 'episode_numbers') + + # if the dirname has a release group/show name I believe it over the filename + final_result.series_name = self._combine_results(dir_name_result, file_name_result, 'series_name') + final_result.extra_info = self._combine_results(dir_name_result, file_name_result, 'extra_info') + final_result.release_group = self._combine_results(dir_name_result, file_name_result, 'release_group') + final_result.version = self._combine_results(dir_name_result, file_name_result, 'version') + + final_result.which_regex = [] + if final_result == file_name_result: + final_result.which_regex = file_name_result.which_regex + elif final_result == dir_name_result: + final_result.which_regex = dir_name_result.which_regex + else: + if file_name_result: + final_result.which_regex += file_name_result.which_regex + if dir_name_result: + final_result.which_regex += dir_name_result.which_regex + + final_result.show = self._combine_results(file_name_result, dir_name_result, 'show') + final_result.quality = self._combine_results(file_name_result, dir_name_result, 'quality') + + if not final_result.show: + raise InvalidShowException( + "Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) + + # if there's no useful info in it then raise an exception + if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and final_result.sports_air_date == None and not final_result.ab_episode_numbers and not final_result.series_name: + raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) + + if cache_result: + name_parser_cache.add(name, final_result) + + logger.log(u"Parsed " + name + " into " + str(final_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG) + return final_result + + +class ParseResult(object): + def __init__(self, + original_name, + series_name=None, + sports_event_id=None, + sports_event_name=None, + sports_air_date=None, + season_number=None, + episode_numbers=None, + extra_info=None, + release_group=None, + air_date=None, + ab_episode_numbers=None, + show=None, + score=None, + quality=None, + version=None + ): + + self.original_name = original_name + + self.series_name = series_name + self.season_number = season_number + if not episode_numbers: + self.episode_numbers = [] + else: + self.episode_numbers = episode_numbers + + if not ab_episode_numbers: + self.ab_episode_numbers = [] + else: + self.ab_episode_numbers = ab_episode_numbers + + if not quality: + self.quality = common.Quality.UNKNOWN + else: + self.quality = quality + + self.extra_info = extra_info + self.release_group = release_group + + self.air_date = air_date + + self.sports_event_id = sports_event_id + self.sports_event_name = sports_event_name + self.sports_air_date = sports_air_date + + self.which_regex = [] + self.show = show + self.score = score + + self.version = version + + def __eq__(self, other): + if not other: + return False + + if self.series_name != other.series_name: + return False + if self.season_number != other.season_number: + return False + if self.episode_numbers != other.episode_numbers: + return False + if self.extra_info != other.extra_info: + return False + if self.release_group != other.release_group: + return False + if self.air_date != other.air_date: + return False + if self.sports_event_id != other.sports_event_id: + return False + if self.sports_event_name != other.sports_event_name: + return False + if self.sports_air_date != other.sports_air_date: + return False + if self.ab_episode_numbers != other.ab_episode_numbers: + return False + if self.show != other.show: + return False + if self.score != other.score: + return False + if self.quality != other.quality: + return False + if self.version != other.version: + return False + + return True + + def __str__(self): + if self.series_name != None: + to_return = self.series_name + u' - ' + else: + to_return = u'' + if self.season_number != None: + to_return += 'S' + str(self.season_number) + if self.episode_numbers and len(self.episode_numbers): + for e in self.episode_numbers: + to_return += 'E' + str(e) + + if self.is_air_by_date: + to_return += str(self.air_date) + if self.is_sports: + to_return += str(self.sports_event_name) + to_return += str(self.sports_event_id) + to_return += str(self.sports_air_date) + if self.ab_episode_numbers: + to_return += ' [ABS: ' + str(self.ab_episode_numbers) + ']' + if self.version: + to_return += ' [ANIME VER: ' + str(self.version) + ']' + + if self.release_group: + to_return += ' [GROUP: ' + self.release_group + ']' + + to_return += ' [ABD: ' + str(self.is_air_by_date) + ']' + to_return += ' [SPORTS: ' + str(self.is_sports) + ']' + to_return += ' [ANIME: ' + str(self.is_anime) + ']' + to_return += ' [whichReg: ' + str(self.which_regex) + ']' + + return to_return.encode('utf-8') + + @property + def is_air_by_date(self): + if self.season_number == None and len(self.episode_numbers) == 0 and self.air_date: + return True + return False + + @property + def is_sports(self): + if self.season_number == None and len(self.episode_numbers) == 0 and self.sports_air_date: + return True + return False + + @property + def is_anime(self): + if len(self.ab_episode_numbers): + return True + return False + + +class NameParserCache(object): + _previous_parsed = {} + _cache_size = 100 + + def add(self, name, parse_result): + self._previous_parsed[name] = parse_result + _current_cache_size = len(self._previous_parsed) + if _current_cache_size > self._cache_size: + for i in range(_current_cache_size - self._cache_size): + del self._previous_parsed[self._previous_parsed.keys()[0]] + + def get(self, name): + if name in self._previous_parsed: + logger.log("Using cached parse result for: " + name, logger.DEBUG) + return self._previous_parsed[name] + + +name_parser_cache = NameParserCache() + + +class InvalidNameException(Exception): + "The given release name is not valid" + + +class InvalidShowException(Exception): "The given show name is not valid" \ No newline at end of file From 22f3a2e41bbc0d9fbdb13bb90a5786253d191c0d Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 12 Aug 2014 18:09:11 +0800 Subject: [PATCH 22/23] Consolidate more provider code Fix tvtorrents issue Remove old providers --- sickbeard/providers/animezb.py | 10 +- sickbeard/providers/bitsoup.py | 8 +- sickbeard/providers/btn.py | 3 - sickbeard/providers/dtt.py | 141 ------------ sickbeard/providers/ezrss.py | 6 +- sickbeard/providers/fanzub.py | 7 +- sickbeard/providers/freshontv.py | 11 +- sickbeard/providers/generic.py | 2 +- sickbeard/providers/hdbits.py | 9 +- sickbeard/providers/hdtorrents.py | 9 +- sickbeard/providers/iptorrents.py | 9 +- sickbeard/providers/newzbin.py | 340 ---------------------------- sickbeard/providers/nyaatorrents.py | 7 +- sickbeard/providers/nzbs_org_old.py | 162 ------------- sickbeard/providers/nzbsrus.py | 116 ---------- sickbeard/providers/omgwtfnzbs.py | 8 +- sickbeard/providers/publichd.py | 253 --------------------- sickbeard/providers/rsstorrent.py | 9 +- sickbeard/providers/tvtorrents.py | 19 +- sickbeard/tvcache.py | 28 +-- 20 files changed, 95 insertions(+), 1062 deletions(-) delete mode 100644 sickbeard/providers/dtt.py delete mode 100644 sickbeard/providers/newzbin.py delete mode 100644 sickbeard/providers/nzbs_org_old.py delete mode 100644 sickbeard/providers/nzbsrus.py delete mode 100644 sickbeard/providers/publichd.py diff --git a/sickbeard/providers/animezb.py b/sickbeard/providers/animezb.py index 5ad05b7c..69b0db91 100644 --- a/sickbeard/providers/animezb.py +++ b/sickbeard/providers/animezb.py @@ -52,9 +52,6 @@ class Animezb(generic.NZBProvider): def imageName(self): return 'animezb.png' - def _checkAuth(self): - return True - def _get_season_search_strings(self, ep_obj): return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] @@ -147,7 +144,12 @@ class AnimezbCache(tvcache.TVCache): logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG) - return self.getRSSFeed(rss_url).entries + data = self.getRSSFeed(rss_url) + + if data and 'entries' in data: + return data.entries + else: + return [] provider = Animezb() diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py index 6b3db525..d4f40289 100644 --- a/sickbeard/providers/bitsoup.py +++ b/sickbeard/providers/bitsoup.py @@ -31,7 +31,7 @@ from sickbeard import db from sickbeard import classes from sickbeard import helpers from sickbeard import show_name_helpers -from sickbeard.exceptions import ex +from sickbeard.exceptions import ex, AuthException from sickbeard.helpers import sanitizeSceneName from sickbeard.bs4_parser import BS4Parser from unidecode import unidecode @@ -75,6 +75,12 @@ class BitSoupProvider(generic.TorrentProvider): quality = Quality.sceneQuality(item[0], anime) return quality + def _checkAuth(self): + if not self.username or not self.password: + raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.") + + return True + def _doLogin(self): login_params = {'username': self.username, diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py index eaa03ee6..4786ff91 100644 --- a/sickbeard/providers/btn.py +++ b/sickbeard/providers/btn.py @@ -315,8 +315,5 @@ class BTNCache(tvcache.TVCache): return self.provider._doSearch(search_params=None, age=seconds_since_last_update) - def _checkAuth(self, data): - return self.provider._checkAuthFromData(data) - provider = BTNProvider() diff --git a/sickbeard/providers/dtt.py b/sickbeard/providers/dtt.py deleted file mode 100644 index bfe881e7..00000000 --- a/sickbeard/providers/dtt.py +++ /dev/null @@ -1,141 +0,0 @@ -# Author: Harm van Tilborg -# URL: https://github.com/hvt/Sick-Beard/tree/dtt -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -import urllib -import sickbeard -import generic - -from sickbeard.common import Quality -from sickbeard import logger -from sickbeard import tvcache -from sickbeard.helpers import sanitizeSceneName -from sickbeard import show_name_helpers -from sickbeard.exceptions import ex - - -class DTTProvider(generic.TorrentProvider): - def __init__(self): - generic.TorrentProvider.__init__(self, "DailyTvTorrents") - self.supportsBacklog = True - - self.enabled = False - self.ratio = None - - self.cache = DTTCache(self) - - self.url = 'http://www.dailytvtorrents.org/' - - def isEnabled(self): - return self.enabled - - def imageName(self): - return 'dailytvtorrents.gif' - - def getQuality(self, item, anime=False): - url = item.enclosures[0].href - quality = Quality.sceneQuality(url) - return quality - - def findSearchResults(self, show, season, episodes, search_mode, manualSearch=False): - return generic.TorrentProvider.findSearchResults(self, show, season, episodes, search_mode, manualSearch) - - def _dtt_show_id(self, show_name): - return sanitizeSceneName(show_name).replace('.', '-').lower() - - def _get_season_search_strings(self, ep_obj): - search_string = [] - - for show_name in set(show_name_helpers.allPossibleShowNames(self.show)): - show_string = sanitizeSceneName(show_name).replace('.', '-').lower() - search_string.append(show_string) - - return search_string - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return self._get_season_search_strings(ep_obj) - - def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0): - - # show_id = self._dtt_show_id(self.show.name) - - params = {"items": "all"} - - if sickbeard.DTT_NORAR: - params.update({"norar": "yes"}) - - if sickbeard.DTT_SINGLE: - params.update({"single": "yes"}) - - searchURL = self.url + "rss/show/" + search_params + "?" + urllib.urlencode(params) - - logger.log(u"Search string: " + searchURL, logger.DEBUG) - - data = self.cache.getRSSFeed(searchURL) - - if not data: - return [] - - try: - items = data.entries - except Exception, e: - logger.log(u"Error trying to load DTT RSS feed: " + ex(e), logger.ERROR) - logger.log(u"RSS data: " + data, logger.DEBUG) - return [] - - results = [] - - for curItem in items: - (title, url) = self._get_title_and_url(curItem) - results.append(curItem) - - return results - - def _get_title_and_url(self, item): - title = item.title - if title: - title = u'' + title - title = title.replace(' ', '.') - - url = item.enclosures[0].href - - return (title, url) - - -class DTTCache(tvcache.TVCache): - def __init__(self, provider): - tvcache.TVCache.__init__(self, provider) - - # only poll DTT every 30 minutes max - self.minTime = 30 - - def _getDailyData(self): - - params = {"items": "all"} - - if sickbeard.DTT_NORAR: - params.update({"norar": "yes"}) - - if sickbeard.DTT_SINGLE: - params.update({"single": "yes"}) - - url = self.provider.url + 'rss/allshows?' + urllib.urlencode(params) - logger.log(u"DTT cache update URL: " + url, logger.DEBUG) - return self.getRSSFeed(url).entries - - -provider = DTTProvider() \ No newline at end of file diff --git a/sickbeard/providers/ezrss.py b/sickbeard/providers/ezrss.py index 233a53e6..feb1765e 100644 --- a/sickbeard/providers/ezrss.py +++ b/sickbeard/providers/ezrss.py @@ -179,7 +179,11 @@ class EZRSSCache(tvcache.TVCache): rss_url = self.provider.url + 'feed/' logger.log(self.provider.name + " cache update URL: " + rss_url, logger.DEBUG) - return self.getRSSFeed(rss_url).entries + data = self.getRSSFeed(rss_url) + if data and 'entries' in data: + return data.entries + else: + return [] provider = EZRSSProvider() diff --git a/sickbeard/providers/fanzub.py b/sickbeard/providers/fanzub.py index ebcd2431..0ad73ccb 100644 --- a/sickbeard/providers/fanzub.py +++ b/sickbeard/providers/fanzub.py @@ -139,7 +139,12 @@ class FanzubCache(tvcache.TVCache): logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG) - return self.getRSSFeed(rss_url).entries + data = self.getRSSFeed(rss_url) + + if data and 'entries' in data: + return data.entries + else: + return [] provider = Fanzub() diff --git a/sickbeard/providers/freshontv.py b/sickbeard/providers/freshontv.py index 5e7f0871..bab6ea7d 100755 --- a/sickbeard/providers/freshontv.py +++ b/sickbeard/providers/freshontv.py @@ -29,7 +29,7 @@ from sickbeard import db from sickbeard import classes from sickbeard import helpers from sickbeard import show_name_helpers -from sickbeard.exceptions import ex +from sickbeard.exceptions import ex, AuthException from sickbeard import clients from lib import requests from lib.requests import exceptions @@ -78,6 +78,13 @@ class FreshOnTVProvider(generic.TorrentProvider): quality = Quality.sceneQuality(item[0], anime) return quality + def _checkAuth(self): + + if not self.username or not self.password: + raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.") + + return True + def _doLogin(self): if any(requests.utils.dict_from_cookiejar(self.session.cookies).values()): return True @@ -301,6 +308,6 @@ class FreshOnTVCache(tvcache.TVCache): def _getDailyData(self): search_params = {'RSS': ['']} - return self.provider._doSearch(search_params).entries + return self.provider._doSearch(search_params) provider = FreshOnTVProvider() \ No newline at end of file diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index 899b93f8..5b02adbe 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -75,7 +75,7 @@ class GenericProvider: return self.getID() + '.png' def _checkAuth(self): - return + return True def _doLogin(self): return True diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py index 66e379e3..85cd6017 100644 --- a/sickbeard/providers/hdbits.py +++ b/sickbeard/providers/hdbits.py @@ -67,9 +67,6 @@ class HDBitsProvider(generic.TorrentProvider): def _checkAuthFromData(self, parsedJSON): - if parsedJSON is None: - return self._checkAuth() - if 'status' in parsedJSON and 'message' in parsedJSON: if parsedJSON.get('status') == 5: logger.log(u"Incorrect authentication credentials for " + self.name + " : " + parsedJSON['message'], @@ -209,13 +206,15 @@ class HDBitsCache(tvcache.TVCache): def _getDailyData(self): parsedJSON = self.provider.getURL(self.provider.rss_url, post_data=self.provider._make_post_data_JSON(), json=True) + + if not self.provider._checkAuthFromData(parsedJSON): + return [] + if parsedJSON and 'data' in parsedJSON: return parsedJSON['data'] else: return [] - def _checkAuth(self, data): - return self.provider._checkAuthFromData(data) provider = HDBitsProvider() diff --git a/sickbeard/providers/hdtorrents.py b/sickbeard/providers/hdtorrents.py index 8b02af0b..a02afbad 100644 --- a/sickbeard/providers/hdtorrents.py +++ b/sickbeard/providers/hdtorrents.py @@ -30,7 +30,7 @@ from sickbeard import db from sickbeard import classes from sickbeard import helpers from sickbeard import show_name_helpers -from sickbeard.exceptions import ex +from sickbeard.exceptions import ex, AuthException from sickbeard import clients from lib import requests from lib.requests import exceptions @@ -82,6 +82,13 @@ class HDTorrentsProvider(generic.TorrentProvider): quality = Quality.sceneQuality(item[0]) return quality + def _checkAuth(self): + + if not self.username or not self.password: + raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.") + + return True + def _doLogin(self): if any(requests.utils.dict_from_cookiejar(self.session.cookies).values()): diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py index 19ae37fd..e24bb328 100644 --- a/sickbeard/providers/iptorrents.py +++ b/sickbeard/providers/iptorrents.py @@ -29,7 +29,7 @@ from sickbeard import db from sickbeard import classes from sickbeard import helpers from sickbeard import show_name_helpers -from sickbeard.exceptions import ex +from sickbeard.exceptions import ex, AuthException from sickbeard import clients from lib import requests from lib.requests import exceptions @@ -74,6 +74,13 @@ class IPTorrentsProvider(generic.TorrentProvider): quality = Quality.sceneQuality(item[0], anime) return quality + def _checkAuth(self): + + if not self.username or not self.password: + raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.") + + return True + def _doLogin(self): login_params = {'username': self.username, diff --git a/sickbeard/providers/newzbin.py b/sickbeard/providers/newzbin.py deleted file mode 100644 index b0211355..00000000 --- a/sickbeard/providers/newzbin.py +++ /dev/null @@ -1,340 +0,0 @@ -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -import os -import re -import sys -import time -import urllib, urlparse - -from datetime import datetime, timedelta - -import sickbeard -import generic - -import sickbeard.encodingKludge as ek -from sickbeard import classes, logger, helpers, exceptions, show_name_helpers -from sickbeard import tvcache -from sickbeard.common import Quality -from sickbeard.exceptions import ex -from lib.dateutil.parser import parse as parseDate - - -class NewzbinDownloader(urllib.FancyURLopener): - def __init__(self): - urllib.FancyURLopener.__init__(self) - - def http_error_default(self, url, fp, errcode, errmsg, headers): - - # if newzbin is throttling us, wait seconds and try again - if errcode == 400: - - newzbinErrCode = int(headers.getheader('X-DNZB-RCode')) - - if newzbinErrCode == 450: - rtext = str(headers.getheader('X-DNZB-RText')) - result = re.search("wait (\d+) seconds", rtext) - logger.log("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + "seconds") - time.sleep(int(result.group(1))) - raise exceptions.NewzbinAPIThrottled() - - elif newzbinErrCode == 401: - raise exceptions.AuthException("Newzbin username or password incorrect") - - elif newzbinErrCode == 402: - raise exceptions.AuthException("Newzbin account not premium status, can't download NZBs") - -class NewzbinProvider(generic.NZBProvider): - def __init__(self): - - generic.NZBProvider.__init__(self, "Newzbin") - - self.supportsBacklog = True - - self.cache = NewzbinCache(self) - - self.url = 'https://www.newzbin2.es/' - - self.NEWZBIN_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S %Z' - - def isEnabled(self): - return sickbeard.NEWZBIN - - def getQuality(self, item, anime=False): - attributes = item.report[0] - attr_dict = {} - - for attribute in attributes.getElementsByTagName('report:attribute'): - cur_attr = attribute.getAttribute('type') - cur_attr_value = helpers.get_xml_text(attribute) - if cur_attr not in attr_dict: - attr_dict[cur_attr] = [cur_attr_value] - else: - attr_dict[cur_attr].append(cur_attr_value) - - logger.log("Finding quality of item based on attributes " + str(attr_dict), logger.DEBUG) - - if self._is_SDTV(attr_dict): - quality = Quality.SDTV - elif self._is_SDDVD(attr_dict): - quality = Quality.SDDVD - elif self._is_HDTV(attr_dict): - quality = Quality.HDTV - elif self._is_WEBDL(attr_dict): - quality = Quality.HDWEBDL - elif self._is_720pBluRay(attr_dict): - quality = Quality.HDBLURAY - elif self._is_1080pBluRay(attr_dict): - quality = Quality.FULLHDBLURAY - else: - quality = Quality.UNKNOWN - - logger.log("Resulting quality: " + str(quality), logger.DEBUG) - - return quality - - def _is_SDTV(self, attrs): - - # Video Fmt: (XviD, DivX, H.264/x264), NOT 720p, NOT 1080p, NOT 1080i - video_fmt = 'Video Fmt' in attrs and ( - 'XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'H.264/x264' in attrs['Video Fmt']) \ - and ('720p' not in attrs['Video Fmt']) \ - and ('1080p' not in attrs['Video Fmt']) \ - and ('1080i' not in attrs['Video Fmt']) - - # Source: TV Cap or HDTV or (None) - source = 'Source' not in attrs or 'TV Cap' in attrs['Source'] or 'HDTV' in attrs['Source'] - - # Subtitles: (None) - subs = 'Subtitles' not in attrs - - return video_fmt and source and subs - - def _is_SDDVD(self, attrs): - - # Video Fmt: (XviD, DivX, H.264/x264), NOT 720p, NOT 1080p, NOT 1080i - video_fmt = 'Video Fmt' in attrs and ( - 'XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'H.264/x264' in attrs['Video Fmt']) \ - and ('720p' not in attrs['Video Fmt']) \ - and ('1080p' not in attrs['Video Fmt']) \ - and ('1080i' not in attrs['Video Fmt']) - - # Source: DVD - source = 'Source' in attrs and 'DVD' in attrs['Source'] - - # Subtitles: (None) - subs = 'Subtitles' not in attrs - - return video_fmt and source and subs - - def _is_HDTV(self, attrs): - # Video Fmt: H.264/x264, 720p - video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \ - and ('720p' in attrs['Video Fmt']) - - # Source: TV Cap or HDTV or (None) - source = 'Source' not in attrs or 'TV Cap' in attrs['Source'] or 'HDTV' in attrs['Source'] - - # Subtitles: (None) - subs = 'Subtitles' not in attrs - - return video_fmt and source and subs - - def _is_WEBDL(self, attrs): - - # Video Fmt: H.264/x264, 720p - video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \ - and ('720p' in attrs['Video Fmt']) - - # Source: WEB-DL - source = 'Source' in attrs and 'WEB-DL' in attrs['Source'] - - # Subtitles: (None) - subs = 'Subtitles' not in attrs - - return video_fmt and source and subs - - def _is_720pBluRay(self, attrs): - - # Video Fmt: H.264/x264, 720p - video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \ - and ('720p' in attrs['Video Fmt']) - - # Source: Blu-ray or HD-DVD - source = 'Source' in attrs and ('Blu-ray' in attrs['Source'] or 'HD-DVD' in attrs['Source']) - - return video_fmt and source - - def _is_1080pBluRay(self, attrs): - - # Video Fmt: H.264/x264, 1080p - video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \ - and ('1080p' in attrs['Video Fmt']) - - # Source: Blu-ray or HD-DVD - source = 'Source' in attrs and ('Blu-ray' in attrs['Source'] or 'HD-DVD' in attrs['Source']) - - return video_fmt and source - - - def getIDFromURL(self, url): - id_regex = re.escape(self.url) + 'browse/post/(\d+)/' - id_match = re.match(id_regex, url) - if not id_match: - return None - else: - return id_match.group(1) - - def downloadResult(self, nzb): - - id = self.getIDFromURL(nzb.url) - if not id: - logger.log("Unable to get an ID from " + str(nzb.url) + ", can't download from Newzbin's API", logger.ERROR) - return False - - logger.log("Downloading an NZB from newzbin with id " + id) - - fileName = ek.ek(os.path.join, sickbeard.NZB_DIR, helpers.sanitizeFileName(nzb.name) + '.nzb') - logger.log("Saving to " + fileName) - - urllib._urlopener = NewzbinDownloader() - - params = urllib.urlencode( - {"username": sickbeard.NEWZBIN_USERNAME, "password": sickbeard.NEWZBIN_PASSWORD, "reportid": id}) - try: - urllib.urlretrieve(self.url + "api/dnzb/", fileName, data=params) - except exceptions.NewzbinAPIThrottled: - logger.log("Done waiting for Newzbin API throttle limit, starting downloads again") - self.downloadResult(nzb) - except (urllib.ContentTooShortError, IOError), e: - logger.log("Error downloading NZB: " + str(sys.exc_info()) + " - " + ex(e), logger.ERROR) - return False - - return True - - def _get_season_search_strings(self, ep_obj): - return ['^' + x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return ['^' + x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - - def _doSearch(self, searchStr, show=None, age=None): - - data = self._getRSSData(searchStr.encode('utf-8')) - - item_list = [] - - try: - items = data.entries - except Exception, e: - logger.log("Error trying to load Newzbin RSS feed: " + ex(e), logger.ERROR) - return [] - - for cur_item in items: - title = cur_item.title - if title == 'Feeds Error': - raise exceptions.AuthException("The feed wouldn't load, probably because of invalid auth info") - if sickbeard.USENET_RETENTION is not None: - try: - dateString = helpers.get_xml_text(cur_item.getElementsByTagName('report:postdate')[0]) - # use the parse (imported as parseDate) function from the dateutil lib - # and we have to remove the timezone info from it because the retention_date will not have one - # and a comparison of them is not possible - post_date = parseDate(dateString).replace(tzinfo=None) - retention_date = datetime.now() - timedelta(days=sickbeard.USENET_RETENTION) - if post_date < retention_date: - logger.log(u"Date " + str(post_date) + " is out of retention range, skipping", logger.DEBUG) - continue - except Exception, e: - logger.log("Error parsing date from Newzbin RSS feed: " + str(e), logger.ERROR) - continue - - item_list.append(cur_item) - - return item_list - - - def _getRSSData(self, search=None): - - params = { - 'searchaction': 'Search', - 'fpn': 'p', - 'category': 8, - 'u_nfo_posts_only': 0, - 'u_url_posts_only': 0, - 'u_comment_posts_only': 0, - 'u_show_passworded': 0, - 'u_v3_retention': 0, - 'ps_rb_video_format': 3082257, - 'ps_rb_language': 4096, - 'sort': 'date', - 'order': 'desc', - 'u_post_results_amt': 50, - 'feed': 'rss', - 'hauth': 1, - } - - if search: - params['q'] = search + " AND " - else: - params['q'] = '' - - params['q'] += 'Attr:Lang~Eng AND NOT Attr:VideoF=DVD' - - url = self.url + "search/?%s" % urllib.urlencode(params) - logger.log("Newzbin search URL: " + url, logger.DEBUG) - - return self.cache.getRSSFeed(url) - - def _checkAuth(self): - if sickbeard.NEWZBIN_USERNAME in (None, "") or sickbeard.NEWZBIN_PASSWORD in (None, ""): - raise exceptions.AuthException("Newzbin authentication details are empty, check your config") - - -class NewzbinCache(tvcache.TVCache): - def __init__(self, provider): - - tvcache.TVCache.__init__(self, provider) - - # only poll Newzbin every 10 mins max - self.minTime = 1 - - def _getDailyData(self): - - return self.provider._getRSSData().entries - - def _parseItem(self, item): - - (title, url) = self.provider._get_title_and_url(item) - - if title == 'Feeds Error': - logger.log("There's an error in the feed, probably bad auth info", logger.DEBUG) - raise exceptions.AuthException("Invalid Newzbin username/password") - - if not title or not url: - logger.log( - "The XML returned from the " + self.provider.name + " feed is incomplete, this result is unusable", - logger.ERROR) - return - - logger.log(u"RSS Feed provider: [" + self.provider.name + "] Attempting to add item to cache: " + title, logger.DEBUG) - - self._addCacheEntry(title, url) - - -provider = NewzbinProvider() diff --git a/sickbeard/providers/nyaatorrents.py b/sickbeard/providers/nyaatorrents.py index f2aed6c3..2ac991cb 100644 --- a/sickbeard/providers/nyaatorrents.py +++ b/sickbeard/providers/nyaatorrents.py @@ -137,7 +137,12 @@ class NyaaCache(tvcache.TVCache): logger.log(u"NyaaTorrents cache update URL: " + url, logger.DEBUG) - return self.getRSSFeed(url).entries + data = self.getRSSFeed(url) + + if data and 'entries' in data: + return data.entries + else: + return [] provider = NyaaProvider() diff --git a/sickbeard/providers/nzbs_org_old.py b/sickbeard/providers/nzbs_org_old.py deleted file mode 100644 index 5e30e82e..00000000 --- a/sickbeard/providers/nzbs_org_old.py +++ /dev/null @@ -1,162 +0,0 @@ -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - - - -import datetime -import re -import time -import urllib - -from xml.dom.minidom import parseString - -import sickbeard -import generic - -from sickbeard import classes, show_name_helpers, helpers - -from sickbeard import exceptions, logger -from sickbeard import tvcache -from sickbeard.exceptions import ex - - -class NZBsProvider(generic.NZBProvider): - def __init__(self): - - generic.NZBProvider.__init__(self, "NZBs.org Old") - - self.supportsBacklog = True - - self.cache = NZBsCache(self) - - self.url = 'https://secure.nzbs.org/' - - def isEnabled(self): - return sickbeard.NZBS - - def _checkAuth(self): - if sickbeard.NZBS_UID in (None, "") or sickbeard.NZBS_HASH in (None, ""): - raise exceptions.AuthException("NZBs.org authentication details are empty, check your config") - - def _get_season_search_strings(self, ep_obj): - return ['^' + x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return ['^' + x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - - def _doSearch(self, curString, show=None, age=None): - - curString = curString.replace('.', ' ') - - params = {"action": "search", - "q": curString.encode('utf-8'), - "dl": 1, - "i": sickbeard.NZBS_UID, - "h": sickbeard.NZBS_HASH, - "age": sickbeard.USENET_RETENTION, - "num": 100, - "type": 1} - - searchURL = self.url + "rss.php?" + urllib.urlencode(params) - - logger.log(u"Search string: " + searchURL, logger.DEBUG) - - data = self.cache.getRSSFeed(searchURL) - - # Pause to avoid 503's - time.sleep(5) - - if data is None: - logger.log(u"Error trying to load NZBs.org RSS feed: " + searchURL, logger.ERROR) - return [] - - items = data.entries - - results = [] - - for curItem in items: - (title, url) = self._get_title_and_url(curItem) - - if not title or not url: - logger.log( - u"The XML returned from the NZBs.org RSS feed is incomplete, this result is unusable: " + data, - logger.ERROR) - continue - - if "&i=" not in url and "&h=" not in url: - raise exceptions.AuthException( - "The NZBs.org result URL has no auth info which means your UID/hash are incorrect, check your config") - - results.append(curItem) - - return results - - def findPropers(self, date=None): - - results = [] - - for curString in (".PROPER.", ".REPACK."): - - for curResult in self._doSearch(curString): - - (title, url) = self._get_title_and_url(curResult) - - pubDate_node = curResult.getElementsByTagName('pubDate')[0] - pubDate = helpers.get_xml_text(pubDate_node) - - match = re.search('(\w{3}, \d{1,2} \w{3} \d{4} \d\d:\d\d:\d\d) [\+\-]\d{4}', pubDate) - if not match: - continue - - resultDate = datetime.datetime.strptime(match.group(1), "%a, %d %b %Y %H:%M:%S") - - if date is None or resultDate > date: - results.append(classes.Proper(title, url, resultDate, self.show)) - - return results - - -class NZBsCache(tvcache.TVCache): - def __init__(self, provider): - tvcache.TVCache.__init__(self, provider) - - # only poll NZBs.org every 15 minutes max - self.minTime = 15 - - def _getRSSData(self): - url = self.provider.url + 'rss.php?' - urlArgs = {'type': 1, - 'dl': 1, - 'num': 100, - 'i': sickbeard.NZBS_UID, - 'h': sickbeard.NZBS_HASH, - 'age': sickbeard.USENET_RETENTION} - - url += urllib.urlencode(urlArgs) - - logger.log(u"NZBs cache update URL: " + url, logger.DEBUG) - - return self.provider.getURL(url) - - def _checkItemAuth(self, title, url): - if "&i=" not in url and "&h=" not in url: - raise exceptions.AuthException( - "The NZBs.org result URL has no auth info which means your UID/hash are incorrect, check your config") - - -provider = NZBsProvider() \ No newline at end of file diff --git a/sickbeard/providers/nzbsrus.py b/sickbeard/providers/nzbsrus.py deleted file mode 100644 index 570b24ed..00000000 --- a/sickbeard/providers/nzbsrus.py +++ /dev/null @@ -1,116 +0,0 @@ -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -import urllib -import generic -import sickbeard - -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree - -from sickbeard import exceptions, logger -from sickbeard import tvcache, show_name_helpers - -class NZBsRUSProvider(generic.NZBProvider): - def __init__(self): - generic.NZBProvider.__init__(self, "NZBs'R'US") - self.cache = NZBsRUSCache(self) - self.url = 'https://www.nzbsrus.com/' - self.supportsBacklog = True - - def isEnabled(self): - return sickbeard.NZBSRUS - - def _checkAuth(self): - if sickbeard.NZBSRUS_UID in (None, "") or sickbeard.NZBSRUS_HASH in (None, ""): - raise exceptions.AuthException("NZBs'R'US authentication details are empty, check your config") - - def _get_season_search_strings(self, ep_obj): - return ['^' + x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return ['^' + x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - - def _doSearch(self, search, show=None, age=None): - params = {'uid': sickbeard.NZBSRUS_UID, - 'key': sickbeard.NZBSRUS_HASH, - 'xml': 1, - 'age': sickbeard.USENET_RETENTION, - 'lang0': 1, # English only from CouchPotato - 'lang1': 1, - 'lang3': 1, - 'c91': 1, # TV:HD - 'c104': 1, # TV:SD-x264 - 'c75': 1, # TV:XviD - 'searchtext': search} - - if not params['age']: - params['age'] = 500 - - searchURL = self.url + 'api.php?' + urllib.urlencode(params) - logger.log(u"NZBS'R'US search url: " + searchURL, logger.DEBUG) - - data = self.cache.getRSSFeed(searchURL) - if not data: - return [] - - items = data.entries - if not len(items) > 0: - logger.log(u"Error trying to parse NZBS'R'US XML data.", logger.ERROR) - logger.log(u"RSS data: " + data, logger.DEBUG) - return [] - - return items - - def _get_title_and_url(self, item): - if item.title: # RSS feed - title = item.title - url = item.link - else: # API item - title = item.name - nzbID = item.id - key = item.key - url = self.url + 'nzbdownload_rss.php' + '/' + \ - nzbID + '/' + sickbeard.NZBSRUS_UID + '/' + key + '/' - return (title, url) - - -class NZBsRUSCache(tvcache.TVCache): - def __init__(self, provider): - tvcache.TVCache.__init__(self, provider) - # only poll NZBs'R'US every 15 minutes max - self.minTime = 15 - - def _getDailyData(self): - url = self.provider.url + 'rssfeed.php?' - urlArgs = {'cat': '91,75,104', # HD,XviD,SD-x264 - 'i': sickbeard.NZBSRUS_UID, - 'h': sickbeard.NZBSRUS_HASH} - - url += urllib.urlencode(urlArgs) - logger.log(u"NZBs'R'US cache update URL: " + url, logger.DEBUG) - - return self.getRSSFeed(url).entries - - def _checkAuth(self, data): - return data != 'Invalid Link' - - -provider = NZBsRUSProvider() diff --git a/sickbeard/providers/omgwtfnzbs.py b/sickbeard/providers/omgwtfnzbs.py index 946a3f41..26177ba5 100644 --- a/sickbeard/providers/omgwtfnzbs.py +++ b/sickbeard/providers/omgwtfnzbs.py @@ -164,9 +164,11 @@ class OmgwtfnzbsCache(tvcache.TVCache): logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG) - return self.getRSSFeed(rss_url).entries + data = self.getRSSFeed(rss_url) - def _checkAuth(self, data): - return self.provider._checkAuthFromData(data) + if data and 'entries' in data: + return data.entries + else: + return [] provider = OmgwtfnzbsProvider() diff --git a/sickbeard/providers/publichd.py b/sickbeard/providers/publichd.py deleted file mode 100644 index ff719c01..00000000 --- a/sickbeard/providers/publichd.py +++ /dev/null @@ -1,253 +0,0 @@ -# Author: Mr_Orange -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of SickRage. -# -# SickRage is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SickRage is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SickRage. If not, see . - -from __future__ import with_statement - -import sys -import os -import traceback -import urllib, urlparse -import re -import datetime -import sickbeard -import generic - -from sickbeard.common import Quality -from sickbeard import logger -from sickbeard import tvcache -from sickbeard import helpers -from sickbeard import db -from sickbeard import classes -from sickbeard.show_name_helpers import allPossibleShowNames, sanitizeSceneName -from sickbeard.exceptions import ex -from sickbeard import encodingKludge as ek -from sickbeard import clients - -from lib import requests -from lib.requests import exceptions -from sickbeard.bs4_parser import BS4Parser -from lib.unidecode import unidecode - - -class PublicHDProvider(generic.TorrentProvider): - def __init__(self): - - generic.TorrentProvider.__init__(self, "PublicHD") - - self.supportsBacklog = True - - self.enabled = False - self.ratio = None - self.minseed = None - self.minleech = None - - self.cache = PublicHDCache(self) - - self.url = 'http://phdproxy.com/' - - self.searchurl = self.url + 'index.php?page=torrents&search=%s&active=0&category=%s&order=5&by=2' #order by seed - - self.categories = {'Season': ['23'], 'Episode': ['7', '14', '24'], 'RSS': ['7', '14', '23', '24']} - - def isEnabled(self): - return self.enabled - - def imageName(self): - return 'publichd.png' - - def getQuality(self, item, anime=False): - - quality = Quality.sceneQuality(item[0], anime) - return quality - - def _get_season_search_strings(self, ep_obj): - search_string = {'Season': []} - - for show_name in set(allPossibleShowNames(self.show)): - if ep_obj.show.air_by_date or ep_obj.show.sports: - ep_string = show_name + str(ep_obj.airdate).split('-')[0] - else: - ep_string = show_name + ' S%02d' % int(ep_obj.scene_season) #1) showName SXX -SXXE - search_string['Season'].append(ep_string) - - if ep_obj.show.air_by_date or ep_obj.show.sports: - ep_string = show_name + ' Season ' + str(ep_obj.airdate).split('-')[0] - else: - ep_string = show_name + ' Season ' + str(ep_obj.scene_season) #2) showName Season X - search_string['Season'].append(ep_string) - - return [search_string] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - - search_string = {'Episode': []} - - if not ep_obj: - return [] - - if self.show.air_by_date: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - str(ep_obj.airdate).replace('-', '|') - search_string['Episode'].append(ep_string) - elif self.show.sports: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - str(ep_obj.airdate).replace('-', '|') + '|' + \ - ep_obj.airdate.strftime('%b') - search_string['Episode'].append(ep_string) - else: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season, - 'episodenumber': ep_obj.scene_episode} - - for x in add_string.split('|'): - to_search = re.sub('\s+', ' ', ep_string + ' %s' % x) - search_string['Episode'].append(to_search) - - return [search_string] - - def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0): - - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_params.keys(): - for search_string in search_params[mode]: - - if mode == 'RSS': - searchURL = self.url + 'index.php?page=torrents&active=1&category=%s' % ( - ';'.join(self.categories[mode])) - logger.log(u"PublicHD cache update URL: " + searchURL, logger.DEBUG) - else: - searchURL = self.searchurl % ( - urllib.quote(unidecode(search_string)), ';'.join(self.categories[mode])) - logger.log(u"Search string: " + searchURL, logger.DEBUG) - - html = self.getURL(searchURL) - if not html: - continue - - #remove unneccecary