def test_sound_as_ep(self): """SeriesParser: test that sound infos are not picked as ep""" for sound in SeriesParser.sounds: s = SeriesParser() s.name = 'FooBar' s.data = 'FooBar %s XViD-FlexGet' % sound assert_raises(ParseWarning, s.parse)
def test_quality_as_ep(self): """SeriesParser: test that qualities are not picked as ep""" from flexget.utils import qualities for quality in qualities.all_components(): s = SeriesParser(name='FooBar') s.data = 'FooBar %s XviD-FlexGet' % quality.name assert_raises(ParseWarning, s.parse)
def test_idiotic_numbering_with_zero(self): """SeriesParser: idiotic 0101, 0102, 0103, .. numbering""" s = SeriesParser(name='test', identified_by='ep') s.parse('Test.0706.720p-FlexGet') assert s.season == 7, 'season missing' assert s.episode == 6, 'episode missing' assert s.identifier == 'S07E06', 'identifier broken'
def test_quality_as_ep(self): """SeriesParser: test that qualities are not picked as ep""" from flexget.utils import qualities for quality in qualities.all_components(): s = SeriesParser(name='FooBar', identified_by='ep') s.data = 'FooBar %s XviD-FlexGet' % quality.name assert_raises(ParseWarning, s.parse)
def test_idiotic_numbering_with_zero(self): """SeriesParser: idiotic 0101, 0102, 0103, .. numbering""" s = SeriesParser(name="test", identified_by="ep") s.parse("Test.0706.720p-FlexGet") assert s.season == 7, "season missing" assert s.episode == 6, "episode missing" assert s.identifier == "S07E06", "identifier broken"
def test_quality_as_ep(self): """SeriesParser: test that qualities are not picked as ep""" from flexget.utils import qualities for quality in qualities.registry.keys(): s = SeriesParser(name='FooBar', identified_by='ep') s.data = 'FooBar %s XviD-FlexGet' % quality assert_raises(ParseWarning, s.parse)
def test_idiotic_invalid(self): """SeriesParser: idiotic confused by invalid""" s = SeriesParser(name='test', identified_by='ep') s.data = 'Test.Revealed.WS.PDTV.XviD-aAF.5190458.TPB.torrent' assert_raises(ParseWarning, s.parse) assert not s.season == 5, 'confused, got season' assert not s.season == 4, 'confused, got season' assert not s.episode == 19, 'confused, got episode' assert not s.episode == 58, 'confused, got episode'
def test_idiotic_invalid(self): """SeriesParser: idiotic confused by invalid""" s = SeriesParser(name="test", identified_by="ep") s.data = "Test.Revealed.WS.PDTV.XviD-aAF.5190458.TPB.torrent" assert_raises(ParseWarning, s.parse) assert not s.season == 5, "confused, got season" assert not s.season == 4, "confused, got season" assert not s.episode == 19, "confused, got episode" assert not s.episode == 58, "confused, got episode"
def test_alternate_names(self): s = SeriesParser('The Show', alternate_names=['Show', 'Completely Different']) s.parse('The Show S01E01') assert s.valid s.parse('Show S01E01') assert s.valid s.parse('Completely.Different.S01E01') assert s.valid s.parse('Not The Show S01E01') assert not s.valid
def process_IN_CREATE(self, event): print "Creating:", event.pathname for sync_dir, series in self.config.sync_dirs.iteritems(): for series_name in series: series = SeriesParser(series_name) filename = os.path.basename(event.pathname) series.parse(filename) if series.valid: print "Match: ", series
def test_name_word_boundries(self): s = SeriesParser(name='test') s.parse('Test.S01E02.720p-FlexGet') assert s.valid, 'normal failed' # In non-exact mode these should match s.parse('Test.crap.S01E02.720p-FlexGet') assert s.valid, 'normal failed' s.parse('Test_crap.S01E02.720p-FlexGet') assert s.valid, 'underscore failed' # However if the title ends mid-word, it should not match s.parse('Testing.S01E02.720p-FlexGet') assert not s.valid, 'word border failed'
def test_parentheticals(self): s = SeriesParser('The Show (US)') # Make sure US is ok outside of parentheses s.parse('The.Show.US.S01E01') assert s.valid # Make sure US is ok inside parentheses s.parse('The Show (US) S01E01') assert s.valid # Make sure it works without US s.parse('The.Show.S01E01') assert s.valid # Make sure it doesn't work with a different country s.parse('The Show (UK) S01E01') assert not s.valid
def test_from_groups(self): """SeriesParser: test from groups""" s = SeriesParser() s.name = 'Test' s.data = 'Test.S01E01-Group' s.allow_groups = ['xxxx', 'group'] s.parse() assert s.group == 'group', 'did not get group'
def test_group_dashes(self): """SeriesParser: group name around extra dashes""" s = SeriesParser() s.name = 'Test' s.data = 'Test.S01E01-FooBar-Group' s.allow_groups = ['xxxx', 'group'] s.parse() assert s.group == 'group', 'did not get group with extra dashes'
def test_id_regexps(self): s = SeriesParser('The Show', id_regexps=['(dog)?e(cat)?']) s.parse('The Show dogecat') assert s.valid assert s.id == 'dog-cat' s.parse('The Show doge') assert s.valid assert s.id == 'dog' s.parse('The Show ecat') assert s.valid assert s.id == 'cat' assert_raises(ParseWarning, s.parse, 'The Show e')
def test_from_groups(self): """SeriesParser: test from groups""" s = SeriesParser() s.name = "Test" s.data = "Test.S01E01-Group" s.allow_groups = ["xxxx", "group"] s.parse() assert s.group == "group", "did not get group"
def test_ignore_seasonpacks(self): """SeriesParser: ignoring season packs""" """ s = SeriesParser(name='The Foo') s.data = 'The.Foo.S04.1080p.FlexGet.5.1' assert_raises(ParseWarning, s.parse) """ s = SeriesParser(name='Something') s.data = 'Something S02 Pack 720p WEB-DL-FlexGet' assert_raises(ParseWarning, s.parse) s = SeriesParser(name='The Foo') s.data = 'The Foo S05 720p BluRay DTS x264-FlexGet' assert_raises(ParseWarning, s.parse) s = SeriesParser(name='The Foo', identified_by='ep') s.data = 'The Foo S05 720p BluRay DTS x264-FlexGet' assert_raises(ParseWarning, s.parse)
def test_ep_as_quality(self): """SeriesParser: test that eps are not picked as qualities""" from flexget.utils import qualities s = SeriesParser(name='FooBar') for quality1 in qualities.all_components(): # Attempt to create an episode number out of quality mock_ep1 = filter(unicode.isdigit, quality1.name) if not mock_ep1: continue for quality2 in qualities.all_components(): mock_ep2 = filter(unicode.isdigit, quality2.name) if not mock_ep2: continue # 720i, 1080i, etc. are failing because # e.g the 720 in 720i can always be taken to mean 720p, # which is a higher priority quality. # Moreover, 1080 as an ep number is always failing because # sequence regexps support at most 3 digits at the moment. # Luckily, all of these cases are discarded by the following, # which also discards the failing cases when episode number # (e.g. 720) is greater or equal than quality number (e.g. 480p). # There's nothing that can be done with those failing cases with the # current # "grab leftmost occurrence of highest quality-like thing" algorithm. if int(mock_ep1) >= int(mock_ep2): continue s.data = 'FooBar - %s %s-FlexGet' % (mock_ep1, quality2.name) s.parse() assert s.episode == int(mock_ep1), "confused episode %s with quality %s" % \ (mock_ep1, quality2.name) # Also test with reversed relative order of episode and quality s.data = '[%s] FooBar - %s [FlexGet]' % (quality2.name, mock_ep1) s.parse() assert s.episode == int(mock_ep1), "confused episode %s with quality %s" % \ (mock_ep1, quality2.name)
def guess_series(self, title): """Returns a valid series parser if this :title: appears to be a series""" parser = SeriesParser(identified_by='ep', allow_seasonless=False) # We need to replace certain characters with spaces to make sure episode parsing works right # We don't remove anything, as the match positions should line up with the original title clean_title = re.sub('[_.,\[\]\(\):]', ' ', title) match = parser.parse_episode(clean_title) if match: if parser.parse_unwanted(clean_title): return elif match['match'].start() > 1: # We start using the original title here, so we can properly ignore unwanted prefixes. # Look for unwanted prefixes to find out where the series title starts start = 0 prefix = re.match('|'.join(parser.ignore_prefixes), title) if prefix: start = prefix.end() # If an episode id is found, assume everything before it is series name name = title[start:match['match'].start()] # Remove possible episode title from series name (anything after a ' - ') name = name.split(' - ')[0] # Replace some special characters with spaces name = re.sub('[\._\(\) ]+', ' ', name).strip(' -') # Normalize capitalization to title case name = capwords(name) # If we didn't get a series name, return if not name: return parser.name = name parser.data = title try: parser.parse(data=title) except ParseWarning, pw: log.debug('ParseWarning: %s' % pw.value) if parser.valid: return parser
def test_invalid_data(self): """SeriesParser: invalid data""" s = SeriesParser() s.name = 'Something Interesting' s.data = 1
def test_exact_name(self): """SeriesParser: test exact/strict name parsing""" s = SeriesParser() s.name = "test" s.data = "Test.Foobar.S01E02.720p-FlexGet" s.parse() assert s.valid, "normal failed" s = SeriesParser() s.strict_name = True s.name = "test" s.data = "Test.A.S01E02.720p-FlexGet" s.parse() assert not s.valid, "strict A failed" s = SeriesParser() s.strict_name = True s.name = "Test AB" s.data = "Test.AB.S01E02.720p-FlexGet" s.parse() assert s.valid, "strict AB failed" s = SeriesParser() s.strict_name = True s.name = "Red Tomato" s.data = "Red Tomato (US) S01E02 720p-FlexGet" s.parse() assert not s.valid, "Red Tomato (US) should not match Red Tomato in exact mode"
def test_invalid_name(self): """SeriesParser: invalid name""" s = SeriesParser() s.name = 1 s.data = 'Something'
def guess_series(self, title, allow_seasonless=False, quality=None): """Returns a valid series parser if this `title` appears to be a series""" parser = SeriesParser(identified_by='auto', allow_seasonless=allow_seasonless) # We need to replace certain characters with spaces to make sure episode parsing works right # We don't remove anything, as the match positions should line up with the original title clean_title = re.sub('[_.,\[\]\(\):]', ' ', title) if parser.parse_unwanted(clean_title): return match = parser.parse_date(clean_title) if match: parser.identified_by = 'date' else: match = parser.parse_episode(clean_title) if match and parser.parse_unwanted(clean_title): return parser.identified_by = 'ep' if not match: return if match['match'].start() > 1: # We start using the original title here, so we can properly ignore unwanted prefixes. # Look for unwanted prefixes to find out where the series title starts start = 0 prefix = re.match('|'.join(parser.ignore_prefixes), title) if prefix: start = prefix.end() # If an episode id is found, assume everything before it is series name name = title[start:match['match'].start()] # Remove possible episode title from series name (anything after a ' - ') name = name.split(' - ')[0] # Replace some special characters with spaces name = re.sub('[\._\(\) ]+', ' ', name).strip(' -') # Normalize capitalization to title case name = capwords(name) # If we didn't get a series name, return if not name: return parser.name = name parser.data = title try: parser.parse(data=title, quality=quality) except ParseWarning as pw: log.debug('ParseWarning: %s' % pw.value) if parser.valid: return parser
def parse_series(self, session, entries, series_name, config): """ Search for :series_name: and populate all series_* fields in entries when successfully parsed :param session: SQLAlchemy session :param entries: List of entries to process :param series_name: Series name which is being processed :param config: Series config being processed """ def get_as_array(config, key): """Return configuration key as array, even if given as a single string""" v = config.get(key, []) if isinstance(v, basestring): return [v] return v # expect flags identified_by = config.get('identified_by', 'auto') series = session.query(Series).filter(Series.name == series_name).first() if series: # configuration always overrides everything if 'identified_by' in config: series.identified_by = config['identified_by'] # if series doesn't have identified_by flag already set, calculate one now if not series.identified_by: series.identified_by = self.auto_identified_by(session, series_name) log.debug('identified_by set to \'%s\' based on series history' % series.identified_by) # set flag from database identified_by = series.identified_by parser = SeriesParser(name=series_name, identified_by=identified_by, name_regexps=get_as_array(config, 'name_regexp'), ep_regexps=get_as_array(config, 'ep_regexp'), id_regexps=get_as_array(config, 'id_regexp'), strict_name=config.get('exact', False), allow_groups=get_as_array(config, 'from_group')) for entry in entries: # skip processed entries if entry.get('series_parser') and entry['series_parser'].valid and not entry.get('series_guessed') and \ entry['series_parser'].name.lower() != series_name.lower(): continue # scan from fields for field in ('title', 'description'): data = entry.get(field) # skip invalid fields if not isinstance(data, basestring) or not data: continue # in case quality will not be found from title, set it from entry['quality'] if available quality = None if entry.get('quality', qualities.UNKNOWN) > qualities.UNKNOWN: log.trace('Setting quality %s from entry field to parser' % entry['quality']) quality = entry['quality'] try: parser.parse(data, field=field, quality=quality) except ParseWarning, pw: log_once(pw.value, logger=log) if parser.valid: break else: continue log.debug('%s detected as %s, field: %s' % (entry['title'], parser, parser.field)) entry['series_parser'] = copy(parser) # add series, season and episode to entry entry['series_name'] = series_name entry['series_guessed'] = config.get('series_guessed', False) if 'quality' in entry and entry['quality'] != parser.quality: log.warning('Found different quality for %s. Was %s, overriding with %s.' % \ (entry['title'], entry['quality'], parser.quality)) entry['quality'] = parser.quality entry['proper'] = parser.proper entry['proper_count'] = parser.proper_count if parser.season and parser.episode: entry['series_season'] = parser.season entry['series_episode'] = parser.episode else: entry['series_season'] = time.gmtime().tm_year entry['series_id'] = parser.identifier
def test_idiotic_numbering(self): """SeriesParser: idiotic 101, 102, 103, .. numbering""" s = SeriesParser(name='test', identified_by='ep') s.parse('Test.706.720p-FlexGet') assert s.season == 7, 'didn' 't pick up season' assert s.episode == 6, 'didn' 't pick up episode'
def parse(self, name=None, data=None, **kwargs): s = SeriesParser(name, **kwargs) s.parse(data) return s
def test_exact_name(self): """SeriesParser: test exact/strict name parsing""" s = SeriesParser() s.name = 'test' s.data = 'Test.Foobar.S01E02.720p-FlexGet' s.parse() assert s.valid, 'normal failed' s = SeriesParser() s.strict_name = True s.name = 'test' s.data = 'Test.A.S01E02.720p-FlexGet' s.parse() assert not s.valid, 'strict A failed' s = SeriesParser() s.strict_name = True s.name = 'Test AB' s.data = 'Test.AB.S01E02.720p-FlexGet' s.parse() assert s.valid, 'strict AB failed' s = SeriesParser() s.strict_name = True s.name = 'Red Tomato' s.data = 'Red Tomato (US) S01E02 720p-FlexGet' s.parse() assert not s.valid, 'Red Tomato (US) should not match Red Tomato in exact mode'
def test_name_with_number(self): """SeriesParser: test number in a name""" s = SeriesParser() s.name = 'Storage 13' s.data = 'Storage 13 no ep number' assert_raises(ParseWarning, s.parse)
def test_idiotic_numbering(self): """SeriesParser: idiotic 101, 102, 103, .. numbering""" s = SeriesParser(name="test", identified_by="ep") s.parse("Test.706.720p-FlexGet") assert s.season == 7, "didn" "t pick up season" assert s.episode == 6, "didn" "t pick up episode"
def parse(self, name, data, **kwargs): s = SeriesParser(name, **kwargs) s.parse(data) return s
def test_idiotic_numbering(self): """SeriesParser: idiotic 101, 102, 103, .. numbering""" s = SeriesParser(name='test', identified_by='ep') s.parse('Test.706.720p-FlexGet') assert s.season == 7, 'didn\'t pick up season' assert s.episode == 6, 'didn\'t pick up episode'