diff --git a/.travis.yml b/.travis.yml index 1243df6..508d47e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - 3.5 - 3.6 - pypy - - pypy3 +# - pypy3 matrix: include: - python: 3.7 diff --git a/guessit/rules/match_processors.py b/guessit/rules/match_processors.py new file mode 100644 index 0000000..0b49372 --- /dev/null +++ b/guessit/rules/match_processors.py @@ -0,0 +1,20 @@ +""" +Match processors +""" +from guessit.rules.common import seps + + +def strip(match, chars=seps): + """ + Strip given characters from match. + + :param chars: + :param match: + :return: + """ + while match.input_string[match.start] in chars: + match.start += 1 + while match.input_string[match.end - 1] in chars: + match.end -= 1 + if not match: + return False diff --git a/guessit/rules/properties/audio_codec.py b/guessit/rules/properties/audio_codec.py index b9f298e..815caff 100644 --- a/guessit/rules/properties/audio_codec.py +++ b/guessit/rules/properties/audio_codec.py @@ -3,9 +3,8 @@ """ audio_codec, audio_profile and audio_channels property """ -from rebulk.remodule import re - from rebulk import Rebulk, Rule, RemoveMatch +from rebulk.remodule import re from ..common import dash from ..common.pattern import is_disabled @@ -23,7 +22,9 @@ def audio_codec(config): # pylint:disable=unused-argument :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]).string_defaults(ignore_case=True) + rebulk = Rebulk()\ + .regex_defaults(flags=re.IGNORECASE, abbreviations=[dash])\ + .string_defaults(ignore_case=True) def audio_codec_priority(match1, match2): """ @@ -61,7 +62,9 @@ def audio_codec(config): # pylint:disable=unused-argument rebulk.string('PCM', value='PCM') rebulk.string('LPCM', value='LPCM') - rebulk.defaults(name='audio_profile', disabled=lambda context: is_disabled(context, 'audio_profile')) + rebulk.defaults(clear=True, + name='audio_profile', + disabled=lambda context: is_disabled(context, 'audio_profile')) rebulk.string('MA', value='Master Audio', tags=['audio_profile.rule', 'DTS-HD']) rebulk.string('HR', 'HRA', value='High Resolution Audio', tags=['audio_profile.rule', 'DTS-HD']) rebulk.string('ES', value='Extended Surround', tags=['audio_profile.rule', 'DTS']) @@ -70,7 +73,9 @@ def audio_codec(config): # pylint:disable=unused-argument rebulk.string('HQ', value='High Quality', tags=['audio_profile.rule', 'Dolby Digital']) rebulk.string('EX', value='EX', tags=['audio_profile.rule', 'Dolby Digital']) - rebulk.defaults(name="audio_channels", disabled=lambda context: is_disabled(context, 'audio_channels')) + rebulk.defaults(clear=True, + name="audio_channels", + disabled=lambda context: is_disabled(context, 'audio_channels')) rebulk.regex('7[01]', value='7.1', validator=seps_after, tags='weak-audio_channels') rebulk.regex('5[01]', value='5.1', validator=seps_after, tags='weak-audio_channels') rebulk.string('20', value='2.0', validator=seps_after, tags='weak-audio_channels') diff --git a/guessit/rules/properties/container.py b/guessit/rules/properties/container.py index 7759950..0f1860a 100644 --- a/guessit/rules/properties/container.py +++ b/guessit/rules/properties/container.py @@ -44,7 +44,8 @@ def container(config): rebulk.regex(r'\.'+build_or_pattern(torrent)+'$', exts=torrent, tags=['extension', 'torrent']) rebulk.regex(r'\.'+build_or_pattern(nzb)+'$', exts=nzb, tags=['extension', 'nzb']) - rebulk.defaults(name='container', + rebulk.defaults(clear=True, + name='container', validator=seps_surround, formatter=lambda s: s.lower(), conflict_solver=lambda match, other: match diff --git a/guessit/rules/properties/episode_title.py b/guessit/rules/properties/episode_title.py index d429c3e..bb2efbd 100644 --- a/guessit/rules/properties/episode_title.py +++ b/guessit/rules/properties/episode_title.py @@ -133,8 +133,7 @@ class EpisodeTitleFromPosition(TitleBaseRule): def hole_filter(self, hole, matches): episode = matches.previous(hole, - lambda previous: any(name in previous.names - for name in self.previous_names), + lambda previous: previous.named(*self.previous_names), 0) crc32 = matches.named('crc32') @@ -179,8 +178,7 @@ class AlternativeTitleReplace(Rule): predicate=lambda match: 'title' in match.tags, index=0) if main_title: episode = matches.previous(main_title, - lambda previous: any(name in previous.names - for name in self.previous_names), + lambda previous: previous.named(*self.previous_names), 0) crc32 = matches.named('crc32') diff --git a/guessit/rules/properties/episodes.py b/guessit/rules/properties/episodes.py index d2e74bc..c1aa9d4 100644 --- a/guessit/rules/properties/episodes.py +++ b/guessit/rules/properties/episodes.py @@ -11,12 +11,13 @@ from rebulk.match import Match from rebulk.remodule import re from rebulk.utils import is_iterable +from guessit.rules import match_processors +from guessit.rules.common.numeral import parse_numeral, numeral from .title import TitleFromPosition from ..common import dash, alt_dash, seps, seps_no_fs from ..common.formatters import strip -from ..common.numeral import numeral, parse_numeral from ..common.pattern import is_disabled -from ..common.validators import compose, seps_surround, seps_before, int_coercable +from ..common.validators import seps_surround, int_coercable, compose from ...reutils import build_or_pattern @@ -29,17 +30,12 @@ def episodes(config): :return: Created Rebulk object :rtype: Rebulk """ + # pylint: disable=too-many-branches,too-many-statements,too-many-locals def is_season_episode_disabled(context): """Whether season and episode rules should be enabled.""" return is_disabled(context, 'episode') or is_disabled(context, 'season') - rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) - rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator', 'episodeMarker', 'seasonMarker']) - - episode_max_range = config['episode_max_range'] - season_max_range = config['season_max_range'] - def episodes_season_chain_breaker(matches): """ Break chains if there's more than 100 offset between two neighbor values. @@ -57,8 +53,6 @@ def episodes(config): return True return False - rebulk.chain_defaults(chain_breaker=episodes_season_chain_breaker) - def season_episode_conflict_solver(match, other): """ Conflict solver for episode/season patterns @@ -76,7 +70,6 @@ def episodes(config): if (other.name == 'audio_channels' and 'weak-audio_channels' not in other.tags and not match.initiator.children.named(match.name + 'Marker')) or ( other.name == 'screen_size' and not int_coercable(other.raw)): - return match if other.name in ('season', 'episode') and match.initiator != other.initiator: if (match.initiator.name in ('weak_episode', 'weak_duplicate') @@ -87,21 +80,6 @@ def episodes(config): return current return '__default__' - season_words = config['season_words'] - episode_words = config['episode_words'] - of_words = config['of_words'] - all_words = config['all_words'] - season_markers = config['season_markers'] - season_ep_markers = config['season_ep_markers'] - disc_markers = config['disc_markers'] - episode_markers = config['episode_markers'] - range_separators = config['range_separators'] - weak_discrete_separators = list(sep for sep in seps_no_fs if sep not in range_separators) - strong_discrete_separators = config['discrete_separators'] - discrete_separators = strong_discrete_separators + weak_discrete_separators - - max_range_gap = config['max_range_gap'] - def ordering_validator(match): """ Validator for season list. They should be in natural order to be validated. @@ -135,65 +113,18 @@ def episodes(config): lambda m: m.name == property_name + 'Separator') separator = match.children.previous(current_match, lambda m: m.name == property_name + 'Separator', 0) - if separator.raw not in range_separators and separator.raw in weak_discrete_separators: - if not 0 < current_match.value - previous_match.value <= max_range_gap + 1: - valid = False - if separator.raw in strong_discrete_separators: - valid = True - break + if separator: + if separator.raw not in range_separators and separator.raw in weak_discrete_separators: + if not 0 < current_match.value - previous_match.value <= max_range_gap + 1: + valid = False + if separator.raw in strong_discrete_separators: + valid = True + break previous_match = current_match return valid return is_consecutive('episode') and is_consecutive('season') - # S01E02, 01x02, S01S02S03 - rebulk.chain(formatter={'season': int, 'episode': int}, - tags=['SxxExx'], - abbreviations=[alt_dash], - children=True, - private_parent=True, - validate_all=True, - validator={'__parent__': ordering_validator}, - conflict_solver=season_episode_conflict_solver, - disabled=is_season_episode_disabled) \ - .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P\d+)@?' + - build_or_pattern(episode_markers + disc_markers, name='episodeMarker') + r'@?(?P\d+)', - validate_all=True, - validator={'__parent__': seps_before}).repeater('+') \ - .regex(build_or_pattern(episode_markers + disc_markers + discrete_separators + range_separators, - name='episodeSeparator', - escape=True) + - r'(?P\d+)').repeater('*') \ - .chain() \ - .regex(r'(?P\d+)@?' + - build_or_pattern(season_ep_markers, name='episodeMarker') + - r'@?(?P\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ - .chain() \ - .regex(r'(?P\d+)@?' + - build_or_pattern(season_ep_markers, name='episodeMarker') + - r'@?(?P\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ - .regex(build_or_pattern(season_ep_markers + discrete_separators + range_separators, - name='episodeSeparator', - escape=True) + - r'(?P\d+)').repeater('*') \ - .chain() \ - .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ - .regex(build_or_pattern(season_markers + discrete_separators + range_separators, - name='seasonSeparator', - escape=True) + - r'(?P\d+)').repeater('*') - - # episode_details property - for episode_detail in ('Special', 'Pilot', 'Unaired', 'Final'): - rebulk.string(episode_detail, value=episode_detail, name='episode_details', - disabled=lambda context: is_disabled(context, 'episode_details')) - def validate_roman(match): """ Validate a roman match if surrounded by separators @@ -206,117 +137,199 @@ def episodes(config): return True return seps_surround(match) + season_words = config['season_words'] + episode_words = config['episode_words'] + of_words = config['of_words'] + all_words = config['all_words'] + season_markers = config['season_markers'] + season_ep_markers = config['season_ep_markers'] + disc_markers = config['disc_markers'] + episode_markers = config['episode_markers'] + range_separators = config['range_separators'] + weak_discrete_separators = list(sep for sep in seps_no_fs if sep not in range_separators) + strong_discrete_separators = config['discrete_separators'] + discrete_separators = strong_discrete_separators + weak_discrete_separators + episode_max_range = config['episode_max_range'] + season_max_range = config['season_max_range'] + max_range_gap = config['max_range_gap'] + + rebulk = Rebulk() \ + .regex_defaults(flags=re.IGNORECASE) \ + .string_defaults(ignore_case=True) \ + .chain_defaults(chain_breaker=episodes_season_chain_breaker) \ + .defaults(private_names=['episodeSeparator', 'seasonSeparator', 'episodeMarker', 'seasonMarker'], + formatter={'season': int, 'episode': int, 'version': int, 'count': int}, + children=True, + private_parent=True, + conflict_solver=season_episode_conflict_solver, + abbreviations=[alt_dash]) + + # S01E02, 01x02, S01S02S03 + rebulk.chain( + validate_all=True, + validator={'__parent__': compose(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ + .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P\d+)@?' + + build_or_pattern(episode_markers + disc_markers, name='episodeMarker') + r'@?(?P\d+)')\ + .repeater('+') \ + .regex(build_or_pattern(episode_markers + disc_markers + discrete_separators + range_separators, + name='episodeSeparator', + escape=True) + + r'(?P\d+)').repeater('*') + + rebulk.chain(validate_all=True, + validator={'__parent__': compose(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ + .regex(r'(?P\d+)@?' + + build_or_pattern(season_ep_markers, name='episodeMarker') + + r'@?(?P\d+)').repeater('+') \ + + rebulk.chain(validate_all=True, + validator={'__parent__': compose(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ + .regex(r'(?P\d+)@?' + + build_or_pattern(season_ep_markers, name='episodeMarker') + + r'@?(?P\d+)') \ + .regex(build_or_pattern(season_ep_markers + discrete_separators + range_separators, + name='episodeSeparator', + escape=True) + + r'(?P\d+)').repeater('*') + + rebulk.chain(validate_all=True, + validator={'__parent__': compose(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ + .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P\d+)') \ + .regex('(?PExtras)', name='other', value='Extras', tags=['no-release-group-prefix']).repeater('?') \ + .regex(build_or_pattern(season_markers + discrete_separators + range_separators, + name='seasonSeparator', + escape=True) + + r'(?P\d+)').repeater('*') + + # episode_details property + for episode_detail in ('Special', 'Pilot', 'Unaired', 'Final'): + rebulk.string(episode_detail, + private_parent=False, + children=False, + value=episode_detail, + name='episode_details', + disabled=lambda context: is_disabled(context, 'episode_details')) + rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator', 'episodeMarker', 'seasonMarker'], - validate_all=True, validator={'__parent__': seps_surround}, children=True, private_parent=True, + validate_all=True, + validator={'__parent__': compose(seps_surround, ordering_validator)}, + children=True, + private_parent=True, conflict_solver=season_episode_conflict_solver) - rebulk.chain(abbreviations=[alt_dash], + rebulk.chain(validate_all=True, + conflict_solver=season_episode_conflict_solver, formatter={'season': parse_numeral, 'count': parse_numeral}, validator={'__parent__': compose(seps_surround, ordering_validator), 'season': validate_roman, 'count': validate_roman}, disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'season')) \ - .defaults(validator=None) \ + .defaults(formatter={'season': parse_numeral, 'count': parse_numeral}, + validator={'season': validate_roman, 'count': validate_roman}, + conflict_solver=season_episode_conflict_solver) \ .regex(build_or_pattern(season_words, name='seasonMarker') + '@?(?P' + numeral + ')') \ .regex(r'' + build_or_pattern(of_words) + '@?(?P' + numeral + ')').repeater('?') \ .regex(r'@?' + build_or_pattern(range_separators + discrete_separators + ['@'], name='seasonSeparator', escape=True) + r'@?(?P\d+)').repeater('*') + rebulk.defaults(abbreviations=[dash]) + rebulk.regex(build_or_pattern(episode_words, name='episodeMarker') + r'-?(?P\d+)' + r'(?:v(?P\d+))?' + r'(?:-?' + build_or_pattern(of_words) + r'-?(?P\d+))?', # Episode 4 - abbreviations=[dash], formatter={'episode': int, 'version': int, 'count': int}, disabled=lambda context: context.get('type') == 'episode' or is_disabled(context, 'episode')) rebulk.regex(build_or_pattern(episode_words, name='episodeMarker') + r'-?(?P' + numeral + ')' + r'(?:v(?P\d+))?' + r'(?:-?' + build_or_pattern(of_words) + r'-?(?P\d+))?', # Episode 4 - abbreviations=[dash], validator={'episode': validate_roman}, - formatter={'episode': parse_numeral, 'version': int, 'count': int}, + formatter={'episode': parse_numeral}, disabled=lambda context: context.get('type') != 'episode' or is_disabled(context, 'episode')) rebulk.regex(r'S?(?P\d+)-?(?:xE|Ex|E|x)-?(?P' + build_or_pattern(all_words) + ')', tags=['SxxExx'], - abbreviations=[dash], - validator=None, - formatter={'season': int, 'other': lambda match: 'Complete'}, + formatter={'other': lambda match: 'Complete'}, disabled=lambda context: is_disabled(context, 'season')) # 12, 13 - rebulk.chain(tags=['weak-episode'], formatter={'episode': int, 'version': int}, + rebulk.chain(tags=['weak-episode'], disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ - .defaults(validator=None) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'(?P\d{2})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?P[x-])(?P\d{2})').repeater('*') + .regex(r'(?P[x-])(?P\d{2})', abbreviations=None).repeater('*') # 012, 013 - rebulk.chain(tags=['weak-episode'], formatter={'episode': int, 'version': int}, + rebulk.chain(tags=['weak-episode'], disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ - .defaults(validator=None) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'0(?P\d{1,2})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?P[x-])0(?P\d{1,2})').repeater('*') + .regex(r'(?P[x-])0(?P\d{1,2})', abbreviations=None).repeater('*') # 112, 113 rebulk.chain(tags=['weak-episode'], - formatter={'episode': int, 'version': int}, name='weak_episode', disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ - .defaults(validator=None) \ + .defaults(validator=None, tags=['weak-episode'], name='weak_episode') \ .regex(r'(?P\d{3,4})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?P[x-])(?P\d{3,4})').repeater('*') + .regex(r'(?P[x-])(?P\d{3,4})', abbreviations=None).repeater('*') # 1, 2, 3 - rebulk.chain(tags=['weak-episode'], formatter={'episode': int, 'version': int}, + rebulk.chain(tags=['weak-episode'], disabled=lambda context: context.get('type') != 'episode' or is_disabled(context, 'episode')) \ - .defaults(validator=None) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'(?P\d)') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?P[x-])(?P\d{1,2})').repeater('*') + .regex(r'(?P[x-])(?P\d{1,2})', abbreviations=None).repeater('*') # e112, e113, 1e18, 3e19 - # TODO: Enhance rebulk for validator to be used globally (season_episode_validator) - rebulk.chain(formatter={'season': int, 'episode': int, 'version': int}, - disabled=lambda context: is_disabled(context, 'episode')) \ + rebulk.chain(disabled=lambda context: is_disabled(context, 'episode')) \ .defaults(validator=None) \ .regex(r'(?P\d{1,2})?(?Pe)(?P\d{1,4})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?Pe|x|-)(?P\d{1,4})').repeater('*') + .regex(r'(?Pe|x|-)(?P\d{1,4})', abbreviations=None).repeater('*') # ep 112, ep113, ep112, ep113 - rebulk.chain(abbreviations=[dash], formatter={'episode': int, 'version': int}, - disabled=lambda context: is_disabled(context, 'episode')) \ + rebulk.chain(disabled=lambda context: is_disabled(context, 'episode')) \ .defaults(validator=None) \ .regex(r'ep-?(?P\d{1,4})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?Pep|e|x|-)(?P\d{1,4})').repeater('*') + .regex(r'(?Pep|e|x|-)(?P\d{1,4})', abbreviations=None).repeater('*') # cap 112, cap 112_114 - rebulk.chain(abbreviations=[dash], - tags=['see-pattern'], - formatter={'season': int, 'episode': int}, + rebulk.chain(tags=['see-pattern'], disabled=is_season_episode_disabled) \ - .defaults(validator=None) \ + .defaults(validator=None, tags=['see-pattern']) \ .regex(r'(?Pcap)-?(?P\d{1,2})(?P\d{2})') \ .regex(r'(?P-)(?P\d{1,2})(?P\d{2})').repeater('?') # 102, 0102 rebulk.chain(tags=['weak-episode', 'weak-duplicate'], - formatter={'season': int, 'episode': int, 'version': int}, name='weak_duplicate', conflict_solver=season_episode_conflict_solver, disabled=lambda context: (context.get('episode_prefer_number', False) or context.get('type') == 'movie') or is_season_episode_disabled(context)) \ - .defaults(validator=None) \ + .defaults(tags=['weak-episode', 'weak-duplicate'], + name='weak_duplicate', + validator=None, + conflict_solver=season_episode_conflict_solver) \ .regex(r'(?P\d{1,2})(?P\d{2})') \ .regex(r'v(?P\d+)').repeater('?') \ - .regex(r'(?Px|-)(?P\d{2})').repeater('*') + .regex(r'(?Px|-)(?P\d{2})', abbreviations=None).repeater('*') - rebulk.regex(r'v(?P\d+)', children=True, private_parent=True, formatter=int, + rebulk.regex(r'v(?P\d+)', + formatter=int, disabled=lambda context: is_disabled(context, 'version')) rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator']) @@ -325,10 +338,15 @@ def episodes(config): # detached of X count (season/episode) rebulk.regex(r'(?P\d+)-?' + build_or_pattern(of_words) + r'-?(?P\d+)-?' + build_or_pattern(episode_words) + '?', - abbreviations=[dash], children=True, private_parent=True, formatter=int, + formatter=int, + pre_match_processor=match_processors.strip, disabled=lambda context: is_disabled(context, 'episode')) - rebulk.regex(r'Minisodes?', name='episode_format', value="Minisode", + rebulk.regex(r'Minisodes?', + children=False, + private_parent=False, + name='episode_format', + value="Minisode", disabled=lambda context: is_disabled(context, 'episode_format')) rebulk.rules(WeakConflictSolver, RemoveInvalidSeason, RemoveInvalidEpisode, diff --git a/guessit/rules/properties/release_group.py b/guessit/rules/properties/release_group.py index 1b4512c..5043d5e 100644 --- a/guessit/rules/properties/release_group.py +++ b/guessit/rules/properties/release_group.py @@ -9,8 +9,8 @@ from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch from rebulk.match import Match from ..common import seps -from ..common.expected import build_expected_function from ..common.comparators import marker_sorted +from ..common.expected import build_expected_function from ..common.formatters import cleanup from ..common.pattern import is_disabled from ..common.validators import int_coercable, seps_surround @@ -72,7 +72,9 @@ _scene_previous_names = ('video_codec', 'source', 'video_api', 'audio_codec', 'a 'audio_channels', 'screen_size', 'other', 'container', 'language', 'subtitle_language', 'subtitle_language.suffix', 'subtitle_language.prefix', 'language.suffix') -_scene_previous_tags = ('release-group-prefix', ) +_scene_previous_tags = ('release-group-prefix',) + +_scene_no_previous_tags = ('no-release-group-prefix',) class DashSeparatedReleaseGroup(Rule): @@ -212,6 +214,17 @@ class SceneReleaseGroup(Rule): super(SceneReleaseGroup, self).__init__() self.value_formatter = value_formatter + @staticmethod + def is_previous_match(match): + """ + Check if match can precede release_group + + :param match: + :return: + """ + return not match.tagged(*_scene_no_previous_tags) if match.name in _scene_previous_names else \ + match.tagged(*_scene_previous_tags) + def when(self, matches, context): # pylint:disable=too-many-locals # If a release_group is found before, ignore this kind of release_group rule. @@ -253,13 +266,12 @@ class SceneReleaseGroup(Rule): if match.start < filepart.start: return False - return not match.private or match.name in _scene_previous_names + return not match.private or self.is_previous_match(match) previous_match = matches.previous(last_hole, previous_match_filter, index=0) - if previous_match and (previous_match.name in _scene_previous_names or - any(tag in previous_match.tags for tag in _scene_previous_tags)) and \ + if previous_match and (self.is_previous_match(previous_match)) and \ not matches.input_string[previous_match.end:last_hole.start].strip(seps) \ and not int_coercable(last_hole.value.strip(seps)): diff --git a/guessit/rules/properties/video_codec.py b/guessit/rules/properties/video_codec.py index b08ddca..842a03c 100644 --- a/guessit/rules/properties/video_codec.py +++ b/guessit/rules/properties/video_codec.py @@ -3,9 +3,8 @@ """ video_codec and video_profile property """ -from rebulk.remodule import re - from rebulk import Rebulk, Rule, RemoveMatch +from rebulk.remodule import re from ..common import dash from ..common.pattern import is_disabled @@ -43,7 +42,8 @@ def video_codec(config): # pylint:disable=unused-argument # http://blog.mediacoderhq.com/h264-profiles-and-levels/ # https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC - rebulk.defaults(name="video_profile", + rebulk.defaults(clear=True, + name="video_profile", validator=seps_surround, disabled=lambda context: is_disabled(context, 'video_profile')) @@ -66,7 +66,8 @@ def video_codec(config): # pylint:disable=unused-argument rebulk.string('DXVA', value='DXVA', name='video_api', disabled=lambda context: is_disabled(context, 'video_api')) - rebulk.defaults(name='color_depth', + rebulk.defaults(clear=True, + name='color_depth', validator=seps_surround, disabled=lambda context: is_disabled(context, 'color_depth')) rebulk.regex('12.?bits?', value='12-bit') diff --git a/guessit/rules/properties/website.py b/guessit/rules/properties/website.py index 00dfadd..299860e 100644 --- a/guessit/rules/properties/website.py +++ b/guessit/rules/properties/website.py @@ -67,7 +67,7 @@ def website(config): """ Validator for next website matches """ - return any(name in ['season', 'episode', 'year'] for name in match.names) + return match.named('season', 'episode', 'year') def when(self, matches, context): to_remove = [] diff --git a/guessit/test/episodes.yml b/guessit/test/episodes.yml index a803733..322d3d0 100644 --- a/guessit/test/episodes.yml +++ b/guessit/test/episodes.yml @@ -201,9 +201,9 @@ ? Series/My Name Is Earl/My.Name.Is.Earl.S01Extras.-.Bad.Karma.DVDRip.XviD.avi : title: My Name Is Earl season: 1 - episode_title: Extras - Bad Karma + episode_title: Bad Karma source: DVD - other: Rip + other: [Extras, Rip] video_codec: Xvid ? series/Freaks And Geeks/Season 1/Episode 4 - Kim Kelly Is My Friend-eng(1).srt diff --git a/guessit/test/various.yml b/guessit/test/various.yml index cf0e661..f8de58e 100644 --- a/guessit/test/various.yml +++ b/guessit/test/various.yml @@ -1114,3 +1114,20 @@ video_codec: H.264 release_group: W4F type: episode + +? NOS4A2.S01E01.The.Shorter.Way.REPACK.720p.AMZN.WEB-DL.DDP5.1.H.264-NTG.mkv +: title: NOS4A2 + season: 1 + episode: 1 + episode_title: The Shorter Way + other: Proper + proper_count: 1 + screen_size: 720p + streaming_service: Amazon Prime + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + release_group: NTG + container: mkv + type: episode diff --git a/setup.py b/setup.py index 3c54ee8..0f0c67c 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: with io.open(os.path.join(here, 'HISTORY.rst'), encoding='utf-8') as f: history = f.read() -install_requires = ['rebulk', 'babelfish', 'python-dateutil', 'six'] +install_requires = ['rebulk>=2', 'babelfish', 'python-dateutil', 'six'] setup_requires = ['pytest-runner'] diff --git a/tox.ini b/tox.ini index 7789485..b6ab208 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,11 @@ [tox] envlist = py27,py34,py35,py36,py37,py38,pypy2,pypy +[testenv:py38] +commands = + {envbindir}/pip install -e .[dev] + {envpython} setup.py test + [testenv] commands = {envbindir}/pip install -e .[dev,test]