diff --git a/CHANGES.md b/CHANGES.md index 6880d0cf..09c4b64a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,9 @@ * Change Search Settings/Torrent/Deluge option texts for improved understanding * Fix Womble's Index searching (ssl disabled for now, old categories are the new active ones again) * Fix Add From Trending Show page to work with Trakt changes +* Add anime unit test cases (port from lad1337/sickbeard) +* Fix normal tv show regex (port from midgetspy/sickbeard) +* Fix anime regex (port from lad1337/sickbeard) [develop changelog] @@ -56,6 +59,10 @@ * Fix restart issue * Fix to use new TorrentDay URLs * Fix typo in menu item Manage/Update XBMC +* Fix NameParser unittests +* Add Anime unittest cases (port from lad1337/sickbeard) +* Fix normal tv show regex (port from midgetspy/sickbeard) +* Fix anime regex (port from lad1337/sickbeard) ### 0.4.0 (2014-12-04 10:50:00 UTC) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 4bd77985..dc665ca0 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -26,8 +26,9 @@ import os.path import regexes import sickbeard -from sickbeard import logger, helpers, scene_numbering, common, exceptions, scene_exceptions, encodingKludge as ek, db +from sickbeard import logger, helpers, scene_numbering, common, scene_exceptions, encodingKludge as ek, db from dateutil import parser +from sickbeard.exceptions import ex class NameParser(object): @@ -36,13 +37,14 @@ class NameParser(object): ANIME_REGEX = 2 def __init__(self, file_name=True, showObj=None, tryIndexers=False, convert=False, - naming_pattern=False): + naming_pattern=False, testing=False): self.file_name = file_name self.showObj = showObj self.tryIndexers = tryIndexers self.convert = convert self.naming_pattern = naming_pattern + self.testing = testing if self.showObj and not self.showObj.is_anime: self._compile_regexes(self.NORMAL_REGEX) @@ -78,7 +80,7 @@ class NameParser(object): def _compile_regexes(self, regexMode): if regexMode == self.ANIME_REGEX: logger.log(u"Using ANIME regexs", logger.DEBUG) - uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes] + uncompiled_regex = [regexes.anime_regexes] elif regexMode == self.NORMAL_REGEX: logger.log(u"Using NORMAL regexs", logger.DEBUG) uncompiled_regex = [regexes.normal_regexes] @@ -86,7 +88,8 @@ class NameParser(object): logger.log(u"Using ALL regexes", logger.DEBUG) uncompiled_regex = [regexes.normal_regexes, regexes.anime_regexes] - self.compiled_regexes = [] + self.compiled_regexes = {0: [], 1: []} + index = 0 for regexItem in uncompiled_regex: for cur_pattern_num, (cur_pattern_name, cur_pattern) in enumerate(regexItem): try: @@ -94,7 +97,8 @@ class NameParser(object): 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)) + self.compiled_regexes[index].append([cur_pattern_num, cur_pattern_name, cur_regex]) + index += 1 def _parse_string(self, name): if not name: @@ -102,233 +106,242 @@ class NameParser(object): matches = [] bestResult = None + for regex in self.compiled_regexes: + for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes[regex]: + match = cur_regex.match(name) - 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 'series_num' in named_groups and match.group('series_num'): - 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 'air_date' in named_groups: - air_date = match.group('air_date') - try: - result.air_date = parser.parse(air_date, fuzzy=True).date() - result.score += 1 - except: + if not match: continue - if 'extra_info' in named_groups: - tmp_extra_info = match.group('extra_info') + result = ParseResult(name) + result.which_regex = [cur_regex_name] + result.score = 0 - cur_regex_num - # 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 + named_groups = match.groupdict().keys() - if 'release_group' in named_groups: - result.release_group = match.group('release_group') - result.score += 1 + 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 '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 + if 'series_num' in named_groups and match.group('series_num'): + result.score += 1 - matches.append(result) + 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 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) + 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 - 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) + 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 - # 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: - airdate = bestResult.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]) - - season_number = None - episode_numbers = [] - - 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): + 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')) + # make an attempt to detect YYYY-DD-MM formats + if month > 12: + tmp_month = month + month = day + day = tmp_month try: - lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() + result.air_date = datetime.date(year, month, day) + except ValueError, e: + raise InvalidNameException(ex(e)) - if bestResult.show.lang: - lINDEXER_API_PARMS['language'] = bestResult.show.lang + if 'extra_info' in named_groups: + tmp_extra_info = match.group('extra_info') - t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS) + # 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 - epObj = t[bestResult.show.indexerid].airedOn(bestResult.air_date)[0] + if 'release_group' in named_groups: + result.release_group = match.group('release_group') + result.score += 1 - season_number = int(epObj["seasonnumber"]) - episode_numbers = [int(epObj["episodenumber"])] - except sickbeard.indexer_episodenotfound: - logger.log(u"Unable to find episode with date " + str(bestResult.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 = [] + 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 - for epNo in episode_numbers: - s = season_number - e = epNo + matches.append(result) - 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 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) - 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 + 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) - if self.convert: - a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid, - bestResult.show.indexer, epAbsNo, - True, scene_season) + # confirm passed in show object indexer id matches result show object indexer id + if show and not self.testing: + if self.showObj and show.indexerid != self.showObj.indexerid: + show = None + bestResult.show = show + elif not show and self.showObj: + bestResult.show = self.showObj - (s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a]) + if bestResult.show and bestResult.show.is_anime and len(self.compiled_regexes[1]) > 1 and regex != 1: + continue - new_absolute_numbers.append(a) - new_episode_numbers.extend(e) - new_season_numbers.append(s) + # 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 - elif bestResult.season_number and len(bestResult.episode_numbers): - for epNo in bestResult.episode_numbers: - s = bestResult.season_number - e = epNo + # get quality + bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime) - 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 = [] + new_season_numbers = [] + new_absolute_numbers = [] - new_episode_numbers.append(e) - new_season_numbers.append(s) + # if we have an air-by-date show then get the real season/episode numbers + if bestResult.is_air_by_date: + airdate = bestResult.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]) - # 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 " - "SickGear does not support this. " - "Sorry." % (str(new_season_numbers))) + season_number = None + episode_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() + if sql_result: + season_number = int(sql_result[0][0]) + episode_numbers = [int(sql_result[0][1])] - # 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 not season_number or not len(episode_numbers): + try: + lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy() - if len(new_absolute_numbers): - bestResult.ab_episode_numbers = new_absolute_numbers + if bestResult.show.lang: + lINDEXER_API_PARMS['language'] = bestResult.show.lang - if len(new_season_numbers) and len(new_episode_numbers): - bestResult.episode_numbers = new_episode_numbers - bestResult.season_number = new_season_numbers[0] + t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS) - if self.convert: - logger.log( - u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8', - 'xmlcharrefreplace'), - logger.DEBUG) + epObj = t[bestResult.show.indexerid].airedOn(bestResult.air_date)[0] - # CPU sleep - time.sleep(0.02) + season_number = int(epObj["seasonnumber"]) + episode_numbers = [int(epObj["episodenumber"])] + except sickbeard.indexer_episodenotfound: + logger.log(u"Unable to find episode with date " + str(bestResult.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 = [] - return bestResult + 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) and not self.testing: + 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) and not self.testing: + 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 " + "SickGear 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 @@ -416,7 +429,7 @@ class NameParser(object): file_name_result = self._parse_string(base_file_name) # use only the direct parent dir - dir_name = os.path.basename(dir_name) + dir_name = ek.ek(os.path.basename, dir_name) # parse the dirname for extra info if needed dir_name_result = self._parse_string(dir_name) @@ -452,8 +465,12 @@ class NameParser(object): 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 self.testing: + pass + #final_result.which_regex = [] + else: + 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 not final_result.ab_episode_numbers and not final_result.series_name: @@ -506,7 +523,7 @@ class ParseResult(object): self.air_date = air_date - self.which_regex = [] + self.which_regex = None self.show = show self.score = score @@ -530,14 +547,14 @@ class ParseResult(object): 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 + #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 @@ -556,8 +573,9 @@ class ParseResult(object): to_return += str(self.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.is_anime: + if self.version: + to_return += ' [ANIME VER: ' + str(self.version) + ']' if self.release_group: to_return += ' [GROUP: ' + self.release_group + ']' diff --git a/sickbeard/name_parser/regexes.py b/sickbeard/name_parser/regexes.py index f3e15cb6..b9b58d30 100644 --- a/sickbeard/name_parser/regexes.py +++ b/sickbeard/name_parser/regexes.py @@ -23,29 +23,31 @@ normal_regexes = [ # Show.Name.S01E02.S01E03.Source.Quality.Etc-Group # Show Name - S01E02 - S01E03 - S01E04 - Ep Name ''' - ^(?P.+?)[. _-]+ # Show_Name and separator - s(?P\d+)[. _-]* # S01 and optional separator - e(?P\d+) # E02 and separator - ([. _-]+s(?P=season_num)[. _-]* # S01 and optional separator - e(?P\d+))+ # E03/etc and separator - [. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), + ^(?P.+?)[. _-]+ # Show_Name and separator + s(?P\d+)[. _-]* # S01 and optional separator + e(?P\d+) # E02 and separator + ([. _-]+s(?P=season_num)[. _-]* # S01 and optional separator + e(?P\d+))+ # E03/etc and separator + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), ('fov_repeat', # Show.Name.1x02.1x03.Source.Quality.Etc-Group # Show Name - 1x02 - 1x03 - 1x04 - Ep Name ''' - ^(?P.+?)[. _-]+ # Show_Name and separator - (?P\d+)x # 1x - (?P\d+) # 02 and separator - ([. _-]+(?P=season_num)x # 1x - (?P\d+))+ # 03/etc and separator - [. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), + ^(?P.+?)[. _-]+ # Show_Name and separator + (?P\d+)x # 1x + (?P\d+) # 02 and separator + ([. _-]+(?P=season_num)x # 1x + (?P\d+))+ # 03/etc and separator + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), ('standard', # Show.Name.S01E02.Source.Quality.Etc-Group @@ -55,15 +57,16 @@ normal_regexes = [ # Show Name - S01E02-03 - My Ep Name # Show.Name.S01.E02.E03 ''' - ^((?P.+?)[. _-]+)? # Show_Name and separator - s(?P\d+)[. _-]* # S01 and optional separator - e(?P\d+) # E02 and separator - (([. _-]*e|-) # linking e/- char - (?P(?!(1080|720|480)[pi])\d+))* # additional E03/etc - [. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), + ^((?P.+?)[. _-]+)? # Show_Name and separator + s(?P\d+)[. _-]* # S01 and optional separator + e(?P\d+) # E02 and separator + (([. _-]*e|-) # linking e/- char + (?P(?!(1080|720|480)[pi])\d+))* # additional E03/etc + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), ('fov', # Show_Name.1x02.Source_Quality_Etc-Group @@ -71,70 +74,64 @@ normal_regexes = [ # Show_Name.1x02x03x04.Source_Quality_Etc-Group # Show Name - 1x02-03-04 - My Ep Name ''' - ^((?P.+?)[\[. _-]+)? # Show_Name and separator - (?P\d+)x # 1x - (?P\d+) # 02 and separator - (([. _-]*x|-) # linking x/- char + ^((?P.+?)[\[. _-]+)? # Show_Name and separator + (?P\d+)x # 1x + (?P\d+) # 02 and separator + (([. _-]*x|-) # linking x/- char (?P - (?!(1080|720|480)[pi])(?!(?<=x)264) # ignore obviously wrong multi-eps - \d+))* # additional x03/etc - [\]. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), + (?!(1080|720|480)[pi])(?!(?<=x)264) # ignore obviously wrong multi-eps + \d+))* # additional x03/etc + [\]. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), ('scene_date_format', # Show.Name.2010.11.23.Source.Quality.Etc-Group # Show Name - 2010-11-23 - Ep Name ''' - ^((?P.+?)[. _-]+)? # Show_Name and separator - (?P(\d+[. _-]\d+[. _-]\d+)|(\d+\w+[. _-]\w+[. _-]\d+)) - [. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), - - ('scene_sports_format', - # Show.Name.100.Event.2010.11.23.Source.Quality.Etc-Group - # Show.Name.2010.11.23.Source.Quality.Etc-Group - # Show Name - 2010-11-23 - Ep Name + ^((?P.+?)[. _-]+)? # Show_Name and separator + (?P\d{4})[. _-]+ # 2010 and separator + (?P\d{2})[. _-]+ # 11 and separator + (?P\d{2}) # 23 and separator + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group ''' - ^(?P.*?(UEFA|MLB|ESPN|WWE|MMA|UFC|TNA|EPL|NASCAR|NBA|NFL|NHL|NRL|PGA|SUPER LEAGUE|FORMULA|FIFA|NETBALL|MOTOGP).*?)[. _-]+ - ((?P\d{1,3})[. _-]+)? - (?P(\d+[. _-]\d+[. _-]\d+)|(\d+\w+[. _-]\w+[. _-]\d+))[. _-]+ - ((?P.+?)((?[^- ]+([. _-]\[.*\])?))?)?$ - '''), + ), ('stupid', # tpz-abc102 ''' - (?P.+?)-\w+?[\. ]? # tpz-abc - (?!264) # don't count x264 - (?P\d{1,2}) # 1 - (?P\d{2})$ # 02 - '''), + (?P.+?)-\w+?[\. ]? # tpz-abc + (?!264) # don't count x264 + (?P\d{1,2}) # 1 + (?P\d{2})$ # 02 + ''' + ), ('verbose', # Show Name Season 1 Episode 2 Ep Name ''' - ^(?P.+?)[. _-]+ # Show Name and separator - season[. _-]+ # season and separator - (?P\d+)[. _-]+ # 1 - episode[. _-]+ # episode and separator - (?P\d+)[. _-]+ # 02 and separator - (?P.+)$ # Source_Quality_Etc- - '''), + ^(?P.+?)[. _-]+ # Show Name and separator + season[. _-]+ # season and separator + (?P\d+)[. _-]+ # 1 + episode[. _-]+ # episode and separator + (?P\d+)[. _-]+ # 02 and separator + (?P.+)$ # Source_Quality_Etc- + ''' + ), ('season_only', # Show.Name.S01.Source.Quality.Etc-Group ''' - ^((?P.+?)[. _-]+)? # Show_Name and separator - s(eason[. _-])? # S01/Season 01 - (?P\d+)[. _-]* # S01 and optional separator - [. _-]*((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group + ^((?P.+?)[. _-]+)? # Show_Name and separator + s(eason[. _-])? # S01/Season 01 + (?P\d+)[. _-]* # S01 and optional separator + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group ''' ), @@ -142,14 +139,14 @@ normal_regexes = [ # Show.Name.E02-03 # Show.Name.E02.2010 ''' - ^((?P.+?)[. _-]+)? # Show_Name and separator - (e(p(isode)?)?|part|pt)[. _-]? # e, ep, episode, or part - (?P(\d+|[ivx]+)) # first ep num - ((([. _-]+(and|&|to)[. _-]+)|-) # and/&/to joiner + ^((?P.+?)[. _-]+)? # Show_Name and separator + (e(p(isode)?)?|part|pt)[. _-]? # e, ep, episode, or part + (?P(\d+|[ivx]+)) # first ep num + ((([. _-]+(and|&|to)[. _-]+)|-) # and/&/to joiner (?P(?!(1080|720|480)[pi])(\d+|[ivx]+))[. _-]) # second ep num - ([. _-]*(?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group + ([. _-]*(?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group ''' ), @@ -158,58 +155,57 @@ normal_regexes = [ # Show.Name.Part.3.Source.Quality.Etc-Group # Show.Name.Part.1.and.Part.2.Blah-Group ''' - ^((?P.+?)[. _-]+)? # Show_Name and separator - (e(p(isode)?)?|part|pt)[. _-]? # e, ep, episode, or part - (?P(\d+|([ivx]+(?=[. _-])))) # first ep num - ([. _-]+((and|&|to)[. _-]+)? # and/&/to joiner - ((e(p(isode)?)?|part|pt)[. _-]?) # e, ep, episode, or part + ^((?P.+?)[. _-]+)? # Show_Name and separator + (e(p(isode)?)?|part|pt)[. _-]? # e, ep, episode, or part + (?P(\d+|([ivx]+(?=[. _-])))) # first ep num + ([. _-]+((and|&|to)[. _-]+)? # and/&/to joiner + ((e(p(isode)?)?|part|pt)[. _-]?) # e, ep, episode, or part (?P(?!(1080|720|480)[pi]) - (\d+|([ivx]+(?=[. _-]))))[. _-])* # second ep num - ([. _-]*(?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group - ''' - ), - - ('no_season', - # Show Name - 01 - Ep Name - # 01 - Ep Name - # 01 - Ep Name - ''' - ^((?P.+?)(?:[. _-]{2,}|[. _]))? # Show_Name and separator - (?P\d{1,3}) # 02 - (?:-(?P\d{1,3}))* # -03-04-05 etc - \s?of?\s?\d{1,3}? # of joiner (with or without spaces) and series total ep - [. _-]+((?P.+?) # Source_Quality_Etc- - ((?[^- ]+([. _-]\[.*\])?))?)?$ # Group + (\d+|([ivx]+(?=[. _-]))))[. _-])* # second ep num + ([. _-]*(?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group ''' ), ('bare', # Show.Name.102.Source.Quality.Etc-Group ''' - ^(?P.+?)[. _-]+ # Show_Name and separator - (?P\d{1,2}) # 1 - (?P\d{2}) # 02 and separator + ^(?P.+?)[. _-]+ # Show_Name and separator + (?P\d{1,2}) # 1 + (?P\d{2}) # 02 and separator ([. _-]+(?P(?!\d{3}[. _-]+)[^-]+) # Source_Quality_Etc- - (-(?P[^- ]+([. _-]\[.*\])?))?)?$ # Group - '''), + (-(?P.+))?)?$ # Group + ''' + ), + + ('no_season', + # Show Name - 01 - Ep Name + # 01 - Ep Name + ''' + ^((?P.+?)(?:[. _-]{2,}|[. _]))? # Show_Name and separator + (?P\d{1,2}) # 01 + (?:-(?P\d{1,2}))* # 02 + [. _-]+((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), ] anime_regexes = [ ('anime_ultimate', - """ + ''' ^(?:\[(?P.+?)\][ ._-]*) (?P.+?)[ ._-]+ - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))?[ ._-]+? + (?P\d{1,3}) + (-(?P\d{1,3}))?[ ._-]+? (?:v(?P[0-9]))? (?:[\w\.]*) (?:(?:(?:[\[\(])(?P\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)(?:[\]\)]))|(?:\d{3,4}[xp])) (?:[ ._]?\[(?P\w+)\])? .*? - """ + ''' ), ('anime_standard', # [Group Name] Show Name.13-14 @@ -219,163 +215,163 @@ anime_regexes = [ # [Group Name] Show Name - 13 # Show Name 13 ''' - ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator - (?P.+?)[ ._-]+ # Show_Name and separator - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # E01 - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # E02 - (v(?P[0-9]))? # version - [ ._-]+\[(?P\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\] # Source_Quality_Etc- - (\[(?P\w{8})\])? # CRC - .*? # Separator and EOL + ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator + (?P.+?)[ ._-]+ # Show_Name and separator + (?P\d{1,3}) # E01 + (-(?P\d{1,3}))? # E02 + (v(?P[0-9]))? # version + [ ._-]+\[(?P\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\] # Source_Quality_Etc- + (\[(?P\w{8})\])? # CRC + .*? # Separator and EOL '''), - ('anime_standard_round', - # TODO examples # [Stratos-Subs]_Infinite_Stratos_-_12_(1280x720_H.264_AAC)_[379759DB] # [ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC) ''' - ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator - (?P.+?)[ ._-]+ # Show_Name and separator - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # E01 - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # E02 - (v(?P[0-9]))? # version - [ ._-]+\((?P(CX[ ._-]?)?\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\) # Source_Quality_Etc- - (\[(?P\w{8})\])? # CRC - .*? # Separator and EOL + ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator + (?P.+?)[ ._-]+ # Show_Name and separator + (?P\d{1,3}) # E01 + (-(?P\d{1,3}))? # E02 + (v(?P[0-9]))? # version + [ ._-]+\((?P(CX[ ._-]?)?\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\) # Source_Quality_Etc- + (\[(?P\w{8})\])? # CRC + .*? # Separator and EOL '''), - ('anime_slash', # [SGKK] Bleach 312v1 [720p/MKV] ''' - ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator - (?P.+?)[ ._-]+ # Show_Name and separator - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # E01 - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # E02 - (v(?P[0-9]))? # version - [ ._-]+\[(?P\d{3,4}p) # Source_Quality_Etc- - (\[(?P\w{8})\])? # CRC - .*? # Separator and EOL + ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator + (?P.+?)[ ._-]+ # Show_Name and separator + (?P\d{1,3}) # E01 + (-(?P\d{1,3}))? # E02 + (v(?P[0-9]))? # version + [ ._-]+\[(?P\d{3,4}p) # Source_Quality_Etc- + (\[(?P\w{8})\])? # CRC + .*? # Separator and EOL '''), - ('anime_standard_codec', # [Ayako]_Infinite_Stratos_-_IS_-_07_[H264][720p][EB7838FC] # [Ayako] Infinite Stratos - IS - 07v2 [H264][720p][44419534] # [Ayako-Shikkaku] Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - 10 [LQ][h264][720p] [8853B21C] ''' - ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator - (?P.+?)[ ._]* # Show_Name and separator - ([ ._-]+-[ ._-]+[A-Z]+[ ._-]+)?[ ._-]+ # funny stuff, this is sooo nuts ! this will kick me in the butt one day - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # E01 - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # E02 - (v(?P[0-9]))? # version - ([ ._-](\[\w{1,2}\])?\[[a-z][.]?\w{2,4}\])? #codec - [ ._-]*\[(?P(\d{3,4}[xp]?\d{0,4})?[\.\w\s-]*)\] # Source_Quality_Etc- - (\[(?P\w{8})\])? - .*? # Separator and EOL + ^(\[(?P.+?)\][ ._-]*)? # Release Group and separator + (?P.+?)[ ._]* # Show_Name and separator + ([ ._-]+-[ ._-]+[A-Z]+[ ._-]+)?[ ._-]+ # this will kick me in the butt one day + (?P\d{1,3}) # E01 + (-(?P\d{1,3}))? # E02 + (v(?P[0-9]))? # version + ([ ._-](\[\w{1,2}\])?\[[a-z][.]?\w{2,4}\])? # codec + [ ._-]*\[(?P(\d{3,4}[xp]?\d{0,4})?[\.\w\s-]*)\] # Source_Quality_Etc- + (\[(?P\w{8})\])? # CRC + .*? # Separator and EOL '''), - - ('anime_codec_crc', - ''' - ^(?:\[(?P.*?)\][ ._-]*)? - (?:(?P.*?)[ ._-]*)? - (?:(?P(((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))[ ._-]*).+? - (?:\[(?P.*?)\][ ._-]*) - (?:\[(?P\w{8})\])? - .*? - '''), - ('anime_and_normal', # Bleach - s16e03-04 - 313-314 # Bleach.s16e03-04.313-314 # Bleach s16e03e04 313-314 ''' - ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator - [sS](?P\d+)[. _-]* # S01 and optional separator - [eE](?P\d+) # epipisode E02 - (([. _-]*e|-) # linking e/- char - (?P\d+))* # additional E03/etc - ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way - ((?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # absolute number - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # "-" as separator and anditional absolute number, all optinal - (v(?P[0-9]))? # the version e.g. "v2" + ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator + [sS](?P\d+)[. _-]* # S01 and optional separator + [eE](?P\d+) # epipisode E02 + (([. _-]*e|-) # linking e/- char + (?P\d+))* # additional E03/etc + ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be + # there(->{2,}) "s16e03-04-313-314" would make sens any way + (?P\d{1,3}) # absolute number + (-(?P\d{1,3}))* # "-" as separator and anditional absolute number, all optinal + (v(?P[0-9]))? # the version e.g. "v2" .*? ''' - ), - ('anime_and_normal_x', # Bleach - s16e03-04 - 313-314 # Bleach.s16e03-04.313-314 # Bleach s16e03e04 313-314 ''' - ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator - (?P\d+)[. _-]* # S01 and optional separator - [xX](?P\d+) # epipisode E02 - (([. _-]*e|-) # linking e/- char - (?P\d+))* # additional E03/etc - ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way - ((?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # absolute number - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # "-" as separator and anditional absolute number, all optinal - (v(?P[0-9]))? # the version e.g. "v2" + ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator + (?P\d+)[. _-]* # S01 and optional separator + [xX](?P\d+) # epipisode E02 + (([. _-]*e|-) # linking e/- char + (?P\d+))* # additional E03/etc + ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be + # there(->{2,}) "s16e03-04-313-314" would make sens any way + (?P\d{1,3}) # absolute number + (-(?P\d{1,3}))* # "-" as separator and anditional absolute number, all optinal + (v(?P[0-9]))? # the version e.g. "v2" .*? ''' - ), - ('anime_and_normal_reverse', # Bleach - 313-314 - s16e03-04 ''' - ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # absolute number - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # "-" as separator and anditional absolute number, all optinal - (v(?P[0-9]))? # the version e.g. "v2" - ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way - [sS](?P\d+)[. _-]* # S01 and optional separator - [eE](?P\d+) # epipisode E02 - (([. _-]*e|-) # linking e/- char - (?P\d+))* # additional E03/etc + ^(?P.+?)[ ._-]+ # start of string and series name and non optinal separator + (?P\d{1,3}) # absolute number + (-(?P\d{1,3}))* # "-" as separator and anditional absolute number, all optinal + (v(?P[0-9]))? # the version e.g. "v2" + ([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be + # there(->{2,}) "s16e03-04-313-314" would make sens any way + [sS](?P\d+)[. _-]* # S01 and optional separator + [eE](?P\d+) # epipisode E02 + (([. _-]*e|-) # linking e/- char + (?P\d+))* # additional E03/etc .*? ''' ), - ('anime_and_normal_front', # 165.Naruto Shippuuden.s08e014 ''' - ^(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # start of string and absolute number - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # "-" as separator and anditional absolute number, all optinal + ^(?P\d{1,3}) # start of string and absolute number + (-(?P\d{1,3}))* # "-" as separator and anditional absolute number, all optinal (v(?P[0-9]))?[ ._-]+ # the version e.g. "v2" (?P.+?)[ ._-]+ - [sS](?P\d+)[. _-]* # S01 and optional separator + [sS](?P\d+)[. _-]* # S01 and optional separator [eE](?P\d+) - (([. _-]*e|-) # linking e/- char - (?P\d+))* # additional E03/etc + (([. _-]*e|-) # linking e/- char + (?P\d+))* # additional E03/etc .*? ''' ), - ('anime_ep_name', ''' - ^(?:\[(?P.+?)\][ ._-]*) - (?P.+?)[ ._-]+ - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))?[ ._-]*? - (?:v(?P[0-9])[ ._-]+?)? - (?:.+?[ ._-]+?)? - \[(?P\w+)\][ ._-]? - (?:\[(?P\w{8})\])? - .*? + ^(?:\[(?P.+?)\][ ._-]*) + (?P.+?)[ ._-]+ + (?P\d{1,3}) + (-(?P\d{1,3}))*[ ._-]*? + (?:v(?P[0-9])[ ._-]+?)? + (?:.+?[ ._-]+?)? + \[(?P\w+)\][ ._-]? + (?:\[(?P\w{8})\])? + .*? ''' ), - ('anime_bare', # One Piece - 102 # [ACX]_Wolf's_Spirit_001.mkv ''' ^(\[(?P.+?)\][ ._-]*)? - (?P.+?)[ ._-]+ # Show_Name and separator - (?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}) # E01 - (-(?P((?!(1080|720|480)[pi])|(?![hx].?264))\d{1,3}))? # E02 - (v(?P[0-9]))? # v2 - .*? # Separator and EOL - ''') -] + (?P.+?)[ ._-]+ # Show_Name and separator + (?\d{3})(?!0p) # E01, while avoiding H.264 and 1080p from being matched + (-(?P\d{3}))* # E02 + (v(?P[0-9]))? # v2 + .*? # Separator and EOL + '''), + + ('standard', + # Show.Name.S01E02.Source.Quality.Etc-Group + # Show Name - S01E02 - My Ep Name + # Show.Name.S01.E03.My.Ep.Name + # Show.Name.S01E02E03.Source.Quality.Etc-Group + # Show Name - S01E02-03 - My Ep Name + # Show.Name.S01.E02.E03 + ''' + ^((?P.+?)[. _-]+)? # Show_Name and separator + s(?P\d+)[. _-]* # S01 and optional separator + e(?P\d+) # E02 and separator + (([. _-]*e|-) # linking e/- char + (?P(?!(1080|720|480)[pi])\d+))* # additional E03/etc + [. _-]*((?P.+?) # Source_Quality_Etc- + ((?[^- ]+))?)?$ # Group + ''' + ), +] \ No newline at end of file diff --git a/tests/name_parser_tests.py b/tests/name_parser_tests.py index 821fed19..2fa64f56 100644 --- a/tests/name_parser_tests.py +++ b/tests/name_parser_tests.py @@ -3,163 +3,244 @@ import unittest import test_lib as test import sys, os.path + sys.path.append(os.path.abspath('..')) sys.path.append(os.path.abspath('../lib')) from sickbeard.name_parser import parser import sickbeard + sickbeard.SYS_ENCODING = 'UTF-8' DEBUG = VERBOSE = False simple_test_cases = { - 'standard': { - 'Mr.Show.Name.S01E02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Mr Show Name', 1, [2], 'Source.Quality.Etc', 'Group'), - 'Show.Name.S01E02': parser.ParseResult(None, 'Show Name', 1, [2]), - 'Show Name - S01E02 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2], 'My Ep Name'), - 'Show.1.0.Name.S01.E03.My.Ep.Name-Group': parser.ParseResult(None, 'Show 1.0 Name', 1, [3], 'My.Ep.Name', 'Group'), - 'Show.Name.S01E02E03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2,3], 'Source.Quality.Etc', 'Group'), - 'Mr. Show Name - S01E02-03 - My Ep Name': parser.ParseResult(None, 'Mr. Show Name', 1, [2,3], 'My Ep Name'), - 'Show.Name.S01.E02.E03': parser.ParseResult(None, 'Show Name', 1, [2,3]), - 'Show.Name-0.2010.S01E02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name-0 2010', 1, [2], 'Source.Quality.Etc', 'Group'), - 'S01E02 Ep Name': parser.ParseResult(None, None, 1, [2], 'Ep Name'), - 'Show Name - S06E01 - 2009-12-20 - Ep Name': parser.ParseResult(None, 'Show Name', 6, [1], '2009-12-20 - Ep Name'), - 'Show Name - S06E01 - -30-': parser.ParseResult(None, 'Show Name', 6, [1], '30-' ), - 'Show-Name-S06E01-720p': parser.ParseResult(None, 'Show-Name', 6, [1], '720p' ), - 'Show-Name-S06E01-1080i': parser.ParseResult(None, 'Show-Name', 6, [1], '1080i' ), - 'Show.Name.S06E01.Other.WEB-DL': parser.ParseResult(None, 'Show Name', 6, [1], 'Other.WEB-DL' ), - 'Show.Name.S06E01 Some-Stuff Here': parser.ParseResult(None, 'Show Name', 6, [1], 'Some-Stuff Here' ), - }, - - 'fov': { - 'Show_Name.1x02.Source_Quality_Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2], 'Source_Quality_Etc', 'Group'), - 'Show Name 1x02': parser.ParseResult(None, 'Show Name', 1, [2]), - 'Show Name 1x02 x264 Test': parser.ParseResult(None, 'Show Name', 1, [2], 'x264 Test'), - 'Show Name - 1x02 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2], 'My Ep Name'), - 'Show_Name.1x02x03x04.Source_Quality_Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2,3,4], 'Source_Quality_Etc', 'Group'), - 'Show Name - 1x02-03-04 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2,3,4], 'My Ep Name'), - '1x02 Ep Name': parser.ParseResult(None, None, 1, [2], 'Ep Name'), - 'Show-Name-1x02-720p': parser.ParseResult(None, 'Show-Name', 1, [2], '720p'), - 'Show-Name-1x02-1080i': parser.ParseResult(None, 'Show-Name', 1, [2], '1080i'), - 'Show Name [05x12] Ep Name': parser.ParseResult(None, 'Show Name', 5, [12], 'Ep Name'), - 'Show.Name.1x02.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2], 'WEB-DL'), - }, + 'standard': { + 'Mr.Show.Name.S01E02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Mr Show Name', 1, [2], 'Source.Quality.Etc', 'Group'), + 'Show.Name.S01E02': parser.ParseResult(None, 'Show Name', 1, [2]), + 'Show Name - S01E02 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2], 'My Ep Name'), + 'Show.1.0.Name.S01.E03.My.Ep.Name-Group': parser.ParseResult(None, 'Show 1.0 Name', 1, [3], 'My.Ep.Name', 'Group'), + 'Show.Name.S01E02E03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2, 3], 'Source.Quality.Etc', 'Group'), + 'Mr. Show Name - S01E02-03 - My Ep Name': parser.ParseResult(None, 'Mr. Show Name', 1, [2, 3], 'My Ep Name'), + 'Show.Name.S01.E02.E03': parser.ParseResult(None, 'Show Name', 1, [2, 3]), + 'Show.Name-0.2010.S01E02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name-0 2010', 1, [2], 'Source.Quality.Etc', 'Group'), + 'S01E02 Ep Name': parser.ParseResult(None, None, 1, [2], 'Ep Name'), + 'Show Name - S06E01 - 2009-12-20 - Ep Name': parser.ParseResult(None, 'Show Name', 6, [1], '2009-12-20 - Ep Name'), + 'Show Name - S06E01 - -30-': parser.ParseResult(None, 'Show Name', 6, [1], '30-'), + 'Show-Name-S06E01-720p': parser.ParseResult(None, 'Show-Name', 6, [1], '720p'), + 'Show-Name-S06E01-1080i': parser.ParseResult(None, 'Show-Name', 6, [1], '1080i'), + 'Show.Name.S06E01.Other.WEB-DL': parser.ParseResult(None, 'Show Name', 6, [1], 'Other.WEB-DL'), + 'Show.Name.S06E01 Some-Stuff Here': parser.ParseResult(None, 'Show Name', 6, [1], 'Some-Stuff Here'), + }, - 'standard_repeat': { - 'Show.Name.S01E02.S01E03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2,3], 'Source.Quality.Etc', 'Group'), - 'Show.Name.S01E02.S01E03': parser.ParseResult(None, 'Show Name', 1, [2,3]), - 'Show Name - S01E02 - S01E03 - S01E04 - Ep Name': parser.ParseResult(None, 'Show Name', 1, [2,3,4], 'Ep Name'), - 'Show.Name.S01E02.S01E03.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2,3], 'WEB-DL'), - }, - - 'fov_repeat': { - 'Show.Name.1x02.1x03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2,3], 'Source.Quality.Etc', 'Group'), - 'Show.Name.1x02.1x03': parser.ParseResult(None, 'Show Name', 1, [2,3]), - 'Show Name - 1x02 - 1x03 - 1x04 - Ep Name': parser.ParseResult(None, 'Show Name', 1, [2,3,4], 'Ep Name'), - 'Show.Name.1x02.1x03.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2,3], 'WEB-DL'), - }, + 'fov': { + 'Show_Name.1x02.Source_Quality_Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2], 'Source_Quality_Etc', 'Group'), + 'Show Name 1x02': parser.ParseResult(None, 'Show Name', 1, [2]), + 'Show Name 1x02 x264 Test': parser.ParseResult(None, 'Show Name', 1, [2], 'x264 Test'), + 'Show Name - 1x02 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2], 'My Ep Name'), + 'Show_Name.1x02x03x04.Source_Quality_Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2, 3, 4], 'Source_Quality_Etc', 'Group'), + 'Show Name - 1x02-03-04 - My Ep Name': parser.ParseResult(None, 'Show Name', 1, [2, 3, 4], 'My Ep Name'), + '1x02 Ep Name': parser.ParseResult(None, None, 1, [2], 'Ep Name'), + 'Show-Name-1x02-720p': parser.ParseResult(None, 'Show-Name', 1, [2], '720p'), + 'Show-Name-1x02-1080i': parser.ParseResult(None, 'Show-Name', 1, [2], '1080i'), + 'Show Name [05x12] Ep Name': parser.ParseResult(None, 'Show Name', 5, [12], 'Ep Name'), + 'Show.Name.1x02.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2], 'WEB-DL'), + }, - 'bare': { - 'Show.Name.102.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2], 'Source.Quality.Etc', 'Group'), - 'show.name.2010.123.source.quality.etc-group': parser.ParseResult(None, 'show name 2010', 1, [23], 'source.quality.etc', 'group'), - 'show.name.2010.222.123.source.quality.etc-group': parser.ParseResult(None, 'show name 2010.222', 1, [23], 'source.quality.etc', 'group'), - 'Show.Name.102': parser.ParseResult(None, 'Show Name', 1, [2]), - 'the.event.401.hdtv-lol': parser.ParseResult(None, 'the event', 4, [1], 'hdtv', 'lol'), - 'show.name.2010.special.hdtv-blah': None, - }, - - 'stupid': { - 'tpz-abc102': parser.ParseResult(None, None, 1, [2], None, 'tpz'), - 'tpz-abc.102': parser.ParseResult(None, None, 1, [2], None, 'tpz'), - }, - - 'no_season': { - 'Show Name - 01 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name'), - '01 - Ep Name': parser.ParseResult(None, None, None, [1], 'Ep Name'), - 'Show Name - 01 - Ep Name - WEB-DL': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name - WEB-DL'), - }, + 'standard_repeat': { + 'Show.Name.S01E02.S01E03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2, 3], 'Source.Quality.Etc', 'Group'), + 'Show.Name.S01E02.S01E03': parser.ParseResult(None, 'Show Name', 1, [2, 3]), + 'Show Name - S01E02 - S01E03 - S01E04 - Ep Name': parser.ParseResult(None, 'Show Name', 1, [2, 3, 4], 'Ep Name'), + 'Show.Name.S01E02.S01E03.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2, 3], 'WEB-DL'), + }, - 'no_season_general': { - 'Show.Name.E23.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [23], 'Source.Quality.Etc', 'Group'), - 'Show Name - Episode 01 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name'), - 'Show.Name.Part.3.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [3], 'Source.Quality.Etc', 'Group'), - 'Show.Name.Part.1.and.Part.2.Blah-Group': parser.ParseResult(None, 'Show Name', None, [1,2], 'Blah', 'Group'), - 'Show.Name.Part.IV.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [4], 'Source.Quality.Etc', 'Group'), - 'Deconstructed.E07.1080i.HDTV.DD5.1.MPEG2-TrollHD': parser.ParseResult(None, 'Deconstructed', None, [7], '1080i.HDTV.DD5.1.MPEG2', 'TrollHD'), - 'Show.Name.E23.WEB-DL': parser.ParseResult(None, 'Show Name', None, [23], 'WEB-DL'), - }, + 'fov_repeat': { + 'Show.Name.1x02.1x03.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2, 3], 'Source.Quality.Etc', 'Group'), + 'Show.Name.1x02.1x03': parser.ParseResult(None, 'Show Name', 1, [2, 3]), + 'Show Name - 1x02 - 1x03 - 1x04 - Ep Name': parser.ParseResult(None, 'Show Name', 1, [2, 3, 4], 'Ep Name'), + 'Show.Name.1x02.1x03.WEB-DL': parser.ParseResult(None, 'Show Name', 1, [2, 3], 'WEB-DL'), + }, - 'no_season_multi_ep': { - 'Show.Name.E23-24.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [23,24], 'Source.Quality.Etc', 'Group'), - 'Show Name - Episode 01-02 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1,2], 'Ep Name'), - 'Show.Name.E23-24.WEB-DL': parser.ParseResult(None, 'Show Name', None, [23,24], 'WEB-DL'), - }, + 'bare': { + 'Show.Name.102.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 1, [2], 'Source.Quality.Etc', 'Group'), + 'show.name.2010.123.source.quality.etc-group': parser.ParseResult(None, 'show name 2010', 1, [23], 'source.quality.etc', 'group'), + 'show.name.2010.222.123.source.quality.etc-group': parser.ParseResult(None, 'show name 2010.222', 1, [23], 'source.quality.etc', 'group'), + 'Show.Name.102': parser.ParseResult(None, 'Show Name', 1, [2]), + 'the.event.401.hdtv-lol': parser.ParseResult(None, 'the event', 4, [1], 'hdtv', 'lol'), + # 'show.name.2010.special.hdtv-blah': None, + }, - 'season_only': { - 'Show.Name.S02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 2, [], 'Source.Quality.Etc', 'Group'), - 'Show Name Season 2': parser.ParseResult(None, 'Show Name', 2), - 'Season 02': parser.ParseResult(None, None, 2), - }, - - 'scene_date_format': { - 'Show.Name.2010.11.23.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [], 'Source.Quality.Etc', 'Group', datetime.date(2010,11,23)), - 'Show Name - 2010.11.23': parser.ParseResult(None, 'Show Name', air_date = datetime.date(2010,11,23)), - 'Show.Name.2010.23.11.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [], 'Source.Quality.Etc', 'Group', datetime.date(2010,11,23)), - 'Show Name - 2010-11-23 - Ep Name': parser.ParseResult(None, 'Show Name', extra_info = 'Ep Name', air_date = datetime.date(2010,11,23)), - '2010-11-23 - Ep Name': parser.ParseResult(None, extra_info = 'Ep Name', air_date = datetime.date(2010,11,23)), - 'Show.Name.2010.11.23.WEB-DL': parser.ParseResult(None, 'Show Name', None, [], 'WEB-DL', None, datetime.date(2010,11,23)), - }, - } + 'stupid': { + 'tpz-abc102': parser.ParseResult(None, None, 1, [2], None, 'tpz'), + 'tpz-abc.102': parser.ParseResult(None, None, 1, [2], None, 'tpz'), + }, + + 'no_season': { + 'Show Name - 01 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name'), + '01 - Ep Name': parser.ParseResult(None, None, None, [1], 'Ep Name'), + 'Show Name - 01 - Ep Name - WEB-DL': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name - WEB-DL'), + }, + + 'no_season_general': { + 'Show.Name.E23.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [23], 'Source.Quality.Etc', 'Group'), + 'Show Name - Episode 01 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1], 'Ep Name'), + 'Show.Name.Part.3.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [3], 'Source.Quality.Etc', 'Group'), + 'Show.Name.Part.1.and.Part.2.Blah-Group': parser.ParseResult(None, 'Show Name', None, [1, 2], 'Blah', 'Group'), + 'Show.Name.Part.IV.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [4], 'Source.Quality.Etc', 'Group'), + 'Deconstructed.E07.1080i.HDTV.DD5.1.MPEG2-TrollHD': parser.ParseResult(None, 'Deconstructed', None, [7], '1080i.HDTV.DD5.1.MPEG2', 'TrollHD'), + 'Show.Name.E23.WEB-DL': parser.ParseResult(None, 'Show Name', None, [23], 'WEB-DL'), + }, + + 'no_season_multi_ep': { + 'Show.Name.E23-24.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [23, 24], 'Source.Quality.Etc', 'Group'), + 'Show Name - Episode 01-02 - Ep Name': parser.ParseResult(None, 'Show Name', None, [1, 2], 'Ep Name'), + 'Show.Name.E23-24.WEB-DL': parser.ParseResult(None, 'Show Name', None, [23, 24], 'WEB-DL'), + }, + + 'season_only': { + 'Show.Name.S02.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', 2, [], 'Source.Quality.Etc', 'Group'), + 'Show Name Season 2': parser.ParseResult(None, 'Show Name', 2), + 'Season 02': parser.ParseResult(None, None, 2), + }, + + 'scene_date_format': { + 'Show.Name.2010.11.23.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [], 'Source.Quality.Etc', 'Group', datetime.date(2010, 11, 23)), + 'Show Name - 2010.11.23': parser.ParseResult(None, 'Show Name', air_date=datetime.date(2010, 11, 23)), + 'Show.Name.2010.23.11.Source.Quality.Etc-Group': parser.ParseResult(None, 'Show Name', None, [], 'Source.Quality.Etc', 'Group', datetime.date(2010, 11, 23)), + 'Show Name - 2010-11-23 - Ep Name': parser.ParseResult(None, 'Show Name', extra_info='Ep Name', air_date=datetime.date(2010, 11, 23)), + '2010-11-23 - Ep Name': parser.ParseResult(None, extra_info='Ep Name', air_date=datetime.date(2010, 11, 23)), + 'Show.Name.2010.11.23.WEB-DL': parser.ParseResult(None, 'Show Name', None, [], 'WEB-DL', None, datetime.date(2010, 11, 23)), + }, + + 'anime_ultimate': { + '[Tsuki] Bleach - 301 [1280x720][61D1D4EE]': parser.ParseResult(None, 'Bleach', None, [], '1280x720', 'Tsuki', None, [301]), + '[Tsuki] Fairy Tail - 70 [1280x720][C4807111]': parser.ParseResult(None, 'Fairy Tail', None, [], '1280x720', 'Tsuki', None, [70]), + '[SGKK] Bleach 312v2 [720p MKV]': parser.ParseResult(None, 'Bleach', None, [], '720p MKV', 'SGKK', None, [312]), + '[BSS-Anon] Tengen Toppa Gurren Lagann - 22-23 [1280x720][h264][6039D9AF]': parser.ParseResult(None, 'Tengen Toppa Gurren Lagann', None, [], '1280x720', 'BSS-Anon', None, [22, 23]), + '[SJSUBS]_Naruto_Shippuden_-_02_[480p AAC]': parser.ParseResult(None, 'Naruto Shippuden', None, [], '480p AAC', 'SJSUBS', None, [2]), + '[SFW-Chihiro] Dance in the Vampire Bund - 12 [1920x1080 Blu-ray FLAC][2F6DBC66].mkv': parser.ParseResult(None, 'Dance in the Vampire Bund', None, [], '1920x1080 Blu-ray FLAC', 'SFW-Chihiro', None, [12]), + '[SHiN-gx] Hanasaku Iroha - 01 [1280x720 h.264 AAC][BDC36683]': parser.ParseResult(None, 'Hanasaku Iroha', None, [], '1280x720 h.264 AAC', 'SHiN-gx', None, [1]), + '[SFW-Chihiro] Dance in the Vampire Bund - 02 [1920x1080 Blu-ray FLAC][C1FA0A09]': parser.ParseResult(None, 'Dance in the Vampire Bund', None, [], '1920x1080 Blu-ray FLAC', 'SFW-Chihiro', None, [2]), + '[HorribleSubs] No. 6 - 11 [720p]': parser.ParseResult(None, 'No. 6', None, [], '720p', 'HorribleSubs', None, [11]), + '[HorribleSubs] D Gray-Man - 312 (480p) [F501C9BE]': parser.ParseResult(None, 'D Gray-Man', None, [], '480p', 'HorribleSubs', None, [312]), + '[SGKK] Tengen Toppa Gurren Lagann - 45-46 (720p h264) [F501C9BE]': parser.ParseResult(None, 'Tengen Toppa Gurren Lagann', None, [], '720p h264', 'SGKK', None, [45, 46]), + '[Stratos-Subs]_Infinite_Stratos_-_12_(1280x720_H.264_AAC)_[379759DB]': parser.ParseResult(None, 'Infinite Stratos', None, [], '1280x720_H.264_AAC', 'Stratos-Subs', None, [12]), + '[ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC)': parser.ParseResult(None, 'Bleach', None, [], 'CX 1280x720 x264 AAC', 'ShinBunBu-Subs', None, [02, 03]), + '[Doki] Hanasaku Iroha - 03 (848x480 h264 AAC) [CB1AA73B]': parser.ParseResult(None, 'Hanasaku Iroha', None, [], '848x480 h264 AAC', 'Doki', None, [03]), + '[UTW]_Fractal_-_01_[h264-720p][96D3F1BF]': parser.ParseResult(None, 'Fractal', None, [], 'h264-720p', 'UTW', None, [1]), + '[a-s]_inuyasha_-_028_rs2_[BFDDF9F2]': parser.ParseResult(None, 'inuyasha', None, [], 'BFDDF9F2', 'a-s', None, [28]), + '[HorribleSubs] Fairy Tail S2 - 37 [1080p]': parser.ParseResult(None,'Fairy Tail S2', None, [], '1080p', 'HorribleSubs', None, [37]), + '[HorribleSubs] Sword Art Online II - 23 [720p]': parser.ParseResult(None, 'Sword Art Online II', None, [], '720p', 'HorribleSubs', None, [23]) + }, + + 'anime_ep_name': { + '[TzaTziki]_One_Piece_279_Chopper_Man_1_[720p][8AE5F25D]': parser.ParseResult(None, 'One Piece', None, [], '720p', 'TzaTziki', None, [279]), + "[ACX]Wolf's_Rain_-_04_-_Scars_in_the_Wasteland_[octavarium]_[82B7E357]": parser.ParseResult(None, "Wolf's Rain", None, [], 'octavarium', 'ACX', None, [4]), + '[ACX]Black Lagoon - 02v2 - Mangrove Heaven [SaintDeath] [7481F875]': parser.ParseResult(None, 'Black Lagoon', None, [], 'SaintDeath', 'ACX', None, [2]), + }, + + "anime_standard_round": { + '[SGKK] Bleach - 312v2 (1280x720 h264 AAC) [F501C9BE]': parser.ParseResult(None, 'Bleach', None, [], '1280x720 h264 AAC', 'SGKK', None, [312]), + }, + + 'anime_slash': { + '[SGKK] Bleach 312v1 [720p/MKV]': parser.ParseResult(None, 'Bleach', None, [], '720p', 'SGKK', None, [312]), + '[SGKK] Bleach 312 [480p/MKV]': parser.ParseResult(None, 'Bleach', None, [], '480p', 'SGKK', None, [312]) + }, + + 'anime_standard_codec': { + '[Ayako]_Infinite_Stratos_-_IS_-_07_[H264][720p][EB7838FC]': parser.ParseResult(None, 'Infinite Stratos', None, [], '720p', 'Ayako', None, [7]), + '[Ayako] Infinite Stratos - IS - 07v2 [H264][720p][44419534]': parser.ParseResult(None, 'Infinite Stratos', None, [], '720p', 'Ayako', None, [7]), + '[Ayako-Shikkaku] Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - 10 [LQ][h264][720p] [8853B21C]': parser.ParseResult(None, 'Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne', None, [], '720p', 'Ayako-Shikkaku', None, [10]), + '[Tsuki] Fairy Tail - 72 [XviD][C4807111]': parser.ParseResult(None, 'Fairy Tail', None, [], 'C4807111', 'Tsuki', None, [72]), + 'Bubblegum Crisis Tokyo 2040 - 25 [aX] [F4E2E558]': parser.ParseResult(None, 'Bubblegum Crisis Tokyo 2040', None, [], "aX", None, None, [25]), + + }, + + 'anime_and_normal': { + 'Bleach - s02e03 - 012 - Name & Name': parser.ParseResult(None, 'Bleach', 2, [3], None, None, None, [12]), + 'Bleach - s02e03e04 - 012-013 - Name & Name': parser.ParseResult(None, 'Bleach', 2, [3, 4], None, None, None, [12, 13]), + 'Bleach - s16e03-04 - 313-314': parser.ParseResult(None, 'Bleach', 16, [3, 4], None, None, None, [313, 314]), + 'Blue Submarine No. 6 s16e03e04 313-314': parser.ParseResult(None, 'Blue Submarine No. 6', 16, [3, 4], None, None, None, [313, 314]), + 'Bleach.s16e03-04.313-314': parser.ParseResult(None, 'Bleach', 16, [3, 4], None, None, None, [313, 314]), + '.hack roots s01e01 001.mkv': parser.ParseResult(None, 'hack roots', 1, [1], None, None, None, [1]), + '.hack sign s01e01 001.mkv': parser.ParseResult(None, 'hack sign', 1, [1], None, None, None, [1]) + + }, + + 'anime_and_normal_reverse': { + 'Bleach - 012 - s02e03 - Name & Name': parser.ParseResult(None, 'Bleach', 2, [3], None, None, None, [12]), + 'Blue Submarine No. 6 - 012-013 - s02e03e04 - Name & Name': parser.ParseResult(None, 'Blue Submarine No. 6', 2, [3, 4], None, None, None, [12, 13]), + '07-GHOST - 012-013 - s02e03e04 - Name & Name': parser.ParseResult(None, '07-GHOST', 2, [3, 4], None, None, None, [12, 13]), + '3x3 Eyes - 012-013 - s02e03-04 - Name & Name': parser.ParseResult(None, '3x3 Eyes', 2, [3, 4], None, None, None, [12, 13]), + }, + + 'anime_and_normal_front': { + '165.Naruto Shippuuden.s08e014': parser.ParseResult(None, 'Naruto Shippuuden', 8, [14], None, None, None, [165]), + '165-166.Naruto Shippuuden.s08e014e015': parser.ParseResult(None, 'Naruto Shippuuden', 8, [14, 15], None, None, None, [165, 166]), + '165-166.07-GHOST.s08e014-015': parser.ParseResult(None, '07-GHOST', 8, [14, 15], None, None, None, [165, 166]), + '165-166.3x3 Eyes.S08E014E015': parser.ParseResult(None, '3x3 Eyes', 8, [14, 15], None, None, None, [165, 166]), + }, + + 'anime_bare': { + 'One Piece 102': parser.ParseResult(None, 'One Piece', None, [], None, None, None, [102]), + 'bleach - 010': parser.ParseResult(None, 'bleach', None, [], None, None, None, [10]), + 'Naruto Shippuden - 314v2': parser.ParseResult(None, 'Naruto Shippuden', None, [], None, None, None, [314]), + 'Blue Submarine No. 6 104-105': parser.ParseResult(None, 'Blue Submarine No. 6', None, [], None, None, None, [104, 105]), + 'Samurai X: Trust & Betrayal (OVA) 001-002': parser.ParseResult(None, 'Samurai X: Trust & Betrayal (OVA)', None, [], None, None, None, [1, 2]), + "[ACX]_Wolf's_Spirit_001.mkv": parser.ParseResult(None, "Wolf's Spirit", None, [], None, 'ACX', None, [1]) + } + +} combination_test_cases = [ - ('/test/path/to/Season 02/03 - Ep Name.avi', - parser.ParseResult(None, None, 2, [3], 'Ep Name'), - ['no_season', 'season_only']), - - ('Show.Name.S02.Source.Quality.Etc-Group/tpz-sn203.avi', - parser.ParseResult(None, 'Show Name', 2, [3], 'Source.Quality.Etc', 'Group'), - ['stupid', 'season_only']), + ('/test/path/to/Season 02/03 - Ep Name.avi', + parser.ParseResult(None, None, 2, [3], 'Ep Name'), + ['no_season', 'season_only']), - ('MythBusters.S08E16.720p.HDTV.x264-aAF/aaf-mb.s08e16.720p.mkv', - parser.ParseResult(None, 'MythBusters', 8, [16], '720p.HDTV.x264', 'aAF'), - ['standard']), - - ('/home/drop/storage/TV/Terminator The Sarah Connor Chronicles/Season 2/S02E06 The Tower is Tall, But the Fall is Short.mkv', - parser.ParseResult(None, None, 2, [6], 'The Tower is Tall, But the Fall is Short'), - ['standard']), - - (r'/Test/TV/Jimmy Fallon/Season 2/Jimmy Fallon - 2010-12-15 - blah.avi', - parser.ParseResult(None, 'Jimmy Fallon', extra_info = 'blah', air_date = datetime.date(2010,12,15)), - ['scene_date_format']), + ('Show.Name.S02.Source.Quality.Etc-Group/tpz-sn203.avi', + parser.ParseResult(None, 'Show Name', 2, [3], 'Source.Quality.Etc', 'Group'), + ['stupid', 'season_only']), - (r'/X/30 Rock/Season 4/30 Rock - 4x22 -.avi', - parser.ParseResult(None, '30 Rock', 4, [22]), - ['fov']), - - ('Season 2\\Show Name - 03-04 - Ep Name.ext', - parser.ParseResult(None, 'Show Name', 2, [3,4], extra_info = 'Ep Name'), - ['no_season', 'season_only']), - - ('Season 02\\03-04-05 - Ep Name.ext', - parser.ParseResult(None, None, 2, [3,4,5], extra_info = 'Ep Name'), - ['no_season', 'season_only']), - ] + ('MythBusters.S08E16.720p.HDTV.x264-aAF/aaf-mb.s08e16.720p.mkv', + parser.ParseResult(None, 'MythBusters', 8, [16], '720p.HDTV.x264', 'aAF'), + ['standard']), + + ('/home/drop/storage/TV/Terminator The Sarah Connor Chronicles/Season 2/S02E06 The Tower is Tall, But the Fall is Short.mkv', + parser.ParseResult(None, None, 2, [6], 'The Tower is Tall, But the Fall is Short'), + ['standard']), + + (r'/Test/TV/Jimmy Fallon/Season 2/Jimmy Fallon - 2010-12-15 - blah.avi', + parser.ParseResult(None, 'Jimmy Fallon', extra_info='blah', air_date=datetime.date(2010, 12, 15)), + ['scene_date_format']), + + (r'/X/30 Rock/Season 4/30 Rock - 4x22 -.avi', + parser.ParseResult(None, '30 Rock', 4, [22]), + ['fov']), + + ('Season 2\\Show Name - 03-04 - Ep Name.ext', + parser.ParseResult(None, 'Show Name', 2, [3, 4], extra_info='Ep Name'), + ['no_season', 'season_only']), + + ('Season 02\\03-04-05 - Ep Name.ext', + parser.ParseResult(None, None, 2, [3, 4, 5], extra_info='Ep Name'), + ['no_season', 'season_only']), +] unicode_test_cases = [ - (u'The.Big.Bang.Theory.2x07.The.Panty.Pi\xf1ata.Polarization.720p.HDTV.x264.AC3-SHELDON.mkv', - parser.ParseResult(None, 'The.Big.Bang.Theory', 2, [7], '720p.HDTV.x264.AC3', 'SHELDON') - ), - ('The.Big.Bang.Theory.2x07.The.Panty.Pi\xc3\xb1ata.Polarization.720p.HDTV.x264.AC3-SHELDON.mkv', - parser.ParseResult(None, 'The.Big.Bang.Theory', 2, [7], '720p.HDTV.x264.AC3', 'SHELDON') - ), - ] + (u'The.Big.Bang.Theory.2x07.The.Panty.Pi\xf1ata.Polarization.720p.HDTV.x264.AC3-SHELDON.mkv', + parser.ParseResult(None, 'The.Big.Bang.Theory', 2, [7], '720p.HDTV.x264.AC3', 'SHELDON') + ), + ('The.Big.Bang.Theory.2x07.The.Panty.Pi\xc3\xb1ata.Polarization.720p.HDTV.x264.AC3-SHELDON.mkv', + parser.ParseResult(None, 'The.Big.Bang.Theory', 2, [7], '720p.HDTV.x264.AC3', 'SHELDON') + ), +] failure_cases = ['7sins-jfcs01e09-720p-bluray-x264'] class UnicodeTests(test.SickbeardTestDBCase): - def _test_unicode(self, name, result): np = parser.NameParser(True) @@ -170,47 +251,46 @@ class UnicodeTests(test.SickbeardTestDBCase): # this shouldn't raise an exception a = repr(str(parse_result)) - + def test_unicode(self): for (name, result) in unicode_test_cases: self._test_unicode(name, result) + class FailureCaseTests(test.SickbeardTestDBCase): - def _test_name(self, name): np = parser.NameParser(True) try: parse_result = np.parse(name) except (parser.InvalidNameException, parser.InvalidShowException): return True - + if VERBOSE: print 'Actual: ', parse_result.which_regex, parse_result return False - + def test_failures(self): for name in failure_cases: self.assertTrue(self._test_name(name)) + class ComboTests(test.SickbeardTestDBCase): - def _test_combo(self, name, result, which_regexes): - + if VERBOSE: print - print 'Testing', name - + print 'Testing', name + np = parser.NameParser(True) try: test_result = np.parse(name) except parser.InvalidShowException: return False - + if DEBUG: print test_result, test_result.which_regex print result, which_regexes - self.assertEqual(test_result, result) for cur_regex in which_regexes: @@ -218,14 +298,14 @@ class ComboTests(test.SickbeardTestDBCase): self.assertEqual(len(which_regexes), len(test_result.which_regex)) def test_combos(self): - + for (name, result, which_regexes) in combination_test_cases: # Normalise the paths. Converts UNIX-style paths into Windows-style # paths when test is run on Windows. self._test_combo(os.path.normpath(name), result, which_regexes) -class BasicTests(test.SickbeardTestDBCase): +class BasicTests(test.SickbeardTestDBCase): def _test_names(self, np, section, transform=None, verbose=False): if VERBOSE or verbose: @@ -245,109 +325,150 @@ class BasicTests(test.SickbeardTestDBCase): return else: test_result = np.parse(cur_test) - - if DEBUG or verbose: + + try: + # self.assertEqual(test_result.which_regex, [section]) + self.assertEqual(test_result, result) + except: print 'air_by_date:', test_result.is_air_by_date, 'air_date:', test_result.air_date print 'anime:', test_result.is_anime, 'ab_episode_numbers:', test_result.ab_episode_numbers print test_result print result - self.assertEqual(test_result.which_regex, [section]) - self.assertEqual(test_result, result) + raise - #def test_standard_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'standard') - # - #def test_standard_repeat_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'standard_repeat') - # - #def test_fov_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'fov') - # - #def test_fov_repeat_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'fov_repeat') - # - #def test_bare_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'bare') - # - #def test_stupid_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'stupid') - # - #def test_no_season_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'no_season') - # - #def test_no_season_general_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'no_season_general') - # - #def test_no_season_multi_ep_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'no_season_multi_ep') - # - #def test_season_only_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'season_only') - # - #def test_scene_date_format_names(self): - # np = parser.NameParser(False) - # self._test_names(np, 'scene_date_format') - # - #def test_standard_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'standard', lambda x: x + '.avi') - # - #def test_standard_repeat_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'standard_repeat', lambda x: x + '.avi') - # - #def test_fov_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'fov', lambda x: x + '.avi') - # - #def test_fov_repeat_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'fov_repeat', lambda x: x + '.avi') - # - #def test_bare_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'bare', lambda x: x + '.avi') - # - #def test_stupid_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'stupid', lambda x: x + '.avi') - # - #def test_no_season_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'no_season', lambda x: x + '.avi') - # - #def test_no_season_general_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'no_season_general', lambda x: x + '.avi') - # - #def test_no_season_multi_ep_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'no_season_multi_ep', lambda x: x + '.avi') - # - #def test_season_only_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'season_only', lambda x: x + '.avi') - # - #def test_scene_date_format_file_names(self): - # np = parser.NameParser() - # self._test_names(np, 'scene_date_format', lambda x: x + '.avi') + + def test_standard_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'standard') + + def test_standard_repeat_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'standard_repeat') + + def test_fov_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'fov') + + def test_fov_repeat_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'fov_repeat') + + def test_bare_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'bare') + + def test_stupid_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'stupid') + + def test_no_season_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'no_season') + + def test_no_season_general_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'no_season_general') + + def test_no_season_multi_ep_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'no_season_multi_ep') + + def test_season_only_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'season_only') + + def test_scene_date_format_names(self): + np = parser.NameParser(False, testing=True) + self._test_names(np, 'scene_date_format') + + def test_standard_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'standard', lambda x: x + '.avi') + + def test_standard_repeat_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'standard_repeat', lambda x: x + '.avi') + + def test_fov_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'fov', lambda x: x + '.avi') + + def test_fov_repeat_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'fov_repeat', lambda x: x + '.avi') + + def test_bare_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'bare', lambda x: x + '.avi') + + def test_stupid_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'stupid', lambda x: x + '.avi') + + def test_no_season_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'no_season', lambda x: x + '.avi') + + def test_no_season_general_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'no_season_general', lambda x: x + '.avi') + + def test_no_season_multi_ep_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'no_season_multi_ep', lambda x: x + '.avi') + + def test_season_only_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'season_only', lambda x: x + '.avi') + + def test_scene_date_format_file_names(self): + np = parser.NameParser(testing=True) + self._test_names(np, 'scene_date_format', lambda x: x + '.avi') def test_combination_names(self): pass + def test_anime_ultimate(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_ultimate') + + def test_anime_ep_name(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_ep_name') + + def test_anime_slash(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_slash') + + def test_anime_codec(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_standard_codec') + + def test_anime_and_normal(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_and_normal') + + def test_anime_and_normal_reverse(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_and_normal_reverse') + + def test_anime_and_normal_front(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_and_normal_front') + + def test_anime_bare(self): + np = parser.NameParser(False, TVShow(is_anime=True), testing=True) + self._test_names(np, 'anime_bare') + + +class TVShow(object): + def __init__(self, is_anime=False): + self.is_anime = is_anime + + if __name__ == '__main__': if len(sys.argv) > 1: - suite = unittest.TestLoader().loadTestsFromName('name_parser_tests.BasicTests.test_'+sys.argv[1]) + suite = unittest.TestLoader().loadTestsFromName('name_parser_tests.BasicTests.test_' + sys.argv[1]) else: suite = unittest.TestLoader().loadTestsFromTestCase(BasicTests) unittest.TextTestRunner(verbosity=2).run(suite)