diff --git a/guessit/rules/properties/bit_rate.py b/guessit/rules/properties/bit_rate.py index 6107b31..9a556dd 100644 --- a/guessit/rules/properties/bit_rate.py +++ b/guessit/rules/properties/bit_rate.py @@ -6,7 +6,7 @@ video_bit_rate and audio_bit_rate properties import re from rebulk import Rebulk -from rebulk.rules import Rule, RenameMatch +from rebulk.rules import Rule, RemoveMatch, RenameMatch from ..common import dash, seps from ..common.pattern import is_disabled @@ -20,7 +20,8 @@ def bit_rate(): :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'bit_rate')) + 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]bps', r'\d+\.\d+-?[kmg]bps', @@ -40,11 +41,23 @@ class BitRateTypeRule(Rule): """ Convert audio bit rate guess into video bit rate. """ - consequence = RenameMatch('video_bit_rate') + consequence = [RenameMatch('video_bit_rate'), RemoveMatch] def when(self, matches, context): - for match in matches.named('audio_bit_rate'): - previous = matches.previous(match, index=0, - predicate=lambda m: m.name in ('source', 'screen_size', 'video_codec')) - if previous and not matches.holes(previous.end, match.start, predicate=lambda m: m.value.strip(seps)): - yield match + to_rename = [] + to_remove = [] + + if is_disabled(context, 'audio_bit_rate'): + to_remove.extend(matches.named('audio_bit_rate')) + else: + video_bit_rate_disabled = is_disabled(context, 'video_bit_rate') + for match in matches.named('audio_bit_rate'): + previous = matches.previous(match, index=0, + predicate=lambda m: m.name in ('source', 'screen_size', 'video_codec')) + if previous and not matches.holes(previous.end, match.start, predicate=lambda m: m.value.strip(seps)): + if video_bit_rate_disabled: + to_remove.append(match) + else: + to_rename.append(match) + + return to_rename, to_remove diff --git a/guessit/rules/properties/crc.py b/guessit/rules/properties/crc.py index a74637e..cb5bdb4 100644 --- a/guessit/rules/properties/crc.py +++ b/guessit/rules/properties/crc.py @@ -16,7 +16,7 @@ def crc(): :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'crc')) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'crc32')) rebulk = rebulk.regex_defaults(flags=re.IGNORECASE) rebulk.defaults(validator=seps_surround) diff --git a/guessit/rules/properties/date.py b/guessit/rules/properties/date.py index b6fa358..b64ae6f 100644 --- a/guessit/rules/properties/date.py +++ b/guessit/rules/properties/date.py @@ -16,10 +16,10 @@ def date(): :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'date')) - rebulk = rebulk.defaults(validator=seps_surround) + rebulk = Rebulk().defaults(validator=seps_surround) rebulk.regex(r"\d{4}", name="year", formatter=int, + disabled=lambda context: is_disabled(context, 'year'), validator=lambda match: seps_surround(match) and valid_year(match.value)) def date_functional(string, context): # pylint:disable=inconsistent-return-statements @@ -35,6 +35,7 @@ def date(): 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__') @@ -51,6 +52,9 @@ class KeepMarkedYearInFilepart(Rule): priority = 64 consequence = RemoveMatch + def enabled(self, context): + return not is_disabled(context, 'year') + def when(self, matches, context): ret = [] if len(matches.named('year')) > 1: diff --git a/guessit/rules/properties/episodes.py b/guessit/rules/properties/episodes.py index 2fbfe37..6985a2c 100644 --- a/guessit/rules/properties/episodes.py +++ b/guessit/rules/properties/episodes.py @@ -29,7 +29,7 @@ def episodes(): # 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') and is_disabled(context, 'season') + 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']) @@ -836,13 +836,22 @@ class RenameToDiscMatch(Rule): Rename episodes detected with `d` episodeMarkers to `disc`. """ - consequence = [RenameMatch('disc'), RenameMatch('discMarker')] + consequence = [RenameMatch('disc'), RenameMatch('discMarker'), RemoveMatch] def when(self, matches, context): discs = [] markers = [] + to_remove = [] + + disc_disabled = is_disabled(context, 'disc') + for marker in matches.named('episodeMarker', predicate=lambda m: m.value.lower() == 'd'): + if disc_disabled: + to_remove.append(marker) + to_remove.extend(marker.initiator.children) + continue + markers.append(marker) discs.extend(sorted(marker.initiator.children.named('episode'), key=lambda m: m.value)) - return discs, markers + return discs, markers, to_remove diff --git a/guessit/rules/properties/language.py b/guessit/rules/properties/language.py index 50cf236..adc7225 100644 --- a/guessit/rules/properties/language.py +++ b/guessit/rules/properties/language.py @@ -37,7 +37,7 @@ def language(): rebulk.functional(find_languages, properties={'language': [None]}, disabled=lambda context: not context.get('allowed_languages')) - rebulk.rules(SubtitlePrefixLanguageRule, SubtitleSuffixLanguageRule, SubtitleExtensionRule) + rebulk.rules(SubtitlePrefixLanguageRule, SubtitleSuffixLanguageRule, SubtitleExtensionRule, RemoveLanguage) return rebulk @@ -193,9 +193,19 @@ class LanguageFinder(object): Helper class to search and return language matches: 'language' and 'subtitle_language' properties """ - def __init__(self, allowed_languages): + def __init__(self, context): + allowed_languages = context.get('allowed_languages') if context else None self.allowed_languages = set([l.lower() for l in allowed_languages or []]) self.common_words = COMMON_WORDS + self.prefixes_map = {} + self.suffixes_map = {} + + if not is_disabled(context, 'subtitle_language'): + self.prefixes_map['subtitle_language'] = subtitle_prefixes + self.suffixes_map['subtitle_language'] = subtitle_suffixes + + self.prefixes_map['language'] = lang_prefixes + self.suffixes_map['language'] = lang_suffixes def find(self, string): """ @@ -256,11 +266,11 @@ class LanguageFinder(object): """ tuples = [ (language_word, language_word.next_word, - {'subtitle_language': subtitle_prefixes, 'language': lang_prefixes}, + self.prefixes_map, lambda string, prefix: string.startswith(prefix), lambda string, prefix: string[len(prefix):]), (language_word.next_word, language_word, - {'subtitle_language': subtitle_suffixes, 'language': lang_suffixes}, + self.suffixes_map, lambda string, suffix: string.endswith(suffix), lambda string, suffix: string[:len(string) - len(suffix)]) ] @@ -351,8 +361,7 @@ def find_languages(string, context=None): :return: list of tuple (property, Language, lang_word, word) """ - allowed_languages = context.get('allowed_languages') if context else None - return LanguageFinder(allowed_languages).find(string) + return LanguageFinder(context).find(string) class SubtitlePrefixLanguageRule(Rule): @@ -455,3 +464,15 @@ class SubtitleExtensionRule(Rule): subtitle_lang = matches.previous(subtitle_extension, lambda match: match.name == 'language', 0) if subtitle_lang: return matches.conflicting(subtitle_lang, lambda m: m.name == 'source'), subtitle_lang + + +class RemoveLanguage(Rule): + """Remove language matches that were not converted to subtitle_language when language is disabled.""" + + consequence = RemoveMatch + + def enabled(self, context): + return is_disabled(context, 'language') + + def when(self, matches, context): + return matches.named('language') diff --git a/guessit/rules/properties/screen_size.py b/guessit/rules/properties/screen_size.py index 689c43e..bb097e7 100644 --- a/guessit/rules/properties/screen_size.py +++ b/guessit/rules/properties/screen_size.py @@ -77,7 +77,10 @@ class PostProcessScreenSize(Rule): aspect_ratio = Match(match.start, match.end, input_string=match.input_string, name='aspect_ratio', value=round(calculated_ar, 3)) - to_append.append(aspect_ratio) + + if not is_disabled(context, 'aspect_ratio'): + to_append.append(aspect_ratio) + if height in self.standard_heights and self.min_ar < calculated_ar < self.max_ar: match.value = '{0}{1}'.format(height, scan_type) else: diff --git a/guessit/test/enable_disable_properties.yml b/guessit/test/enable_disable_properties.yml new file mode 100644 index 0000000..6f69f93 --- /dev/null +++ b/guessit/test/enable_disable_properties.yml @@ -0,0 +1,315 @@ +? vorbis +: options: --exclude audio_codec + -audio_codec: Vorbis + +? DTS-ES +: options: --exclude audio_profile + audio_codec: DTS + -audio_profile: Extended Surround + +? DTS.ES +: options: --include audio_codec + audio_codec: DTS + -audio_profile: Extended Surround + +? 5.1 +? 5ch +? 6ch +: options: --exclude audio_channels + -audio_channels: '5.1' + +? Movie Title-x01-Other Title.mkv +? Movie Title-x01-Other Title +? directory/Movie Title-x01-Other Title/file.mkv +: options: --exclude bonus + -bonus: 1 + -bonus_title: Other Title + +? Title-x02-Bonus Title.mkv +: options: --include bonus + bonus: 2 + -bonus_title: Other Title + +? cd 1of3 +: options: --exclude cd + -cd: 1 + -cd_count: 3 + +? This.Is.Us +: options: --exclude country + title: This Is Us + -country: US + +? 2015.01.31 +: options: --exclude date + year: 2015 + -date: 2015-01-31 + +? Something 2 mar 2013) +: options: --exclude date + -date: 2013-03-02 + +? 2012 2009 S01E02 2015 # If no year is marked, the second one is guessed. +: options: --exclude year + -year: 2009 + +? Director's cut +: options: --exclude edition + -edition: Director's Cut + +? 2x5 +? 2X5 +? 02x05 +? 2X05 +? 02x5 +? S02E05 +? s02e05 +? s02e5 +? s2e05 +? s02ep05 +? s2EP5 +: options: --exclude season + -season: 2 + -episode: 5 + +? 2x6 +? 2X6 +? 02x06 +? 2X06 +? 02x6 +? S02E06 +? s02e06 +? s02e6 +? s2e06 +? s02ep06 +? s2EP6 +: options: --exclude episode + -season: 2 + -episode: 6 + +? serie Season 2 other +: options: --exclude season + -season: 2 + +? Some Dummy Directory/S02 Some Series/E01-Episode title.mkv +: options: --exclude episode_title + -episode_title: Episode title + season: 2 + episode: 1 + +? Another Dummy Directory/S02 Some Series/E01-Episode title.mkv +: options: --include season --include episode + -episode_title: Episode title + season: 2 + episode: 1 + +# pattern contains season and episode: it wont work enabling only one +? Some Series S03E01E02 +: options: --include episode + -season: 3 + -episode: [1, 2] + +# pattern contains season and episode: it wont work enabling only one +? Another Series S04E01E02 +: options: --include season + -season: 4 + -episode: [1, 2] + +? Show.Name.Season.4.Episode.1 +: options: --include episode + -season: 4 + episode: 1 + +? Another.Show.Name.Season.4.Episode.1 +: options: --include season + season: 4 + -episode: 1 + +? Some Series S01 02 03 +: options: --exclude season + -season: [1, 2, 3] + +? Some Series E01 02 04 +: options: --exclude episode + -episode: [1, 2, 4] + +? A very special episode s06 special +: options: -t episode --exclude episode_details + season: 6 + -episode_details: Special + +? S01D02.3-5-GROUP +: options: --exclude disc + -season: 1 + -disc: [2, 3, 4, 5] + -episode: [2, 3, 4, 5] + +? S01D02&4-6&8 +: options: --exclude season + -season: 1 + -disc: [2, 4, 5, 6, 8] + -episode: [2, 4, 5, 6, 8] + +? Film Title-f01-Series Title.mkv +: options: --exclude film + -film: 1 + -film_title: Film Title + +? Another Film Title-f01-Series Title.mkv +: options: --exclude film_title + film: 1 + -film_title: Film Title + +? English +? .ENG. +: options: --exclude language + -language: English + +? SubFrench +? SubFr +? STFr +: options: --exclude subtitle_language + -language: French + -subtitle_language: French + +? ST.FR +: options: --exclude subtitle_language + language: French + -subtitle_language: French + +? ENG.-.sub.FR +? ENG.-.FR Sub +: options: --include language + language: [English, French] + -subtitle_language: French + +? ENG.-.SubFR +: options: --include language + language: English + -subtitle_language: French + +? ENG.-.FRSUB +? ENG.-.FRSUBS +? ENG.-.FR-SUBS +: options: --include subtitle_language + -language: English + subtitle_language: French + +? DVD.Real.XViD +? DVD.fix.XViD +: options: --exclude other + -other: Proper + -proper_count: 1 + +? Part 3 +? Part III +? Part Three +? Part Trois +? Part3 +: options: --exclude part + -part: 3 + +? Some.Title.XViD-by.Artik[SEDG].avi +: options: --exclude release_group + -release_group: Artik[SEDG] + +? "[ABC] Some.Title.avi" +? some/folder/[ABC]Some.Title.avi +: options: --exclude release_group + -release_group: ABC + +? 360p +? 360px +? "360" +? +500x360 +: options: --exclude screen_size + -screen_size: 360p + +? 640x360 +: options: --exclude aspect_ratio + screen_size: 360p + -aspect_ratio: 1.778 + +? 8196x4320 +: options: --exclude screen_size + -screen_size: 4320p + -aspect_ratio: 1.897 + +? 4.3gb +: options: --exclude size + -size: 4.3GB + +? VhS_rip +? VHS.RIP +: options: --exclude source + -source: VHS + -other: Rip + +? DVD.RIP +: options: --include other + -source: DVD + -other: Rip + +? Title Only.avi +: options: --exclude title + -title: Title Only + +? h265 +? x265 +? h.265 +? x.265 +? hevc +: options: --exclude video_codec + -video_codec: H.265 + +? hevc10 +: options: --include color_depth + -video_codec: H.265 + -color_depth: 10-bit + +? HEVC-YUV420P10 +: options: --include color_depth + -video_codec: H.265 + color_depth: 10-bit + +? h265-HP +: options: --exclude video_profile + video_codec: H.265 + -video_profile: High + +? House.of.Cards.2013.S02E03.1080p.NF.WEBRip.DD5.1.x264-NTb.mkv +? House.of.Cards.2013.S02E03.1080p.Netflix.WEBRip.DD5.1.x264-NTb.mkv +: options: --exclude streaming_service + -streaming_service: Netflix + +? wawa.co.uk +: options: --exclude website + -website: wawa.co.uk + +? movie.mkv +: options: --exclude mimetype + -mimetype: video/x-matroska + +? another movie.mkv +: options: --exclude container + -container: mkv + +? series s02e01 +: options: --exclude type + -type: episode + +? series s02e01 +: options: --exclude type + -type: episode + +? Hotel.Hell.S01E01.720p.DD5.1.448kbps-ALANiS +: options: --exclude audio_bit_rate + -audio_bit_rate: 448Kbps + +? Katy Perry - Pepsi & Billboard Summer Beats Concert Series 2012 1080i HDTV 20 Mbps DD2.0 MPEG2-TrollHD.ts +: options: --exclude video_bit_rate + -video_bit_rate: 20Mbps + +? "[Figmentos] Monster 34 - At the End of Darkness [781219F1].mkv" +: options: --exclude crc32 + -crc32: 781219F1