def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): sources = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'source') if len(sources) < 2: continue for candidate in reversed(sources): previous = matches.previous(candidate, predicate=lambda match: match.name == 'screen_size') next_range = matches.range(candidate.end, filepart.end, lambda match: match.name in ('audio_codec', 'video_codec', 'release_group')) # If we have at least 3 matches near by, then discard the other sources if len(previous) + len(next_range) > 1: invalid_sources = {f.value for f in sources[0:-1]} to_remove = matches.named('source', predicate=lambda m: m.value in invalid_sources) return to_remove if matches.conflicting(candidate): to_remove = matches.named('source', predicate=lambda m: m.value in candidate.value) return to_remove
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ to_remove = [] fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): m_x264 = matches.ending(filepart.end, predicate=lambda match: match.name == 'video_codec' and match.value == 'H.264', index=0) if not m_x264: continue m_hdtv = matches.previous(m_x264, predicate=lambda match: match.name == 'source' and match.value == 'HDTV', index=0) if not m_hdtv: continue video_codecs = matches.range(filepart.start, filepart.end, lambda match: match.name == 'video_codec') sources = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'source') if len(video_codecs) > 1: to_remove.append(m_x264) if len(sources) <= 1: m_hdtv.tags.append('tvchaosuk') if len(sources) > 1: to_remove.append(m_hdtv) if len(video_codecs) <= 1: m_x264.tags.append('tvchaosuk') return to_remove
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ to_remove = [] to_append = [] fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): season = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'season', index=0) if not season: continue episodes = matches.range(season.end, filepart.end, predicate=lambda match: match.name == 'episode' and 'weak-episode' in match.tags) if not episodes: continue if len(episodes) != len(matches.holes(season.end, episodes[-1].start, predicate=lambda hole: hole.raw in simple_separator)): continue for episode in episodes: new_season = copy.copy(episode) new_season.name = 'season' to_append.append(new_season) to_remove.append(episode) return to_append, to_remove
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ if context.get('show_type') == 'normal' or not matches.tagged('anime'): return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): seasons = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'season') for season in seasons: if not season.parent or not season.parent.private: continue title = matches.previous(season, index=-1, predicate=lambda match: match.name == 'title' and match.end <= filepart.end) episode_title = matches.next(season, index=0, predicate=lambda match: (match.name == 'episode_title' and match.end <= filepart.end and match.value.isdigit())) # the previous match before the season is the series name and # the match after season is episode title and episode title is a number if not title or not episode_title: continue to_remove = [] to_append = [] # adjust title to append the series name. # Only the season.parent contains the S prefix in its value new_title = copy.copy(title) new_title.value = ' '.join([title.value, season.parent.value]) new_title.end = season.end # other fileparts might have the same season to be removed from the matches # e.g.: /Show.Name.S2/[Group].Show.Name.S2.-.19.[1080p] to_remove.extend(matches.named('season', predicate=lambda match: match.value == season.value)) to_remove.append(title) to_append.append(new_title) # move episode_title to absolute_episode absolute_episode = copy.copy(episode_title) absolute_episode.name = 'absolute_episode' absolute_episode.value = int(episode_title.value) # always keep episode (subliminal needs it) episode = copy.copy(absolute_episode) episode.name = 'episode' to_remove.append(episode_title) to_append.append(absolute_episode) to_append.append(episode) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): # retrieve all problematic titles problematic_titles = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name in self.properties) to_remove = [] to_append = [] for title in problematic_titles: m = self.absolute_re.search(title.raw) if not m: continue # Remove the problematic title to_remove.append(title) # Remove the title suffix new_value = title.raw[0: m.start()] if new_value: # Add the correct title new_title = copy.copy(title) new_title.value = cleanup(new_value) new_title.end = title.start + m.start() to_append.append(new_title) # and add the absolute episode range g = m.groupdict() if not g['absolute_episode_end'] and title.name != 'alternative_title': continue absolute_episode_start = int(g['absolute_episode_start']) absolute_episode_end = int(g['absolute_episode_end'] or g['absolute_episode_start']) for i in range(absolute_episode_start, absolute_episode_end + 1): episode = copy.copy(title) episode.name = 'absolute_episode' episode.value = i if i == absolute_episode_start: episode.start = title.start + m.start('absolute_episode_start') episode.end = title.start + m.end('absolute_episode_start') elif i < absolute_episode_end: episode.start = title.start + m.end('absolute_episode_start') episode.end = title.start + m.start('absolute_episode_end') else: episode.start = title.start + m.start('absolute_episode_end') episode.end = title.start + m.end('absolute_episode_end') to_append.append(episode) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ # Do not concatenate the titles if it's not an anime and there are languages if not matches.tagged('anime') and matches.named('language'): return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): title = matches.range( filepart.start, filepart.end, predicate=lambda match: match.name == 'title', index=0) if not title: continue alternative_titles = matches.range( filepart.start, filepart.end, predicate=lambda match: match.name == 'alternative_title') if not alternative_titles: continue previous = title alias = copy.copy(title) alias.name = 'alias' alias.value = title.value # extended title is the concatenation between title and all alternative titles for alternative_title in alternative_titles: holes = matches.holes(start=previous.end, end=alternative_title.start) # if the separator is a dash, add an extra space before and after if six.PY3: separators = [ ' ' + h.value + ' ' if h.value == '-' else h.value for h in holes ] separator = ' '.join(separators) if separators else ' ' else: separators = [ b' ' + h.value + b' ' if h.value == b'-' else h.value for h in holes ] separator = b' '.join(separators) if separators else b' ' alias.value += separator + alternative_title.value previous = alternative_title alias.end = previous.end return [alias]
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ # Don't add additional alias if we already have one from the previous rules if matches.named('alias'): return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): title = matches.range( filepart.start, filepart.end, predicate=lambda match: match.name == 'title', index=0) if not title: continue after_title = matches.next(title, index=0, predicate=lambda match: (match.end <= filepart.end and match. name in self.affected_names)) # only if there's a country or year if not after_title: continue # skip if season == year. E.g.: Show.Name.S2016E01 if matches.conflicting( after_title, predicate=lambda match: match.name == 'season', index=0): continue # Only add country or year if the next match is season, episode or date next_match = matches.next(after_title, index=0, predicate=lambda match: match.name in ('season', 'episode', 'date')) if next_match: alias = copy.copy(title) alias.name = 'alias' if six.PY3: alias.value = alias.value + ' ' + re.sub( r'\W*', '', after_title.raw) else: alias.value = alias.value + b' ' + re.sub( r'\W*', b'', after_title.raw) alias.end = after_title.end alias.raw_end = after_title.raw_end return [alias]
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ if context.get('show_type') == 'normal': return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): # get the group (e.g.: [abc]) at the beginning of this filepart group = matches.markers.at_index( filepart.start, index=0, predicate=lambda marker: marker.name == 'group') if not group or matches.at_match(group): continue # don't use websites as release group websites = matches.named('website') if websites and any(ws for ws in websites if ws.value in group.value): continue if (not matches.tagged('anime') and not matches.named('video_profile') and matches.named('season') and matches.named('episode')): continue groups = matches.range( filepart.start, filepart.end, predicate=lambda match: match.name == 'release_group') if not groups: continue to_remove = [] to_append = [] if group: to_remove.extend(groups) rg = copy.copy(groups[0]) rg.start = group.start rg.end = group.end rg.value = cleanup(group.value) rg.tags = ['anime'] to_append.append(rg) else: # anime should pick the first in the list and discard the rest to_remove.append(groups[1:]) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): # retrieve all problematic groups problematic_groups = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'release_group') to_remove = [] to_append = [] for group in problematic_groups: filename = fileparts[-1].raw m = self.absolute_re.search(filename) if not m: continue # Remove the problematic group to_remove.append(group) # and add the absolute episode range g = m.groupdict() if not g['absolute_episode_end']: continue absolute_episode_start = int(g['absolute_episode_start']) absolute_episode_end = int(g['absolute_episode_end'] or g['absolute_episode_start']) for i in range(absolute_episode_start, absolute_episode_end + 1): episode = copy.copy(group) episode.name = 'absolute_episode' episode.value = i if i == absolute_episode_start: episode.start = group.start + m.start('absolute_episode_start') episode.end = group.start + m.end('absolute_episode_start') elif i < absolute_episode_end: episode.start = group.start + m.end('absolute_episode_start') episode.end = group.start + m.start('absolute_episode_end') else: episode.start = group.start + m.start('absolute_episode_end') episode.end = group.start + m.end('absolute_episode_end') to_append.append(episode) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ if context.get('show_type') == 'normal': return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): # get the group (e.g.: [abc]) at the beginning of this filepart group = matches.markers.at_index(filepart.start, index=0, predicate=lambda marker: marker.name == 'group') if not group or matches.at_match(group): continue # don't use websites as release group websites = matches.named('website') if websites and any(ws for ws in websites if ws.value in group.value): continue if (not matches.tagged('anime') and not matches.named('video_profile') and matches.named('season') and matches.named('episode')): continue groups = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'release_group') if not groups: continue to_remove = [] to_append = [] if group: to_remove.extend(groups) rg = copy.copy(groups[0]) rg.start = group.start rg.end = group.end rg.value = cleanup(group.value) rg.tags = ['anime'] to_append.append(rg) else: # anime should pick the first in the list and discard the rest to_remove.append(groups[1:]) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ # Do not concatenate the titles if it's not an anime and there are languages if not matches.tagged('anime') and matches.named('language'): return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): title = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'title', index=0) if not title: continue alternative_titles = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'alternative_title') if not alternative_titles: continue previous = title alias = copy.copy(title) alias.name = 'alias' alias.value = title.value # extended title is the concatenation between title and all alternative titles for alternative_title in alternative_titles: holes = matches.holes(start=previous.end, end=alternative_title.start) # if the separator is a dash, add an extra space before and after if six.PY3: separators = [' ' + h.value + ' ' if h.value == '-' else h.value for h in holes] separator = ' '.join(separators) if separators else ' ' else: separators = [b' ' + h.value + b' ' if h.value == b'-' else h.value for h in holes] separator = b' '.join(separators) if separators else b' ' alias.value += separator + alternative_title.value previous = alternative_title alias.end = previous.end return [alias]
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ # Don't add additional alias if we already have one from the previous rules if matches.named('alias'): return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): title = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'title', index=0) if not title: continue after_title = matches.next(title, index=0, predicate=lambda match: ( match.end <= filepart.end and match.name in self.affected_names)) # only if there's a country or year if not after_title: continue # skip if season == year. E.g.: Show.Name.S2016E01 if matches.conflicting(after_title, predicate=lambda match: match.name == 'season', index=0): continue # Only add country or year if the next match is season, episode or date next_match = matches.next(after_title, index=0, predicate=lambda match: match.name in ('season', 'episode', 'date')) if next_match: alias = copy.copy(title) alias.name = 'alias' if six.PY3: alias.value = alias.value + ' ' + re.sub(r'\W*', '', after_title.raw) else: alias.value = alias.value + b' ' + re.sub(r'\W*', b'', after_title.raw) alias.end = after_title.end alias.raw_end = after_title.raw_end return [alias]
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ # only if there's no season and no episode to_rename = [] fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): parts = matches.range(filepart.start, filepart.end, predicate=lambda match: match.name == 'part') if parts and not matches.range(filepart.start, filepart.end, predicate=lambda match: match.name in ('season', 'episode', 'date')): to_rename.extend(parts) return to_rename
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ if not matches.named('type', lambda m: m.value == 'episode'): return to_rename = [] file_parts = matches.markers.named('path') for file_part in marker_sorted(file_parts, matches): parts = matches.range(file_part.start, file_part.end, predicate=lambda match: match.name == 'part') # Only apply when there's no episode if parts and not matches.range(file_part.start, file_part.end, predicate=lambda match: match.name == 'episode'): to_rename.extend(parts) return to_rename
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ season = matches.named('season') year = matches.named('year') valid_season = bool(season) and (not year or season[0].value != year[0].value) anime_type = context.get('show_type') == 'anime' # only for shows that seems to be animes if not anime_type and valid_season: return # if it's not detected as anime and season is valid, then skip. if not matches.named('video_profile') and not matches.tagged('anime') and valid_season: is_anime = False groups = matches.markers.named('group') for group in groups: screen_size = matches.range(group.start, group.end, index=0, predicate=lambda match: match.name == 'screen_size') if screen_size: is_anime = True screen_size.tags.append('anime') break if not is_anime: return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): season = matches.range(filepart.start, filepart.end, index=0, predicate=lambda match: match.name == 'season' and match.raw.isdigit()) if not season: continue episodes = matches.named('episode') if episodes: to_append = [] to_remove = [] for episode in episodes: if 'anime' in episode.tags or (anime_type and not valid_season): absolute_episode = copy.copy(episode) absolute_episode.name = 'absolute_episode' to_append.append(absolute_episode) episode_title = matches.next( episode, index=0, predicate=lambda match: match.name == 'episode_title' and match.value.isdigit(), ) if episode_title and to_append: if matches.input_string[episode.end:episode_title.start + 1] in range_separator: end_value = int(episode_title.value) for i in range(absolute_episode.value + 1, end_value + 1): episode = copy.copy(absolute_episode) episode.value = i if i < end_value: episode.start = episode.end episode.end = episode_title.start else: episode.start = episode_title.start episode.end = episode_title.end ep = copy.copy(episode) ep.name = 'episode' to_append.append(episode) to_append.append(ep) to_remove.append(episode_title) return to_remove, to_append
def when(self, matches, context): """Evaluate the rule. :param matches: :type matches: rebulk.match.Matches :param context: :type context: dict :return: """ weak_duplicate = matches.tagged('weak-duplicate', index=0) # only for shows that seems to be animes if context.get('show_type') == 'normal' or not weak_duplicate: return # if it's not detected as anime and season (weak_duplicate) is not 0, then skip. if not matches.named('video_profile') and not matches.tagged('anime') and weak_duplicate.value > 0: is_anime = False groups = matches.markers.named('group') for group in groups: screen_size = matches.range(group.start, group.end, index=0, predicate=lambda match: match.name == 'screen_size') if screen_size: is_anime = True screen_size.tags.append('anime') break if not is_anime: return fileparts = matches.markers.named('path') for filepart in marker_sorted(fileparts, matches): season = matches.range(filepart.start, filepart.end, index=0, predicate=lambda match: match.name == 'season' and match.raw.isdigit()) if not season: continue episode = matches.next(season, index=0, predicate=lambda match: (match.name == 'episode' and match.end <= filepart.end and match.raw.isdigit())) # there should be season and episode and the episode should start right after the season and both raw values # should be digit if season and episode and season.end == episode.start: # then make them an absolute episode: absolute_episode = copy.copy(episode) absolute_episode.name = 'absolute_episode' absolute_episode.start = season.start # raw value contains the season and episode altogether absolute_episode.value = int(episode.parent.raw if episode.parent else episode.raw) # always keep episode (subliminal needs it) corrected_episode = copy.copy(absolute_episode) corrected_episode.name = 'episode' corrected_episode.start = season.start to_remove = [season, episode] to_append = [absolute_episode, corrected_episode] episode_title = matches.next(episode, index=0, predicate=lambda match: match.name == 'episode_title' and match.value.isdigit()) if episode_title: if matches.input_string[episode.end:episode_title.start + 1] in range_separator: end_value = int(episode_title.value) for i in range(absolute_episode.value + 1, end_value + 1): episode = copy.copy(absolute_episode) episode.value = i if i < end_value: episode.start = episode.end episode.end = episode_title.start else: episode.start = episode_title.start episode.end = episode_title.end ep = copy.copy(episode) ep.name = 'episode' to_append.append(episode) to_append.append(ep) to_remove.append(episode_title) return to_remove, to_append