def part(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) prefixes = ['pt', 'part'] def validate_roman(match): """ Validate a roman match if surrounded by separators :param match: :type match: :return: :rtype: """ if int_coercable(match.raw): return True return seps_surround(match) rebulk.regex(build_or_pattern(prefixes) + r'-?(?P<part>' + numeral + r')', prefixes=prefixes, validate_all=True, private_parent=True, children=True, formatter=parse_numeral, validator={'part': compose(validate_roman, lambda m: 0 < m.value < 100)}) return rebulk
def country(config, common_words): """ Builder for rebulk object. :param config: rule configuration :type config: dict :param common_words: common words :type common_words: set :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'country')) rebulk = rebulk.defaults(name='country') def find_countries(string, context=None): """ Find countries in given string. """ allowed_countries = context.get('allowed_countries') if context else None return CountryFinder(allowed_countries, common_words).find(string) rebulk.functional(find_countries, # Prefer language and any other property over country if not US or GB. conflict_solver=lambda match, other: match if other.name != 'language' or match.value not in (babelfish.Country('US'), babelfish.Country('GB')) else other, properties={'country': [None]}, disabled=lambda context: not context.get('allowed_countries')) babelfish.country_converters['guessit'] = GuessitCountryConverter(config['synonyms']) return rebulk
def country(config, common_words): """ Builder for rebulk object. :param config: rule configuration :type config: dict :param common_words: common words :type common_words: set :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'country')) rebulk = rebulk.defaults(name='country') def find_countries(string, context=None): """ Find countries in given string. """ allowed_countries = context.get( 'allowed_countries') if context else None return CountryFinder(allowed_countries, common_words).find(string) rebulk.functional( find_countries, # Prefer language and any other property over country if not US or GB. conflict_solver=lambda match, other: match if other.name != 'language' or match.value not in (babelfish.Country('US'), babelfish.Country('GB')) else other, properties={'country': [None]}, disabled=lambda context: not context.get('allowed_countries')) babelfish.country_converters['guessit'] = GuessitCountryConverter( config['synonyms']) return rebulk
def complete_words(rebulk: Rebulk, season_words, complete_article_words): """ Custom pattern to find complete seasons from words. """ season_words_pattern = build_or_pattern(season_words) complete_article_words_pattern = build_or_pattern(complete_article_words) def validate_complete(match): """ Make sure season word is are defined. :param match: :type match: :return: :rtype: """ children = match.children if not children.named('completeWordsBefore') and not children.named( 'completeWordsAfter'): return False return True rebulk.regex( '(?P<completeArticle>' + complete_article_words_pattern + '-)?' + '(?P<completeWordsBefore>' + season_words_pattern + '-)?' + 'Complete' + '(?P<completeWordsAfter>-' + season_words_pattern + ')?', private_names=[ 'completeArticle', 'completeWordsBefore', 'completeWordsAfter' ], value={'other': 'Complete'}, tags=['release-group-prefix'], validator={'__parent__': and_(seps_surround, validate_complete)})
def cds(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.regex( r"cd-?(?P<cd>\d+)(?:-?of-?(?P<cd_count>\d+))?", validator={"cd": lambda match: 0 < match.value < 100, "cd_count": lambda match: 0 < match.value < 100}, formatter={"cd": int, "cd_count": int}, children=True, private_parent=True, properties={"cd": [None], "cd_count": [None]}, ) rebulk.regex( r"(?P<cd_count>\d+)-?cds?", validator={"cd": lambda match: 0 < match.value < 100, "cd_count": lambda match: 0 < match.value < 100}, formatter={"cd_count": int}, children=True, private_parent=True, properties={"cd": [None], "cd_count": [None]}, ) return rebulk
def audio_codec(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() \ .regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) \ .string_defaults(ignore_case=True) def audio_codec_priority(match1, match2): """ Gives priority to audio_codec :param match1: :type match1: :param match2: :type match2: :return: :rtype: """ if match1.name == 'audio_codec' and match2.name in [ 'audio_profile', 'audio_channels' ]: return match2 if match1.name in ['audio_profile', 'audio_channels' ] and match2.name == 'audio_codec': return match1 return '__default__' rebulk.defaults( name='audio_codec', conflict_solver=audio_codec_priority, disabled=lambda context: is_disabled(context, 'audio_codec')) load_config_patterns(rebulk, config.get('audio_codec')) rebulk.defaults( clear=True, name='audio_profile', disabled=lambda context: is_disabled(context, 'audio_profile')) load_config_patterns(rebulk, config.get('audio_profile')) rebulk.defaults( clear=True, name="audio_channels", disabled=lambda context: is_disabled(context, 'audio_channels')) load_config_patterns(rebulk, config.get('audio_channels')) rebulk.rules(DtsHDRule, DtsRule, AacRule, DolbyDigitalRule, AudioValidatorRule, HqConflictRule, AudioChannelsValidatorRule) return rebulk
def bit_rate(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: (is_disabled( context, 'audio_bit_rate') and is_disabled(context, 'video_bit_rate'))) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='audio_bit_rate', validator=seps_surround) rebulk.regex( r'\d+-?[kmg]b(ps|its?)', r'\d+\.\d+-?[kmg]b(ps|its?)', conflict_solver=(lambda match, other: match if other.name == 'audio_channels' and 'weak-audio_channels' not in other.tags else other), formatter=BitRate.fromstring, tags=['release-group-prefix']) rebulk.rules(BitRateTypeRule) return rebulk
def release_group(config): """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ forbidden_groupnames = config['forbidden_names'] groupname_ignore_seps = config['ignored_seps'] groupname_seps = ''.join( [c for c in seps if c not in groupname_ignore_seps]) def clean_groupname(string): """ Removes and strip separators from input_string :param string: :type string: :return: :rtype: """ string = string.strip(groupname_seps) if not (string.endswith(tuple(groupname_ignore_seps)) and string.startswith(tuple(groupname_ignore_seps))) \ and not any(i in string.strip(groupname_ignore_seps) for i in groupname_ignore_seps): string = string.strip(groupname_ignore_seps) for forbidden in forbidden_groupnames: if string.lower().startswith( forbidden) and string[len(forbidden):len(forbidden) + 1] in seps: string = string[len(forbidden):] string = string.strip(groupname_seps) if string.lower().endswith( forbidden) and string[-len(forbidden) - 1:-len(forbidden)] in seps: string = string[:len(forbidden)] string = string.strip(groupname_seps) return string rebulk = Rebulk( disabled=lambda context: is_disabled(context, 'release_group')) expected_group = build_expected_function('expected_group') rebulk.functional( expected_group, name='release_group', tags=['expected'], validator=seps_surround, conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_group')) return rebulk.rules(DashSeparatedReleaseGroup(clean_groupname), SceneReleaseGroup(clean_groupname), AnimeReleaseGroup)
def film(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE) rebulk.regex(r'f(\d{1,2})', name='film', private_parent=True, children=True, formatter=int) rebulk.rules(FilmTitleRule) return rebulk
def mimetype(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'mimetype')) rebulk.rules(Mimetype) return rebulk
def type_(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'type')) rebulk = rebulk.rules(TypeProcessor) return rebulk
def part(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) prefixes = ['pt', 'part'] rebulk.regex(build_or_pattern(prefixes) + r'-?(' + numeral + r')', prefixes=prefixes, name='part', validate_all=True, private_parent=True, children=True, formatter=parse_numeral) return rebulk
def film(config): # pylint:disable=unused-argument """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, validate_all=True, validator={'__parent__': seps_surround}) rebulk.regex(r'f(\d{1,2})', name='film', private_parent=True, children=True, formatter=int, disabled=lambda context: is_disabled(context, 'film')) rebulk.rules(FilmTitleRule) return rebulk
def release_group(config): """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ forbidden_groupnames = config['forbidden_names'] groupname_ignore_seps = config['ignored_seps'] groupname_seps = ''.join([c for c in seps if c not in groupname_ignore_seps]) def clean_groupname(string): """ Removes and strip separators from input_string :param string: :type string: :return: :rtype: """ string = string.strip(groupname_seps) if not (string.endswith(tuple(groupname_ignore_seps)) and string.startswith(tuple(groupname_ignore_seps))) \ and not any(i in string.strip(groupname_ignore_seps) for i in groupname_ignore_seps): string = string.strip(groupname_ignore_seps) for forbidden in forbidden_groupnames: if string.lower().startswith(forbidden) and string[len(forbidden):len(forbidden) + 1] in seps: string = string[len(forbidden):] string = string.strip(groupname_seps) if string.lower().endswith(forbidden) and string[-len(forbidden) - 1:-len(forbidden)] in seps: string = string[:len(forbidden)] string = string.strip(groupname_seps) return string rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'release_group')) expected_group = build_expected_function('expected_group') rebulk.functional(expected_group, name='release_group', tags=['expected'], validator=seps_surround, conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_group')) return rebulk.rules( DashSeparatedReleaseGroup(clean_groupname), SceneReleaseGroup(clean_groupname), AnimeReleaseGroup )
def cd(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'cd')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) load_config_patterns(rebulk, config) return rebulk
def expected(input_string, context): """ Expected property functional pattern. :param input_string: :type input_string: :param context: :type context: :return: :rtype: """ ret = [] for search in context.get(context_key): if search.startswith('re:'): search = search[3:] search = search.replace(' ', '-') matches = Rebulk().regex(search, abbreviations=[dash], flags=re.IGNORECASE) \ .matches(input_string, context) for match in matches: ret.append(match.span) else: value = search for sep in seps: input_string = input_string.replace(sep, ' ') search = search.replace(sep, ' ') for start in find_all(input_string, search, ignore_case=True): ret.append({'start': start, 'end': start + len(search), 'value': value}) return ret
def type_(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(TypeProcessor)
def language(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() rebulk.string(*subtitle_prefixes, name="subtitle_language.prefix", ignore_case=True, private=True, validator=seps_surround) rebulk.string(*subtitle_suffixes, name="subtitle_language.suffix", ignore_case=True, private=True, validator=seps_surround) rebulk.functional(find_languages, properties={'language': [None]}) rebulk.rules(SubtitlePrefixLanguageRule, SubtitleSuffixLanguageRule, SubtitleExtensionRule) return rebulk
def processors(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(EnlargeGroupMatches, EquivalentHoles, RemoveAmbiguous, SeasonYear, Processors)
def size(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ def format_size(value): """Format size using uppercase and no space.""" return re.sub(r'(?<=\d)[.](?=[^\d])', '', value.upper()) rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='size', validator=seps_surround) rebulk.regex(r'\d+\.?[mgt]b', r'\d+\.\d+[mgt]b', formatter=format_size, tags=['release-group-prefix']) return rebulk
def release_group(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(SceneReleaseGroup, AnimeReleaseGroup, ExpectedReleaseGroup)
def mimetype(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(Mimetype)
def release_group(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() expected_group = build_expected_function('expected_group') rebulk.functional(expected_group, name='release_group', tags=['expected'], validator=seps_surround, conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_group')) return rebulk.rules(SceneReleaseGroup, AnimeReleaseGroup)
def bonus(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE) rebulk.regex(r'x(\d+)', name='bonus', private_parent=True, children=True, formatter=int, validator={'__parent__': lambda match: seps_surround}, conflict_solver=lambda match, conflicting: match if conflicting.name in ['video_codec', 'episode'] and 'bonus-conflict' not in conflicting.tags else '__default__') rebulk.rules(BonusTitleRule) return rebulk
def country(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().defaults(name='country') rebulk.functional(find_countries, # Prefer language and any other property over country if not US or GB. conflict_solver=lambda match, other: match if other.name != 'language' or match.value not in [babelfish.Country('US'), babelfish.Country('GB')] else other, properties={'country': [None]}) return rebulk
def title(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().rules(TitleFromPosition, PreferTitleWithYear) expected_title = build_expected_function('expected_title') rebulk.functional(expected_title, name='title', tags=['expected', 'title'], validator=seps_surround, formatter=formatters(cleanup, reorder_title), conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_title')) return rebulk
def bonus(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'bonus')) rebulk = rebulk.regex_defaults(name='bonus', flags=re.IGNORECASE) load_config_patterns(rebulk, config.get('bonus')) rebulk.rules(BonusTitleRule) return rebulk
def date(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().defaults(validator=seps_surround) rebulk.regex( r"\d{4}", name="year", formatter=int, validator=lambda match: seps_surround(match) and valid_year(match.value) ) def date_functional(string, context): """ Search for date in the string and retrieves match :param string: :return: """ ret = search_date(string, context.get("date_year_first"), context.get("date_day_first")) if ret: return ret[0], ret[1], {"value": ret[2]} rebulk.functional( date_functional, name="date", properties={"date": [None]}, conflict_solver=lambda match, other: other if other.name in ["episode", "season"] else "__default__", ) rebulk.rules(KeepMarkedYearInFilepart) return rebulk
def bonus(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'bonus')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE) rebulk.regex(r'x(\d+)', name='bonus', private_parent=True, children=True, formatter=int, validator={'__parent__': seps_surround}, validate_all=True, conflict_solver=lambda match, conflicting: match if conflicting.name in ('video_codec', 'episode') and 'weak-episode' not in conflicting.tags else '__default__') rebulk.rules(BonusTitleRule) return rebulk
def rules(): """Return all our custom rules to be applied to the guessit api. IMPORTANT: - DO NOT define priority or dependency in each rule. Just define order here. - Only allowed dependency is TypeProcessor because we want to apply rules for certain types only """ return Rebulk().rules(RenamePartsToEpisodeNumbers, AppendPartToMovieTile, AppendLineToMovieTitle, AppendUsToMovieTitle, PrependXxxToMovieTitle, VhsAsMovieTitle)
def episode_title(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().rules(EpisodeTitleFromPosition, AlternativeTitleReplace, TitleToEpisodeTitle, Filepart3EpisodeTitle, Filepart2EpisodeTitle) return rebulk
def title(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().rules(TitleFromPosition, PreferTitleWithYear) def expected_title(input_string, context): """ Expected title functional pattern. :param input_string: :type input_string: :param context: :type context: :return: :rtype: """ ret = [] for search in context.get('expected_title'): if search.startswith('re:'): search = search[3:] search = search.replace(' ', '-') matches = RePattern(search, abbreviations=[dash], flags=re.IGNORECASE).matches( input_string, context) for match in matches: ret.append(match.span) else: for start in find_all(input_string, search, ignore_case=True): ret.append((start, start + len(search))) return ret rebulk.functional( expected_title, name='title', tags=['expected'], conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_title')) return rebulk
def title(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().rules(TitleFromPosition, PreferTitleWithYear) expected_title = build_expected_function('expected_title') rebulk.functional( expected_title, name='title', tags=['expected', 'title'], validator=seps_surround, formatter=formatters(cleanup, reorder_title), conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_title')) return rebulk
def processors(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(EnlargeGroupMatches, EquivalentHoles, RemoveLessSpecificSeasonEpisode('season'), RemoveLessSpecificSeasonEpisode('episode'), RemoveAmbiguous, SeasonYear, Processors, StripSeparators)
def groups(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() rebulk.defaults(name="group", marker=True) starting = '([{' ending = ')]}' def mark_groups(input_string): """ Functional pattern to mark groups (...), [...] and {...}. :param input_string: :return: """ openings = ([], [], []) i = 0 ret = [] for char in input_string: start_type = starting.find(char) if start_type > -1: openings[start_type].append(i) i += 1 end_type = ending.find(char) if end_type > -1: try: start_index = openings[end_type].pop() ret.append((start_index, i)) except IndexError: pass return ret rebulk.functional(mark_groups) return rebulk
def title(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'title')) rebulk.rules(TitleFromPosition, PreferTitleWithYear) expected_title = build_expected_function('expected_title') rebulk.functional(expected_title, name='title', tags=['expected', 'title'], validator=seps_surround, formatter=formatters(cleanup, reorder_title), conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_title')) return rebulk
def episode_title(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ previous_names = ('episode', 'episode_count', 'season', 'season_count', 'date', 'title', 'year') rebulk = Rebulk( disabled=lambda context: is_disabled(context, 'episode_title')) rebulk = rebulk.rules(RemoveConflictsWithEpisodeTitle(previous_names), EpisodeTitleFromPosition(previous_names), AlternativeTitleReplace(previous_names), TitleToEpisodeTitle, Filepart3EpisodeTitle, Filepart2EpisodeTitle, RenameEpisodeTitleWhenMovieType) return rebulk
def episode_title(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ previous_names = ('episode', 'episode_count', 'season', 'season_count', 'date', 'title', 'year') rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'episode_title')) rebulk = rebulk.rules(RemoveConflictsWithEpisodeTitle(previous_names), EpisodeTitleFromPosition(previous_names), AlternativeTitleReplace(previous_names), TitleToEpisodeTitle, Filepart3EpisodeTitle, Filepart2EpisodeTitle, RenameEpisodeTitleWhenMovieType) return rebulk
def part(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) prefixes = ['pt', 'part'] if REGEX_AVAILABLE: rebulk.regex(r'\L<prefixes>-?(' + numeral + r')', prefixes=prefixes, name='part', validate_all=True, private_parent=True, children=True, formatter=parse_numeral) else: rebulk.regex(build_or_pattern(prefixes) + r'-?(' + numeral + r')', prefixes=prefixes, name='part', validate_all=True, private_parent=True, children=True, formatter=parse_numeral) return rebulk
def cds(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'cd')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.regex(r'cd-?(?P<cd>\d+)(?:-?of-?(?P<cd_count>\d+))?', validator={'cd': lambda match: 0 < match.value < 100, 'cd_count': lambda match: 0 < match.value < 100}, formatter={'cd': int, 'cd_count': int}, children=True, private_parent=True, properties={'cd': [None], 'cd_count': [None]}) rebulk.regex(r'(?P<cd_count>\d+)-?cds?', validator={'cd': lambda match: 0 < match.value < 100, 'cd_count': lambda match: 0 < match.value < 100}, formatter={'cd_count': int}, children=True, private_parent=True, properties={'cd': [None], 'cd_count': [None]}) return rebulk
def part(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'part')) rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) prefixes = config['prefixes'] def validate_roman(match): """ Validate a roman match if surrounded by separators :param match: :type match: :return: :rtype: """ if int_coercable(match.raw): return True return seps_surround(match) rebulk.regex(build_or_pattern(prefixes) + r'-?(?P<part>' + numeral + r')', prefixes=prefixes, validate_all=True, private_parent=True, children=True, formatter=parse_numeral, validator={'part': compose(validate_roman, lambda m: 0 < m.value < 100)}) return rebulk
def path(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() rebulk.defaults(name="path", marker=True) def mark_path(input_string, context): """ Functional pattern to mark path elements. :param input_string: :return: """ ret = [] if context.get('name_only', False): ret.append((0, len(input_string))) else: indices = list(find_all(input_string, '/')) indices += list(find_all(input_string, '\\')) indices += [-1, len(input_string)] indices.sort() for i in range(0, len(indices) - 1): ret.append((indices[i] + 1, indices[i + 1])) return ret rebulk.functional(mark_path) return rebulk
def processors(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ return Rebulk().rules(EnlargeGroupMatches, EquivalentHoles, RemoveLessSpecificSeasonEpisode('season'), RemoveLessSpecificSeasonEpisode('episode'), RemoveAmbiguous, SeasonYear, Processors, StripSeparators)
def cds(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.regex(r'cd-?(?P<cd>\d+)(?:-?of-?(?P<cd_count>\d+))?', validator={'cd': lambda match: match.value > 0, 'cd_count': lambda match: match.value > 0}, formatter={'cd': int, 'cd_count': int}, children=True, private_parent=True, properties={'cd': [None], 'cd_count': [None]}) rebulk.regex(r'(?P<cd_count>\d+)-?cds?', validator={'cd': lambda match: match.value > 0, 'cd_count': lambda match: match.value > 0}, formatter={'cd_count': int}, children=True, private_parent=True, properties={'cd': [None], 'cd_count': [None]}) return rebulk
def size(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'size')) rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='size', validator=seps_surround) rebulk.regex(r'\d+-?[mgt]b', r'\d+\.\d+-?[mgt]b', formatter=Size.fromstring, tags=['release-group-prefix']) return rebulk
def title(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().rules(TitleFromPosition, PreferTitleWithYear) def expected_title(input_string, context): """ Expected title functional pattern. :param input_string: :type input_string: :param context: :type context: :return: :rtype: """ ret = [] for search in context.get('expected_title'): if search.startswith('re:'): search = search[3:] search = search.replace(' ', '-') matches = RePattern(search, abbreviations=[dash], flags=re.IGNORECASE).matches(input_string, context) for match in matches: # Instance of 'list' has no 'span' member (no-member). Seems to be a pylint bug. # pylint: disable=no-member ret.append(match.span) else: for start in find_all(input_string, search, ignore_case=True): ret.append((start, start+len(search))) return ret rebulk.functional(expected_title, name='title', tags=['expected'], conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_title')) return rebulk
def streaming_service(config): # pylint: disable=too-many-statements,unused-argument """Streaming service property. :param config: rule configuration :type config: dict :return: :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'streaming_service')) rebulk = rebulk.string_defaults(ignore_case=True).regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='streaming_service', tags=['source-prefix']) for value, items in config.items(): patterns = items if isinstance(items, list) else [items] for pattern in patterns: if pattern.startswith('re:'): rebulk.regex(pattern, value=value) else: rebulk.string(pattern, value=value) rebulk.rules(ValidateStreamingService) return rebulk
def when(self, matches, context): expected_rebulk = Rebulk().defaults(name='release_group') for expected_group in context.get('expected_group'): if expected_group.startswith('re:'): expected_group = expected_group[3:] expected_group = expected_group.replace(' ', '-') expected_rebulk.regex(expected_group, abbreviations=[dash], flags=re.IGNORECASE) else: expected_rebulk.string(expected_group, ignore_case=True) matches = expected_rebulk.matches(matches.input_string, context) return matches
def film(config): # pylint:disable=unused-argument """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'film')) rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash ]).string_defaults(ignore_case=True) rebulk.defaults(name='film', validator=seps_surround) load_config_patterns(rebulk, config.get('film')) rebulk.rules(FilmTitleRule) return rebulk
def crc(): """ Builder for rebulk object. :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE) rebulk.defaults(validator=seps_surround) rebulk.regex('(?:[a-fA-F]|[0-9]){8}', name='crc32', conflict_solver=lambda match, other: match if other.name in ['episode', 'season'] else '__default__') rebulk.functional(guess_idnumber, name='uuid', conflict_solver=lambda match, other: match if other.name in ['episode', 'season'] else '__default__') return rebulk
def crc(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'crc32')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE) rebulk.defaults(validator=seps_surround) rebulk.regex('(?:[a-fA-F]|[0-9]){8}', name='crc32', conflict_solver=lambda match, other: other if other.name in ['episode', 'season'] else '__default__') rebulk.functional(guess_idnumber, name='uuid', conflict_solver=lambda match, other: match if other.name in ['episode', 'season'] else '__default__') return rebulk
def bit_rate(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: (is_disabled(context, 'audio_bit_rate') and is_disabled(context, 'video_bit_rate'))) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='audio_bit_rate', validator=seps_surround) rebulk.regex(r'\d+-?[kmg]b(ps|its?)', r'\d+\.\d+-?[kmg]b(ps|its?)', conflict_solver=( lambda match, other: match if other.name == 'audio_channels' and 'weak-audio_channels' not in other.tags else other ), formatter=BitRate.fromstring, tags=['release-group-prefix']) rebulk.rules(BitRateTypeRule) return rebulk
def date(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk().defaults(validator=seps_surround) rebulk.regex(r"\d{4}", name="year", formatter=int, disabled=lambda context: is_disabled(context, 'year'), conflict_solver=lambda match, other: other if other.name in ('episode', 'season') and len(other.raw) < len(match.raw) else '__default__', validator=lambda match: seps_surround(match) and valid_year(match.value)) def date_functional(string, context): # pylint:disable=inconsistent-return-statements """ Search for date in the string and retrieves match :param string: :return: """ ret = search_date(string, context.get('date_year_first'), context.get('date_day_first')) if ret: return ret[0], ret[1], {'value': ret[2]} rebulk.functional(date_functional, name="date", properties={'date': [None]}, disabled=lambda context: is_disabled(context, 'date'), conflict_solver=lambda match, other: other if other.name in ('episode', 'season', 'crc32') else '__default__') rebulk.rules(KeepMarkedYearInFilepart) return rebulk
def bonus(config): # pylint:disable=unused-argument """ Builder for rebulk object. :param config: rule configuration :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'bonus')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE) rebulk.regex(r'x(\d+)', name='bonus', private_parent=True, children=True, formatter=int, validator={'__parent__': lambda match: seps_surround}, conflict_solver=lambda match, conflicting: match if conflicting.name in ('video_codec', 'episode') and 'weak-episode' not in conflicting.tags else '__default__') rebulk.rules(BonusTitleRule) return rebulk