#!/usr/bin/env python # -*- coding: utf-8 -*- """ release_group property """ import copy from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch from ..common import seps from ..common.expected import build_expected_function from ..common.comparators import marker_sorted from ..common.formatters import cleanup from ..common.validators import int_coercable, seps_surround from ..properties.title import TitleFromPosition 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) forbidden_groupnames = ['rip', 'by', 'for', 'par', 'pour', 'bonus'] groupname_ignore_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 _scene_previous_names = ['video_codec', 'format', 'video_api', 'audio_codec', 'audio_profile', 'video_profile', 'audio_channels', 'screen_size', 'other', 'container', 'language', 'subtitle_language', 'subtitle_language.suffix', 'subtitle_language.prefix', 'language.suffix'] _scene_previous_tags = ['release-group-prefix'] class SceneReleaseGroup(Rule): """ Add release_group match in existing matches (scene format). Something.XViD-ReleaseGroup.mkv """ dependency = [TitleFromPosition] consequence = AppendMatch properties = {'release_group': [None]} def when(self, matches, context): # If a release_group is found before, ignore this kind of release_group rule. ret = [] for filepart in marker_sorted(matches.markers.named('path'), matches): # pylint:disable=cell-var-from-loop start, end = filepart.span titles = matches.named('title', predicate=lambda m: m.start >= start and m.end <= end) def keep_only_first_title(match): """ Keep only first title from this filepart, as other ones are most likely release group. :param match: :type match: :return: :rtype: """ return match in titles[1:] last_hole = matches.holes(start, end + 1, formatter=clean_groupname, ignore=keep_only_first_title, predicate=lambda hole: cleanup(hole.value), index=-1) if last_hole: def previous_match_filter(match): """ Filter to apply to find previous match :param match: :type match: :return: :rtype: """ if match.start < filepart.start: return False return not match.private or match.name in _scene_previous_names 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 \ not matches.input_string[previous_match.end:last_hole.start].strip(seps) \ and not int_coercable(last_hole.value.strip(seps)): last_hole.name = 'release_group' last_hole.tags = ['scene'] # if hole is inside a group marker with same value, remove [](){} ... group = matches.markers.at_match(last_hole, lambda marker: marker.name == 'group', 0) if group: group.formatter = clean_groupname if group.value == last_hole.value: last_hole.start = group.start + 1 last_hole.end = group.end - 1 last_hole.tags = ['anime'] ignored_matches = matches.range(last_hole.start, last_hole.end, keep_only_first_title) for ignored_match in ignored_matches: matches.remove(ignored_match) ret.append(last_hole) return ret class AnimeReleaseGroup(Rule): """ Add release_group match in existing matches (anime format) ...[ReleaseGroup] Something.mkv """ dependency = [SceneReleaseGroup, TitleFromPosition] consequence = [RemoveMatch, AppendMatch] properties = {'release_group': [None]} def when(self, matches, context): to_remove = [] to_append = [] # If a release_group is found before, ignore this kind of release_group rule. if matches.named('release_group'): return if not matches.named('episode') and not matches.named('season') and matches.named('release_group'): # This doesn't seems to be an anime, and we already found another release_group. return for filepart in marker_sorted(matches.markers.named('path'), matches): # pylint:disable=bad-continuation empty_group = matches.markers.range(filepart.start, filepart.end, lambda marker: (marker.name == 'group' and not matches.range(marker.start, marker.end, lambda m: 'weak-language' not in m.tags) and marker.value.strip(seps) and not int_coercable(marker.value.strip(seps))), 0) if empty_group: group = copy.copy(empty_group) group.marker = False group.raw_start += 1 group.raw_end -= 1 group.tags = ['anime'] group.name = 'release_group' to_append.append(group) to_remove.extend(matches.range(empty_group.start, empty_group.end, lambda m: 'weak-language' in m.tags)) return to_remove, to_append