diff options
author | Louis Vézina <[email protected]> | 2020-05-20 11:29:39 -0400 |
---|---|---|
committer | Louis Vézina <[email protected]> | 2020-05-20 11:29:39 -0400 |
commit | 376e13d7f1ab8e6d9202c1a51a96526de0f11163 (patch) | |
tree | 77e911bffc9a59ed2868a673f0733ad38f5b4ccd /libs/guessit | |
parent | 5b44007bbb7ef49bbf4087b43dbb948433639fbe (diff) | |
download | bazarr-376e13d7f1ab8e6d9202c1a51a96526de0f11163.tar.gz bazarr-376e13d7f1ab8e6d9202c1a51a96526de0f11163.zip |
Upgraded GuessIt to 3.0.1
Diffstat (limited to 'libs/guessit')
72 files changed, 10021 insertions, 2809 deletions
diff --git a/libs/guessit/__init__.py b/libs/guessit/__init__.py index 22e9dbb07..03f8d2084 100644 --- a/libs/guessit/__init__.py +++ b/libs/guessit/__init__.py @@ -3,7 +3,12 @@ """ Extracts as much information as possible from a video file. """ +from . import monkeypatch as _monkeypatch + from .api import guessit, GuessItApi from .options import ConfigurationException +from .rules.common.quantity import Size from .__version__ import __version__ + +_monkeypatch.monkeypatch_rebulk() diff --git a/libs/guessit/__main__.py b/libs/guessit/__main__.py index 79c26617b..fad196d6b 100644 --- a/libs/guessit/__main__.py +++ b/libs/guessit/__main__.py @@ -17,7 +17,13 @@ from rebulk.__version__ import __version__ as __rebulk_version__ from guessit import api from guessit.__version__ import __version__ from guessit.jsonutils import GuessitEncoder -from guessit.options import argument_parser, parse_options, load_config +from guessit.options import argument_parser, parse_options, load_config, merge_options + + +try: + from collections import OrderedDict +except ImportError: # pragma: no-cover + from ordereddict import OrderedDict # pylint:disable=import-error def guess_filename(filename, options): @@ -45,7 +51,7 @@ def guess_filename(filename, options): import yaml from guessit import yamlutils - ystr = yaml.dump({filename: dict(guess)}, Dumper=yamlutils.CustomDumper, default_flow_style=False, + ystr = yaml.dump({filename: OrderedDict(guess)}, Dumper=yamlutils.CustomDumper, default_flow_style=False, allow_unicode=True) i = 0 for yline in ystr.splitlines(): @@ -91,9 +97,9 @@ def display_properties(options): print(4 * ' ' + '[!] %s' % (property_value,)) -def main(args=None): # pylint:disable=too-many-branches +def fix_argv_encoding(): """ - Main function for entry point + Fix encoding of sys.argv on windows Python 2 """ if six.PY2 and os.name == 'nt': # pragma: no cover # see http://bugs.python.org/issue2128 @@ -102,11 +108,21 @@ def main(args=None): # pylint:disable=too-many-branches for i, j in enumerate(sys.argv): sys.argv[i] = j.decode(locale.getpreferredencoding()) + +def main(args=None): # pylint:disable=too-many-branches + """ + Main function for entry point + """ + fix_argv_encoding() + if args is None: # pragma: no cover options = parse_options() else: options = parse_options(args) - options = load_config(options) + + config = load_config(options) + options = merge_options(config, options) + if options.get('verbose'): logging.basicConfig(stream=sys.stdout, format='%(message)s') logging.getLogger().setLevel(logging.DEBUG) @@ -126,7 +142,7 @@ def main(args=None): # pylint:disable=too-many-branches if options.get('yaml'): try: - import yaml # pylint:disable=unused-variable + import yaml # pylint:disable=unused-variable,unused-import except ImportError: # pragma: no cover del options['yaml'] print('PyYAML is not installed. \'--yaml\' option will be ignored ...', file=sys.stderr) diff --git a/libs/guessit/__version__.py b/libs/guessit/__version__.py index 703c455a8..e505897bb 100644 --- a/libs/guessit/__version__.py +++ b/libs/guessit/__version__.py @@ -4,4 +4,4 @@ Version module """ # pragma: no cover -__version__ = '2.1.4' +__version__ = '3.1.1' diff --git a/libs/guessit/api.py b/libs/guessit/api.py index 3194af88a..8e306340b 100644 --- a/libs/guessit/api.py +++ b/libs/guessit/api.py @@ -3,26 +3,28 @@ """ API functions that can be used by external software """ + try: from collections import OrderedDict except ImportError: # pragma: no-cover from ordereddict import OrderedDict # pylint:disable=import-error +import os import traceback import six - from rebulk.introspector import introspect -from .rules import rebulk_builder -from .options import parse_options from .__version__ import __version__ +from .options import parse_options, load_config, merge_options +from .rules import rebulk_builder class GuessitException(Exception): """ Exception raised when guessit fails to perform a guess because of an internal error. """ + def __init__(self, string, options): super(GuessitException, self).__init__("An internal error has occured in guessit.\n" "===================== Guessit Exception Report =====================\n" @@ -41,12 +43,27 @@ class GuessitException(Exception): self.options = options +def configure(options=None, rules_builder=rebulk_builder, force=False): + """ + Load configuration files and initialize rebulk rules if required. + + :param options: + :type options: dict + :param rules_builder: + :type rules_builder: + :param force: + :type force: bool + :return: + """ + default_api.configure(options, rules_builder=rules_builder, force=force) + + def guessit(string, options=None): """ Retrieves all matches from string as a dict :param string: the filename or release name :type string: str - :param options: the filename or release name + :param options: :type options: str|dict :return: :rtype: @@ -58,65 +75,138 @@ def properties(options=None): """ Retrieves all properties with possible values that can be guessed :param options: - :type options: + :type options: str|dict :return: :rtype: """ return default_api.properties(options) +def suggested_expected(titles, options=None): + """ + Return a list of suggested titles to be used as `expected_title` based on the list of titles + :param titles: the filename or release name + :type titles: list|set|dict + :param options: + :type options: str|dict + :return: + :rtype: list of str + """ + return default_api.suggested_expected(titles, options) + + class GuessItApi(object): """ An api class that can be configured with custom Rebulk configuration. """ - def __init__(self, rebulk): - """ - :param rebulk: Rebulk instance to use. - :type rebulk: Rebulk - :return: - :rtype: - """ - self.rebulk = rebulk + def __init__(self): + """Default constructor.""" + self.rebulk = None + self.config = None + self.load_config_options = None + self.advanced_config = None - @staticmethod - def _fix_option_encoding(value): + @classmethod + def _fix_encoding(cls, value): if isinstance(value, list): - return [GuessItApi._fix_option_encoding(item) for item in value] + return [cls._fix_encoding(item) for item in value] + if isinstance(value, dict): + return {cls._fix_encoding(k): cls._fix_encoding(v) for k, v in value.items()} if six.PY2 and isinstance(value, six.text_type): - return value.encode("utf-8") + return value.encode('utf-8') if six.PY3 and isinstance(value, six.binary_type): return value.decode('ascii') return value - def guessit(self, string, options=None): + @classmethod + def _has_same_properties(cls, dic1, dic2, values): + for value in values: + if dic1.get(value) != dic2.get(value): + return False + return True + + def configure(self, options=None, rules_builder=rebulk_builder, force=False, sanitize_options=True): + """ + Load configuration files and initialize rebulk rules if required. + + :param options: + :type options: str|dict + :param rules_builder: + :type rules_builder: + :param force: + :type force: bool + :return: + :rtype: dict + """ + if sanitize_options: + options = parse_options(options, True) + options = self._fix_encoding(options) + + if self.config is None or self.load_config_options is None or force or \ + not self._has_same_properties(self.load_config_options, + options, + ['config', 'no_user_config', 'no_default_config']): + config = load_config(options) + config = self._fix_encoding(config) + self.load_config_options = options + else: + config = self.config + + advanced_config = merge_options(config.get('advanced_config'), options.get('advanced_config')) + + should_build_rebulk = force or not self.rebulk or not self.advanced_config or \ + self.advanced_config != advanced_config + + if should_build_rebulk: + self.advanced_config = advanced_config + self.rebulk = rules_builder(advanced_config) + + self.config = config + return self.config + + def guessit(self, string, options=None): # pylint: disable=too-many-branches """ Retrieves all matches from string as a dict :param string: the filename or release name - :type string: str - :param options: the filename or release name + :type string: str|Path + :param options: :type options: str|dict :return: :rtype: """ try: + from pathlib import Path + if isinstance(string, Path): + try: + # Handle path-like object + string = os.fspath(string) + except AttributeError: + string = str(string) + except ImportError: + pass + + try: options = parse_options(options, True) + options = self._fix_encoding(options) + config = self.configure(options, sanitize_options=False) + options = merge_options(config, options) result_decode = False result_encode = False - fixed_options = {} - for (key, value) in options.items(): - key = GuessItApi._fix_option_encoding(key) - value = GuessItApi._fix_option_encoding(value) - fixed_options[key] = value - options = fixed_options - - if six.PY2 and isinstance(string, six.text_type): - string = string.encode("utf-8") - result_decode = True - if six.PY3 and isinstance(string, six.binary_type): - string = string.decode('ascii') - result_encode = True + if six.PY2: + if isinstance(string, six.text_type): + string = string.encode("utf-8") + result_decode = True + elif isinstance(string, six.binary_type): + string = six.binary_type(string) + if six.PY3: + if isinstance(string, six.binary_type): + string = string.decode('ascii') + result_encode = True + elif isinstance(string, six.text_type): + string = six.text_type(string) + matches = self.rebulk.matches(string, options) if result_decode: for match in matches: @@ -139,6 +229,10 @@ class GuessItApi(object): :return: :rtype: """ + options = parse_options(options, True) + options = self._fix_encoding(options) + config = self.configure(options, sanitize_options=False) + options = merge_options(config, options) unordered = introspect(self.rebulk, options).properties ordered = OrderedDict() for k in sorted(unordered.keys(), key=six.text_type): @@ -147,5 +241,23 @@ class GuessItApi(object): ordered = self.rebulk.customize_properties(ordered) return ordered + def suggested_expected(self, titles, options=None): + """ + Return a list of suggested titles to be used as `expected_title` based on the list of titles + :param titles: the filename or release name + :type titles: list|set|dict + :param options: + :type options: str|dict + :return: + :rtype: list of str + """ + suggested = [] + for title in titles: + guess = self.guessit(title, options) + if len(guess) != 2 or 'title' not in guess: + suggested.append(title) + + return suggested + -default_api = GuessItApi(rebulk_builder()) +default_api = GuessItApi() diff --git a/libs/guessit/backports.py b/libs/guessit/backports.py index 3e94e27ad..c149a6b5d 100644 --- a/libs/guessit/backports.py +++ b/libs/guessit/backports.py @@ -4,7 +4,7 @@ Backports """ # pragma: no-cover -# pylint: disabled +# pylint: skip-file def cmp_to_key(mycmp): """functools.cmp_to_key backport""" diff --git a/libs/guessit/config/options.json b/libs/guessit/config/options.json index 11b477481..da7c70306 100644 --- a/libs/guessit/config/options.json +++ b/libs/guessit/config/options.json @@ -1,5 +1,586 @@ { "expected_title": [ - "OSS 117" - ] -}
\ No newline at end of file + "OSS 117", + "This is Us" + ], + "allowed_countries": [ + "au", + "gb", + "us" + ], + "allowed_languages": [ + "ca", + "cs", + "de", + "en", + "es", + "fr", + "he", + "hi", + "hu", + "it", + "ja", + "ko", + "mul", + "nl", + "no", + "pl", + "pt", + "ro", + "ru", + "sv", + "te", + "uk", + "und" + ], + "advanced_config": { + "common_words": [ + "ca", + "cat", + "de", + "he", + "it", + "no", + "por", + "rum", + "se", + "st", + "sub" + ], + "groups": { + "starting": "([{", + "ending": ")]}" + }, + "audio_codec": { + "audio_channels": { + "1.0": [ + "1ch", + "mono" + ], + "2.0": [ + "2ch", + "stereo", + "re:(2[\\W_]0(?:ch)?)(?=[^\\d]|$)" + ], + "5.1": [ + "5ch", + "6ch", + "re:(5[\\W_][01](?:ch)?)(?=[^\\d]|$)", + "re:(6[\\W_]0(?:ch)?)(?=[^\\d]|$)" + ], + "7.1": [ + "7ch", + "8ch", + "re:(7[\\W_][01](?:ch)?)(?=[^\\d]|$)" + ] + } + }, + "container": { + "subtitles": [ + "srt", + "idx", + "sub", + "ssa", + "ass" + ], + "info": [ + "nfo" + ], + "videos": [ + "3g2", + "3gp", + "3gp2", + "asf", + "avi", + "divx", + "flv", + "iso", + "m4v", + "mk2", + "mk3d", + "mka", + "mkv", + "mov", + "mp4", + "mp4a", + "mpeg", + "mpg", + "ogg", + "ogm", + "ogv", + "qt", + "ra", + "ram", + "rm", + "ts", + "vob", + "wav", + "webm", + "wma", + "wmv" + ], + "torrent": [ + "torrent" + ], + "nzb": [ + "nzb" + ] + }, + "country": { + "synonyms": { + "ES": [ + "españa" + ], + "GB": [ + "UK" + ], + "BR": [ + "brazilian", + "bra" + ], + "CA": [ + "québec", + "quebec", + "qc" + ], + "MX": [ + "Latinoamérica", + "latin america" + ] + } + }, + "episodes": { + "season_max_range": 100, + "episode_max_range": 100, + "max_range_gap": 1, + "season_markers": [ + "s" + ], + "season_ep_markers": [ + "x" + ], + "disc_markers": [ + "d" + ], + "episode_markers": [ + "xe", + "ex", + "ep", + "e", + "x" + ], + "range_separators": [ + "-", + "~", + "to", + "a" + ], + "discrete_separators": [ + "+", + "&", + "and", + "et" + ], + "season_words": [ + "season", + "saison", + "seizoen", + "seasons", + "saisons", + "tem", + "temp", + "temporada", + "temporadas", + "stagione" + ], + "episode_words": [ + "episode", + "episodes", + "eps", + "ep", + "episodio", + "episodios", + "capitulo", + "capitulos" + ], + "of_words": [ + "of", + "sur" + ], + "all_words": [ + "All" + ] + }, + "language": { + "synonyms": { + "ell": [ + "gr", + "greek" + ], + "spa": [ + "esp", + "español", + "espanol" + ], + "fra": [ + "français", + "vf", + "vff", + "vfi", + "vfq" + ], + "swe": [ + "se" + ], + "por_BR": [ + "po", + "pb", + "pob", + "ptbr", + "br", + "brazilian" + ], + "deu_CH": [ + "swissgerman", + "swiss german" + ], + "nld_BE": [ + "flemish" + ], + "cat": [ + "català", + "castellano", + "espanol castellano", + "español castellano" + ], + "ces": [ + "cz" + ], + "ukr": [ + "ua" + ], + "zho": [ + "cn" + ], + "jpn": [ + "jp" + ], + "hrv": [ + "scr" + ], + "mul": [ + "multi", + "dl" + ] + }, + "subtitle_affixes": [ + "sub", + "subs", + "esub", + "esubs", + "subbed", + "custom subbed", + "custom subs", + "custom sub", + "customsubbed", + "customsubs", + "customsub", + "soft subtitles", + "soft subs" + ], + "subtitle_prefixes": [ + "st", + "vost", + "subforced", + "fansub", + "hardsub", + "legenda", + "legendas", + "legendado", + "subtitulado", + "soft", + "subtitles" + ], + "subtitle_suffixes": [ + "subforced", + "fansub", + "hardsub" + ], + "language_affixes": [ + "dublado", + "dubbed", + "dub" + ], + "language_prefixes": [ + "true" + ], + "language_suffixes": [ + "audio" + ], + "weak_affixes": [ + "v", + "audio", + "true" + ] + }, + "part": { + "prefixes": [ + "pt", + "part" + ] + }, + "release_group": { + "forbidden_names": [ + "bonus", + "by", + "for", + "par", + "pour", + "rip" + ], + "ignored_seps": "[]{}()" + }, + "screen_size": { + "frame_rates": [ + "23.976", + "24", + "25", + "29.970", + "30", + "48", + "50", + "60", + "120" + ], + "min_ar": 1.333, + "max_ar": 1.898, + "interlaced": [ + "360", + "480", + "576", + "900", + "1080" + ], + "progressive": [ + "360", + "480", + "540", + "576", + "900", + "1080", + "368", + "720", + "1440", + "2160", + "4320" + ] + }, + "website": { + "safe_tlds": [ + "com", + "net", + "org" + ], + "safe_subdomains": [ + "www" + ], + "safe_prefixes": [ + "co", + "com", + "net", + "org" + ], + "prefixes": [ + "from" + ] + }, + "streaming_service": { + "A&E": [ + "AE", + "A&E" + ], + "ABC": "AMBC", + "ABC Australia": "AUBC", + "Al Jazeera English": "AJAZ", + "AMC": "AMC", + "Amazon Prime": [ + "AMZN", + "Amazon", + "re:Amazon-?Prime" + ], + "Adult Swim": [ + "AS", + "re:Adult-?Swim" + ], + "America's Test Kitchen": "ATK", + "Animal Planet": "ANPL", + "AnimeLab": "ANLB", + "AOL": "AOL", + "ARD": "ARD", + "BBC iPlayer": [ + "iP", + "re:BBC-?iPlayer" + ], + "BravoTV": "BRAV", + "Canal+": "CNLP", + "Cartoon Network": "CN", + "CBC": "CBC", + "CBS": "CBS", + "CNBC": "CNBC", + "Comedy Central": [ + "CC", + "re:Comedy-?Central" + ], + "Channel 4": "4OD", + "CHRGD": "CHGD", + "Cinemax": "CMAX", + "Country Music Television": "CMT", + "Comedians in Cars Getting Coffee": "CCGC", + "Crunchy Roll": [ + "CR", + "re:Crunchy-?Roll" + ], + "Crackle": "CRKL", + "CSpan": "CSPN", + "CTV": "CTV", + "CuriosityStream": "CUR", + "CWSeed": "CWS", + "Daisuki": "DSKI", + "DC Universe": "DCU", + "Deadhouse Films": "DHF", + "DramaFever": [ + "DF", + "DramaFever" + ], + "Digiturk Diledigin Yerde": "DDY", + "Discovery": [ + "DISC", + "Discovery" + ], + "Disney": [ + "DSNY", + "Disney" + ], + "DIY Network": "DIY", + "Doc Club": "DOCC", + "DPlay": "DPLY", + "E!": "ETV", + "ePix": "EPIX", + "El Trece": "ETTV", + "ESPN": "ESPN", + "Esquire": "ESQ", + "Family": "FAM", + "Family Jr": "FJR", + "Food Network": "FOOD", + "Fox": "FOX", + "Freeform": "FREE", + "FYI Network": "FYI", + "Global": "GLBL", + "GloboSat Play": "GLOB", + "Hallmark": "HLMK", + "HBO Go": [ + "HBO", + "re:HBO-?Go" + ], + "HGTV": "HGTV", + "History": [ + "HIST", + "History" + ], + "Hulu": "HULU", + "Investigation Discovery": "ID", + "IFC": "IFC", + "iTunes": "iTunes", + "ITV": "ITV", + "Knowledge Network": "KNOW", + "Lifetime": "LIFE", + "Motor Trend OnDemand": "MTOD", + "MBC": [ + "MBC", + "MBCVOD" + ], + "MSNBC": "MNBC", + "MTV": "MTV", + "National Geographic": [ + "NATG", + "re:National-?Geographic" + ], + "NBA TV": [ + "NBA", + "re:NBA-?TV" + ], + "NBC": "NBC", + "Netflix": [ + "NF", + "Netflix" + ], + "NFL": "NFL", + "NFL Now": "NFLN", + "NHL GameCenter": "GC", + "Nickelodeon": [ + "NICK", + "Nickelodeon" + ], + "Norsk Rikskringkasting": "NRK", + "OnDemandKorea": [ + "ODK", + "OnDemandKorea" + ], + "PBS": "PBS", + "PBS Kids": "PBSK", + "Playstation Network": "PSN", + "Pluzz": "PLUZ", + "RTE One": "RTE", + "SBS (AU)": "SBS", + "SeeSo": [ + "SESO", + "SeeSo" + ], + "Shomi": "SHMI", + "Spike": "SPIK", + "Spike TV": [ + "SPKE", + "re:Spike-?TV" + ], + "Sportsnet": "SNET", + "Sprout": "SPRT", + "Stan": "STAN", + "Starz": "STZ", + "Sveriges Television": "SVT", + "SwearNet": "SWER", + "Syfy": "SYFY", + "TBS": "TBS", + "TFou": "TFOU", + "The CW": [ + "CW", + "re:The-?CW" + ], + "TLC": "TLC", + "TubiTV": "TUBI", + "TV3 Ireland": "TV3", + "TV4 Sweeden": "TV4", + "TVING": "TVING", + "TV Land": [ + "TVL", + "re:TV-?Land" + ], + "UFC": "UFC", + "UKTV": "UKTV", + "Univision": "UNIV", + "USA Network": "USAN", + "Velocity": "VLCT", + "VH1": "VH1", + "Viceland": "VICE", + "Viki": "VIKI", + "Vimeo": "VMEO", + "VRV": "VRV", + "W Network": "WNET", + "WatchMe": "WME", + "WWE Network": "WWEN", + "Xbox Video": "XBOX", + "Yahoo": "YHOO", + "YouTube Red": "RED", + "ZDF": "ZDF" + } + } +} diff --git a/libs/guessit/jsonutils.py b/libs/guessit/jsonutils.py index 7d6ff7055..0a0ac3a6e 100644 --- a/libs/guessit/jsonutils.py +++ b/libs/guessit/jsonutils.py @@ -4,14 +4,10 @@ JSON Utils """ import json -try: - from collections import OrderedDict -except ImportError: # pragma: no-cover - from ordereddict import OrderedDict # pylint:disable=import-error +from six import text_type from rebulk.match import Match - class GuessitEncoder(json.JSONEncoder): """ JSON Encoder for guessit response @@ -19,14 +15,8 @@ class GuessitEncoder(json.JSONEncoder): def default(self, o): # pylint:disable=method-hidden if isinstance(o, Match): - ret = OrderedDict() - ret['value'] = o.value - if o.raw: - ret['raw'] = o.raw - ret['start'] = o.start - ret['end'] = o.end - return ret - elif hasattr(o, 'name'): # Babelfish languages/countries long name - return str(o.name) - else: # pragma: no cover - return str(o) + return o.advanced + if hasattr(o, 'name'): # Babelfish languages/countries long name + return text_type(o.name) + # pragma: no cover + return text_type(o) diff --git a/libs/guessit/monkeypatch.py b/libs/guessit/monkeypatch.py new file mode 100644 index 000000000..33e7c46ee --- /dev/null +++ b/libs/guessit/monkeypatch.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Monkeypatch initialisation functions +""" + +try: + from collections import OrderedDict +except ImportError: # pragma: no-cover + from ordereddict import OrderedDict # pylint:disable=import-error + +from rebulk.match import Match + + +def monkeypatch_rebulk(): + """Monkeypatch rebulk classes""" + + @property + def match_advanced(self): + """ + Build advanced dict from match + :param self: + :return: + """ + + ret = OrderedDict() + ret['value'] = self.value + if self.raw: + ret['raw'] = self.raw + ret['start'] = self.start + ret['end'] = self.end + return ret + + Match.advanced = match_advanced diff --git a/libs/guessit/options.py b/libs/guessit/options.py index fcf39e8a7..8fa6825cc 100644 --- a/libs/guessit/options.py +++ b/libs/guessit/options.py @@ -3,10 +3,12 @@ """ Options """ +import copy import json import os import pkgutil import shlex + from argparse import ArgumentParser import six @@ -42,6 +44,10 @@ def build_argument_parser(): help='Expected title to parse (can be used multiple times)') naming_opts.add_argument('-G', '--expected-group', action='append', dest='expected_group', default=None, help='Expected release group (can be used multiple times)') + naming_opts.add_argument('--includes', action='append', dest='includes', default=None, + help='List of properties to be detected') + naming_opts.add_argument('--excludes', action='append', dest='excludes', default=None, + help='List of properties to be ignored') input_opts = opts.add_argument_group("Input") input_opts.add_argument('-f', '--input-file', dest='input_file', default=None, @@ -65,14 +71,20 @@ def build_argument_parser(): conf_opts = opts.add_argument_group("Configuration") conf_opts.add_argument('-c', '--config', dest='config', action='append', default=None, - help='Filepath to the configuration file. Configuration contains the same options as ' - 'those command line options, but option names have "-" characters replaced with "_". ' - 'If not defined, guessit tries to read a configuration default configuration file at ' - '~/.guessit/options.(json|yml|yaml) and ~/.config/guessit/options.(json|yml|yaml). ' - 'Set to "false" to disable default configuration file loading.') - conf_opts.add_argument('--no-embedded-config', dest='no_embedded_config', action='store_true', + help='Filepath to configuration file. Configuration file contains the same ' + 'options as those from command line options, but option names have "-" characters ' + 'replaced with "_". This configuration will be merged with default and user ' + 'configuration files.') + conf_opts.add_argument('--no-user-config', dest='no_user_config', action='store_true', + default=None, + help='Disable user configuration. If not defined, guessit tries to read configuration files ' + 'at ~/.guessit/options.(json|yml|yaml) and ~/.config/guessit/options.(json|yml|yaml)') + conf_opts.add_argument('--no-default-config', dest='no_default_config', action='store_true', default=None, - help='Disable default configuration.') + help='Disable default configuration. This should be done only if you are providing a full ' + 'configuration through user configuration or --config option. If no "advanced_config" ' + 'is provided by another configuration file, it will still be loaded from default ' + 'configuration.') information_opts = opts.add_argument_group("Information") information_opts.add_argument('-p', '--properties', dest='properties', action='store_true', default=None, @@ -92,7 +104,7 @@ def parse_options(options=None, api=False): :param options: :type options: :param api - :type boolean + :type api: boolean :return: :rtype: """ @@ -116,93 +128,113 @@ class ConfigurationException(Exception): """ Exception related to configuration file. """ - pass + pass # pylint:disable=unnecessary-pass def load_config(options): """ - Load configuration from configuration file, if defined. + Load options from configuration files, if defined and present. :param options: :type options: :return: :rtype: """ - config_files_enabled = True - custom_config_files = None - if options.get('config') is not None: - custom_config_files = options.get('config') - if not custom_config_files \ - or not custom_config_files[0] \ - or custom_config_files[0].lower() in ['0', 'no', 'false', 'disabled']: - config_files_enabled = False - configurations = [] - if config_files_enabled: + + if not options.get('no_default_config'): + default_options_data = pkgutil.get_data('guessit', 'config/options.json').decode('utf-8') + default_options = json.loads(default_options_data) + configurations.append(default_options) + + config_files = [] + + if not options.get('no_user_config'): home_directory = os.path.expanduser("~") cwd = os.getcwd() yaml_supported = False try: - import yaml # pylint: disable=unused-variable + import yaml # pylint:disable=unused-variable,unused-import yaml_supported = True except ImportError: pass - config_file_locations = get_config_file_locations(home_directory, cwd, yaml_supported) - config_files = [f for f in config_file_locations if os.path.exists(f)] - if custom_config_files: - config_files = config_files + custom_config_files + config_file_locations = get_options_file_locations(home_directory, cwd, yaml_supported) + config_files = [f for f in config_file_locations if os.path.exists(f)] - for config_file in config_files: - config_file_options = load_config_file(config_file) - if config_file_options: - configurations.append(config_file_options) + custom_config_files = options.get('config') + if custom_config_files: + config_files = config_files + custom_config_files - if not options.get('no_embedded_config'): - embedded_options_data = pkgutil.get_data('guessit', 'config/options.json').decode("utf-8") - embedded_options = json.loads(embedded_options_data) - configurations.append(embedded_options) + for config_file in config_files: + config_file_options = load_config_file(config_file) + if config_file_options: + configurations.append(config_file_options) + config = {} if configurations: - configurations.append(options) - return merge_configurations(*configurations) + config = merge_options(*configurations) - return options + if 'advanced_config' not in config: + # Guessit doesn't work without advanced_config, so we use default if no configuration files provides it. + default_options_data = pkgutil.get_data('guessit', 'config/options.json').decode('utf-8') + default_options = json.loads(default_options_data) + config['advanced_config'] = default_options['advanced_config'] + return config -def merge_configurations(*configurations): + +def merge_options(*options): """ - Merge configurations into a single options dict. - :param configurations: - :type configurations: + Merge options into a single options dict. + :param options: + :type options: :return: :rtype: """ merged = {} + if options: + if options[0]: + merged.update(copy.deepcopy(options[0])) + + for options in options[1:]: + if options: + pristine = options.get('pristine') + + if pristine is True: + merged = {} + elif pristine: + for to_reset in pristine: + if to_reset in merged: + del merged[to_reset] - for options in configurations: - pristine = options.get('pristine') - - if pristine: - if pristine is True: - merged = {} - else: - for to_reset in pristine: - if to_reset in merged: - del merged[to_reset] - - for (option, value) in options.items(): - if value is not None and option != 'pristine': - if option in merged.keys() and isinstance(merged[option], list): - merged[option].extend(value) - elif isinstance(value, list): - merged[option] = list(value) - else: - merged[option] = value + for (option, value) in options.items(): + merge_option_value(option, value, merged) return merged +def merge_option_value(option, value, merged): + """ + Merge option value + :param option: + :param value: + :param merged: + :return: + """ + if value is not None and option != 'pristine': + if option in merged.keys() and isinstance(merged[option], list): + for val in value: + if val not in merged[option]: + merged[option].append(val) + elif option in merged.keys() and isinstance(merged[option], dict): + merged[option] = merge_options(merged[option], value) + elif isinstance(value, list): + merged[option] = list(value) + else: + merged[option] = value + + def load_config_file(filepath): """ Load a configuration as an options dict. @@ -220,17 +252,24 @@ def load_config_file(filepath): try: import yaml with open(filepath) as config_file_data: - return yaml.load(config_file_data) + return yaml.load(config_file_data, yaml.SafeLoader) except ImportError: # pragma: no cover raise ConfigurationException('Configuration file extension is not supported. ' 'PyYAML should be installed to support "%s" file' % ( filepath,)) + + try: + # Try to load input as JSON + return json.loads(filepath) + except: # pylint: disable=bare-except + pass + raise ConfigurationException('Configuration file extension is not supported for "%s" file.' % (filepath,)) -def get_config_file_locations(homedir, cwd, yaml_supported=False): +def get_options_file_locations(homedir, cwd, yaml_supported=False): """ - Get all possible locations for configuration file. + Get all possible locations for options file. :param homedir: user home directory :type homedir: basestring :param cwd: current working directory diff --git a/libs/guessit/rules/__init__.py b/libs/guessit/rules/__init__.py index d01bc6b28..f16bc4e0f 100644 --- a/libs/guessit/rules/__init__.py +++ b/libs/guessit/rules/__init__.py @@ -10,7 +10,7 @@ from .markers.groups import groups from .properties.episodes import episodes from .properties.container import container -from .properties.format import format_ +from .properties.source import source from .properties.video_codec import video_codec from .properties.audio_codec import audio_codec from .properties.screen_size import screen_size @@ -24,6 +24,7 @@ from .properties.release_group import release_group from .properties.streaming_service import streaming_service from .properties.other import other from .properties.size import size +from .properties.bit_rate import bit_rate from .properties.edition import edition from .properties.cds import cds from .properties.bonus import bonus @@ -36,44 +37,50 @@ from .properties.type import type_ from .processors import processors -def rebulk_builder(): +def rebulk_builder(config): """ Default builder for main Rebulk object used by api. :return: Main Rebulk object :rtype: Rebulk """ + def _config(name): + return config.get(name, {}) + rebulk = Rebulk() - rebulk.rebulk(path()) - rebulk.rebulk(groups()) - - rebulk.rebulk(episodes()) - rebulk.rebulk(container()) - rebulk.rebulk(format_()) - rebulk.rebulk(video_codec()) - rebulk.rebulk(audio_codec()) - rebulk.rebulk(screen_size()) - rebulk.rebulk(website()) - rebulk.rebulk(date()) - rebulk.rebulk(title()) - rebulk.rebulk(episode_title()) - rebulk.rebulk(language()) - rebulk.rebulk(country()) - rebulk.rebulk(release_group()) - rebulk.rebulk(streaming_service()) - rebulk.rebulk(other()) - rebulk.rebulk(size()) - rebulk.rebulk(edition()) - rebulk.rebulk(cds()) - rebulk.rebulk(bonus()) - rebulk.rebulk(film()) - rebulk.rebulk(part()) - rebulk.rebulk(crc()) - - rebulk.rebulk(processors()) - - rebulk.rebulk(mimetype()) - rebulk.rebulk(type_()) + common_words = frozenset(_config('common_words')) + + rebulk.rebulk(path(_config('path'))) + rebulk.rebulk(groups(_config('groups'))) + + rebulk.rebulk(episodes(_config('episodes'))) + rebulk.rebulk(container(_config('container'))) + rebulk.rebulk(source(_config('source'))) + rebulk.rebulk(video_codec(_config('video_codec'))) + rebulk.rebulk(audio_codec(_config('audio_codec'))) + rebulk.rebulk(screen_size(_config('screen_size'))) + rebulk.rebulk(website(_config('website'))) + rebulk.rebulk(date(_config('date'))) + rebulk.rebulk(title(_config('title'))) + rebulk.rebulk(episode_title(_config('episode_title'))) + rebulk.rebulk(language(_config('language'), common_words)) + rebulk.rebulk(country(_config('country'), common_words)) + rebulk.rebulk(release_group(_config('release_group'))) + rebulk.rebulk(streaming_service(_config('streaming_service'))) + rebulk.rebulk(other(_config('other'))) + rebulk.rebulk(size(_config('size'))) + rebulk.rebulk(bit_rate(_config('bit_rate'))) + rebulk.rebulk(edition(_config('edition'))) + rebulk.rebulk(cds(_config('cds'))) + rebulk.rebulk(bonus(_config('bonus'))) + rebulk.rebulk(film(_config('film'))) + rebulk.rebulk(part(_config('part'))) + rebulk.rebulk(crc(_config('crc'))) + + rebulk.rebulk(processors(_config('processors'))) + + rebulk.rebulk(mimetype(_config('mimetype'))) + rebulk.rebulk(type_(_config('type'))) def customize_properties(properties): """ diff --git a/libs/guessit/rules/common/comparators.py b/libs/guessit/rules/common/comparators.py index ee104ba68..f46f0c119 100644 --- a/libs/guessit/rules/common/comparators.py +++ b/libs/guessit/rules/common/comparators.py @@ -13,9 +13,12 @@ def marker_comparator_predicate(match): """ Match predicate used in comparator """ - return not match.private and \ - match.name not in ['proper_count', 'title', 'episode_title', 'alternative_title'] and \ - not (match.name == 'container' and 'extension' in match.tags) + return ( + not match.private + and match.name not in ('proper_count', 'title') + and not (match.name == 'container' and 'extension' in match.tags) + and not (match.name == 'other' and match.value == 'Rip') + ) def marker_weight(matches, marker, predicate): @@ -50,9 +53,8 @@ def marker_comparator(matches, markers, predicate): matches_count = marker_weight(matches, marker2, predicate) - marker_weight(matches, marker1, predicate) if matches_count: return matches_count - len_diff = len(marker2) - len(marker1) - if len_diff: - return len_diff + + # give preference to rightmost path return markers.index(marker2) - markers.index(marker1) return comparator diff --git a/libs/guessit/rules/common/date.py b/libs/guessit/rules/common/date.py index d6fb523a1..e513af9f5 100644 --- a/libs/guessit/rules/common/date.py +++ b/libs/guessit/rules/common/date.py @@ -42,7 +42,7 @@ def _is_int(string): return False -def _guess_day_first_parameter(groups): +def _guess_day_first_parameter(groups): # pylint:disable=inconsistent-return-statements """ If day_first is not defined, use some heuristic to fix it. It helps to solve issues with python dateutils 2.5.3 parser changes. @@ -57,17 +57,17 @@ def _guess_day_first_parameter(groups): if _is_int(groups[0]) and valid_year(int(groups[0][:4])): return False # If match ends with a long year, the day_first is forced to true. - elif _is_int(groups[-1]) and valid_year(int(groups[-1][-4:])): + if _is_int(groups[-1]) and valid_year(int(groups[-1][-4:])): return True # If match starts with a short year, then day_first is force to false. - elif _is_int(groups[0]) and int(groups[0][:2]) > 31: + if _is_int(groups[0]) and int(groups[0][:2]) > 31: return False # If match ends with a short year, then day_first is force to true. - elif _is_int(groups[-1]) and int(groups[-1][-2:]) > 31: + if _is_int(groups[-1]) and int(groups[-1][-2:]) > 31: return True -def search_date(string, year_first=None, day_first=None): +def search_date(string, year_first=None, day_first=None): # pylint:disable=inconsistent-return-statements """Looks for date patterns, and if found return the date and group span. Assumes there are sentinels at the beginning and end of the string that diff --git a/libs/guessit/rules/common/formatters.py b/libs/guessit/rules/common/formatters.py index 6bd09b159..2a64dee9f 100644 --- a/libs/guessit/rules/common/formatters.py +++ b/libs/guessit/rules/common/formatters.py @@ -25,7 +25,7 @@ def _potential_before(i, input_string): :return: :rtype: bool """ - return i - 2 >= 0 and input_string[i] == input_string[i - 2] and input_string[i - 1] not in seps + return i - 1 >= 0 and input_string[i] in seps and input_string[i - 2] in seps and input_string[i - 1] not in seps def _potential_after(i, input_string): diff --git a/libs/guessit/rules/common/pattern.py b/libs/guessit/rules/common/pattern.py new file mode 100644 index 000000000..5f560f2c9 --- /dev/null +++ b/libs/guessit/rules/common/pattern.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Pattern utility functions +""" + + +def is_disabled(context, name): + """Whether a specific pattern is disabled. + + The context object might define an inclusion list (includes) or an exclusion list (excludes) + A pattern is considered disabled if it's found in the exclusion list or + it's not found in the inclusion list and the inclusion list is not empty or not defined. + + :param context: + :param name: + :return: + """ + if not context: + return False + + excludes = context.get('excludes') + if excludes and name in excludes: + return True + + includes = context.get('includes') + return includes and name not in includes diff --git a/libs/guessit/rules/common/quantity.py b/libs/guessit/rules/common/quantity.py new file mode 100644 index 000000000..bbd41fbb9 --- /dev/null +++ b/libs/guessit/rules/common/quantity.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Quantities: Size +""" +import re +from abc import abstractmethod + +import six + +from ..common import seps + + +class Quantity(object): + """ + Represent a quantity object with magnitude and units. + """ + + parser_re = re.compile(r'(?P<magnitude>\d+(?:[.]\d+)?)(?P<units>[^\d]+)?') + + def __init__(self, magnitude, units): + self.magnitude = magnitude + self.units = units + + @classmethod + @abstractmethod + def parse_units(cls, value): + """ + Parse a string to a proper unit notation. + """ + raise NotImplementedError + + @classmethod + def fromstring(cls, string): + """ + Parse the string into a quantity object. + :param string: + :return: + """ + values = cls.parser_re.match(string).groupdict() + try: + magnitude = int(values['magnitude']) + except ValueError: + magnitude = float(values['magnitude']) + units = cls.parse_units(values['units']) + + return cls(magnitude, units) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + if isinstance(other, six.string_types): + return str(self) == other + if not isinstance(other, self.__class__): + return NotImplemented + return self.magnitude == other.magnitude and self.units == other.units + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return '<{0} [{1}]>'.format(self.__class__.__name__, self) + + def __str__(self): + return '{0}{1}'.format(self.magnitude, self.units) + + +class Size(Quantity): + """ + Represent size. + + e.g.: 1.1GB, 300MB + """ + + @classmethod + def parse_units(cls, value): + return value.strip(seps).upper() + + +class BitRate(Quantity): + """ + Represent bit rate. + + e.g.: 320Kbps, 1.5Mbps + """ + + @classmethod + def parse_units(cls, value): + value = value.strip(seps).capitalize() + for token in ('bits', 'bit'): + value = value.replace(token, 'bps') + + return value + + +class FrameRate(Quantity): + """ + Represent frame rate. + + e.g.: 24fps, 60fps + """ + + @classmethod + def parse_units(cls, value): + return 'fps' diff --git a/libs/guessit/rules/common/validators.py b/libs/guessit/rules/common/validators.py index 0e79b9896..0d0eb3eb7 100644 --- a/libs/guessit/rules/common/validators.py +++ b/libs/guessit/rules/common/validators.py @@ -28,7 +28,7 @@ def int_coercable(string): return False -def compose(*validators): +def and_(*validators): """ Compose validators functions :param validators: @@ -49,3 +49,26 @@ def compose(*validators): return False return True return composed + + +def or_(*validators): + """ + Compose validators functions + :param validators: + :type validators: + :return: + :rtype: + """ + def composed(string): + """ + Composed validators function + :param string: + :type string: + :return: + :rtype: + """ + for validator in validators: + if validator(string): + return True + return False + return composed diff --git a/libs/guessit/rules/common/words.py b/libs/guessit/rules/common/words.py index 8882acb3d..cccbc7d23 100644 --- a/libs/guessit/rules/common/words.py +++ b/libs/guessit/rules/common/words.py @@ -32,48 +32,3 @@ def iter_words(string): i += 1 if inside_word: yield _Word(span=(last_sep_index+1, i), value=string[last_sep_index+1:i]) - - -# list of common words which could be interpreted as properties, but which -# are far too common to be able to say they represent a property in the -# middle of a string (where they most likely carry their commmon meaning) -COMMON_WORDS = frozenset([ - # english words - 'is', 'it', 'am', 'mad', 'men', 'man', 'run', 'sin', 'st', 'to', - 'no', 'non', 'war', 'min', 'new', 'car', 'day', 'bad', 'bat', 'fan', - 'fry', 'cop', 'zen', 'gay', 'fat', 'one', 'cherokee', 'got', 'an', 'as', - 'cat', 'her', 'be', 'hat', 'sun', 'may', 'my', 'mr', 'rum', 'pi', 'bb', - 'bt', 'tv', 'aw', 'by', 'md', 'mp', 'cd', 'lt', 'gt', 'in', 'ad', 'ice', - 'ay', 'at', 'star', 'so', 'he', 'do', 'ax', 'mx', - # french words - 'bas', 'de', 'le', 'son', 'ne', 'ca', 'ce', 'et', 'que', - 'mal', 'est', 'vol', 'or', 'mon', 'se', 'je', 'tu', 'me', - 'ne', 'ma', 'va', 'au', 'lu', - # japanese words, - 'wa', 'ga', 'ao', - # spanish words - 'la', 'el', 'del', 'por', 'mar', 'al', - # italian words - 'un', - # other - 'ind', 'arw', 'ts', 'ii', 'bin', 'chan', 'ss', 'san', 'oss', 'iii', - 'vi', 'ben', 'da', 'lt', 'ch', 'sr', 'ps', 'cx', 'vo', - # new from babelfish - 'mkv', 'avi', 'dmd', 'the', 'dis', 'cut', 'stv', 'des', 'dia', 'and', - 'cab', 'sub', 'mia', 'rim', 'las', 'une', 'par', 'srt', 'ano', 'toy', - 'job', 'gag', 'reel', 'www', 'for', 'ayu', 'csi', 'ren', 'moi', 'sur', - 'fer', 'fun', 'two', 'big', 'psy', 'air', - # movie title - 'brazil', 'jordan', - # release groups - 'bs', # Bosnian - 'kz', - # countries - 'gt', 'lt', 'im', - # part/pt - 'pt', - # screener - 'scr', - # quality - 'sd', 'hr' -]) diff --git a/libs/guessit/rules/markers/groups.py b/libs/guessit/rules/markers/groups.py index bbe69d1c3..4716d15d7 100644 --- a/libs/guessit/rules/markers/groups.py +++ b/libs/guessit/rules/markers/groups.py @@ -6,17 +6,20 @@ Groups markers (...), [...] and {...} from rebulk import Rebulk -def groups(): +def groups(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ rebulk = Rebulk() rebulk.defaults(name="group", marker=True) - starting = '([{' - ending = ')]}' + starting = config['starting'] + ending = config['ending'] def mark_groups(input_string): """ diff --git a/libs/guessit/rules/markers/path.py b/libs/guessit/rules/markers/path.py index 5e487ea6b..6d993b75a 100644 --- a/libs/guessit/rules/markers/path.py +++ b/libs/guessit/rules/markers/path.py @@ -8,9 +8,12 @@ from rebulk import Rebulk from rebulk.utils import find_all -def path(): +def path(config): # pylint:disable=unused-argument """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ @@ -22,6 +25,7 @@ def path(): Functional pattern to mark path elements. :param input_string: + :param context: :return: """ ret = [] diff --git a/libs/guessit/rules/match_processors.py b/libs/guessit/rules/match_processors.py new file mode 100644 index 000000000..0b49372fe --- /dev/null +++ b/libs/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/libs/guessit/rules/processors.py b/libs/guessit/rules/processors.py index 0f21d0835..5b018140c 100644 --- a/libs/guessit/rules/processors.py +++ b/libs/guessit/rules/processors.py @@ -36,6 +36,7 @@ class EnlargeGroupMatches(CustomRule): if starting or ending: return starting, ending + return False def then(self, matches, when_response, context): starting, ending = when_response @@ -193,6 +194,23 @@ class SeasonYear(Rule): return ret +class YearSeason(Rule): + """ + If a year is found, no season found, and episode is found, create an match with season. + """ + priority = POST_PROCESS + consequence = AppendMatch + + def when(self, matches, context): + ret = [] + if not matches.named('season') and matches.named('episode'): + for year in matches.named('year'): + season = copy.copy(year) + season.name = 'season' + ret.append(season) + return ret + + class Processors(CustomRule): """ Empty rule for ordering post_processing properly. @@ -226,13 +244,16 @@ class StripSeparators(CustomRule): match.raw_end -= 1 -def processors(): +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) + RemoveAmbiguous, SeasonYear, YearSeason, Processors, StripSeparators) diff --git a/libs/guessit/rules/properties/audio_codec.py b/libs/guessit/rules/properties/audio_codec.py index 922df9289..815caff99 100644 --- a/libs/guessit/rules/properties/audio_codec.py +++ b/libs/guessit/rules/properties/audio_codec.py @@ -3,22 +3,28 @@ """ audio_codec, audio_profile and audio_channels property """ +from rebulk import Rebulk, Rule, RemoveMatch from rebulk.remodule import re -from rebulk import Rebulk, Rule, RemoveMatch from ..common import dash +from ..common.pattern import is_disabled from ..common.validators import seps_before, seps_after audio_properties = ['audio_codec', 'audio_profile', 'audio_channels'] -def audio_codec(): +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) + rebulk = Rebulk()\ + .regex_defaults(flags=re.IGNORECASE, abbreviations=[dash])\ + .string_defaults(ignore_case=True) def audio_codec_priority(match1, match2): """ @@ -36,37 +42,53 @@ def audio_codec(): return match1 return '__default__' - rebulk.defaults(name="audio_codec", conflict_solver=audio_codec_priority) + rebulk.defaults(name='audio_codec', + conflict_solver=audio_codec_priority, + disabled=lambda context: is_disabled(context, 'audio_codec')) rebulk.regex("MP3", "LAME", r"LAME(?:\d)+-?(?:\d)+", value="MP3") - rebulk.regex('Dolby', 'DolbyDigital', 'Dolby-Digital', 'DD', 'AC3D?', value='AC3') - rebulk.regex("DolbyAtmos", "Dolby-Atmos", "Atmos", value="DolbyAtmos") + rebulk.string("MP2", value="MP2") + rebulk.regex('Dolby', 'DolbyDigital', 'Dolby-Digital', 'DD', 'AC3D?', value='Dolby Digital') + rebulk.regex('Dolby-?Atmos', 'Atmos', value='Dolby Atmos') rebulk.string("AAC", value="AAC") - rebulk.string('EAC3', 'DDP', 'DD+', value="EAC3") + rebulk.string('EAC3', 'DDP', 'DD+', value='Dolby Digital Plus') rebulk.string("Flac", value="FLAC") rebulk.string("DTS", value="DTS") - rebulk.regex("True-?HD", value="TrueHD") - - rebulk.defaults(name="audio_profile") - rebulk.string("HD", value="HD", tags="DTS") - rebulk.regex("HD-?MA", value="HDMA", tags="DTS") - rebulk.string("HE", value="HE", tags="AAC") - rebulk.string("LC", value="LC", tags="AAC") - rebulk.string("HQ", value="HQ", tags="AC3") - - rebulk.defaults(name="audio_channels") - rebulk.regex(r'(7[\W_][01](?:ch)?)(?:[^\d]|$)', value='7.1', children=True) - rebulk.regex(r'(5[\W_][01](?:ch)?)(?:[^\d]|$)', value='5.1', children=True) - rebulk.regex(r'(2[\W_]0(?:ch)?)(?:[^\d]|$)', value='2.0', children=True) + rebulk.regex('DTS-?HD', 'DTS(?=-?MA)', value='DTS-HD', + conflict_solver=lambda match, other: other if other.name == 'audio_codec' else '__default__') + rebulk.regex('True-?HD', value='Dolby TrueHD') + rebulk.string('Opus', value='Opus') + rebulk.string('Vorbis', value='Vorbis') + rebulk.string('PCM', value='PCM') + rebulk.string('LPCM', value='LPCM') + + 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']) + rebulk.string('HE', value='High Efficiency', tags=['audio_profile.rule', 'AAC']) + rebulk.string('LC', value='Low Complexity', tags=['audio_profile.rule', 'AAC']) + 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(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') - rebulk.string('7ch', '8ch', value='7.1') - rebulk.string('5ch', '6ch', value='5.1') - rebulk.string('2ch', 'stereo', value='2.0') - rebulk.string('1ch', 'mono', value='1.0') - rebulk.rules(DtsRule, AacRule, Ac3Rule, AudioValidatorRule, HqConflictRule, AudioChannelsValidatorRule) + for value, items in config.get('audio_channels').items(): + for item in items: + if item.startswith('re:'): + rebulk.regex(item[3:], value=value, children=True) + else: + rebulk.string(item, value=value) + + rebulk.rules(DtsHDRule, DtsRule, AacRule, DolbyDigitalRule, AudioValidatorRule, HqConflictRule, + AudioChannelsValidatorRule) return rebulk @@ -111,25 +133,49 @@ class AudioProfileRule(Rule): super(AudioProfileRule, self).__init__() self.codec = codec + def enabled(self, context): + return not is_disabled(context, 'audio_profile') + def when(self, matches, context): - profile_list = matches.named('audio_profile', lambda match: self.codec in match.tags) + profile_list = matches.named('audio_profile', + lambda match: 'audio_profile.rule' in match.tags and + self.codec in match.tags) ret = [] for profile in profile_list: - codec = matches.previous(profile, lambda match: match.name == 'audio_codec' and match.value == self.codec) + codec = matches.at_span(profile.span, + lambda match: match.name == 'audio_codec' and + match.value == self.codec, 0) + if not codec: + codec = matches.previous(profile, + lambda match: match.name == 'audio_codec' and + match.value == self.codec) if not codec: - codec = matches.next(profile, lambda match: match.name == 'audio_codec' and match.value == self.codec) + codec = matches.next(profile, + lambda match: match.name == 'audio_codec' and + match.value == self.codec) if not codec: ret.append(profile) + if codec: + ret.extend(matches.conflicting(profile)) return ret +class DtsHDRule(AudioProfileRule): + """ + Rule to validate DTS-HD profile + """ + + def __init__(self): + super(DtsHDRule, self).__init__('DTS-HD') + + class DtsRule(AudioProfileRule): """ Rule to validate DTS profile """ def __init__(self): - super(DtsRule, self).__init__("DTS") + super(DtsRule, self).__init__('DTS') class AacRule(AudioProfileRule): @@ -138,16 +184,16 @@ class AacRule(AudioProfileRule): """ def __init__(self): - super(AacRule, self).__init__("AAC") + super(AacRule, self).__init__('AAC') -class Ac3Rule(AudioProfileRule): +class DolbyDigitalRule(AudioProfileRule): """ - Rule to validate AC3 profile + Rule to validate Dolby Digital profile """ def __init__(self): - super(Ac3Rule, self).__init__("AC3") + super(DolbyDigitalRule, self).__init__('Dolby Digital') class HqConflictRule(Rule): @@ -155,16 +201,16 @@ class HqConflictRule(Rule): Solve conflict between HQ from other property and from audio_profile. """ - dependency = [DtsRule, AacRule, Ac3Rule] + dependency = [DtsHDRule, DtsRule, AacRule, DolbyDigitalRule] consequence = RemoveMatch + def enabled(self, context): + return not is_disabled(context, 'audio_profile') + def when(self, matches, context): - hq_audio = matches.named('audio_profile', lambda match: match.value == 'HQ') + hq_audio = matches.named('audio_profile', lambda m: m.value == 'High Quality') hq_audio_spans = [match.span for match in hq_audio] - hq_other = matches.named('other', lambda match: match.span in hq_audio_spans) - - if hq_other: - return hq_other + return matches.named('other', lambda m: m.span in hq_audio_spans) class AudioChannelsValidatorRule(Rule): @@ -174,6 +220,9 @@ class AudioChannelsValidatorRule(Rule): priority = 128 consequence = RemoveMatch + def enabled(self, context): + return not is_disabled(context, 'audio_channels') + def when(self, matches, context): ret = [] diff --git a/libs/guessit/rules/properties/bit_rate.py b/libs/guessit/rules/properties/bit_rate.py new file mode 100644 index 000000000..d279c9f1c --- /dev/null +++ b/libs/guessit/rules/properties/bit_rate.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +video_bit_rate and audio_bit_rate properties +""" +import re + +from rebulk import Rebulk +from rebulk.rules import Rule, RemoveMatch, RenameMatch + +from ..common import dash, seps +from ..common.pattern import is_disabled +from ..common.quantity import BitRate +from ..common.validators import seps_surround + + +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 + + +class BitRateTypeRule(Rule): + """ + Convert audio bit rate guess into video bit rate. + """ + consequence = [RenameMatch('video_bit_rate'), RemoveMatch] + + def when(self, matches, context): + 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)): + after = matches.next(match, index=0, predicate=lambda m: m.name == 'audio_codec') + if after and not matches.holes(match.end, after.start, predicate=lambda m: m.value.strip(seps)): + bitrate = match.value + if bitrate.units == 'Kbps' or (bitrate.units == 'Mbps' and bitrate.magnitude < 10): + continue + + if video_bit_rate_disabled: + to_remove.append(match) + else: + to_rename.append(match) + + if to_rename or to_remove: + return to_rename, to_remove + return False diff --git a/libs/guessit/rules/properties/bonus.py b/libs/guessit/rules/properties/bonus.py index e37613e9d..54087aa31 100644 --- a/libs/guessit/rules/properties/bonus.py +++ b/libs/guessit/rules/properties/bonus.py @@ -9,21 +9,27 @@ from rebulk import Rebulk, AppendMatch, Rule from .title import TitleFromPosition from ..common.formatters import cleanup +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def bonus(): +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().regex_defaults(flags=re.IGNORECASE) + 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}, + validator={'__parent__': seps_surround}, + validate_all=True, conflict_solver=lambda match, conflicting: match - if conflicting.name in ['video_codec', 'episode'] and 'bonus-conflict' not in conflicting.tags + if conflicting.name in ('video_codec', 'episode') and 'weak-episode' not in conflicting.tags else '__default__') rebulk.rules(BonusTitleRule) @@ -40,7 +46,7 @@ class BonusTitleRule(Rule): properties = {'bonus_title': [None]} - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements bonus_number = matches.named('bonus', lambda match: not match.private, index=0) if bonus_number: filepath = matches.markers.at_match(bonus_number, lambda marker: marker.name == 'path', 0) diff --git a/libs/guessit/rules/properties/cds.py b/libs/guessit/rules/properties/cds.py index db1407d65..873df6fef 100644 --- a/libs/guessit/rules/properties/cds.py +++ b/libs/guessit/rules/properties/cds.py @@ -6,16 +6,22 @@ cd and cd_count properties from rebulk.remodule import re from rebulk import Rebulk + from ..common import dash +from ..common.pattern import is_disabled -def cds(): +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().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) + 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, diff --git a/libs/guessit/rules/properties/container.py b/libs/guessit/rules/properties/container.py index 52889e143..0f1860af3 100644 --- a/libs/guessit/rules/properties/container.py +++ b/libs/guessit/rules/properties/container.py @@ -8,33 +8,35 @@ from rebulk.remodule import re from rebulk import Rebulk from ..common import seps +from ..common.pattern import is_disabled from ..common.validators import seps_surround from ...reutils import build_or_pattern -def container(): +def container(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'container')) + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) rebulk.defaults(name='container', formatter=lambda value: value.strip(seps), tags=['extension'], conflict_solver=lambda match, other: other - if other.name in ['format', 'video_codec'] or + if other.name in ('source', 'video_codec') or other.name == 'container' and 'extension' not in other.tags else '__default__') - subtitles = ['srt', 'idx', 'sub', 'ssa', 'ass'] - info = ['nfo'] - videos = ['3g2', '3gp', '3gp2', 'asf', 'avi', 'divx', 'flv', 'm4v', 'mk2', - 'mka', 'mkv', 'mov', 'mp4', 'mp4a', 'mpeg', 'mpg', 'ogg', 'ogm', - 'ogv', 'qt', 'ra', 'ram', 'rm', 'ts', 'wav', 'webm', 'wma', 'wmv', - 'iso', 'vob'] - torrent = ['torrent'] - nzb = ['nzb'] + subtitles = config['subtitles'] + info = config['info'] + videos = config['videos'] + torrent = config['torrent'] + nzb = config['nzb'] rebulk.regex(r'\.'+build_or_pattern(subtitles)+'$', exts=subtitles, tags=['extension', 'subtitle']) rebulk.regex(r'\.'+build_or_pattern(info)+'$', exts=info, tags=['extension', 'info']) @@ -42,15 +44,16 @@ def container(): 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 - if other.name in ['format', - 'video_codec'] or other.name == 'container' and 'extension' in other.tags + if other.name in ('source', + 'video_codec') or other.name == 'container' and 'extension' in other.tags else '__default__') - rebulk.string(*[sub for sub in subtitles if sub not in ['sub']], tags=['subtitle']) + rebulk.string(*[sub for sub in subtitles if sub not in ('sub', 'ass')], tags=['subtitle']) rebulk.string(*videos, tags=['video']) rebulk.string(*torrent, tags=['torrent']) rebulk.string(*nzb, tags=['nzb']) diff --git a/libs/guessit/rules/properties/country.py b/libs/guessit/rules/properties/country.py index 5b390df06..172c29903 100644 --- a/libs/guessit/rules/properties/country.py +++ b/libs/guessit/rules/properties/country.py @@ -7,41 +7,50 @@ country property import babelfish from rebulk import Rebulk -from ..common.words import COMMON_WORDS, iter_words +from ..common.pattern import is_disabled +from ..common.words import iter_words -def country(): +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().defaults(name='country') + 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')] + if other.name != 'language' or match.value not in (babelfish.Country('US'), + babelfish.Country('GB')) else other, - properties={'country': [None]}) - - return rebulk + properties={'country': [None]}, + disabled=lambda context: not context.get('allowed_countries')) + babelfish.country_converters['guessit'] = GuessitCountryConverter(config['synonyms']) -COUNTRIES_SYN = {'ES': ['españa'], - 'GB': ['UK'], - 'BR': ['brazilian', 'bra'], - 'CA': ['québec', 'quebec', 'qc'], - # FIXME: this one is a bit of a stretch, not sure how to do it properly, though... - 'MX': ['Latinoamérica', 'latin america']} + return rebulk class GuessitCountryConverter(babelfish.CountryReverseConverter): # pylint: disable=missing-docstring - def __init__(self): + def __init__(self, synonyms): self.guessit_exceptions = {} - for alpha2, synlist in COUNTRIES_SYN.items(): + for alpha2, synlist in synonyms.items(): for syn in synlist: self.guessit_exceptions[syn.lower()] = alpha2 @@ -78,32 +87,28 @@ class GuessitCountryConverter(babelfish.CountryReverseConverter): # pylint: dis raise babelfish.CountryReverseError(name) -babelfish.country_converters['guessit'] = GuessitCountryConverter() - +class CountryFinder(object): + """Helper class to search and return country matches.""" -def is_allowed_country(country_object, context=None): - """ - Check if country is allowed. - """ - if context and context.get('allowed_countries'): - allowed_countries = context.get('allowed_countries') - return country_object.name.lower() in allowed_countries or country_object.alpha2.lower() in allowed_countries - return True + def __init__(self, allowed_countries, common_words): + self.allowed_countries = {l.lower() for l in allowed_countries or []} + self.common_words = common_words + def find(self, string): + """Return all matches for country.""" + for word_match in iter_words(string.strip().lower()): + word = word_match.value + if word.lower() in self.common_words: + continue -def find_countries(string, context=None): - """ - Find countries in given string. - """ - ret = [] - for word_match in iter_words(string.strip().lower()): - word = word_match.value - if word.lower() in COMMON_WORDS: - continue - try: - country_object = babelfish.Country.fromguessit(word) - if is_allowed_country(country_object, context): - ret.append((word_match.span[0], word_match.span[1], {'value': country_object})) - except babelfish.Error: - continue - return ret + try: + country_object = babelfish.Country.fromguessit(word) + if (country_object.name.lower() in self.allowed_countries or + country_object.alpha2.lower() in self.allowed_countries): + yield self._to_rebulk_match(word_match, country_object) + except babelfish.Error: + continue + + @classmethod + def _to_rebulk_match(cls, word, value): + return word.span[0], word.span[1], {'value': value} diff --git a/libs/guessit/rules/properties/crc.py b/libs/guessit/rules/properties/crc.py index f655bc131..eedee93d0 100644 --- a/libs/guessit/rules/properties/crc.py +++ b/libs/guessit/rules/properties/crc.py @@ -6,20 +6,25 @@ crc and uuid properties from rebulk.remodule import re from rebulk import Rebulk +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def crc(): +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().regex_defaults(flags=re.IGNORECASE) + 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: match + conflict_solver=lambda match, other: other if other.name in ['episode', 'season'] else '__default__') diff --git a/libs/guessit/rules/properties/date.py b/libs/guessit/rules/properties/date.py index 0b6083bd7..e50cdfa3f 100644 --- a/libs/guessit/rules/properties/date.py +++ b/libs/guessit/rules/properties/date.py @@ -6,21 +6,29 @@ date and year properties from rebulk import Rebulk, RemoveMatch, Rule from ..common.date import search_date, valid_year +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def date(): +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): + def date_functional(string, context): # pylint:disable=inconsistent-return-statements """ Search for date in the string and retrieves match @@ -33,8 +41,9 @@ 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'] + if other.name in ('episode', 'season', 'crc32') else '__default__') rebulk.rules(KeepMarkedYearInFilepart) @@ -49,6 +58,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/libs/guessit/rules/properties/edition.py b/libs/guessit/rules/properties/edition.py index a6d05a7e4..822aa4ee3 100644 --- a/libs/guessit/rules/properties/edition.py +++ b/libs/guessit/rules/properties/edition.py @@ -7,28 +7,34 @@ from rebulk.remodule import re from rebulk import Rebulk from ..common import dash +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def edition(): +def edition(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) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'edition')) + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]).string_defaults(ignore_case=True) rebulk.defaults(name='edition', validator=seps_surround) - rebulk.regex('collector', 'collector-edition', 'edition-collector', value='Collector Edition') - rebulk.regex('special-edition', 'edition-special', value='Special Edition', + rebulk.regex('collector', "collector'?s?-edition", 'edition-collector', value='Collector') + rebulk.regex('special-edition', 'edition-special', value='Special', conflict_solver=lambda match, other: other if other.name == 'episode_details' and other.value == 'Special' else '__default__') - rebulk.string('se', value='Special Edition', tags='has-neighbor') - rebulk.regex('criterion-edition', 'edition-criterion', value='Criterion Edition') - rebulk.regex('deluxe', 'deluxe-edition', 'edition-deluxe', value='Deluxe Edition') - rebulk.regex('limited', 'limited-edition', value='Limited Edition', tags=['has-neighbor', 'release-group-prefix']) - rebulk.regex(r'theatrical-cut', r'theatrical-edition', r'theatrical', value='Theatrical Edition') + rebulk.string('se', value='Special', tags='has-neighbor') + rebulk.string('ddc', value="Director's Definitive Cut") + rebulk.regex('criterion-edition', 'edition-criterion', 'CC', value='Criterion') + rebulk.regex('deluxe', 'deluxe-edition', 'edition-deluxe', value='Deluxe') + rebulk.regex('limited', 'limited-edition', value='Limited', tags=['has-neighbor', 'release-group-prefix']) + rebulk.regex(r'theatrical-cut', r'theatrical-edition', r'theatrical', value='Theatrical') rebulk.regex(r"director'?s?-cut", r"director'?s?-cut-edition", r"edition-director'?s?-cut", 'DC', value="Director's Cut") rebulk.regex('extended', 'extended-?cut', 'extended-?version', @@ -37,5 +43,10 @@ def edition(): for value in ('Remastered', 'Uncensored', 'Uncut', 'Unrated'): rebulk.string(value, value=value, tags=['has-neighbor', 'release-group-prefix']) rebulk.string('Festival', value='Festival', tags=['has-neighbor-before', 'has-neighbor-after']) + rebulk.regex('imax', 'imax-edition', value='IMAX') + rebulk.regex('fan-edit(?:ion)?', 'fan-collection', value='Fan') + rebulk.regex('ultimate-edition', value='Ultimate') + rebulk.regex("ultimate-collector'?s?-edition", value=['Ultimate', 'Collector']) + rebulk.regex('ultimate-fan-edit(?:ion)?', 'ultimate-fan-collection', value=['Ultimate', 'Fan']) return rebulk diff --git a/libs/guessit/rules/properties/episode_title.py b/libs/guessit/rules/properties/episode_title.py index 6d5016f13..ece8921d2 100644 --- a/libs/guessit/rules/properties/episode_title.py +++ b/libs/guessit/rules/properties/episode_title.py @@ -9,26 +9,32 @@ from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch, RenameMatch, POST_PRO from ..common import seps, title_seps from ..common.formatters import cleanup +from ..common.pattern import is_disabled +from ..common.validators import or_ from ..properties.title import TitleFromPosition, TitleBaseRule from ..properties.type import TypeProcessor -def episode_title(): +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_details', 'episode_count', + previous_names = ('episode', 'episode_count', 'season', 'season_count', 'date', 'title', 'year') - rebulk = Rebulk().rules(RemoveConflictsWithEpisodeTitle(previous_names), - EpisodeTitleFromPosition(previous_names), - AlternativeTitleReplace(previous_names), - TitleToEpisodeTitle, - Filepart3EpisodeTitle, - Filepart2EpisodeTitle, - RenameEpisodeTitleWhenMovieType) + 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 @@ -43,7 +49,7 @@ class RemoveConflictsWithEpisodeTitle(Rule): def __init__(self, previous_names): super(RemoveConflictsWithEpisodeTitle, self).__init__() self.previous_names = previous_names - self.next_names = ('streaming_service', 'screen_size', 'format', + self.next_names = ('streaming_service', 'screen_size', 'source', 'video_codec', 'audio_codec', 'other', 'container') self.affected_if_holes_after = ('part', ) self.affected_names = ('part', 'year') @@ -53,13 +59,11 @@ class RemoveConflictsWithEpisodeTitle(Rule): for filepart in matches.markers.named('path'): for match in matches.range(filepart.start, filepart.end, predicate=lambda m: m.name in self.affected_names): - before = matches.previous(match, index=0, - predicate=lambda m, fp=filepart: not m.private and m.start >= fp.start) + before = matches.range(filepart.start, match.start, predicate=lambda m: not m.private, index=-1) if not before or before.name not in self.previous_names: continue - after = matches.next(match, index=0, - predicate=lambda m, fp=filepart: not m.private and m.end <= fp.end) + after = matches.range(match.end, filepart.end, predicate=lambda m: not m.private, index=0) if not after or after.name not in self.next_names: continue @@ -100,16 +104,15 @@ class TitleToEpisodeTitle(Rule): for title in titles: title_groups[title.value].append(title) + episode_titles = [] if len(title_groups) < 2: - return + return episode_titles - episode_titles = [] for title in titles: if matches.previous(title, lambda match: match.name == 'episode'): episode_titles.append(title) - if episode_titles: - return episode_titles + return episode_titles def then(self, matches, when_response, context): for title in when_response: @@ -131,8 +134,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') @@ -150,7 +152,7 @@ class EpisodeTitleFromPosition(TitleBaseRule): return False return super(EpisodeTitleFromPosition, self).should_remove(match, matches, filepart, hole, context) - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements if matches.named('episode_title'): return return super(EpisodeTitleFromPosition, self).when(matches, context) @@ -167,7 +169,7 @@ class AlternativeTitleReplace(Rule): super(AlternativeTitleReplace, self).__init__() self.previous_names = previous_names - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements if matches.named('episode_title'): return @@ -177,8 +179,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') @@ -202,7 +203,7 @@ class RenameEpisodeTitleWhenMovieType(Rule): dependency = TypeProcessor consequence = RenameMatch - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements if matches.named('episode_title', lambda m: 'alternative-replaced' not in m.tags) \ and not matches.named('type', lambda m: m.value == 'episode'): return matches.named('episode_title') @@ -221,12 +222,18 @@ class Filepart3EpisodeTitle(Rule): Serie name/SO1/E01-episode_title.mkv AAAAAAAAAA/BBB/CCCCCCCCCCCCCCCCCCCC + Serie name/SO1/episode_title-E01.mkv + AAAAAAAAAA/BBB/CCCCCCCCCCCCCCCCCCCC + If CCCC contains episode and BBB contains seasonNumber Then title is to be found in AAAA. """ consequence = AppendMatch('title') - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements + if matches.tagged('filepart-title'): + return + fileparts = matches.markers.named('path') if len(fileparts) < 3: return @@ -241,6 +248,7 @@ class Filepart3EpisodeTitle(Rule): if season: hole = matches.holes(subdirectory.start, subdirectory.end, + ignore=or_(lambda match: 'weak-episode' in match.tags, TitleBaseRule.is_ignored), formatter=cleanup, seps=title_seps, predicate=lambda match: match.value, index=0) if hole: @@ -267,7 +275,10 @@ class Filepart2EpisodeTitle(Rule): """ consequence = AppendMatch('title') - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements + if matches.tagged('filepart-title'): + return + fileparts = matches.markers.named('path') if len(fileparts) < 2: return @@ -280,7 +291,10 @@ class Filepart2EpisodeTitle(Rule): season = (matches.range(directory.start, directory.end, lambda match: match.name == 'season', 0) or matches.range(filename.start, filename.end, lambda match: match.name == 'season', 0)) if season: - hole = matches.holes(directory.start, directory.end, formatter=cleanup, seps=title_seps, + hole = matches.holes(directory.start, directory.end, + ignore=or_(lambda match: 'weak-episode' in match.tags, TitleBaseRule.is_ignored), + formatter=cleanup, seps=title_seps, predicate=lambda match: match.value, index=0) if hole: + hole.tags.append('filepart-title') return hole diff --git a/libs/guessit/rules/properties/episodes.py b/libs/guessit/rules/properties/episodes.py index f74ae48e9..345c785de 100644 --- a/libs/guessit/rules/properties/episodes.py +++ b/libs/guessit/rules/properties/episodes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -episode, season, episode_count, season_count and episode_details properties +episode, season, disc, episode_count, season_count and episode_details properties """ import copy from collections import defaultdict @@ -11,24 +11,30 @@ 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 +from ..common import dash, alt_dash, seps, seps_no_fs from ..common.formatters import strip -from ..common.numeral import numeral, parse_numeral -from ..common.validators import compose, seps_surround, seps_before, int_coercable +from ..common.pattern import is_disabled +from ..common.validators import seps_surround, int_coercable, and_ from ...reutils import build_or_pattern -def episodes(): +def episodes(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ + # pylint: disable=too-many-branches,too-many-statements,too-many-locals - rebulk = Rebulk() - rebulk.regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) - rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator', 'episodeMarker', 'seasonMarker']) + def is_season_episode_disabled(context): + """Whether season and episode rules should be enabled.""" + return is_disabled(context, 'episode') or is_disabled(context, 'season') def episodes_season_chain_breaker(matches): """ @@ -39,16 +45,14 @@ def episodes(): :rtype: """ eps = matches.named('episode') - if len(eps) > 1 and abs(eps[-1].value - eps[-2].value) > 100: + if len(eps) > 1 and abs(eps[-1].value - eps[-2].value) > episode_max_range: return True seasons = matches.named('season') - if len(seasons) > 1 and abs(seasons[-1].value - seasons[-2].value) > 100: + if len(seasons) > 1 and abs(seasons[-1].value - seasons[-2].value) > season_max_range: 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 @@ -57,40 +61,25 @@ def episodes(): :param other: :return: """ - if match.name == 'episode' and other.name in \ - ['screen_size', 'video_codec', 'audio_codec', 'audio_channels', 'container', 'date', 'year'] \ - and 'weak-audio_channels' not in other.tags: - return match - if match.name == 'season' and other.name in \ - ['screen_size', 'video_codec', 'audio_codec', 'audio_channels', 'container', 'date'] \ - and 'weak-audio_channels' not in other.tags: - return match - if match.name in ['season', 'episode'] and other.name in ['season', 'episode'] \ - and match.initiator != other.initiator: - if 'weak-episode' in match.tags or 'x' in match.initiator.raw.lower(): + if match.name != other.name: + if match.name == 'episode' and other.name == 'year': return match - if 'weak-episode' in other.tags or 'x' in other.initiator.raw.lower(): - return other + if match.name in ('season', 'episode'): + if other.name in ('video_codec', 'audio_codec', 'container', 'date'): + return match + 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') + and other.initiator.name in ('weak_episode', 'weak_duplicate')): + return '__default__' + for current in (match, other): + if 'weak-episode' in current.tags or 'x' in current.initiator.raw.lower(): + return current return '__default__' - season_episode_seps = [] - season_episode_seps.extend(seps) - season_episode_seps.extend(['x', 'X', 'e', 'E']) - - season_words = ['season', 'saison', 'seizoen', 'serie', 'seasons', 'saisons', 'series', - 'tem', 'temp', 'temporada', 'temporadas', 'stagione'] - episode_words = ['episode', 'episodes', 'eps', 'ep', 'episodio', - 'episodios', 'capitulo', 'capitulos'] - of_words = ['of', 'sur'] - all_words = ['All'] - season_markers = ["S"] - season_ep_markers = ["x"] - episode_markers = ["xE", "Ex", "EP", "E", "x"] - range_separators = ['-', '~', 'to', 'a'] - weak_discrete_separators = list(sep for sep in seps if sep not in range_separators) - strong_discrete_separators = ['+', '&', 'and', 'et'] - discrete_separators = strong_discrete_separators + weak_discrete_separators - def ordering_validator(match): """ Validator for season list. They should be in natural order to be validated. @@ -124,180 +113,228 @@ def episodes(): 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 current_match.value - previous_match.value == 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') + 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) + + 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(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) \ + rebulk.chain( + tags=['SxxExx'], + validate_all=True, + validator={'__parent__': and_(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P<season>\d+)@?' + - build_or_pattern(episode_markers, name='episodeMarker') + r'@?(?P<episode>\d+)', - validate_all=True, - validator={'__parent__': seps_before}).repeater('+') \ - .regex(build_or_pattern(episode_markers + discrete_separators + range_separators, + build_or_pattern(episode_markers + disc_markers, name='episodeMarker') + r'@?(?P<episode>\d+)')\ + .repeater('+') \ + .regex(build_or_pattern(episode_markers + disc_markers + discrete_separators + range_separators, name='episodeSeparator', escape=True) + - r'(?P<episode>\d+)').repeater('*') \ - .chain() \ + r'(?P<episode>\d+)').repeater('*') + + rebulk.chain(tags=['SxxExx'], + validate_all=True, + validator={'__parent__': and_(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ .regex(r'(?P<season>\d+)@?' + build_or_pattern(season_ep_markers, name='episodeMarker') + - r'@?(?P<episode>\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ - .chain() \ + r'@?(?P<episode>\d+)').repeater('+') \ + + rebulk.chain(tags=['SxxExx'], + validate_all=True, + validator={'__parent__': and_(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ .regex(r'(?P<season>\d+)@?' + build_or_pattern(season_ep_markers, name='episodeMarker') + - r'@?(?P<episode>\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ + r'@?(?P<episode>\d+)') \ .regex(build_or_pattern(season_ep_markers + discrete_separators + range_separators, name='episodeSeparator', escape=True) + - r'(?P<episode>\d+)').repeater('*') \ - .chain() \ - .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P<season>\d+)', - validate_all=True, - validator={'__parent__': seps_before}) \ + r'(?P<episode>\d+)').repeater('*') + + rebulk.chain(tags=['SxxExx'], + validate_all=True, + validator={'__parent__': and_(seps_surround, ordering_validator)}, + disabled=is_season_episode_disabled) \ + .defaults(tags=['SxxExx']) \ + .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P<season>\d+)') \ + .regex('(?P<other>Extras)', 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<season>\d+)').repeater('*') # episode_details property - for episode_detail in ('Special', 'Bonus', 'Omake', 'Ova', 'Oav', 'Pilot', 'Unaired'): - rebulk.string(episode_detail, value=episode_detail, name='episode_details') - rebulk.regex(r'Extras?', name='episode_details', value='Extras') - - 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) + 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__': and_(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), + validator={'__parent__': and_(seps_surround, ordering_validator), 'season': validate_roman, - 'count': validate_roman}) \ - .defaults(validator=None) \ + 'count': validate_roman}, + disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'season')) \ + .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<season>' + numeral + ')') \ .regex(r'' + build_or_pattern(of_words) + '@?(?P<count>' + numeral + ')').repeater('?') \ .regex(r'@?' + build_or_pattern(range_separators + discrete_separators + ['@'], name='seasonSeparator', escape=True) + r'@?(?P<season>\d+)').repeater('*') + rebulk.defaults(abbreviations=[dash]) + rebulk.regex(build_or_pattern(episode_words, name='episodeMarker') + r'-?(?P<episode>\d+)' + r'(?:v(?P<version>\d+))?' + r'(?:-?' + build_or_pattern(of_words) + r'-?(?P<count>\d+))?', # Episode 4 - abbreviations=[dash], formatter={'episode': int, 'version': int, 'count': int}, - disabled=lambda context: context.get('type') == 'episode') + disabled=lambda context: context.get('type') == 'episode' or is_disabled(context, 'episode')) rebulk.regex(build_or_pattern(episode_words, name='episodeMarker') + r'-?(?P<episode>' + numeral + ')' + r'(?:v(?P<version>\d+))?' + r'(?:-?' + build_or_pattern(of_words) + r'-?(?P<count>\d+))?', # Episode 4 - abbreviations=[dash], validator={'episode': validate_roman}, - formatter={'episode': parse_numeral, 'version': int, 'count': int}, - disabled=lambda context: context.get('type') != 'episode') + formatter={'episode': parse_numeral}, + disabled=lambda context: context.get('type') != 'episode' or is_disabled(context, 'episode')) rebulk.regex(r'S?(?P<season>\d+)-?(?:xE|Ex|E|x)-?(?P<other>' + 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=['bonus-conflict', 'weak-movie', 'weak-episode'], formatter={'episode': int, 'version': int}, - disabled=lambda context: context.get('type') == 'movie') \ - .defaults(validator=None) \ + rebulk.chain(tags=['weak-episode'], + disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'(?P<episode>\d{2})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{2})').repeater('*') + .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{2})', abbreviations=None).repeater('*') # 012, 013 - rebulk.chain(tags=['bonus-conflict', 'weak-movie', 'weak-episode'], formatter={'episode': int, 'version': int}, - disabled=lambda context: context.get('type') == 'movie') \ - .defaults(validator=None) \ + rebulk.chain(tags=['weak-episode'], + disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'0(?P<episode>\d{1,2})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>[x-])0(?P<episode>\d{1,2})').repeater('*') + .regex(r'(?P<episodeSeparator>[x-])0(?P<episode>\d{1,2})', abbreviations=None).repeater('*') # 112, 113 - rebulk.chain(tags=['bonus-conflict', 'weak-movie', 'weak-episode'], formatter={'episode': int, 'version': int}, - disabled=lambda context: (not context.get('episode_prefer_number', False) or - context.get('type') == 'movie')) \ - .defaults(validator=None) \ + rebulk.chain(tags=['weak-episode'], + name='weak_episode', + disabled=lambda context: context.get('type') == 'movie' or is_disabled(context, 'episode')) \ + .defaults(validator=None, tags=['weak-episode'], name='weak_episode') \ .regex(r'(?P<episode>\d{3,4})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{3,4})').repeater('*') + .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{3,4})', abbreviations=None).repeater('*') # 1, 2, 3 - rebulk.chain(tags=['bonus-conflict', 'weak-movie', 'weak-episode'], formatter={'episode': int, 'version': int}, - disabled=lambda context: context.get('type') != 'episode') \ - .defaults(validator=None) \ + rebulk.chain(tags=['weak-episode'], + disabled=lambda context: context.get('type') != 'episode' or is_disabled(context, 'episode')) \ + .defaults(validator=None, tags=['weak-episode']) \ .regex(r'(?P<episode>\d)') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{1,2})').repeater('*') + .regex(r'(?P<episodeSeparator>[x-])(?P<episode>\d{1,2})', abbreviations=None).repeater('*') - # e112, e113 - # TODO: Enhance rebulk for validator to be used globally (season_episode_validator) - rebulk.chain(formatter={'episode': int, 'version': int}) \ + # e112, e113, 1e18, 3e19 + rebulk.chain(disabled=lambda context: is_disabled(context, 'episode')) \ .defaults(validator=None) \ - .regex(r'(?P<episodeMarker>e)(?P<episode>\d{1,4})') \ + .regex(r'(?P<season>\d{1,2})?(?P<episodeMarker>e)(?P<episode>\d{1,4})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>e|x|-)(?P<episode>\d{1,4})').repeater('*') + .regex(r'(?P<episodeSeparator>e|x|-)(?P<episode>\d{1,4})', abbreviations=None).repeater('*') # ep 112, ep113, ep112, ep113 - rebulk.chain(abbreviations=[dash], formatter={'episode': int, 'version': int}) \ + rebulk.chain(disabled=lambda context: is_disabled(context, 'episode')) \ .defaults(validator=None) \ .regex(r'ep-?(?P<episode>\d{1,4})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>ep|e|x|-)(?P<episode>\d{1,4})').repeater('*') + .regex(r'(?P<episodeSeparator>ep|e|x|-)(?P<episode>\d{1,4})', abbreviations=None).repeater('*') # cap 112, cap 112_114 - rebulk.chain(abbreviations=[dash], - tags=['see-pattern'], - formatter={'season': int, 'episode': int}) \ - .defaults(validator=None) \ + rebulk.chain(tags=['see-pattern'], + disabled=is_season_episode_disabled) \ + .defaults(validator=None, tags=['see-pattern']) \ .regex(r'(?P<seasonMarker>cap)-?(?P<season>\d{1,2})(?P<episode>\d{2})') \ .regex(r'(?P<episodeSeparator>-)(?P<season>\d{1,2})(?P<episode>\d{2})').repeater('?') # 102, 0102 - rebulk.chain(tags=['bonus-conflict', 'weak-movie', 'weak-episode', 'weak-duplicate'], - formatter={'season': int, 'episode': int, 'version': int}, - conflict_solver=lambda match, other: match if other.name == 'year' else '__default__', + rebulk.chain(tags=['weak-episode', 'weak-duplicate'], + name='weak_duplicate', + conflict_solver=season_episode_conflict_solver, disabled=lambda context: (context.get('episode_prefer_number', False) or - context.get('type') == 'movie')) \ - .defaults(validator=None) \ + context.get('type') == 'movie') or is_season_episode_disabled(context)) \ + .defaults(tags=['weak-episode', 'weak-duplicate'], + name='weak_duplicate', + validator=None, + conflict_solver=season_episode_conflict_solver) \ .regex(r'(?P<season>\d{1,2})(?P<episode>\d{2})') \ .regex(r'v(?P<version>\d+)').repeater('?') \ - .regex(r'(?P<episodeSeparator>x|-)(?P<episode>\d{2})').repeater('*') + .regex(r'(?P<episodeSeparator>x|-)(?P<episode>\d{2})', abbreviations=None).repeater('*') - rebulk.regex(r'v(?P<version>\d+)', children=True, private_parent=True, formatter=int) + rebulk.regex(r'v(?P<version>\d+)', + formatter=int, + disabled=lambda context: is_disabled(context, 'version')) rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator']) @@ -305,17 +342,105 @@ def episodes(): # detached of X count (season/episode) rebulk.regex(r'(?P<episode>\d+)-?' + build_or_pattern(of_words) + r'-?(?P<count>\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?', + children=False, + private_parent=False, + name='episode_format', + value="Minisode", + disabled=lambda context: is_disabled(context, 'episode_format')) + + rebulk.rules(WeakConflictSolver, RemoveInvalidSeason, RemoveInvalidEpisode, + SeePatternRange(range_separators + ['_']), + EpisodeNumberSeparatorRange(range_separators), + SeasonSeparatorRange(range_separators), RemoveWeakIfMovie, RemoveWeakIfSxxExx, RemoveWeakDuplicate, + EpisodeDetailValidator, RemoveDetachedEpisodeNumber, VersionValidator, RemoveWeak(episode_words), + RenameToAbsoluteEpisode, CountValidator, EpisodeSingleDigitValidator, RenameToDiscMatch) - rebulk.regex(r'Minisodes?', name='episode_format', value="Minisode") + return rebulk - rebulk.rules(RemoveInvalidSeason, RemoveInvalidEpisode, - SeePatternRange(range_separators + ['_']), EpisodeNumberSeparatorRange(range_separators), - SeasonSeparatorRange(range_separators), RemoveWeakIfMovie, RemoveWeakIfSxxExx, - RemoveWeakDuplicate, EpisodeDetailValidator, RemoveDetachedEpisodeNumber, VersionValidator, - CountValidator, EpisodeSingleDigitValidator) - return rebulk +class WeakConflictSolver(Rule): + """ + Rule to decide whether weak-episode or weak-duplicate matches should be kept. + + If an anime is detected: + - weak-duplicate matches should be removed + - weak-episode matches should be tagged as anime + Otherwise: + - weak-episode matches are removed unless they're part of an episode range match. + """ + priority = 128 + consequence = [RemoveMatch, AppendMatch] + + def enabled(self, context): + return context.get('type') != 'movie' + + @classmethod + def is_anime(cls, matches): + """Return True if it seems to be an anime. + + Anime characteristics: + - version, crc32 matches + - screen_size inside brackets + - release_group at start and inside brackets + """ + if matches.named('version') or matches.named('crc32'): + return True + + for group in matches.markers.named('group'): + if matches.range(group.start, group.end, predicate=lambda m: m.name == 'screen_size'): + return True + if matches.markers.starting(group.start, predicate=lambda m: m.name == 'path'): + hole = matches.holes(group.start, group.end, index=0) + if hole and hole.raw == group.raw: + return True + + return False + + def when(self, matches, context): + to_remove = [] + to_append = [] + anime_detected = self.is_anime(matches) + for filepart in matches.markers.named('path'): + weak_matches = matches.range(filepart.start, filepart.end, predicate=( + lambda m: m.initiator.name == 'weak_episode')) + weak_dup_matches = matches.range(filepart.start, filepart.end, predicate=( + lambda m: m.initiator.name == 'weak_duplicate')) + if anime_detected: + if weak_matches: + to_remove.extend(weak_dup_matches) + for match in matches.range(filepart.start, filepart.end, predicate=( + lambda m: m.name == 'episode' and m.initiator.name != 'weak_duplicate')): + episode = copy.copy(match) + episode.tags = episode.tags + ['anime'] + to_append.append(episode) + to_remove.append(match) + elif weak_dup_matches: + episodes_in_range = matches.range(filepart.start, filepart.end, predicate=( + lambda m: + m.name == 'episode' and m.initiator.name == 'weak_episode' + and m.initiator.children.named('episodeSeparator') + )) + if not episodes_in_range and not matches.range(filepart.start, filepart.end, + predicate=lambda m: 'SxxExx' in m.tags): + to_remove.extend(weak_matches) + else: + for match in episodes_in_range: + episode = copy.copy(match) + episode.tags = [] + to_append.append(episode) + to_remove.append(match) + + if to_append: + to_remove.extend(weak_dup_matches) + + if to_remove or to_append: + return to_remove, to_append + return False class CountValidator(Rule): @@ -341,7 +466,9 @@ class CountValidator(Rule): season_count.append(count) else: to_remove.append(count) - return to_remove, episode_count, season_count + if to_remove or episode_count or season_count: + return to_remove, episode_count, season_count + return False class SeePatternRange(Rule): @@ -376,7 +503,9 @@ class SeePatternRange(Rule): to_remove.append(separator) - return to_remove, to_append + if to_remove or to_append: + return to_remove, to_append + return False class AbstractSeparatorRange(Rule): @@ -396,14 +525,16 @@ class AbstractSeparatorRange(Rule): to_append = [] for separator in matches.named(self.property_name + 'Separator'): - previous_match = matches.previous(separator, lambda match: match.name == self.property_name, 0) - next_match = matches.next(separator, lambda match: match.name == self.property_name, 0) + previous_match = matches.previous(separator, lambda m: m.name == self.property_name, 0) + next_match = matches.next(separator, lambda m: m.name == self.property_name, 0) + initiator = separator.initiator if previous_match and next_match and separator.value in self.range_separators: to_remove.append(next_match) for episode_number in range(previous_match.value + 1, next_match.value): match = copy.copy(next_match) match.value = episode_number + initiator.children.append(match) to_append.append(match) to_append.append(next_match) to_remove.append(separator) @@ -415,9 +546,11 @@ class AbstractSeparatorRange(Rule): if separator not in self.range_separators: separator = strip(separator) if separator in self.range_separators: + initiator = previous_match.initiator for episode_number in range(previous_match.value + 1, next_match.value): match = copy.copy(next_match) match.value = episode_number + initiator.children.append(match) to_append.append(match) to_append.append(Match(previous_match.end, next_match.start - 1, name=self.property_name + 'Separator', @@ -428,15 +561,51 @@ class AbstractSeparatorRange(Rule): previous_match = next_match - return to_remove, to_append + if to_remove or to_append: + return to_remove, to_append + return False + + +class RenameToAbsoluteEpisode(Rule): + """ + Rename episode to absolute_episodes. + + Absolute episodes are only used if two groups of episodes are detected: + S02E04-06 25-27 + 25-27 S02E04-06 + 2x04-06 25-27 + 28. Anime Name S02E05 + The matches in the group with higher episode values are renamed to absolute_episode. + """ + + consequence = RenameMatch('absolute_episode') + + def when(self, matches, context): # pylint:disable=inconsistent-return-statements + initiators = {match.initiator for match in matches.named('episode') + if len(match.initiator.children.named('episode')) > 1} + if len(initiators) != 2: + ret = [] + for filepart in matches.markers.named('path'): + if matches.range(filepart.start + 1, filepart.end, predicate=lambda m: m.name == 'episode'): + ret.extend( + matches.starting(filepart.start, predicate=lambda m: m.initiator.name == 'weak_episode')) + return ret + + initiators = sorted(initiators, key=lambda item: item.end) + if not matches.holes(initiators[0].end, initiators[1].start, predicate=lambda m: m.raw.strip(seps)): + first_range = matches.named('episode', predicate=lambda m: m.initiator == initiators[0]) + second_range = matches.named('episode', predicate=lambda m: m.initiator == initiators[1]) + if len(first_range) == len(second_range): + if second_range[0].value > first_range[0].value: + return second_range + if first_range[0].value > second_range[0].value: + return first_range class EpisodeNumberSeparatorRange(AbstractSeparatorRange): """ Remove separator matches and create matches for episoderNumber range. """ - priority = 128 - consequence = [RemoveMatch, AppendMatch] def __init__(self, range_separators): super(EpisodeNumberSeparatorRange, self).__init__(range_separators, "episode") @@ -446,8 +615,6 @@ class SeasonSeparatorRange(AbstractSeparatorRange): """ Remove separator matches and create matches for season range. """ - priority = 128 - consequence = [RemoveMatch, AppendMatch] def __init__(self, range_separators): super(SeasonSeparatorRange, self).__init__(range_separators, "season") @@ -455,7 +622,7 @@ class SeasonSeparatorRange(AbstractSeparatorRange): class RemoveWeakIfMovie(Rule): """ - Remove weak-movie tagged matches if it seems to be a movie. + Remove weak-episode tagged matches if it seems to be a movie. """ priority = 64 consequence = RemoveMatch @@ -471,19 +638,69 @@ class RemoveWeakIfMovie(Rule): year = matches.range(filepart.start, filepart.end, predicate=lambda m: m.name == 'year', index=0) if year: remove = True - next_match = matches.next(year, predicate=lambda m, fp=filepart: m.private and m.end <= fp.end, index=0) - if next_match and not matches.at_match(next_match, predicate=lambda m: m.name == 'year'): + next_match = matches.range(year.end, filepart.end, predicate=lambda m: m.private, index=0) + if (next_match and not matches.holes(year.end, next_match.start, predicate=lambda m: m.raw.strip(seps)) + and not matches.at_match(next_match, predicate=lambda m: m.name == 'year')): to_ignore.add(next_match.initiator) + to_ignore.update(matches.range(filepart.start, filepart.end, + predicate=lambda m: len(m.children.named('episode')) > 1)) + + to_remove.extend(matches.conflicting(year)) if remove: - to_remove.extend(matches.tagged('weak-movie', predicate=lambda m: m.initiator not in to_ignore)) + to_remove.extend(matches.tagged('weak-episode', predicate=( + lambda m: m.initiator not in to_ignore and 'anime' not in m.tags))) return to_remove +class RemoveWeak(Rule): + """ + Remove weak-episode matches which appears after video, source, and audio matches. + """ + priority = 16 + consequence = RemoveMatch, AppendMatch + + def __init__(self, episode_words): + super(RemoveWeak, self).__init__() + self.episode_words = episode_words + + def when(self, matches, context): + to_remove = [] + to_append = [] + for filepart in matches.markers.named('path'): + weaks = matches.range(filepart.start, filepart.end, predicate=lambda m: 'weak-episode' in m.tags) + if weaks: + weak = weaks[0] + previous = matches.previous(weak, predicate=lambda m: m.name in ( + 'audio_codec', 'screen_size', 'streaming_service', 'source', 'video_profile', + 'audio_channels', 'audio_profile'), index=0) + if previous and not matches.holes( + previous.end, weak.start, predicate=lambda m: m.raw.strip(seps)): + if previous.raw.lower() in self.episode_words: + try: + episode = copy.copy(weak) + episode.name = 'episode' + episode.value = int(weak.value) + episode.start = previous.start + episode.private = False + episode.tags = [] + + to_append.append(episode) + except ValueError: + pass + + to_remove.extend(weaks) + if to_remove or to_append: + return to_remove, to_append + return False + + class RemoveWeakIfSxxExx(Rule): """ - Remove weak-movie tagged matches if SxxExx pattern is matched. + Remove weak-episode tagged matches if SxxExx pattern is matched. + + Weak episodes at beginning of filepart are kept. """ priority = 64 consequence = RemoveMatch @@ -492,9 +709,10 @@ class RemoveWeakIfSxxExx(Rule): to_remove = [] for filepart in matches.markers.named('path'): if matches.range(filepart.start, filepart.end, - predicate=lambda match: not match.private and 'SxxExx' in match.tags): - to_remove.extend(matches.range( - filepart.start, filepart.end, predicate=lambda match: 'weak-movie' in match.tags)) + predicate=lambda m: not m.private and 'SxxExx' in m.tags): + for match in matches.range(filepart.start, filepart.end, predicate=lambda m: 'weak-episode' in m.tags): + if match.start != filepart.start or match.initiator.name != 'weak_episode': + to_remove.append(match) return to_remove @@ -575,7 +793,7 @@ class RemoveWeakDuplicate(Rule): for filepart in matches.markers.named('path'): patterns = defaultdict(list) for match in reversed(matches.range(filepart.start, filepart.end, - predicate=lambda match: 'weak-duplicate' in match.tags)): + predicate=lambda m: 'weak-duplicate' in m.tags)): if match.pattern in patterns[match.name]: to_remove.append(match) else: @@ -615,15 +833,15 @@ class RemoveDetachedEpisodeNumber(Rule): episode_numbers = [] episode_values = set() - for match in matches.named('episode', lambda match: not match.private and 'weak-movie' in match.tags): + for match in matches.named('episode', lambda m: not m.private and 'weak-episode' in m.tags): if match.value not in episode_values: episode_numbers.append(match) episode_values.add(match.value) - episode_numbers = list(sorted(episode_numbers, key=lambda match: match.value)) + episode_numbers = list(sorted(episode_numbers, key=lambda m: m.value)) if len(episode_numbers) > 1 and \ - episode_numbers[0].value < 10 and \ - episode_numbers[1].value - episode_numbers[0].value != 1: + episode_numbers[0].value < 10 and \ + episode_numbers[1].value - episode_numbers[0].value != 1: parent = episode_numbers[0] while parent: # TODO: Add a feature in rebulk to avoid this ... ret.append(parent) @@ -664,3 +882,31 @@ class EpisodeSingleDigitValidator(Rule): if not matches.range(*group.span, predicate=lambda match: match.name == 'title'): ret.append(episode) return ret + + +class RenameToDiscMatch(Rule): + """ + Rename episodes detected with `d` episodeMarkers to `disc`. + """ + + 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)) + + if discs or markers or to_remove: + return discs, markers, to_remove + return False diff --git a/libs/guessit/rules/properties/film.py b/libs/guessit/rules/properties/film.py index 8cd0561e4..3c7e6c0ff 100644 --- a/libs/guessit/rules/properties/film.py +++ b/libs/guessit/rules/properties/film.py @@ -7,10 +7,11 @@ from rebulk import Rebulk, AppendMatch, Rule from rebulk.remodule import re from ..common.formatters import cleanup +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def film(): +def film(config): # pylint:disable=unused-argument """ Builder for rebulk object. :return: Created Rebulk object @@ -18,7 +19,8 @@ def film(): """ 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) + 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) @@ -33,7 +35,10 @@ class FilmTitleRule(Rule): properties = {'film_title': [None]} - def when(self, matches, context): + def enabled(self, context): + return not is_disabled(context, 'film_title') + + def when(self, matches, context): # pylint:disable=inconsistent-return-statements bonus_number = matches.named('film', lambda match: not match.private, index=0) if bonus_number: filepath = matches.markers.at_match(bonus_number, lambda marker: marker.name == 'path', 0) diff --git a/libs/guessit/rules/properties/format.py b/libs/guessit/rules/properties/format.py deleted file mode 100644 index 83a9a2f65..000000000 --- a/libs/guessit/rules/properties/format.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -format property -""" -from rebulk.remodule import re - -from rebulk import Rebulk, RemoveMatch, Rule -from ..common import dash -from ..common.validators import seps_before, seps_after - - -def format_(): - """ - Builder for rebulk object. - :return: Created Rebulk object - :rtype: Rebulk - """ - rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) - rebulk.defaults(name="format", tags=['video-codec-prefix', 'streaming_service.suffix']) - - rebulk.regex("VHS", "VHS-?Rip", value="VHS") - rebulk.regex("CAM", "CAM-?Rip", "HD-?CAM", value="Cam") - rebulk.regex("TELESYNC", "TS", "HD-?TS", value="Telesync") - rebulk.regex("WORKPRINT", "WP", value="Workprint") - rebulk.regex("TELECINE", "TC", value="Telecine") - rebulk.regex("PPV", "PPV-?Rip", value="PPV") # Pay Per View - rebulk.regex("SD-?TV", "SD-?TV-?Rip", "Rip-?SD-?TV", "TV-?Rip", - "Rip-?TV", "TV-?(?=Dub)", value="TV") # TV is too common to allow matching - rebulk.regex("DVB-?Rip", "DVB", "PD-?TV", value="DVB") - rebulk.regex("DVD", "DVD-?Rip", "VIDEO-?TS", "DVD-?R(?:$|(?!E))", # "DVD-?R(?:$|^E)" => DVD-Real ... - "DVD-?9", "DVD-?5", value="DVD") - - rebulk.regex("HD-?TV", "TV-?RIP-?HD", "HD-?TV-?RIP", "HD-?RIP", value="HDTV", - conflict_solver=lambda match, other: other if other.name == 'other' else '__default__') - rebulk.regex("VOD", "VOD-?Rip", value="VOD") - rebulk.regex("WEB-?Rip", "WEB-?DL-?Rip", "WEB-?Cap", value="WEBRip") - rebulk.regex("WEB-?DL", "WEB-?HD", "WEB", "DL-?WEB", "DL(?=-?Mux)", value="WEB-DL") - rebulk.regex("HD-?DVD-?Rip", "HD-?DVD", value="HD-DVD") - rebulk.regex("Blu-?ray(?:-?Rip)?", "B[DR]", "B[DR]-?Rip", "BD[59]", "BD25", "BD50", value="BluRay") - rebulk.regex("AHDTV", value="AHDTV") - rebulk.regex('UHD-?TV', 'UHD-?Rip', value='UHDTV', - conflict_solver=lambda match, other: other if other.name == 'other' else '__default__') - rebulk.regex("HDTC", value="HDTC") - rebulk.regex("DSR", "DSR?-?Rip", "SAT-?Rip", "DTH", "DTH-?Rip", value="SATRip") - - rebulk.rules(ValidateFormat) - - return rebulk - - -class ValidateFormat(Rule): - """ - Validate format with screener property, with video_codec property or separated - """ - priority = 64 - consequence = RemoveMatch - - def when(self, matches, context): - ret = [] - for format_match in matches.named('format'): - if not seps_before(format_match) and \ - not matches.range(format_match.start - 1, format_match.start - 2, - lambda match: 'format-prefix' in match.tags): - ret.append(format_match) - continue - if not seps_after(format_match) and \ - not matches.range(format_match.end, format_match.end + 1, - lambda match: 'format-suffix' in match.tags): - ret.append(format_match) - continue - return ret diff --git a/libs/guessit/rules/properties/language.py b/libs/guessit/rules/properties/language.py index af60c6d9d..3f83bc344 100644 --- a/libs/guessit/rules/properties/language.py +++ b/libs/guessit/rules/properties/language.py @@ -11,55 +11,82 @@ import babelfish from rebulk import Rebulk, Rule, RemoveMatch, RenameMatch from rebulk.remodule import re -from ..common.words import iter_words, COMMON_WORDS +from ..common import seps +from ..common.pattern import is_disabled +from ..common.words import iter_words from ..common.validators import seps_surround -def language(): +def language(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() + subtitle_both = config['subtitle_affixes'] + subtitle_prefixes = sorted(subtitle_both + config['subtitle_prefixes'], key=length_comparator) + subtitle_suffixes = sorted(subtitle_both + config['subtitle_suffixes'], key=length_comparator) + lang_both = config['language_affixes'] + lang_prefixes = sorted(lang_both + config['language_prefixes'], key=length_comparator) + lang_suffixes = sorted(lang_both + config['language_suffixes'], key=length_comparator) + weak_affixes = frozenset(config['weak_affixes']) + + rebulk = Rebulk(disabled=lambda context: (is_disabled(context, 'language') and + is_disabled(context, 'subtitle_language'))) rebulk.string(*subtitle_prefixes, name="subtitle_language.prefix", ignore_case=True, private=True, - validator=seps_surround, tags=['release-group-prefix']) + validator=seps_surround, tags=['release-group-prefix'], + disabled=lambda context: is_disabled(context, 'subtitle_language')) rebulk.string(*subtitle_suffixes, name="subtitle_language.suffix", ignore_case=True, private=True, - validator=seps_surround) + validator=seps_surround, + disabled=lambda context: is_disabled(context, 'subtitle_language')) rebulk.string(*lang_suffixes, name="language.suffix", ignore_case=True, private=True, - validator=seps_surround, tags=['format-suffix']) - rebulk.functional(find_languages, properties={'language': [None]}) - rebulk.rules(SubtitlePrefixLanguageRule, SubtitleSuffixLanguageRule, SubtitleExtensionRule) + validator=seps_surround, tags=['source-suffix'], + disabled=lambda context: is_disabled(context, 'language')) - return rebulk + def find_languages(string, context=None): + """Find languages in the string + :return: list of tuple (property, Language, lang_word, word) + """ + return LanguageFinder(context, subtitle_prefixes, subtitle_suffixes, + lang_prefixes, lang_suffixes, weak_affixes).find(string) -COMMON_WORDS_STRICT = frozenset(['brazil']) + rebulk.functional(find_languages, + properties={'language': [None]}, + disabled=lambda context: not context.get('allowed_languages')) + rebulk.rules(SubtitleExtensionRule, + SubtitlePrefixLanguageRule, + SubtitleSuffixLanguageRule, + RemoveLanguage, + RemoveInvalidLanguages(common_words)) -UNDETERMINED = babelfish.Language('und') + babelfish.language_converters['guessit'] = GuessitConverter(config['synonyms']) -SYN = {('ell', None): ['gr', 'greek'], - ('spa', None): ['esp', 'español', 'espanol'], - ('fra', None): ['français', 'vf', 'vff', 'vfi', 'vfq'], - ('swe', None): ['se'], - ('por', 'BR'): ['po', 'pb', 'pob', 'ptbr', 'br', 'brazilian'], - ('cat', None): ['català', 'castellano', 'espanol castellano', 'español castellano'], - ('ces', None): ['cz'], - ('ukr', None): ['ua'], - ('zho', None): ['cn'], - ('jpn', None): ['jp'], - ('hrv', None): ['scr'], - ('mul', None): ['multi', 'dl']} # http://scenelingo.wordpress.com/2009/03/24/what-does-dl-mean/ + return rebulk + + +UNDETERMINED = babelfish.Language('und') +MULTIPLE = babelfish.Language('mul') +NON_SPECIFIC_LANGUAGES = frozenset([UNDETERMINED, MULTIPLE]) class GuessitConverter(babelfish.LanguageReverseConverter): # pylint: disable=missing-docstring _with_country_regexp = re.compile(r'(.*)\((.*)\)') _with_country_regexp2 = re.compile(r'(.*)-(.*)') - def __init__(self): + def __init__(self, synonyms): self.guessit_exceptions = {} - for (alpha3, country), synlist in SYN.items(): + for code, synlist in synonyms.items(): + if '_' in code: + (alpha3, country) = code.split('_') + else: + (alpha3, country) = (code, None) for syn in synlist: self.guessit_exceptions[syn.lower()] = (alpha3, country, None) @@ -76,15 +103,7 @@ class GuessitConverter(babelfish.LanguageReverseConverter): # pylint: disable=m return str(babelfish.Language(alpha3, country, script)) def reverse(self, name): # pylint:disable=arguments-differ - with_country = (GuessitConverter._with_country_regexp.match(name) or - GuessitConverter._with_country_regexp2.match(name)) - name = name.lower() - if with_country: - lang = babelfish.Language.fromguessit(with_country.group(1).strip()) - lang.country = babelfish.Country.fromguessit(with_country.group(2).strip()) - return lang.alpha3, lang.country.alpha2 if lang.country else None, lang.script or None - # exceptions come first, as they need to override a potential match # with any of the other guessers try: @@ -96,7 +115,8 @@ class GuessitConverter(babelfish.LanguageReverseConverter): # pylint: disable=m babelfish.Language.fromalpha3b, babelfish.Language.fromalpha2, babelfish.Language.fromname, - babelfish.Language.fromopensubtitles]: + babelfish.Language.fromopensubtitles, + babelfish.Language.fromietf]: try: reverse = conv(name) return reverse.alpha3, reverse.country, reverse.script @@ -113,24 +133,6 @@ def length_comparator(value): return len(value) -babelfish.language_converters['guessit'] = GuessitConverter() - - -subtitle_both = ['sub', 'subs', 'subbed', 'custom subbed', 'custom subs', - 'custom sub', 'customsubbed', 'customsubs', 'customsub', - 'soft subtitles', 'soft subs'] -subtitle_prefixes = sorted(subtitle_both + - ['st', 'vost', 'subforced', 'fansub', 'hardsub', - 'legenda', 'legendas', 'legendado', 'subtitulado', - 'soft', 'subtitles'], key=length_comparator) -subtitle_suffixes = sorted(subtitle_both + - ['subforced', 'fansub', 'hardsub'], key=length_comparator) -lang_both = ['dublado', 'dubbed', 'dub'] -lang_suffixes = sorted(lang_both + ['audio'], key=length_comparator) -lang_prefixes = sorted(lang_both + ['true'], key=length_comparator) - -weak_prefixes = ('audio', 'true') - _LanguageMatch = namedtuple('_LanguageMatch', ['property_name', 'word', 'lang']) @@ -149,7 +151,7 @@ class LanguageWord(object): self.next_word = next_word @property - def extended_word(self): + def extended_word(self): # pylint:disable=inconsistent-return-statements """ Return the extended word for this instance, if any. """ @@ -175,10 +177,17 @@ def to_rebulk_match(language_match): end = word.end name = language_match.property_name if language_match.lang == UNDETERMINED: - return start, end, dict(name=name, value=word.value.lower(), - formatter=babelfish.Language, tags=['weak-language']) + return start, end, { + 'name': name, + 'value': word.value.lower(), + 'formatter': babelfish.Language, + 'tags': ['weak-language'] + } - return start, end, dict(name=name, value=language_match.lang) + return start, end, { + 'name': name, + 'value': language_match.lang + } class LanguageFinder(object): @@ -186,10 +195,21 @@ class LanguageFinder(object): Helper class to search and return language matches: 'language' and 'subtitle_language' properties """ - def __init__(self, allowed_languages): - self.parsed = dict() - self.allowed_languages = allowed_languages - self.common_words = COMMON_WORDS_STRICT if allowed_languages else COMMON_WORDS + def __init__(self, context, + subtitle_prefixes, subtitle_suffixes, + lang_prefixes, lang_suffixes, weak_affixes): + allowed_languages = context.get('allowed_languages') if context else None + self.allowed_languages = {l.lower() for l in allowed_languages or []} + self.weak_affixes = weak_affixes + 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): """ @@ -250,11 +270,11 @@ class LanguageFinder(object): """ tuples = [ (language_word, language_word.next_word, - dict(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, - dict(subtitle_language=subtitle_suffixes, language=lang_suffixes), + self.suffixes_map, lambda string, suffix: string.endswith(suffix), lambda string, suffix: string[:len(string) - len(suffix)]) ] @@ -271,7 +291,7 @@ class LanguageFinder(object): if match: yield match - def find_match_for_word(self, word, fallback_word, affixes, is_affix, strip_affix): + def find_match_for_word(self, word, fallback_word, affixes, is_affix, strip_affix): # pylint:disable=inconsistent-return-statements """ Return the language match for the given word and affixes. """ @@ -280,8 +300,6 @@ class LanguageFinder(object): continue word_lang = current_word.value.lower() - if word_lang in self.common_words: - continue for key, parts in affixes.items(): for part in parts: @@ -291,30 +309,31 @@ class LanguageFinder(object): match = None value = strip_affix(word_lang, part) if not value: - if fallback_word: - match = self.find_language_match_for_word(fallback_word, key=key, force=True) + if fallback_word and ( + abs(fallback_word.start - word.end) <= 1 or abs(word.start - fallback_word.end) <= 1): + match = self.find_language_match_for_word(fallback_word, key=key) - if not match and part not in weak_prefixes: + if not match and part not in self.weak_affixes: match = self.create_language_match(key, LanguageWord(current_word.start, current_word.end, 'und', current_word.input_string)) - elif value not in self.common_words: + else: match = self.create_language_match(key, LanguageWord(current_word.start, current_word.end, value, current_word.input_string)) if match: return match - def find_language_match_for_word(self, word, key='language', force=False): + def find_language_match_for_word(self, word, key='language'): # pylint:disable=inconsistent-return-statements """ Return the language match for the given word. """ for current_word in (word.extended_word, word): - if current_word and (force or current_word.value.lower() not in self.common_words): + if current_word: match = self.create_language_match(key, current_word) if match: return match - def create_language_match(self, key, word): + def create_language_match(self, key, word): # pylint:disable=inconsistent-return-statements """ Create a LanguageMatch for a given word """ @@ -323,40 +342,21 @@ class LanguageFinder(object): if lang is not None: return _LanguageMatch(property_name=key, word=word, lang=lang) - def parse_language(self, lang_word): + def parse_language(self, lang_word): # pylint:disable=inconsistent-return-statements """ Parse the lang_word into a valid Language. Multi and Undetermined languages are also valid languages. """ - if lang_word in self.parsed: - return self.parsed[lang_word] - try: lang = babelfish.Language.fromguessit(lang_word) - if self.allowed_languages: - if (hasattr(lang, 'name') and lang.name.lower() in self.allowed_languages) \ - or (hasattr(lang, 'alpha2') and lang.alpha2.lower() in self.allowed_languages) \ - or lang.alpha3.lower() in self.allowed_languages: - self.parsed[lang_word] = lang - return lang - # Keep language with alpha2 equivalent. Others are probably - # uncommon languages. - elif lang in ('mul', UNDETERMINED) or hasattr(lang, 'alpha2'): - self.parsed[lang_word] = lang + if ((hasattr(lang, 'name') and lang.name.lower() in self.allowed_languages) or + (hasattr(lang, 'alpha2') and lang.alpha2.lower() in self.allowed_languages) or + lang.alpha3.lower() in self.allowed_languages): return lang - self.parsed[lang_word] = None except babelfish.Error: - self.parsed[lang_word] = None - - -def find_languages(string, context=None): - """Find languages in the string - - :return: list of tuple (property, Language, lang_word, word) - """ - return LanguageFinder(context.get('allowed_languages')).find(string) + pass class SubtitlePrefixLanguageRule(Rule): @@ -367,6 +367,9 @@ class SubtitlePrefixLanguageRule(Rule): properties = {'subtitle_language': [None]} + def enabled(self, context): + return not is_disabled(context, 'subtitle_language') + def when(self, matches, context): to_rename = [] to_remove = matches.named('subtitle_language.prefix') @@ -387,7 +390,9 @@ class SubtitlePrefixLanguageRule(Rule): to_remove.extend(matches.conflicting(lang)) if prefix in to_remove: to_remove.remove(prefix) - return to_rename, to_remove + if to_rename or to_remove: + return to_rename, to_remove + return False def then(self, matches, when_response, context): to_rename, to_remove = when_response @@ -412,6 +417,9 @@ class SubtitleSuffixLanguageRule(Rule): properties = {'subtitle_language': [None]} + def enabled(self, context): + return not is_disabled(context, 'subtitle_language') + def when(self, matches, context): to_append = [] to_remove = matches.named('subtitle_language.suffix') @@ -421,7 +429,9 @@ class SubtitleSuffixLanguageRule(Rule): to_append.append(lang) if suffix in to_remove: to_remove.remove(suffix) - return to_append, to_remove + if to_append or to_remove: + return to_append, to_remove + return False def then(self, matches, when_response, context): to_rename, to_remove = when_response @@ -436,17 +446,65 @@ class SubtitleExtensionRule(Rule): """ Convert language guess as subtitle_language if next match is a subtitle extension. - Since it's a strong match, it also removes any conflicting format with it. + Since it's a strong match, it also removes any conflicting source with it. """ consequence = [RemoveMatch, RenameMatch('subtitle_language')] properties = {'subtitle_language': [None]} - def when(self, matches, context): + def enabled(self, context): + return not is_disabled(context, 'subtitle_language') + + def when(self, matches, context): # pylint:disable=inconsistent-return-statements subtitle_extension = matches.named('container', lambda match: 'extension' in match.tags and 'subtitle' in match.tags, 0) if subtitle_extension: subtitle_lang = matches.previous(subtitle_extension, lambda match: match.name == 'language', 0) if subtitle_lang: - return matches.conflicting(subtitle_lang, lambda m: m.name == 'format'), subtitle_lang + for weak in matches.named('subtitle_language', predicate=lambda m: 'weak-language' in m.tags): + weak.private = True + + 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') + + +class RemoveInvalidLanguages(Rule): + """Remove language matches that matches the blacklisted common words.""" + + consequence = RemoveMatch + priority = 32 + + def __init__(self, common_words): + """Constructor.""" + super(RemoveInvalidLanguages, self).__init__() + self.common_words = common_words + + def when(self, matches, context): + to_remove = [] + for match in matches.range(0, len(matches.input_string), + predicate=lambda m: m.name in ('language', 'subtitle_language')): + if match.raw.lower() not in self.common_words: + continue + + group = matches.markers.at_match(match, index=0, predicate=lambda m: m.name == 'group') + if group and ( + not matches.range( + group.start, group.end, predicate=lambda m: m.name not in ('language', 'subtitle_language') + ) and (not matches.holes(group.start, group.end, predicate=lambda m: m.value.strip(seps)))): + continue + + to_remove.append(match) + + return to_remove diff --git a/libs/guessit/rules/properties/mimetype.py b/libs/guessit/rules/properties/mimetype.py index c57ada778..f9e642ffa 100644 --- a/libs/guessit/rules/properties/mimetype.py +++ b/libs/guessit/rules/properties/mimetype.py @@ -8,16 +8,23 @@ import mimetypes from rebulk import Rebulk, CustomRule, POST_PROCESS from rebulk.match import Match +from ..common.pattern import is_disabled from ...rules.processors import Processors -def mimetype(): +def mimetype(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(Mimetype) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'mimetype')) + rebulk.rules(Mimetype) + + return rebulk class Mimetype(CustomRule): diff --git a/libs/guessit/rules/properties/other.py b/libs/guessit/rules/properties/other.py index 5fead16ef..c7dc9a88e 100644 --- a/libs/guessit/rules/properties/other.py +++ b/libs/guessit/rules/properties/other.py @@ -5,38 +5,55 @@ other property """ import copy -from rebulk import Rebulk, Rule, RemoveMatch, POST_PROCESS, AppendMatch +from rebulk import Rebulk, Rule, RemoveMatch, RenameMatch, POST_PROCESS, AppendMatch from rebulk.remodule import re from ..common import dash from ..common import seps -from ..common.validators import seps_after, seps_before, seps_surround, compose +from ..common.pattern import is_disabled +from ..common.validators import seps_after, seps_before, seps_surround, and_ from ...reutils import build_or_pattern from ...rules.common.formatters import raw_cleanup -def other(): +def other(config): # pylint:disable=unused-argument,too-many-statements """ 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) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'other')) + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]).string_defaults(ignore_case=True) rebulk.defaults(name="other", validator=seps_surround) - rebulk.regex('Audio-?Fix', 'Audio-?Fixed', value='AudioFix') - rebulk.regex('Sync-?Fix', 'Sync-?Fixed', value='SyncFix') - rebulk.regex('Dual', 'Dual-?Audio', value='DualAudio') - rebulk.regex('ws', 'wide-?screen', value='WideScreen') - rebulk.regex('Re-?Enc(?:oded)?', value='ReEncoded') + rebulk.regex('Audio-?Fix', 'Audio-?Fixed', value='Audio Fixed') + rebulk.regex('Sync-?Fix', 'Sync-?Fixed', value='Sync Fixed') + rebulk.regex('Dual', 'Dual-?Audio', value='Dual Audio') + rebulk.regex('ws', 'wide-?screen', value='Widescreen') + rebulk.regex('Re-?Enc(?:oded)?', value='Reencoded') + + rebulk.string('Repack', 'Rerip', value='Proper', + tags=['streaming_service.prefix', 'streaming_service.suffix']) + rebulk.string('Proper', value='Proper', + tags=['has-neighbor', 'streaming_service.prefix', 'streaming_service.suffix']) + + rebulk.regex('Real-Proper', 'Real-Repack', 'Real-Rerip', value='Proper', + tags=['streaming_service.prefix', 'streaming_service.suffix', 'real']) + rebulk.regex('Real', value='Proper', + tags=['has-neighbor', 'streaming_service.prefix', 'streaming_service.suffix', 'real']) - rebulk.string('Real', 'Fix', 'Fixed', value='Proper', tags=['has-neighbor-before', 'has-neighbor-after']) - rebulk.string('Proper', 'Repack', 'Rerip', 'Dirfix', 'Nfofix', 'Prooffix', value='Proper', + rebulk.string('Fix', 'Fixed', value='Fix', tags=['has-neighbor-before', 'has-neighbor-after', + 'streaming_service.prefix', 'streaming_service.suffix']) + rebulk.string('Dirfix', 'Nfofix', 'Prooffix', value='Fix', tags=['streaming_service.prefix', 'streaming_service.suffix']) - rebulk.regex('(?:Proof-?)?Sample-?Fix', value='Proper', + rebulk.regex('(?:Proof-?)?Sample-?Fix', value='Fix', tags=['streaming_service.prefix', 'streaming_service.suffix']) - rebulk.string('Fansub', value='Fansub', tags='has-neighbor') - rebulk.string('Fastsub', value='Fastsub', tags='has-neighbor') + + rebulk.string('Fansub', value='Fan Subtitled', tags='has-neighbor') + rebulk.string('Fastsub', value='Fast Subtitled', tags='has-neighbor') season_words = build_or_pattern(["seasons?", "series?"]) complete_articles = build_or_pattern(["The"]) @@ -60,30 +77,42 @@ def other(): private_names=['completeArticle', 'completeWordsBefore', 'completeWordsAfter'], value={'other': 'Complete'}, tags=['release-group-prefix'], - validator={'__parent__': compose(seps_surround, validate_complete)}) - rebulk.string('R5', 'RC', value='R5') + validator={'__parent__': and_(seps_surround, validate_complete)}) + rebulk.string('R5', value='Region 5') + rebulk.string('RC', value='Region C') rebulk.regex('Pre-?Air', value='Preair') - rebulk.regex('(?:PS-?)?Vita', value='PS Vita') + rebulk.regex('(?:PS-?)Vita', value='PS Vita') + rebulk.regex('Vita', value='PS Vita', tags='has-neighbor') + rebulk.regex('(HD)(?P<another>Rip)', value={'other': 'HD', 'another': 'Rip'}, + private_parent=True, children=True, validator={'__parent__': seps_surround}, validate_all=True) - for value in ( - 'Screener', 'Remux', '3D', 'mHD', 'HDLight', 'HQ', 'DDC', 'HR', 'PAL', 'SECAM', 'NTSC', - 'CC', 'LD', 'MD', 'XXX'): + for value in ('Screener', 'Remux', 'PAL', 'SECAM', 'NTSC', 'XXX'): rebulk.string(value, value=value) - - rebulk.string('LDTV', value='LD') + rebulk.string('3D', value='3D', tags='has-neighbor') + + rebulk.string('HQ', value='High Quality', tags='uhdbluray-neighbor') + rebulk.string('HR', value='High Resolution') + rebulk.string('LD', value='Line Dubbed') + rebulk.string('MD', value='Mic Dubbed') + rebulk.string('mHD', 'HDLight', value='Micro HD') + rebulk.string('LDTV', value='Low Definition') + rebulk.string('HFR', value='High Frame Rate') + rebulk.string('VFR', value='Variable Frame Rate') rebulk.string('HD', value='HD', validator=None, tags=['streaming_service.prefix', 'streaming_service.suffix']) - rebulk.regex('Full-?HD', 'FHD', value='FullHD', validator=None, + rebulk.regex('Full-?HD', 'FHD', value='Full HD', validator=None, tags=['streaming_service.prefix', 'streaming_service.suffix']) - rebulk.regex('Ultra-?(?:HD)?', 'UHD', value='UltraHD', validator=None, + rebulk.regex('Ultra-?(?:HD)?', 'UHD', value='Ultra HD', validator=None, tags=['streaming_service.prefix', 'streaming_service.suffix']) + rebulk.regex('Upscaled?', value='Upscaled') - for value in ('Complete', 'Classic', 'LiNE', 'Bonus', 'Trailer', 'FINAL', 'Retail', + for value in ('Complete', 'Classic', 'Bonus', 'Trailer', 'Retail', 'Colorized', 'Internal'): rebulk.string(value, value=value, tags=['has-neighbor', 'release-group-prefix']) + rebulk.regex('LiNE', value='Line Audio', tags=['has-neighbor-before', 'has-neighbor-after', 'release-group-prefix']) rebulk.regex('Read-?NFO', value='Read NFO') rebulk.string('CONVERT', value='Converted', tags='has-neighbor') - rebulk.string('DOCU', value='Documentary', tags='has-neighbor') + rebulk.string('DOCU', 'DOKU', value='Documentary', tags='has-neighbor') rebulk.string('OM', value='Open Matte', tags='has-neighbor') rebulk.string('STV', value='Straight to Video', tags='has-neighbor') rebulk.string('OAR', value='Original Aspect Ratio', tags='has-neighbor') @@ -92,16 +121,30 @@ def other(): for coast in ('East', 'West'): rebulk.regex(r'(?:Live-)?(?:Episode-)?' + coast + '-?(?:Coast-)?Feed', value=coast + ' Coast Feed') - rebulk.string('VO', 'OV', value='OV', tags='has-neighbor') + rebulk.string('VO', 'OV', value='Original Video', tags='has-neighbor') + rebulk.string('Ova', 'Oav', value='Original Animated Video') rebulk.regex('Scr(?:eener)?', value='Screener', validator=None, - tags=['other.validate.screener', 'format-prefix', 'format-suffix']) + tags=['other.validate.screener', 'source-prefix', 'source-suffix']) rebulk.string('Mux', value='Mux', validator=seps_after, - tags=['other.validate.mux', 'video-codec-prefix', 'format-suffix']) - rebulk.string('HC', value='Hardcoded Subtitles') + tags=['other.validate.mux', 'video-codec-prefix', 'source-suffix']) + rebulk.string('HC', 'vost', value='Hardcoded Subtitles') - rebulk.rules(ValidateHasNeighbor, ValidateHasNeighborAfter, ValidateHasNeighborBefore, ValidateScreenerRule, - ValidateMuxRule, ValidateHardcodedSubs, ValidateStreamingServiceNeighbor, ProperCountRule) + rebulk.string('SDR', value='Standard Dynamic Range', tags='uhdbluray-neighbor') + rebulk.regex('HDR(?:10)?', value='HDR10', tags='uhdbluray-neighbor') + rebulk.regex('Dolby-?Vision', value='Dolby Vision', tags='uhdbluray-neighbor') + rebulk.regex('BT-?2020', value='BT.2020', tags='uhdbluray-neighbor') + + rebulk.string('Sample', value='Sample', tags=['at-end', 'not-a-release-group']) + rebulk.string('Extras', value='Extras', tags='has-neighbor') + rebulk.regex('Digital-?Extras?', value='Extras') + rebulk.string('Proof', value='Proof', tags=['at-end', 'not-a-release-group']) + rebulk.string('Obfuscated', 'Scrambled', value='Obfuscated', tags=['at-end', 'not-a-release-group']) + rebulk.string('xpost', 'postbot', 'asrequested', value='Repost', tags='not-a-release-group') + + rebulk.rules(RenameAnotherToOther, ValidateHasNeighbor, ValidateHasNeighborAfter, ValidateHasNeighborBefore, + ValidateScreenerRule, ValidateMuxRule, ValidateHardcodedSubs, ValidateStreamingServiceNeighbor, + ValidateAtEnd, ValidateReal, ProperCountRule) return rebulk @@ -116,7 +159,7 @@ class ProperCountRule(Rule): properties = {'proper_count': [None]} - def when(self, matches, context): + def when(self, matches, context): # pylint:disable=inconsistent-return-statements propers = matches.named('other', lambda match: match.value == 'Proper') if propers: raws = {} # Count distinct raw values @@ -124,15 +167,32 @@ class ProperCountRule(Rule): raws[raw_cleanup(proper.raw)] = proper proper_count_match = copy.copy(propers[-1]) proper_count_match.name = 'proper_count' - proper_count_match.value = len(raws) + + value = 0 + for raw in raws.values(): + value += 2 if 'real' in raw.tags else 1 + + proper_count_match.value = value return proper_count_match +class RenameAnotherToOther(Rule): + """ + Rename `another` properties to `other` + """ + priority = 32 + consequence = RenameMatch('other') + + def when(self, matches, context): + return matches.named('another') + + class ValidateHasNeighbor(Rule): """ Validate tag has-neighbor """ consequence = RemoveMatch + priority = 64 def when(self, matches, context): ret = [] @@ -158,6 +218,7 @@ class ValidateHasNeighborBefore(Rule): Validate tag has-neighbor-before that previous match exists. """ consequence = RemoveMatch + priority = 64 def when(self, matches, context): ret = [] @@ -177,6 +238,7 @@ class ValidateHasNeighborAfter(Rule): Validate tag has-neighbor-after that next match exists. """ consequence = RemoveMatch + priority = 64 def when(self, matches, context): ret = [] @@ -201,8 +263,8 @@ class ValidateScreenerRule(Rule): def when(self, matches, context): ret = [] for screener in matches.named('other', lambda match: 'other.validate.screener' in match.tags): - format_match = matches.previous(screener, lambda match: match.name == 'format', 0) - if not format_match or matches.input_string[format_match.end:screener.start].strip(seps): + source_match = matches.previous(screener, lambda match: match.initiator.name == 'source', 0) + if not source_match or matches.input_string[source_match.end:screener.start].strip(seps): ret.append(screener) return ret @@ -217,8 +279,8 @@ class ValidateMuxRule(Rule): def when(self, matches, context): ret = [] for mux in matches.named('other', lambda match: 'other.validate.mux' in match.tags): - format_match = matches.previous(mux, lambda match: match.name == 'format', 0) - if not format_match: + source_match = matches.previous(mux, lambda match: match.initiator.name == 'source', 0) + if not source_match: ret.append(mux) return ret @@ -257,16 +319,18 @@ class ValidateStreamingServiceNeighbor(Rule): def when(self, matches, context): to_remove = [] for match in matches.named('other', - predicate=lambda m: ('streaming_service.prefix' in m.tags or - 'streaming_service.suffix' in m.tags)): - + predicate=lambda m: (m.initiator.name != 'source' + and ('streaming_service.prefix' in m.tags + or 'streaming_service.suffix' in m.tags))): + match = match.initiator if not seps_after(match): if 'streaming_service.prefix' in match.tags: next_match = matches.next(match, lambda m: m.name == 'streaming_service', 0) if next_match and not matches.holes(match.end, next_match.start, predicate=lambda m: m.value.strip(seps)): continue - + if match.children: + to_remove.extend(match.children) to_remove.append(match) elif not seps_before(match): @@ -276,6 +340,44 @@ class ValidateStreamingServiceNeighbor(Rule): predicate=lambda m: m.value.strip(seps)): continue + if match.children: + to_remove.extend(match.children) to_remove.append(match) return to_remove + + +class ValidateAtEnd(Rule): + """Validate other which should occur at the end of a filepart.""" + + priority = 32 + consequence = RemoveMatch + + def when(self, matches, context): + to_remove = [] + for filepart in matches.markers.named('path'): + for match in matches.range(filepart.start, filepart.end, + predicate=lambda m: m.name == 'other' and 'at-end' in m.tags): + if (matches.holes(match.end, filepart.end, predicate=lambda m: m.value.strip(seps)) or + matches.range(match.end, filepart.end, predicate=lambda m: m.name not in ( + 'other', 'container'))): + to_remove.append(match) + + return to_remove + + +class ValidateReal(Rule): + """ + Validate Real + """ + consequence = RemoveMatch + priority = 64 + + def when(self, matches, context): + ret = [] + for filepart in matches.markers.named('path'): + for match in matches.range(filepart.start, filepart.end, lambda m: m.name == 'other' and 'real' in m.tags): + if not matches.range(filepart.start, match.start): + ret.append(match) + + return ret diff --git a/libs/guessit/rules/properties/part.py b/libs/guessit/rules/properties/part.py index d274f7fbb..c1123394d 100644 --- a/libs/guessit/rules/properties/part.py +++ b/libs/guessit/rules/properties/part.py @@ -7,20 +7,25 @@ from rebulk.remodule import re from rebulk import Rebulk from ..common import dash -from ..common.validators import seps_surround, int_coercable, compose +from ..common.pattern import is_disabled +from ..common.validators import seps_surround, int_coercable, and_ from ..common.numeral import numeral, parse_numeral from ...reutils import build_or_pattern -def part(): +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().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'part')) + rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) - prefixes = ['pt', 'part'] + prefixes = config['prefixes'] def validate_roman(match): """ @@ -36,6 +41,6 @@ def part(): 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)}) + validator={'part': and_(validate_roman, lambda m: 0 < m.value < 100)}) return rebulk diff --git a/libs/guessit/rules/properties/release_group.py b/libs/guessit/rules/properties/release_group.py index ace3f0eb3..ecff808b4 100644 --- a/libs/guessit/rules/properties/release_group.py +++ b/libs/guessit/rules/properties/release_group.py @@ -6,22 +6,53 @@ release_group property import copy 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 from ..properties.title import TitleFromPosition -def release_group(): +def release_group(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ - rebulk = 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.strip() + + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'release_group')) expected_group = build_expected_function('expected_group') @@ -30,42 +61,142 @@ def release_group(): conflict_solver=lambda match, other: other, disabled=lambda context: not context.get('expected_group')) - return rebulk.rules(SceneReleaseGroup, AnimeReleaseGroup) + return rebulk.rules( + DashSeparatedReleaseGroup(clean_groupname), + SceneReleaseGroup(clean_groupname), + AnimeReleaseGroup + ) -forbidden_groupnames = ['rip', 'by', 'for', 'par', 'pour', 'bonus'] +_scene_previous_names = ('video_codec', 'source', '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',) -groupname_ignore_seps = '[]{}()' -groupname_seps = ''.join([c for c in seps if c not in groupname_ignore_seps]) +_scene_no_previous_tags = ('no-release-group-prefix',) -def clean_groupname(string): +class DashSeparatedReleaseGroup(Rule): """ - Removes and strip separators from input_string - :param string: - :type string: - :return: - :rtype: + Detect dash separated release groups that might appear at the end or at the beginning of a release name. + + Series.S01E02.Pilot.DVDRip.x264-CS.mkv + release_group: CS + abc-the.title.name.1983.1080p.bluray.x264.mkv + release_group: abc + + At the end: Release groups should be dash-separated and shouldn't contain spaces nor + appear in a group with other matches. The preceding matches should be separated by dot. + If a release group is found, the conflicting matches are removed. + + At the beginning: Release groups should be dash-separated and shouldn't contain spaces nor appear in a group. + It should be followed by a hole with dot-separated words. + Detection only happens if no matches exist at the beginning. """ - 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'] + consequence = [RemoveMatch, AppendMatch] + + def __init__(self, value_formatter): + """Default constructor.""" + super(DashSeparatedReleaseGroup, self).__init__() + self.value_formatter = value_formatter + + @classmethod + def is_valid(cls, matches, candidate, start, end, at_end): # pylint:disable=inconsistent-return-statements + """ + Whether a candidate is a valid release group. + """ + if not at_end: + if len(candidate.value) <= 1: + return False + + if matches.markers.at_match(candidate, predicate=lambda m: m.name == 'group'): + return False + + first_hole = matches.holes(candidate.end, end, predicate=lambda m: m.start == candidate.end, index=0) + if not first_hole: + return False + + raw_value = first_hole.raw + return raw_value[0] == '-' and '-' not in raw_value[1:] and '.' in raw_value and ' ' not in raw_value + + group = matches.markers.at_match(candidate, predicate=lambda m: m.name == 'group', index=0) + if group and matches.at_match(group, predicate=lambda m: not m.private and m.span != candidate.span): + return False + + count = 0 + match = candidate + while match: + current = matches.range(start, + match.start, + index=-1, + predicate=lambda m: not m.private and not 'expected' in m.tags) + if not current: + break + + separator = match.input_string[current.end:match.start] + if not separator and match.raw[0] == '-': + separator = '-' + + match = current + + if count == 0: + if separator != '-': + break + + count += 1 + continue + + if separator == '.': + return True + + def detect(self, matches, start, end, at_end): # pylint:disable=inconsistent-return-statements + """ + Detect release group at the end or at the beginning of a filepart. + """ + candidate = None + if at_end: + container = matches.ending(end, lambda m: m.name == 'container', index=0) + if container: + end = container.start + + candidate = matches.ending(end, index=0, predicate=( + lambda m: not m.private and not ( + m.name == 'other' and 'not-a-release-group' in m.tags + ) and '-' not in m.raw and m.raw.strip() == m.raw)) + + if not candidate: + if at_end: + candidate = matches.holes(start, end, seps=seps, index=-1, + predicate=lambda m: m.end == end and m.raw.strip(seps) and m.raw[0] == '-') + else: + candidate = matches.holes(start, end, seps=seps, index=0, + predicate=lambda m: m.start == start and m.raw.strip(seps)) + + if candidate and self.is_valid(matches, candidate, start, end, at_end): + return candidate + + def when(self, matches, context): # pylint:disable=inconsistent-return-statements + if matches.named('release_group'): + return + + to_remove = [] + to_append = [] + for filepart in matches.markers.named('path'): + candidate = self.detect(matches, filepart.start, filepart.end, True) + if candidate: + to_remove.extend(matches.at_match(candidate)) + else: + candidate = self.detect(matches, filepart.start, filepart.end, False) + + if candidate: + releasegroup = Match(candidate.start, candidate.end, name='release_group', + formatter=self.value_formatter, input_string=candidate.input_string) -_scene_previous_tags = ['release-group-prefix'] + if releasegroup.value: + to_append.append(releasegroup) + if to_remove or to_append: + return to_remove, to_append class SceneReleaseGroup(Rule): @@ -79,7 +210,23 @@ class SceneReleaseGroup(Rule): properties = {'release_group': [None]} - def when(self, matches, context): + def __init__(self, value_formatter): + """Default constructor.""" + 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. ret = [] @@ -87,6 +234,8 @@ class SceneReleaseGroup(Rule): for filepart in marker_sorted(matches.markers.named('path'), matches): # pylint:disable=cell-var-from-loop start, end = filepart.span + if matches.named('release_group', predicate=lambda m: m.start >= start and m.end <= end): + continue titles = matches.named('title', predicate=lambda m: m.start >= start and m.end <= end) @@ -101,7 +250,7 @@ class SceneReleaseGroup(Rule): """ return match in titles[1:] - last_hole = matches.holes(start, end + 1, formatter=clean_groupname, + last_hole = matches.holes(start, end + 1, formatter=self.value_formatter, ignore=keep_only_first_title, predicate=lambda hole: cleanup(hole.value), index=-1) @@ -118,13 +267,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)): @@ -134,7 +282,7 @@ class SceneReleaseGroup(Rule): # 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 + group.formatter = self.value_formatter if group.value == last_hole.value: last_hole.start = group.start + 1 last_hole.end = group.end - 1 @@ -165,11 +313,11 @@ class AnimeReleaseGroup(Rule): # If a release_group is found before, ignore this kind of release_group rule. if matches.named('release_group'): - return + return False 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 + return False for filepart in marker_sorted(matches.markers.named('path'), matches): @@ -193,4 +341,7 @@ class AnimeReleaseGroup(Rule): 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 + + if to_remove or to_append: + return to_remove, to_append + return False diff --git a/libs/guessit/rules/properties/screen_size.py b/libs/guessit/rules/properties/screen_size.py index b7732ab61..77d5d0521 100644 --- a/libs/guessit/rules/properties/screen_size.py +++ b/libs/guessit/rules/properties/screen_size.py @@ -3,67 +3,115 @@ """ screen_size property """ +from rebulk.match import Match from rebulk.remodule import re -from rebulk import Rebulk, Rule, RemoveMatch +from rebulk import Rebulk, Rule, RemoveMatch, AppendMatch + +from ..common.pattern import is_disabled +from ..common.quantity import FrameRate from ..common.validators import seps_surround from ..common import dash, seps +from ...reutils import build_or_pattern -def screen_size(): +def screen_size(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ - def conflict_solver(match, other): - """ - Conflict solver for most screen_size. - """ - if other.name == 'screen_size': - if 'resolution' in other.tags: - # The chtouile to solve conflict in "720 x 432" string matching both 720p pattern - int_value = _digits_re.findall(match.raw)[-1] - if other.value.startswith(int_value): - return match - return other - return '__default__' - - rebulk = Rebulk().string_defaults(ignore_case=True).regex_defaults(flags=re.IGNORECASE) - rebulk.defaults(name="screen_size", validator=seps_surround, conflict_solver=conflict_solver) - - rebulk.regex(r"(?:\d{3,}(?:x|\*))?360(?:i|p?x?)", value="360p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?368(?:i|p?x?)", value="368p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?480(?:i|p?x?)", value="480p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?576(?:i|p?x?)", value="576p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?720(?:i|p?(?:50|60)?x?)", value="720p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?720(?:p(?:50|60)?x?)", value="720p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?720p?hd", value="720p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?900(?:i|p?x?)", value="900p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?1080i", value="1080i") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?1080p?x?", value="1080p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?1080(?:p(?:50|60)?x?)", value="1080p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?1080p?hd", value="1080p") - rebulk.regex(r"(?:\d{3,}(?:x|\*))?2160(?:i|p?x?)", value="4K") - rebulk.string('4k', value='4K') - - _digits_re = re.compile(r'\d+') - - rebulk.defaults(name="screen_size", validator=seps_surround) - rebulk.regex(r'\d{3,}-?(?:x|\*)-?\d{3,}', - formatter=lambda value: 'x'.join(_digits_re.findall(value)), - abbreviations=[dash], - tags=['resolution'], + interlaced = frozenset(config['interlaced']) + progressive = frozenset(config['progressive']) + frame_rates = [re.escape(rate) for rate in config['frame_rates']] + min_ar = config['min_ar'] + max_ar = config['max_ar'] + + rebulk = Rebulk() + rebulk = rebulk.string_defaults(ignore_case=True).regex_defaults(flags=re.IGNORECASE) + + rebulk.defaults(name='screen_size', validator=seps_surround, abbreviations=[dash], + disabled=lambda context: is_disabled(context, 'screen_size')) + + frame_rate_pattern = build_or_pattern(frame_rates, name='frame_rate') + interlaced_pattern = build_or_pattern(interlaced, name='height') + progressive_pattern = build_or_pattern(progressive, name='height') + + res_pattern = r'(?:(?P<width>\d{3,4})(?:x|\*))?' + rebulk.regex(res_pattern + interlaced_pattern + r'(?P<scan_type>i)' + frame_rate_pattern + '?') + rebulk.regex(res_pattern + progressive_pattern + r'(?P<scan_type>p)' + frame_rate_pattern + '?') + rebulk.regex(res_pattern + progressive_pattern + r'(?P<scan_type>p)?(?:hd)') + rebulk.regex(res_pattern + progressive_pattern + r'(?P<scan_type>p)?x?') + rebulk.string('4k', value='2160p') + rebulk.regex(r'(?P<width>\d{3,4})-?(?:x|\*)-?(?P<height>\d{3,4})', conflict_solver=lambda match, other: '__default__' if other.name == 'screen_size' else other) - rebulk.rules(ScreenSizeOnlyOne, RemoveScreenSizeConflicts) + rebulk.regex(frame_rate_pattern + '(p|fps)', name='frame_rate', + formatter=FrameRate.fromstring, disabled=lambda context: is_disabled(context, 'frame_rate')) + + rebulk.rules(PostProcessScreenSize(progressive, min_ar, max_ar), ScreenSizeOnlyOne, ResolveScreenSizeConflicts) return rebulk +class PostProcessScreenSize(Rule): + """ + Process the screen size calculating the aspect ratio if available. + + Convert to a standard notation (720p, 1080p, etc) when it's a standard resolution and + aspect ratio is valid or not available. + + It also creates an aspect_ratio match when available. + """ + consequence = AppendMatch + + def __init__(self, standard_heights, min_ar, max_ar): + super(PostProcessScreenSize, self).__init__() + self.standard_heights = standard_heights + self.min_ar = min_ar + self.max_ar = max_ar + + def when(self, matches, context): + to_append = [] + for match in matches.named('screen_size'): + if not is_disabled(context, 'frame_rate'): + for frame_rate in match.children.named('frame_rate'): + frame_rate.formatter = FrameRate.fromstring + to_append.append(frame_rate) + + values = match.children.to_dict() + if 'height' not in values: + continue + + scan_type = (values.get('scan_type') or 'p').lower() + height = values['height'] + if 'width' not in values: + match.value = '{0}{1}'.format(height, scan_type) + continue + + width = values['width'] + calculated_ar = float(width) / float(height) + + aspect_ratio = Match(match.start, match.end, input_string=match.input_string, + name='aspect_ratio', value=round(calculated_ar, 3)) + + 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: + match.value = '{0}x{1}'.format(width, height) + + return to_append + + class ScreenSizeOnlyOne(Rule): """ - Keep a single screen_size pet filepath part. + Keep a single screen_size per filepath part. """ consequence = RemoveMatch @@ -72,15 +120,15 @@ class ScreenSizeOnlyOne(Rule): for filepart in matches.markers.named('path'): screensize = list(reversed(matches.range(filepart.start, filepart.end, lambda match: match.name == 'screen_size'))) - if len(screensize) > 1: + if len(screensize) > 1 and len(set((match.value for match in screensize))) > 1: to_remove.extend(screensize[1:]) return to_remove -class RemoveScreenSizeConflicts(Rule): +class ResolveScreenSizeConflicts(Rule): """ - Remove season and episode matches which conflicts with screen_size match. + Resolve screen_size conflicts with season and episode matches. """ consequence = RemoveMatch @@ -95,14 +143,21 @@ class RemoveScreenSizeConflicts(Rule): if not conflicts: continue + has_neighbor = False video_profile = matches.range(screensize.end, filepart.end, lambda match: match.name == 'video_profile', 0) if video_profile and not matches.holes(screensize.end, video_profile.start, predicate=lambda h: h.value and h.value.strip(seps)): to_remove.extend(conflicts) + has_neighbor = True - date = matches.previous(screensize, lambda match: match.name == 'date', 0) - if date and not matches.holes(date.end, screensize.start, - predicate=lambda h: h.value and h.value.strip(seps)): + previous = matches.previous(screensize, index=0, predicate=( + lambda m: m.name in ('date', 'source', 'other', 'streaming_service'))) + if previous and not matches.holes(previous.end, screensize.start, + predicate=lambda h: h.value and h.value.strip(seps)): to_remove.extend(conflicts) + has_neighbor = True + + if not has_neighbor: + to_remove.append(screensize) return to_remove diff --git a/libs/guessit/rules/properties/size.py b/libs/guessit/rules/properties/size.py index 84f0303ca..c61580c04 100644 --- a/libs/guessit/rules/properties/size.py +++ b/libs/guessit/rules/properties/size.py @@ -7,23 +7,24 @@ import re from rebulk import Rebulk -from ..common.validators import seps_surround from ..common import dash +from ..common.quantity import Size +from ..common.pattern import is_disabled +from ..common.validators import seps_surround -def size(): +def size(config): # pylint:disable=unused-argument """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :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 = 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=format_size, tags=['release-group-prefix']) + rebulk.regex(r'\d+-?[mgt]b', r'\d+\.\d+-?[mgt]b', formatter=Size.fromstring, tags=['release-group-prefix']) return rebulk diff --git a/libs/guessit/rules/properties/source.py b/libs/guessit/rules/properties/source.py new file mode 100644 index 000000000..2fe55618f --- /dev/null +++ b/libs/guessit/rules/properties/source.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +source property +""" +import copy + +from rebulk.remodule import re + +from rebulk import AppendMatch, Rebulk, RemoveMatch, Rule + +from .audio_codec import HqConflictRule +from ..common import dash, seps +from ..common.pattern import is_disabled +from ..common.validators import seps_before, seps_after, or_ + + +def source(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, 'source')) + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], private_parent=True, children=True) + rebulk = rebulk.defaults(name='source', + tags=['video-codec-prefix', 'streaming_service.suffix'], + validate_all=True, + validator={'__parent__': or_(seps_before, seps_after)}) + + rip_prefix = '(?P<other>Rip)-?' + rip_suffix = '-?(?P<other>Rip)' + rip_optional_suffix = '(?:' + rip_suffix + ')?' + + def build_source_pattern(*patterns, **kwargs): + """Helper pattern to build source pattern.""" + prefix_format = kwargs.get('prefix') or '' + suffix_format = kwargs.get('suffix') or '' + + string_format = prefix_format + '({0})' + suffix_format + return [string_format.format(pattern) for pattern in patterns] + + def demote_other(match, other): # pylint: disable=unused-argument + """Default conflict solver with 'other' property.""" + return other if other.name == 'other' or other.name == 'release_group' else '__default__' + + rebulk.regex(*build_source_pattern('VHS', suffix=rip_optional_suffix), + value={'source': 'VHS', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('CAM', suffix=rip_optional_suffix), + value={'source': 'Camera', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('HD-?CAM', suffix=rip_optional_suffix), + value={'source': 'HD Camera', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TELESYNC', 'TS', suffix=rip_optional_suffix), + value={'source': 'Telesync', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('HD-?TELESYNC', 'HD-?TS', suffix=rip_optional_suffix), + value={'source': 'HD Telesync', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('WORKPRINT', 'WP'), value='Workprint') + rebulk.regex(*build_source_pattern('TELECINE', 'TC', suffix=rip_optional_suffix), + value={'source': 'Telecine', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('HD-?TELECINE', 'HD-?TC', suffix=rip_optional_suffix), + value={'source': 'HD Telecine', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('PPV', suffix=rip_optional_suffix), + value={'source': 'Pay-per-view', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('SD-?TV', suffix=rip_optional_suffix), + value={'source': 'TV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TV', suffix=rip_suffix), # TV is too common to allow matching + value={'source': 'TV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TV', 'SD-?TV', prefix=rip_prefix), + value={'source': 'TV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TV-?(?=Dub)'), value='TV') + rebulk.regex(*build_source_pattern('DVB', 'PD-?TV', suffix=rip_optional_suffix), + value={'source': 'Digital TV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('DVD', suffix=rip_optional_suffix), + value={'source': 'DVD', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('DM', suffix=rip_optional_suffix), + value={'source': 'Digital Master', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('VIDEO-?TS', 'DVD-?R(?:$|(?!E))', # 'DVD-?R(?:$|^E)' => DVD-Real ... + 'DVD-?9', 'DVD-?5'), value='DVD') + + rebulk.regex(*build_source_pattern('HD-?TV', suffix=rip_optional_suffix), conflict_solver=demote_other, + value={'source': 'HDTV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TV-?HD', suffix=rip_suffix), conflict_solver=demote_other, + value={'source': 'HDTV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('TV', suffix='-?(?P<other>Rip-?HD)'), conflict_solver=demote_other, + value={'source': 'HDTV', 'other': 'Rip'}) + + rebulk.regex(*build_source_pattern('VOD', suffix=rip_optional_suffix), + value={'source': 'Video on Demand', 'other': 'Rip'}) + + rebulk.regex(*build_source_pattern('WEB', 'WEB-?DL', suffix=rip_suffix), + value={'source': 'Web', 'other': 'Rip'}) + # WEBCap is a synonym to WEBRip, mostly used by non english + rebulk.regex(*build_source_pattern('WEB-?(?P<another>Cap)', suffix=rip_optional_suffix), + value={'source': 'Web', 'other': 'Rip', 'another': 'Rip'}) + rebulk.regex(*build_source_pattern('WEB-?DL', 'WEB-?U?HD', 'DL-?WEB', 'DL(?=-?Mux)'), + value={'source': 'Web'}) + rebulk.regex('(WEB)', value='Web', tags='weak.source') + + rebulk.regex(*build_source_pattern('HD-?DVD', suffix=rip_optional_suffix), + value={'source': 'HD-DVD', 'other': 'Rip'}) + + rebulk.regex(*build_source_pattern('Blu-?ray', 'BD', 'BD[59]', 'BD25', 'BD50', suffix=rip_optional_suffix), + value={'source': 'Blu-ray', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('(?P<another>BR)-?(?=Scr(?:eener)?)', '(?P<another>BR)-?(?=Mux)'), # BRRip + value={'source': 'Blu-ray', 'another': 'Reencoded'}) + rebulk.regex(*build_source_pattern('(?P<another>BR)', suffix=rip_suffix), # BRRip + value={'source': 'Blu-ray', 'other': 'Rip', 'another': 'Reencoded'}) + + rebulk.regex(*build_source_pattern('Ultra-?Blu-?ray', 'Blu-?ray-?Ultra'), value='Ultra HD Blu-ray') + + rebulk.regex(*build_source_pattern('AHDTV'), value='Analog HDTV') + rebulk.regex(*build_source_pattern('UHD-?TV', suffix=rip_optional_suffix), conflict_solver=demote_other, + value={'source': 'Ultra HDTV', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('UHD', suffix=rip_suffix), conflict_solver=demote_other, + value={'source': 'Ultra HDTV', 'other': 'Rip'}) + + rebulk.regex(*build_source_pattern('DSR', 'DTH', suffix=rip_optional_suffix), + value={'source': 'Satellite', 'other': 'Rip'}) + rebulk.regex(*build_source_pattern('DSR?', 'SAT', suffix=rip_suffix), + value={'source': 'Satellite', 'other': 'Rip'}) + + rebulk.rules(ValidateSourcePrefixSuffix, ValidateWeakSource, UltraHdBlurayRule) + + return rebulk + + +class UltraHdBlurayRule(Rule): + """ + Replace other:Ultra HD and source:Blu-ray with source:Ultra HD Blu-ray + """ + dependency = HqConflictRule + consequence = [RemoveMatch, AppendMatch] + + @classmethod + def find_ultrahd(cls, matches, start, end, index): + """Find Ultra HD match.""" + return matches.range(start, end, index=index, predicate=( + lambda m: not m.private and m.name == 'other' and m.value == 'Ultra HD' + )) + + @classmethod + def validate_range(cls, matches, start, end): + """Validate no holes or invalid matches exist in the specified range.""" + return ( + not matches.holes(start, end, predicate=lambda m: m.value.strip(seps)) and + not matches.range(start, end, predicate=( + lambda m: not m.private and ( + m.name not in ('screen_size', 'color_depth') and ( + m.name != 'other' or 'uhdbluray-neighbor' not in m.tags)))) + ) + + def when(self, matches, context): + to_remove = [] + to_append = [] + for filepart in matches.markers.named('path'): + for match in matches.range(filepart.start, filepart.end, predicate=( + lambda m: not m.private and m.name == 'source' and m.value == 'Blu-ray')): + other = self.find_ultrahd(matches, filepart.start, match.start, -1) + if not other or not self.validate_range(matches, other.end, match.start): + other = self.find_ultrahd(matches, match.end, filepart.end, 0) + if not other or not self.validate_range(matches, match.end, other.start): + if not matches.range(filepart.start, filepart.end, predicate=( + lambda m: m.name == 'screen_size' and m.value == '2160p')): + continue + + if other: + other.private = True + + new_source = copy.copy(match) + new_source.value = 'Ultra HD Blu-ray' + to_remove.append(match) + to_append.append(new_source) + + if to_remove or to_append: + return to_remove, to_append + return False + + +class ValidateSourcePrefixSuffix(Rule): + """ + Validate source with source prefix, source suffix. + """ + priority = 64 + consequence = RemoveMatch + + def when(self, matches, context): + ret = [] + for filepart in matches.markers.named('path'): + for match in matches.range(filepart.start, filepart.end, predicate=lambda m: m.name == 'source'): + match = match.initiator + if not seps_before(match) and \ + not matches.range(match.start - 1, match.start - 2, + lambda m: 'source-prefix' in m.tags): + if match.children: + ret.extend(match.children) + ret.append(match) + continue + if not seps_after(match) and \ + not matches.range(match.end, match.end + 1, + lambda m: 'source-suffix' in m.tags): + if match.children: + ret.extend(match.children) + ret.append(match) + continue + + return ret + + +class ValidateWeakSource(Rule): + """ + Validate weak source + """ + dependency = [ValidateSourcePrefixSuffix] + priority = 64 + consequence = RemoveMatch + + def when(self, matches, context): + ret = [] + for filepart in matches.markers.named('path'): + for match in matches.range(filepart.start, filepart.end, predicate=lambda m: m.name == 'source'): + # if there are more than 1 source in this filepart, just before the year and with holes for the title + # most likely the source is part of the title + if 'weak.source' in match.tags \ + and matches.range(match.end, filepart.end, predicate=lambda m: m.name == 'source') \ + and matches.holes(filepart.start, match.start, + predicate=lambda m: m.value.strip(seps), index=-1): + if match.children: + ret.extend(match.children) + ret.append(match) + continue + + return ret diff --git a/libs/guessit/rules/properties/streaming_service.py b/libs/guessit/rules/properties/streaming_service.py index b31690d33..f467f20a6 100644 --- a/libs/guessit/rules/properties/streaming_service.py +++ b/libs/guessit/rules/properties/streaming_service.py @@ -8,64 +8,30 @@ import re from rebulk import Rebulk from rebulk.rules import Rule, RemoveMatch +from ..common.pattern import is_disabled from ...rules.common import seps, dash +from ...rules.common.validators import seps_before, seps_after -def streaming_service(): +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().string_defaults(ignore_case=True).regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) - rebulk.defaults(name='streaming_service', tags=['format-prefix']) - - rebulk.string('AE', 'A&E', value='A&E') - rebulk.string('AMBC', value='ABC') - rebulk.string('AMC', value='AMC') - rebulk.string('AMZN', 'AmazonPrime', value='Amazon Prime') - rebulk.regex('Amazon-Prime', value='Amazon Prime') - rebulk.string('AS', 'AdultSwim', value='Adult Swim') - rebulk.regex('Adult-Swim', value='Adult Swim') - rebulk.string('iP', 'BBCiPlayer', value='BBC iPlayer') - rebulk.regex('BBC-iPlayer', value='BBC iPlayer') - rebulk.string('CBS', value='CBS') - rebulk.string('CC', 'ComedyCentral', value='Comedy Central') - rebulk.regex('Comedy-Central', value='Comedy Central') - rebulk.string('CR', 'CrunchyRoll', value='Crunchy Roll') - rebulk.regex('Crunchy-Roll', value='Crunchy Roll') - rebulk.string('CW', 'TheCW', value='The CW') - rebulk.regex('The-CW', value='The CW') - rebulk.string('DISC', 'Discovery', value='Discovery') - rebulk.string('DIY', value='DIY Network') - rebulk.string('DSNY', 'Disney', value='Disney') - rebulk.string('EPIX', 'ePix', value='ePix') - rebulk.string('HBO', 'HBOGo', value='HBO Go') - rebulk.regex('HBO-Go', value='HBO Go') - rebulk.string('HIST', 'History', value='History') - rebulk.string('ID', value='Investigation Discovery') - rebulk.string('IFC', 'IFC', value='IFC') - rebulk.string('PBS', 'PBS', value='PBS') - rebulk.string('NATG', 'NationalGeographic', value='National Geographic') - rebulk.regex('National-Geographic', value='National Geographic') - rebulk.string('NBA', 'NBATV', value='NBA TV') - rebulk.regex('NBA-TV', value='NBA TV') - rebulk.string('NBC', value='NBC') - rebulk.string('NFL', value='NFL') - rebulk.string('NICK', 'Nickelodeon', value='Nickelodeon') - rebulk.string('NF', 'Netflix', value='Netflix') - rebulk.string('iTunes', value='iTunes') - rebulk.string('RTE', value='RTÉ One') - rebulk.string('SESO', 'SeeSo', value='SeeSo') - rebulk.string('SPKE', 'SpikeTV', 'Spike TV', value='Spike TV') - rebulk.string('SYFY', 'Syfy', value='Syfy') - rebulk.string('TFOU', 'TFou', value='TFou') - rebulk.string('TLC', value='TLC') - rebulk.string('TV3', value='TV3 Ireland') - rebulk.string('TV4', value='TV4 Sweeden') - rebulk.string('TVL', 'TVLand', 'TV Land', value='TV Land') - rebulk.string('UFC', value='UFC') - rebulk.string('USAN', value='USA Network') + 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) @@ -75,11 +41,11 @@ def streaming_service(): class ValidateStreamingService(Rule): """Validate streaming service matches.""" - priority = 32 + priority = 128 consequence = RemoveMatch def when(self, matches, context): - """Streaming service is always before format. + """Streaming service is always before source. :param matches: :type matches: rebulk.match.Matches @@ -93,16 +59,20 @@ class ValidateStreamingService(Rule): previous_match = matches.previous(service, lambda match: 'streaming_service.prefix' in match.tags, 0) has_other = service.initiator and service.initiator.children.named('other') - if not has_other and \ - (not next_match or matches.holes(service.end, next_match.start, - predicate=lambda match: match.value.strip(seps))) and \ - (not previous_match or matches.holes(previous_match.end, service.start, - predicate=lambda match: match.value.strip(seps))): - to_remove.append(service) - continue + if not has_other: + if (not next_match or + matches.holes(service.end, next_match.start, + predicate=lambda match: match.value.strip(seps)) or + not seps_before(service)): + if (not previous_match or + matches.holes(previous_match.end, service.start, + predicate=lambda match: match.value.strip(seps)) or + not seps_after(service)): + to_remove.append(service) + continue if service.value == 'Comedy Central': - # Current match is a valid streaming service, removing invalid closed caption (CC) matches - to_remove.extend(matches.named('other', predicate=lambda match: match.value == 'CC')) + # Current match is a valid streaming service, removing invalid Criterion Collection (CC) matches + to_remove.extend(matches.named('edition', predicate=lambda match: match.value == 'Criterion')) return to_remove diff --git a/libs/guessit/rules/properties/title.py b/libs/guessit/rules/properties/title.py index e87ceb6de..0d2630167 100644 --- a/libs/guessit/rules/properties/title.py +++ b/libs/guessit/rules/properties/title.py @@ -8,21 +8,31 @@ from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch, AppendTags from rebulk.formatters import formatters from .film import FilmTitleRule -from .language import SubtitlePrefixLanguageRule, SubtitleSuffixLanguageRule, SubtitleExtensionRule +from .language import ( + SubtitlePrefixLanguageRule, + SubtitleSuffixLanguageRule, + SubtitleExtensionRule, + NON_SPECIFIC_LANGUAGES +) from ..common import seps, title_seps from ..common.comparators import marker_sorted from ..common.expected import build_expected_function from ..common.formatters import cleanup, reorder_title +from ..common.pattern import is_disabled from ..common.validators import seps_surround -def title(): +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().rules(TitleFromPosition, PreferTitleWithYear) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'title')) + rebulk.rules(TitleFromPosition, PreferTitleWithYear) expected_title = build_expected_function('expected_title') @@ -83,18 +93,25 @@ class TitleBaseRule(Rule): :rtype: """ cropped_holes = [] + group_markers = matches.markers.named('group') + for group_marker in group_markers: + path_marker = matches.markers.at_match(group_marker, predicate=lambda m: m.name == 'path', index=0) + if path_marker and path_marker.span == group_marker.span: + group_markers.remove(group_marker) + for hole in holes: - group_markers = matches.markers.named('group') cropped_holes.extend(hole.crop(group_markers)) + return cropped_holes - def is_ignored(self, match): + @staticmethod + def is_ignored(match): """ Ignore matches when scanning for title (hole). Full word language and countries won't be ignored if they are uppercase. """ - return not (len(match) > 3 and match.raw.isupper()) and match.name in ['language', 'country', 'episode_details'] + return not (len(match) > 3 and match.raw.isupper()) and match.name in ('language', 'country', 'episode_details') def should_keep(self, match, to_keep, matches, filepart, hole, starting): """ @@ -114,7 +131,7 @@ class TitleBaseRule(Rule): :return: :rtype: """ - if match.name in ['language', 'country']: + if match.name in ('language', 'country'): # Keep language if exactly matching the hole. if len(hole.value) == len(match.raw): return True @@ -125,9 +142,10 @@ class TitleBaseRule(Rule): for outside in outside_matches: other_languages.extend(matches.range(outside.start, outside.end, lambda c_match: c_match.name == match.name and - c_match not in to_keep)) + c_match not in to_keep and + c_match.value not in NON_SPECIFIC_LANGUAGES)) - if not other_languages: + if not other_languages and (not starting or len(match.raw) <= 3): return True return False @@ -145,7 +163,7 @@ class TitleBaseRule(Rule): return match.start >= hole.start and match.end <= hole.end return True - def check_titles_in_filepart(self, filepart, matches, context): + def check_titles_in_filepart(self, filepart, matches, context): # pylint:disable=inconsistent-return-statements """ Find title in filepart (ignoring language) """ @@ -154,12 +172,11 @@ class TitleBaseRule(Rule): holes = matches.holes(start, end + 1, formatter=formatters(cleanup, reorder_title), ignore=self.is_ignored, - predicate=lambda hole: hole.value) + predicate=lambda m: m.value) holes = self.holes_process(holes, matches) for hole in holes: - # pylint:disable=cell-var-from-loop if not hole or (self.hole_filter and not self.hole_filter(hole, matches)): continue @@ -170,8 +187,8 @@ class TitleBaseRule(Rule): if ignored_matches: for ignored_match in reversed(ignored_matches): - # pylint:disable=undefined-loop-variable - trailing = matches.chain_before(hole.end, seps, predicate=lambda match: match == ignored_match) + # pylint:disable=undefined-loop-variable, cell-var-from-loop + trailing = matches.chain_before(hole.end, seps, predicate=lambda m: m == ignored_match) if trailing: should_keep = self.should_keep(ignored_match, to_keep, matches, filepart, hole, False) if should_keep: @@ -188,7 +205,7 @@ class TitleBaseRule(Rule): for ignored_match in ignored_matches: if ignored_match not in to_keep: starting = matches.chain_after(hole.start, seps, - predicate=lambda match: match == ignored_match) + predicate=lambda m: m == ignored_match) if starting: should_keep = self.should_keep(ignored_match, to_keep, matches, filepart, hole, True) if should_keep: @@ -214,7 +231,7 @@ class TitleBaseRule(Rule): hole.tags = self.match_tags if self.alternative_match_name: # Split and keep values that can be a title - titles = hole.split(title_seps, lambda match: match.value) + titles = hole.split(title_seps, lambda m: m.value) for title_match in list(titles[1:]): previous_title = titles[titles.index(title_match) - 1] separator = matches.input_string[previous_title.end:title_match.start] @@ -231,14 +248,15 @@ class TitleBaseRule(Rule): return titles, to_remove def when(self, matches, context): + ret = [] + to_remove = [] + if matches.named(self.match_name, lambda match: 'expected' in match.tags): - return + return False fileparts = [filepart for filepart in list(marker_sorted(matches.markers.named('path'), matches)) if not self.filepart_filter or self.filepart_filter(filepart, matches)] - to_remove = [] - # Priorize fileparts containing the year years_fileparts = [] for filepart in fileparts: @@ -246,7 +264,6 @@ class TitleBaseRule(Rule): if year_match: years_fileparts.append(filepart) - ret = [] for filepart in fileparts: try: years_fileparts.remove(filepart) @@ -268,7 +285,9 @@ class TitleBaseRule(Rule): ret.extend(titles) to_remove.extend(to_remove_c) - return ret, to_remove + if ret or to_remove: + return ret, to_remove + return False class TitleFromPosition(TitleBaseRule): @@ -282,6 +301,9 @@ class TitleFromPosition(TitleBaseRule): def __init__(self): super(TitleFromPosition, self).__init__('title', ['title'], 'alternative_title') + def enabled(self, context): + return not is_disabled(context, 'alternative_title') + class PreferTitleWithYear(Rule): """ @@ -302,7 +324,7 @@ class PreferTitleWithYear(Rule): if filepart: year_match = matches.range(filepart.start, filepart.end, lambda match: match.name == 'year', 0) if year_match: - group = matches.markers.at_match(year_match, lambda group: group.name == 'group') + group = matches.markers.at_match(year_match, lambda m: m.name == 'group') if group: with_year_in_group.append(title_match) else: @@ -310,16 +332,18 @@ class PreferTitleWithYear(Rule): to_tag = [] if with_year_in_group: - title_values = set([title_match.value for title_match in with_year_in_group]) + title_values = {title_match.value for title_match in with_year_in_group} to_tag.extend(with_year_in_group) elif with_year: - title_values = set([title_match.value for title_match in with_year]) + title_values = {title_match.value for title_match in with_year} to_tag.extend(with_year) else: - title_values = set([title_match.value for title_match in titles]) + title_values = {title_match.value for title_match in titles} to_remove = [] for title_match in titles: if title_match.value not in title_values: to_remove.append(title_match) - return to_remove, to_tag + if to_remove or to_tag: + return to_remove, to_tag + return False diff --git a/libs/guessit/rules/properties/type.py b/libs/guessit/rules/properties/type.py index 6d798b643..6a2877ef9 100644 --- a/libs/guessit/rules/properties/type.py +++ b/libs/guessit/rules/properties/type.py @@ -6,6 +6,7 @@ type property from rebulk import CustomRule, Rebulk, POST_PROCESS from rebulk.match import Match +from ..common.pattern import is_disabled from ...rules.processors import Processors @@ -19,13 +20,19 @@ def _type(matches, value): matches.append(Match(len(matches.input_string), len(matches.input_string), name='type', value=value)) -def type_(): +def type_(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(TypeProcessor) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'type')) + rebulk = rebulk.rules(TypeProcessor) + + return rebulk class TypeProcessor(CustomRule): @@ -45,9 +52,10 @@ class TypeProcessor(CustomRule): episode = matches.named('episode') season = matches.named('season') + absolute_episode = matches.named('absolute_episode') episode_details = matches.named('episode_details') - if episode or season or episode_details: + if episode or season or episode_details or absolute_episode: return 'episode' film = matches.named('film') diff --git a/libs/guessit/rules/properties/video_codec.py b/libs/guessit/rules/properties/video_codec.py index 86661469c..842a03c73 100644 --- a/libs/guessit/rules/properties/video_codec.py +++ b/libs/guessit/rules/properties/video_codec.py @@ -3,47 +3,76 @@ """ 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 from ..common.validators import seps_after, seps_before, seps_surround -def video_codec(): +def video_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) - rebulk.defaults(name="video_codec", tags=['format-suffix', 'streaming_service.suffix']) - - rebulk.regex(r"Rv\d{2}", value="Real") - rebulk.regex("Mpeg2", value="Mpeg2") - rebulk.regex("DVDivX", "DivX", value="DivX") - rebulk.regex("XviD", value="XviD") - rebulk.regex("[hx]-?264(?:-?AVC(HD)?)?", "MPEG-?4(?:-?AVC(HD)?)", "AVC(?:HD)?", value="h264") - rebulk.regex("[hx]-?265(?:-?HEVC)?", "HEVC", value="h265") - rebulk.regex('(?P<video_codec>hevc)(?P<video_profile>10)', value={'video_codec': 'h265', 'video_profile': '10bit'}, + rebulk = Rebulk() + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]).string_defaults(ignore_case=True) + rebulk.defaults(name="video_codec", + tags=['source-suffix', 'streaming_service.suffix'], + disabled=lambda context: is_disabled(context, 'video_codec')) + + rebulk.regex(r'Rv\d{2}', value='RealVideo') + rebulk.regex('Mpe?g-?2', '[hx]-?262', value='MPEG-2') + rebulk.string("DVDivX", "DivX", value="DivX") + rebulk.string('XviD', value='Xvid') + rebulk.regex('VC-?1', value='VC-1') + rebulk.string('VP7', value='VP7') + rebulk.string('VP8', 'VP80', value='VP8') + rebulk.string('VP9', value='VP9') + rebulk.regex('[hx]-?263', value='H.263') + rebulk.regex('[hx]-?264', '(MPEG-?4)?AVC(?:HD)?', value='H.264') + rebulk.regex('[hx]-?265', 'HEVC', value='H.265') + rebulk.regex('(?P<video_codec>hevc)(?P<color_depth>10)', value={'video_codec': 'H.265', 'color_depth': '10-bit'}, tags=['video-codec-suffix'], children=True) # http://blog.mediacoderhq.com/h264-profiles-and-levels/ - # http://fr.wikipedia.org/wiki/H.264 - rebulk.defaults(name="video_profile", validator=seps_surround) - - rebulk.regex('10.?bits?', 'Hi10P?', 'YUV420P10', value='10bit') - rebulk.regex('8.?bits?', value='8bit') - - rebulk.string('BP', value='BP', tags='video_profile.rule') - rebulk.string('XP', 'EP', value='XP', tags='video_profile.rule') - rebulk.string('MP', value='MP', tags='video_profile.rule') - rebulk.string('HP', 'HiP', value='HP', tags='video_profile.rule') - rebulk.regex('Hi422P', value='Hi422P', tags='video_profile.rule') - rebulk.regex('Hi444PP', value='Hi444PP', tags='video_profile.rule') - - rebulk.string('DXVA', value='DXVA', name='video_api') + # https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC + rebulk.defaults(clear=True, + name="video_profile", + validator=seps_surround, + disabled=lambda context: is_disabled(context, 'video_profile')) + + rebulk.string('BP', value='Baseline', tags='video_profile.rule') + rebulk.string('XP', 'EP', value='Extended', tags='video_profile.rule') + rebulk.string('MP', value='Main', tags='video_profile.rule') + rebulk.string('HP', 'HiP', value='High', tags='video_profile.rule') + + # https://en.wikipedia.org/wiki/Scalable_Video_Coding + rebulk.string('SC', 'SVC', value='Scalable Video Coding', tags='video_profile.rule') + # https://en.wikipedia.org/wiki/AVCHD + rebulk.regex('AVC(?:HD)?', value='Advanced Video Codec High Definition', tags='video_profile.rule') + # https://en.wikipedia.org/wiki/H.265/HEVC + rebulk.string('HEVC', value='High Efficiency Video Coding', tags='video_profile.rule') + + rebulk.regex('Hi422P', value='High 4:2:2') + rebulk.regex('Hi444PP', value='High 4:4:4 Predictive') + rebulk.regex('Hi10P?', value='High 10') # no profile validation is required + + rebulk.string('DXVA', value='DXVA', name='video_api', + disabled=lambda context: is_disabled(context, 'video_api')) + + 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') + rebulk.regex('10.?bits?', 'YUV420P10', 'Hi10P?', value='10-bit') + rebulk.regex('8.?bits?', value='8-bit') rebulk.rules(ValidateVideoCodec, VideoProfileRule) @@ -52,11 +81,14 @@ def video_codec(): class ValidateVideoCodec(Rule): """ - Validate video_codec with format property or separated + Validate video_codec with source property or separated """ priority = 64 consequence = RemoveMatch + def enabled(self, context): + return not is_disabled(context, 'video_codec') + def when(self, matches, context): ret = [] for codec in matches.named('video_codec'): @@ -77,11 +109,16 @@ class VideoProfileRule(Rule): """ consequence = RemoveMatch + def enabled(self, context): + return not is_disabled(context, 'video_profile') + def when(self, matches, context): profile_list = matches.named('video_profile', lambda match: 'video_profile.rule' in match.tags) ret = [] for profile in profile_list: - codec = matches.previous(profile, lambda match: match.name == 'video_codec') + codec = matches.at_span(profile.span, lambda match: match.name == 'video_codec', 0) + if not codec: + codec = matches.previous(profile, lambda match: match.name == 'video_codec') if not codec: codec = matches.next(profile, lambda match: match.name == 'video_codec') if not codec: diff --git a/libs/guessit/rules/properties/website.py b/libs/guessit/rules/properties/website.py index afca57abb..c19653117 100644 --- a/libs/guessit/rules/properties/website.py +++ b/libs/guessit/rules/properties/website.py @@ -9,28 +9,35 @@ from rebulk.remodule import re from rebulk import Rebulk, Rule, RemoveMatch from ..common import seps from ..common.formatters import cleanup +from ..common.pattern import is_disabled from ..common.validators import seps_surround from ...reutils import build_or_pattern -def website(): +def website(config): """ Builder for rebulk object. + + :param config: rule configuration + :type config: dict :return: Created Rebulk object :rtype: Rebulk """ - rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) + rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'website')) + rebulk = rebulk.regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) rebulk.defaults(name="website") - tlds = [l.strip().decode('utf-8') - for l in resource_stream('guessit', 'tlds-alpha-by-domain.txt').readlines() - if b'--' not in l][1:] # All registered domain extension - - safe_tlds = ['com', 'org', 'net'] # For sure a website extension - safe_subdomains = ['www'] # For sure a website subdomain - safe_prefix = ['co', 'com', 'org', 'net'] # Those words before a tlds are sure + with resource_stream('guessit', 'tlds-alpha-by-domain.txt') as tld_file: + tlds = [ + tld.strip().decode('utf-8') + for tld in tld_file.readlines() + if b'--' not in tld + ][1:] # All registered domain extension - website_prefixes = ['from'] + safe_tlds = config['safe_tlds'] # For sure a website extension + safe_subdomains = config['safe_subdomains'] # For sure a website subdomain + safe_prefix = config['safe_prefixes'] # Those words before a tlds are sure + website_prefixes = config['prefixes'] rebulk.regex(r'(?:[^a-z0-9]|^)((?:'+build_or_pattern(safe_subdomains) + r'\.)+(?:[a-z-]+\.)+(?:'+build_or_pattern(tlds) + @@ -60,7 +67,7 @@ def website(): """ 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 = [] @@ -73,7 +80,9 @@ def website(): if not safe: suffix = matches.next(website_match, PreferTitleOverWebsite.valid_followers, 0) if suffix: - to_remove.append(website_match) + group = matches.markers.at_match(website_match, lambda marker: marker.name == 'group', 0) + if not group: + to_remove.append(website_match) return to_remove rebulk.rules(PreferTitleOverWebsite, ValidateWebsitePrefix) diff --git a/libs/guessit/test/enable_disable_properties.yml b/libs/guessit/test/enable_disable_properties.yml new file mode 100644 index 000000000..ada9c3478 --- /dev/null +++ b/libs/guessit/test/enable_disable_properties.yml @@ -0,0 +1,335 @@ +? 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: Fix + -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.mp4 +: options: --exclude mimetype + -mimetype: video/mp4 + +? 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 + +? 1080p25 +: options: --exclude frame_rate + screen_size: 1080p + -frame_rate: 25fps + +? 1080p25 +: options: --exclude screen_size + -screen_size: 1080p + -frame_rate: 25fps + +? 1080p25 +: options: --include frame_rate + -screen_size: 1080p + -frame_rate: 25fps + +? 1080p 30fps +: options: --exclude screen_size + -screen_size: 1080p + frame_rate: 30fps diff --git a/libs/guessit/test/episodes.yml b/libs/guessit/test/episodes.yml index 3b563709a..4bbbde4ab 100644 --- a/libs/guessit/test/episodes.yml +++ b/libs/guessit/test/episodes.yml @@ -6,8 +6,8 @@ season: 2 episode: 5 episode_title: Vaginatown - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: 0TV container: avi @@ -18,8 +18,8 @@ episode_title: Hello, Bandit language: English subtitle_language: French - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: AlFleNi-TeaM website: tvu.org.ru container: avi @@ -29,8 +29,8 @@ season: 1 episode: 3 episode_title: Right Place, Wrong Time - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: NoTV ? Series/Duckman/Duckman - S1E13 Joking The Chicken (unedited).avi @@ -89,7 +89,7 @@ episode: 2 episode_title: 65 Million Years Off language: english - format: DVD + source: DVD other: Complete ? series/Psych/Psych S02 Season 2 Complete English DVD/Psych.S02E03.Psy.Vs.Psy.Français.srt @@ -97,7 +97,7 @@ season: 2 episode: 3 episode_title: Psy Vs Psy - format: DVD + source: DVD language: English subtitle_language: French other: Complete @@ -107,7 +107,7 @@ season: 1 episode: 1 episode_title: Toutes Couleurs Unies - format: DVB + source: Digital TV release_group: Kceb language: french website: tvu.org.ru @@ -132,15 +132,16 @@ episode_title: 18-5-4 language: english subtitle_language: french - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: AlFleNi-TeaM website: tvu.org.ru ? series/__ Incomplete __/Dr Slump (Catalan)/Dr._Slump_-_003_DVB-Rip_Catalan_by_kelf.avi : title: Dr Slump episode: 3 - format: DVB + source: Digital TV + other: Rip language: catalan # Disabling this test because it just doesn't looks like a serie ... @@ -166,7 +167,8 @@ season: 4 episode: 7 episode_title: Cherokee Hair Tampons - format: DVD + source: DVD + other: Rip website: tvu.org.ru ? Series/Kaamelott/Kaamelott - Livre V - Ep 23 - Le Forfait.avi @@ -192,16 +194,17 @@ episode_format: Minisode episode: 1 episode_title: Good Cop Bad Cop - format: WEBRip - video_codec: XviD + source: Web + other: Rip + video_codec: Xvid ? 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 - format: DVD - episode_details: Extras - video_codec: XviD + episode_title: Bad Karma + source: DVD + other: [Extras, Rip] + video_codec: Xvid ? series/Freaks And Geeks/Season 1/Episode 4 - Kim Kelly Is My Friend-eng(1).srt : title: Freaks And Geeks @@ -256,7 +259,7 @@ : title: new girl season: 1 episode: 17 - format: HDTV + source: HDTV release_group: lol ? Kaamelott - 5x44x45x46x47x48x49x50.avi @@ -296,25 +299,24 @@ season: 1 episode: 3 episode_title: Health Care - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: LOL ? /Volumes/data-1/Series/Futurama/Season 3/Futurama_-_S03_DVD_Bonus_-_Deleted_Scenes_Part_3.ogm : title: Futurama season: 3 part: 3 + source: DVD other: Bonus - episode_title: Deleted Scenes - format: DVD ? Ben.and.Kate.S01E02.720p.HDTV.X264-DIMENSION.mkv : title: Ben and Kate season: 1 episode: 2 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: DIMENSION ? /volume1/TV Series/Drawn Together/Season 1/Drawn Together 1x04 Requiem for a Reality Show.avi @@ -328,10 +330,10 @@ season: 5 episode: 6 screen_size: 720p - format: WEB-DL + source: Web audio_channels: "5.1" - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 release_group: CtrlHD ? /media/bdc64bfe-e36f-4af8-b550-e6fd2dfaa507/TV_Shows/Doctor Who (2005)/Saison 6/Doctor Who (2005) - S06E13 - The Wedding of River Song.mkv @@ -354,10 +356,10 @@ episode: 3 episode_title: Adventures in Baby-Getting screen_size: 720p - format: WEB-DL + source: Web audio_channels: "5.1" - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 release_group: CtrlHD ? /home/disaster/Videos/TV/Merlin/merlin_2008.5x02.arthurs_bane_part_two.repack.720p_hdtv_x264-fov.mkv @@ -367,8 +369,8 @@ part: 2 episode_title: arthurs bane screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: fov year: 2008 other: Proper @@ -386,28 +388,28 @@ episode: 18 episode_title: Sheltered screen_size: 720p - format: WEB-DL + source: Web audio_channels: "5.1" - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 ? Game of Thrones S03E06 1080i HDTV DD5.1 MPEG2-TrollHD.ts : title: Game of Thrones season: 3 episode: 6 screen_size: 1080i - format: HDTV + source: HDTV audio_channels: "5.1" - audio_codec: AC3 - video_codec: Mpeg2 + audio_codec: Dolby Digital + video_codec: MPEG-2 release_group: TrollHD ? gossip.girl.s01e18.hdtv.xvid-2hd.eng.srt : title: gossip girl season: 1 episode: 18 - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: 2hd subtitle_language: english @@ -416,8 +418,8 @@ season: 3 episode: [1, 2] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: IMMERSE ? Wheels.S03E01-02.720p.HDTV.x264-IMMERSE.mkv @@ -425,8 +427,8 @@ season: 3 episode: [1, 2] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: IMMERSE ? Wheels.S03E01-E02.720p.HDTV.x264-IMMERSE.mkv @@ -434,8 +436,8 @@ season: 3 episode: [1, 2] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: IMMERSE ? Wheels.S03E01-04.720p.HDTV.x264-IMMERSE.mkv @@ -443,8 +445,8 @@ season: 3 episode: [1, 2, 3, 4] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: IMMERSE ? Marvels.Agents.of.S.H.I.E.L.D-S01E06.720p.HDTV.X264-DIMENSION.mkv @@ -452,8 +454,8 @@ season: 1 episode: 6 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: DIMENSION ? Marvels.Agents.of.S.H.I.E.L.D.S01E06.720p.HDTV.X264-DIMENSION.mkv @@ -461,8 +463,8 @@ season: 1 episode: 6 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: DIMENSION ? Marvels.Agents.of.S.H.I.E.L.D..S01E06.720p.HDTV.X264-DIMENSION.mkv @@ -470,8 +472,8 @@ season: 1 episode: 6 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: DIMENSION ? Series/Friday Night Lights/Season 1/Friday Night Lights S01E19 - Ch-Ch-Ch-Ch-Changes.avi @@ -483,22 +485,24 @@ ? Dexter Saison VII FRENCH.BDRip.XviD-MiND.nfo : title: Dexter season: 7 - video_codec: XviD + video_codec: Xvid language: French - format: BluRay + source: Blu-ray + other: Rip release_group: MiND ? Dexter Saison sept FRENCH.BDRip.XviD-MiND.nfo : title: Dexter season: 7 - video_codec: XviD + video_codec: Xvid language: French - format: BluRay + source: Blu-ray + other: Rip release_group: MiND ? "Pokémon S16 - E29 - 1280*720 HDTV VF.mkv" : title: Pokémon - format: HDTV + source: HDTV language: French season: 16 episode: 29 @@ -506,20 +510,20 @@ ? One.Piece.E576.VOSTFR.720p.HDTV.x264-MARINE-FORD.mkv : episode: 576 - video_codec: h264 - format: HDTV + video_codec: H.264 + source: HDTV title: One Piece release_group: MARINE-FORD subtitle_language: French screen_size: 720p ? Dexter.S08E12.FINAL.MULTi.1080p.BluRay.x264-MiND.mkv -: video_codec: h264 +: video_codec: H.264 episode: 12 season: 8 - format: BluRay + source: Blu-ray title: Dexter - other: FINAL + episode_details: Final language: Multiple languages release_group: MiND screen_size: 1080p @@ -536,30 +540,30 @@ screen_size: 720p season: 1 title: Falling Skies - video_codec: h264 - other: HDLight + video_codec: H.264 + other: Micro HD ? Sleepy.Hollow.S01E09.720p.WEB-DL.DD5.1.H.264-BP.mkv : episode: 9 - video_codec: h264 - format: WEB-DL + video_codec: H.264 + source: Web title: Sleepy Hollow audio_channels: "5.1" screen_size: 720p season: 1 - video_profile: BP - audio_codec: AC3 +# video_profile: BP # TODO: related to https://github.com/guessit-io/guessit/issues/458#issuecomment-305719715 + audio_codec: Dolby Digital ? Sleepy.Hollow.S01E09.720p.WEB-DL.DD5.1.H.264-BS.mkv : episode: 9 - video_codec: h264 - format: WEB-DL + video_codec: H.264 + source: Web title: Sleepy Hollow audio_channels: "5.1" screen_size: 720p season: 1 release_group: BS - audio_codec: AC3 + audio_codec: Dolby Digital ? Battlestar.Galactica.S00.Pilot.FRENCH.DVDRip.XviD-NOTAG.avi : title: Battlestar Galactica @@ -567,8 +571,9 @@ episode_details: Pilot episode_title: Pilot language: French - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: NOTAG ? The Big Bang Theory S00E00 Unaired Pilot VOSTFR TVRip XviD-VioCs @@ -576,8 +581,9 @@ season: 0 episode: 0 subtitle_language: French - format: TV - video_codec: XviD + source: TV + other: Rip + video_codec: Xvid release_group: VioCs episode_details: [Unaired, Pilot] @@ -585,10 +591,10 @@ : title: The Big Bang Theory season: 1 episode: 0 - format: TV - video_codec: XviD + source: TV + video_codec: Xvid release_group: GIGGITY - other: Proper + other: [Proper, Rip] proper_count: 1 episode_details: [Unaired, Pilot] @@ -598,8 +604,8 @@ year: 2014 episode: 18 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: KILLERS ? 2.Broke.Girls.S03E10.480p.HDTV.x264-mSD.mkv @@ -607,29 +613,15 @@ season: 3 episode: 10 screen_size: 480p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: mSD -? 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 -: title: House of Cards - year: 2013 - season: 2 - episode: 3 - screen_size: 1080p - streaming_service: Netflix - format: WEBRip - audio_channels: "5.1" - audio_codec: AC3 - video_codec: h264 - release_group: NTb - ? the.100.109.hdtv-lol.mp4 : title: the 100 season: 1 episode: 9 - format: HDTV + source: HDTV release_group: lol ? Criminal.Minds.5x03.Reckoner.ENG.-.sub.FR.HDTV.XviD-STi.[tvu.org.ru].avi @@ -638,8 +630,8 @@ subtitle_language: French season: 5 episode: 3 - video_codec: XviD - format: HDTV + video_codec: Xvid + source: HDTV website: tvu.org.ru release_group: STi episode_title: Reckoner @@ -651,7 +643,7 @@ ? '[Evil-Saizen]_Laughing_Salesman_14_[DVD][1C98686A].mkv' : crc32: 1C98686A episode: 14 - format: DVD + source: DVD release_group: Evil-Saizen title: Laughing Salesman @@ -680,22 +672,22 @@ : audio_codec: AAC crc32: 99E8E009 episode: 1 - format: BluRay + source: Blu-ray release_group: Daisei screen_size: 720p title: Free!:Iwatobi Swim Club - video_profile: 10bit + color_depth: 10-bit ? '[Tsundere] Boku wa Tomodachi ga Sukunai - 03 [BDRip h264 1920x1080 10bit FLAC][AF0C22CC].mkv' : audio_codec: FLAC crc32: AF0C22CC episode: 3 - format: BluRay + source: Blu-ray release_group: Tsundere screen_size: 1080p title: Boku wa Tomodachi ga Sukunai - video_codec: h264 - video_profile: 10bit + video_codec: H.264 + color_depth: 10-bit ? '[t.3.3.d]_Mikakunin_de_Shinkoukei_-_12_[720p][5DDC1352].mkv' : crc32: 5DDC1352 @@ -710,12 +702,12 @@ release_group: Anime-Koi screen_size: 720p title: Sabagebu! - video_codec: h264 + video_codec: H.264 ? '[aprm-Diogo4D] [BD][1080p] Nagi no Asukara 08 [4D102B7C].mkv' : crc32: 4D102B7C episode: 8 - format: BluRay + source: Blu-ray release_group: aprm-Diogo4D screen_size: 1080p title: Nagi no Asukara @@ -748,7 +740,7 @@ ? '[DeadFish] Tari Tari - 01 [BD][720p][AAC].mp4' : audio_codec: AAC episode: 1 - format: BluRay + source: Blu-ray release_group: DeadFish screen_size: 720p title: Tari Tari @@ -759,12 +751,12 @@ release_group: NoobSubs screen_size: 720p title: Sword Art Online II - video_profile: 8bit + color_depth: 8-bit ? '[DeadFish] 01 - Tari Tari [BD][720p][AAC].mp4' : audio_codec: AAC episode: 1 - format: BluRay + source: Blu-ray release_group: DeadFish screen_size: 720p title: Tari Tari @@ -775,12 +767,12 @@ release_group: NoobSubs screen_size: 720p title: Sword Art Online II - video_profile: 8bit + color_depth: 8-bit ? '[DeadFish] 12 - Tari Tari [BD][720p][AAC].mp4' : audio_codec: AAC episode: 12 - format: BluRay + source: Blu-ray release_group: DeadFish screen_size: 720p title: Tari Tari @@ -788,7 +780,7 @@ ? Something.Season.2.1of4.Ep.Title.HDTV.torrent : episode_count: 4 episode: 1 - format: HDTV + source: HDTV season: 2 title: Something episode_title: Title @@ -797,7 +789,7 @@ ? Something.Season.2of5.3of9.Ep.Title.HDTV.torrent : episode_count: 9 episode: 3 - format: HDTV + source: HDTV season: 2 season_count: 5 title: Something @@ -805,7 +797,7 @@ container: torrent ? Something.Other.Season.3of5.Complete.HDTV.torrent -: format: HDTV +: source: HDTV other: Complete season: 3 season_count: 5 @@ -827,22 +819,22 @@ ? W2Test.123.HDTV.XViD-FlexGet : episode: 23 season: 1 - format: HDTV + source: HDTV release_group: FlexGet title: W2Test - video_codec: XviD + video_codec: Xvid ? W2Test.123.HDTV.XViD-FlexGet : options: --episode-prefer-number episode: 123 - format: HDTV + source: HDTV release_group: FlexGet title: W2Test - video_codec: XviD + video_codec: Xvid ? FooBar.0307.PDTV-FlexGet : episode: 7 - format: DVB + source: Digital TV release_group: FlexGet season: 3 title: FooBar @@ -851,53 +843,51 @@ ? FooBar.307.PDTV-FlexGet : options: --episode-prefer-number episode: 307 - format: DVB + source: Digital TV release_group: FlexGet title: FooBar ? FooBar.07.PDTV-FlexGet -: options: --episode-prefer-number - episode: 7 - format: DVB +: episode: 7 + source: Digital TV release_group: FlexGet title: FooBar ? FooBar.7.PDTV-FlexGet -: options: --episode-prefer-number - episode: 7 - format: DVB +: episode: 7 + source: Digital TV release_group: FlexGet title: FooBar ? FooBar.0307.PDTV-FlexGet : episode: 7 - format: DVB + source: Digital TV release_group: FlexGet season: 3 title: FooBar ? FooBar.307.PDTV-FlexGet : episode: 7 - format: DVB + source: Digital TV release_group: FlexGet season: 3 title: FooBar ? FooBar.07.PDTV-FlexGet : episode: 7 - format: DVB + source: Digital TV release_group: FlexGet title: FooBar ? FooBar.07v4.PDTV-FlexGet : episode: 7 version: 4 - format: DVB + source: Digital TV release_group: FlexGet title: FooBar ? FooBar.7.PDTV-FlexGet -: format: DVB +: source: Digital TV release_group: FlexGet title: FooBar 7 type: movie @@ -905,7 +895,7 @@ ? FooBar.7.PDTV-FlexGet : options: -t episode episode: 7 - format: DVB + source: Digital TV release_group: FlexGet title: FooBar @@ -913,13 +903,13 @@ : options: -t episode episode: 7 version: 3 - format: DVB + source: Digital TV release_group: FlexGet title: FooBar ? Test.S02E01.hdtv.real.proper : episode: 1 - format: HDTV + source: HDTV other: Proper proper_count: 2 season: 2 @@ -927,7 +917,7 @@ ? Real.Test.S02E01.hdtv.proper : episode: 1 - format: HDTV + source: HDTV other: Proper proper_count: 1 season: 2 @@ -935,7 +925,7 @@ ? Test.Real.S02E01.hdtv.proper : episode: 1 - format: HDTV + source: HDTV other: Proper proper_count: 1 season: 2 @@ -943,7 +933,7 @@ ? Test.S02E01.hdtv.proper : episode: 1 - format: HDTV + source: HDTV other: Proper proper_count: 1 season: 2 @@ -951,7 +941,7 @@ ? Test.S02E01.hdtv.real.repack.proper : episode: 1 - format: HDTV + source: HDTV other: Proper proper_count: 3 season: 2 @@ -959,10 +949,10 @@ ? Date.Show.03-29-2012.HDTV.XViD-FlexGet : date: 2012-03-29 - format: HDTV + source: HDTV release_group: FlexGet title: Date Show - video_codec: XviD + video_codec: Xvid ? Something.1x5.Season.Complete-FlexGet : episode: 5 @@ -1000,13 +990,13 @@ audio_codec: AAC country: US episode: 14 - format: HDTV + source: HDTV release_group: NOGRP screen_size: 720p season: 2013 title: FlexGet episode_title: Title Here - video_codec: h264 + video_codec: H.264 year: 2013 ? FlexGet.14.of.21.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP @@ -1014,25 +1004,25 @@ audio_codec: AAC episode_count: 21 episode: 14 - format: HDTV + source: HDTV release_group: NOGRP screen_size: 720p title: FlexGet episode_title: Title Here - video_codec: h264 + video_codec: H.264 ? FlexGet.Series.2013.14.of.21.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP : audio_channels: '5.1' audio_codec: AAC episode_count: 21 episode: 14 - format: HDTV + source: HDTV release_group: NOGRP screen_size: 720p season: 2013 - title: FlexGet + title: FlexGet Series episode_title: Title Here - video_codec: h264 + video_codec: H.264 year: 2013 ? Something.S04E05E09 @@ -1055,12 +1045,14 @@ title: FooBar ? FooBar 360 -: screen_size: 360p +: season: 3 + episode: 60 title: FooBar + -screen_size: 360p ? BarFood christmas special HDTV : options: --expected-title BarFood - format: HDTV + source: HDTV title: BarFood episode_title: christmas special episode_details: Special @@ -1082,47 +1074,47 @@ ? Test.13.HDTV-Ignored : episode: 13 - format: HDTV + source: HDTV release_group: Ignored title: Test ? Test.13.HDTV-Ignored : options: --expected-series test episode: 13 - format: HDTV + source: HDTV release_group: Ignored title: Test ? Test.13.HDTV-Ignored : title: Test episode: 13 - format: HDTV + source: HDTV release_group: Ignored ? Test.13.HDTV-Ignored : episode: 13 - format: HDTV + source: HDTV release_group: Ignored title: Test ? Test.13.HDTV-FlexGet : episode: 13 - format: HDTV + source: HDTV release_group: FlexGet title: Test ? Test.14.HDTV-Name : episode: 14 - format: HDTV + source: HDTV release_group: Name title: Test ? Real.Time.With.Bill.Maher.2014.10.31.HDTV.XviD-AFG.avi : date: 2014-10-31 - format: HDTV + source: HDTV release_group: AFG title: Real Time With Bill Maher - video_codec: XviD + video_codec: Xvid ? Arrow.S03E21.Al.Sah-Him.1080p.WEB-DL.DD5.1.H.264-BS.mkv : title: Arrow @@ -1130,11 +1122,11 @@ episode: 21 episode_title: Al Sah-Him screen_size: 1080p - audio_codec: AC3 + audio_codec: Dolby Digital audio_channels: "5.1" - video_codec: h264 + video_codec: H.264 release_group: BS - format: WEB-DL + source: Web ? How to Make It in America - S02E06 - I'm Sorry, Who's Yosi?.mkv : title: How to Make It in America @@ -1144,63 +1136,27 @@ ? 24.S05E07.FRENCH.DVDRip.XviD-FiXi0N.avi : episode: 7 - format: DVD + source: DVD + other: Rip language: fr season: 5 title: '24' - video_codec: XviD + video_codec: Xvid release_group: FiXi0N ? 12.Monkeys.S01E12.FRENCH.BDRip.x264-VENUE.mkv : episode: 12 - format: BluRay + source: Blu-ray + other: Rip language: fr release_group: VENUE season: 1 title: 12 Monkeys - video_codec: h264 - -? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.CC.WEBRip.AAC2.0.x264-BTW.mkv -? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.ComedyCentral.WEBRip.AAC2.0.x264-BTW.mkv -? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.Comedy.Central.WEBRip.AAC2.0.x264-BTW.mkv -: audio_channels: '2.0' - audio_codec: AAC - date: 2015-07-01 - format: WEBRip - edition: Extended - release_group: BTW - screen_size: 720p - streaming_service: Comedy Central - title: The Daily Show - episode_title: Kirsten Gillibrand - video_codec: h264 - -? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.Interview.720p.CC.WEBRip.AAC2.0.x264-BTW.mkv -: audio_channels: '2.0' - audio_codec: AAC - date: 2015-07-01 - format: WEBRip - release_group: BTW - screen_size: 720p - streaming_service: Comedy Central - title: The Daily Show - episode_title: Kirsten Gillibrand Extended Interview - video_codec: h264 - -? The.Daily.Show.2015.07.02.Sarah.Vowell.CC.WEBRip.AAC2.0.x264-BTW.mkv -: audio_channels: '2.0' - audio_codec: AAC - date: 2015-07-02 - format: WEBRip - release_group: BTW - streaming_service: Comedy Central - title: The Daily Show - episode_title: Sarah Vowell - video_codec: h264 + video_codec: H.264 ? 90.Day.Fiance.S02E07.I.Have.To.Tell.You.Something.720p.HDTV.x264-W4F : episode: 7 - format: HDTV + source: HDTV screen_size: 720p season: 2 title: 90 Day Fiance @@ -1209,42 +1165,44 @@ ? Doctor.Who.2005.S04E06.FRENCH.LD.DVDRip.XviD-TRACKS.avi : episode: 6 - format: DVD + source: DVD language: fr release_group: TRACKS season: 4 title: Doctor Who - other: LD - video_codec: XviD + other: [Line Dubbed, Rip] + video_codec: Xvid year: 2005 ? Astro.Le.Petit.Robot.S01E01+02.FRENCH.DVDRiP.X264.INT-BOOLZ.mkv : episode: [1, 2] - format: DVD + source: DVD + other: Rip language: fr release_group: INT-BOOLZ season: 1 title: Astro Le Petit Robot - video_codec: h264 + video_codec: H.264 ? Annika.Bengtzon.2012.E01.Le.Testament.De.Nobel.FRENCH.DVDRiP.XViD-STVFRV.avi : episode: 1 - format: DVD + source: DVD + other: Rip language: fr release_group: STVFRV title: Annika Bengtzon episode_title: Le Testament De Nobel - video_codec: XviD + video_codec: Xvid year: 2012 ? Dead.Set.02.FRENCH.LD.DVDRip.XviD-EPZ.avi : episode: 2 - format: DVD + source: DVD language: fr - other: LD + other: [Line Dubbed, Rip] release_group: EPZ title: Dead Set - video_codec: XviD + video_codec: Xvid ? Phineas and Ferb S01E00 & S01E01 & S01E02 : episode: [0, 1, 2] @@ -1253,11 +1211,11 @@ ? Show.Name.S01E02.S01E03.HDTV.XViD.Etc-Group : episode: [2, 3] - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - S01E02 - S01E03 - S01E04 - Ep Name : episode: [2, 3, 4] @@ -1267,11 +1225,11 @@ ? Show.Name.1x02.1x03.HDTV.XViD.Etc-Group : episode: [2, 3] - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - 1x02 - 1x03 - 1x04 - Ep Name : episode: [2, 3, 4] @@ -1281,11 +1239,11 @@ ? Show.Name.S01E02.HDTV.XViD.Etc-Group : episode: 2 - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - S01E02 - My Ep Name : episode: 2 @@ -1301,11 +1259,11 @@ ? Show.Name.S01E02E03.HDTV.XViD.Etc-Group : episode: [2, 3] - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - S01E02-03 - My Ep Name : episode: [2, 3] @@ -1320,11 +1278,11 @@ ? Show_Name.1x02.HDTV_XViD_Etc-Group : episode: 2 - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - 1x02 - My Ep Name : episode: 2 @@ -1334,11 +1292,11 @@ ? Show_Name.1x02x03x04.HDTV_XViD_Etc-Group : episode: [2, 3, 4] - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show Name - 1x02-03-04 - My Ep Name : episode: [2, 3, 4] @@ -1351,25 +1309,25 @@ : date: 2010-11-23 season: 1 episode: 0 - format: HDTV + source: HDTV release_group: Etc-Group title: Show Name episode_title: Event - video_codec: XviD + video_codec: Xvid ? Show.Name.101.Event.2010.11.23.HDTV.XViD.Etc-Group : date: 2010-11-23 season: 1 episode: 1 - format: HDTV + source: HDTV release_group: Etc-Group title: Show Name episode_title: Event - video_codec: XviD + video_codec: Xvid ? Show.Name.2010.11.23.HDTV.XViD.Etc-Group : date: 2010-11-23 - format: HDTV + source: HDTV release_group: Etc-Group title: Show Name @@ -1385,11 +1343,11 @@ episode_title: Ep Name ? Show.Name.S01.HDTV.XViD.Etc-Group -: format: HDTV +: source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? Show.Name.E02-03 : episode: [2, 3] @@ -1408,8 +1366,8 @@ ? Show.Name.Part.3.HDTV.XViD.Etc-Group : part: 3 title: Show Name - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: Etc-Group type: movie # Fallback to movie type because we can't tell it's a series ... @@ -1431,11 +1389,11 @@ ? Show.Name.102.HDTV.XViD.Etc-Group : episode: 2 - format: HDTV + source: HDTV release_group: Etc-Group season: 1 title: Show Name - video_codec: XviD + video_codec: Xvid ? '[HorribleSubs] Maria the Virgin Witch - 01 [720p].mkv' : episode: 1 @@ -1444,29 +1402,26 @@ title: Maria the Virgin Witch ? '[ISLAND]One_Piece_679_[VOSTFR]_[V1]_[8bit]_[720p]_[EB7838FC].mp4' -: options: -E - crc32: EB7838FC +: crc32: EB7838FC episode: 679 release_group: ISLAND screen_size: 720p title: One Piece subtitle_language: fr - video_profile: 8bit + color_depth: 8-bit version: 1 ? '[ISLAND]One_Piece_679_[VOSTFR]_[8bit]_[720p]_[EB7838FC].mp4' -: options: -E - crc32: EB7838FC +: crc32: EB7838FC episode: 679 release_group: ISLAND screen_size: 720p title: One Piece subtitle_language: fr - video_profile: 8bit + color_depth: 8-bit ? '[Kaerizaki-Fansub]_One_Piece_679_[VOSTFR][HD_1280x720].mp4' -: options: -E - episode: 679 +: episode: 679 other: HD release_group: Kaerizaki-Fansub screen_size: 720p @@ -1474,19 +1429,15 @@ subtitle_language: fr ? '[Kaerizaki-Fansub]_One_Piece_679_[VOSTFR][FANSUB][HD_1280x720].mp4' -: options: -E - episode: 679 - other: - - Fansub - - HD +: episode: 679 + other: [Fan Subtitled, HD] release_group: Kaerizaki-Fansub screen_size: 720p title: One Piece subtitle_language: fr ? '[Kaerizaki-Fansub]_One_Piece_681_[VOSTFR][HD_1280x720]_V2.mp4' -: options: -E - episode: 681 +: episode: 681 other: HD release_group: Kaerizaki-Fansub screen_size: 720p @@ -1495,8 +1446,7 @@ version: 2 ? '[Kaerizaki-Fansub] High School DxD New 04 VOSTFR HD (1280x720) V2.mp4' -: options: -E - episode: 4 +: episode: 4 other: HD release_group: Kaerizaki-Fansub screen_size: 720p @@ -1505,8 +1455,7 @@ version: 2 ? '[Kaerizaki-Fansub] One Piece 603 VOSTFR PS VITA (960x544) V2.mp4' -: options: -E - episode: 603 +: episode: 603 release_group: Kaerizaki-Fansub other: PS Vita screen_size: 960x544 @@ -1540,13 +1489,12 @@ release_group: Stratos-Subs screen_size: 720p title: Infinite Stratos - video_codec: h264 + video_codec: H.264 # [ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC) ? '[SGKK] Bleach 312v1 [720p/MKV]' -: options: -E # guessit 1.x for episode only when version is guessed, but it's doesn't make it consistent. - episode: 312 +: episode: 312 release_group: SGKK screen_size: 720p title: Bleach @@ -1558,7 +1506,7 @@ release_group: Ayako screen_size: 720p title: Infinite Stratos - video_codec: h264 + video_codec: H.264 ? '[Ayako] Infinite Stratos - IS - 07v2 [H264][720p][44419534]' : crc32: '44419534' @@ -1566,7 +1514,7 @@ release_group: Ayako screen_size: 720p title: Infinite Stratos - video_codec: h264 + video_codec: H.264 version: 2 ? '[Ayako-Shikkaku] Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - 10 [LQ][h264][720p] [8853B21C]' @@ -1575,18 +1523,15 @@ release_group: Ayako-Shikkaku screen_size: 720p title: Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - video_codec: h264 + video_codec: H.264 -# TODO: Add support for absolute episodes ? Bleach - s16e03-04 - 313-314 -? Bleach.s16e03-04.313-314 -? Bleach.s16e03-04.313-314 -? Bleach - s16e03-04 - 313-314 -? Bleach.s16e03-04.313-314 +? Bleach.s16e03-04.313-314-GROUP ? Bleach s16e03e04 313-314 -: episode: [3, 4] +: title: Bleach season: 16 - title: Bleach + episode: [3, 4] + absolute_episode: [313, 314] ? Bleach - 313-314 : options: -E @@ -1599,7 +1544,7 @@ release_group: ShinBunBu-Subs screen_size: 720p title: Bleach - video_codec: h264 + video_codec: H.264 ? 003. Show Name - Ep Name.avi : episode: 3 @@ -1623,23 +1568,23 @@ ? Project.Runway.S14E00.and.S14E01.(Eng.Subs).SDTV.x264-[2Maverick].mp4 : episode: [0, 1] - format: TV + source: TV release_group: 2Maverick season: 14 title: Project Runway subtitle_language: en - video_codec: h264 + video_codec: H.264 ? '[Hatsuyuki-Kaitou]_Fairy_Tail_2_-_16-20_[720p][10bit].torrent' : episode: [16, 17, 18, 19, 20] release_group: Hatsuyuki-Kaitou screen_size: 720p title: Fairy Tail 2 - video_profile: 10bit + color_depth: 10-bit ? '[Hatsuyuki-Kaitou]_Fairy_Tail_2_-_16-20_(191-195)_[720p][10bit].torrent' -: options: -E - episode: [16, 17, 18, 19, 20, 191, 192, 193, 194, 195] +: episode: [16, 17, 18, 19, 20] + absolute_episode: [191, 192, 193, 194, 195] release_group: Hatsuyuki-Kaitou screen_size: 720p title: Fairy Tail 2 @@ -1653,15 +1598,15 @@ ? The.Good.Wife.S06E01.E10.720p.WEB-DL.DD5.1.H.264-CtrlHD/The.Good.Wife.S06E09.Trust.Issues.720p.WEB-DL.DD5.1.H.264-CtrlHD.mkv : audio_channels: '5.1' - audio_codec: AC3 + audio_codec: Dolby Digital episode: 9 - format: WEB-DL + source: Web release_group: CtrlHD screen_size: 720p season: 6 title: The Good Wife episode_title: Trust Issues - video_codec: h264 + video_codec: H.264 ? Fear the Walking Dead - 01x02 - So Close, Yet So Far.REPACK-KILLERS.French.C.updated.Addic7ed.com.mkv : episode: 2 @@ -1683,16 +1628,15 @@ ? /av/unsorted/The.Daily.Show.2015.07.22.Jake.Gyllenhaal.720p.HDTV.x264-BATV.mkv : date: 2015-07-22 - format: HDTV + source: HDTV release_group: BATV screen_size: 720p title: The Daily Show episode_title: Jake Gyllenhaal - video_codec: h264 + video_codec: H.264 ? "[7.1.7.8.5] Foo Bar - 11 (H.264) [5235532D].mkv" -: options: -E - episode: 11 +: episode: 11 ? my 720p show S01E02 : options: -T "my 720p show" @@ -1723,58 +1667,56 @@ screen_size: 720p season: 1 title: Foo's & Bars - video_codec: XviD + video_codec: Xvid year: 2009 ? Date.Series.10-11-2008.XViD : date: 2008-11-10 - title: Date - video_codec: XviD + title: Date Series + video_codec: Xvid ? Scrubs/SEASON-06/Scrubs.S06E09.My.Perspective.DVDRip.XviD-WAT/scrubs.s06e09.dvdrip.xvid-wat.avi : container: avi episode: 9 episode_title: My Perspective - format: DVD - mimetype: video/x-msvideo + source: DVD + other: Rip release_group: WAT season: 6 title: Scrubs - video_codec: XviD + video_codec: Xvid ? '[PuyaSubs!] Digimon Adventure tri - 01 [720p][F9967949].mkv' : container: mkv crc32: F9967949 episode: 1 - mimetype: video/x-matroska release_group: PuyaSubs! screen_size: 720p title: Digimon Adventure tri ? Sherlock.S01.720p.BluRay.x264-AVCHD -: format: BluRay +: source: Blu-ray screen_size: 720p season: 1 title: Sherlock - video_codec: h264 + video_codec: H.264 ? Running.Wild.With.Bear.Grylls.S02E07.Michael.B.Jordan.PROPER.HDTV.x264-W4F.avi : container: avi episode: 7 episode_title: Michael B Jordan - format: HDTV - mimetype: video/x-msvideo + source: HDTV other: Proper proper_count: 1 release_group: W4F season: 2 title: Running Wild With Bear Grylls - video_codec: h264 + video_codec: H.264 ? Homeland.S05E11.Our.Man.in.Damascus.German.Sub.720p.HDTV.x264.iNTERNAL-BaCKToRG : episode: 11 episode_title: Our Man in Damascus - format: HDTV + source: HDTV other: Internal release_group: BaCKToRG screen_size: 720p @@ -1782,14 +1724,14 @@ subtitle_language: de title: Homeland type: episode - video_codec: h264 + video_codec: H.264 ? Breaking.Bad.S01E01.2008.BluRay.VC1.1080P.5.1.WMV-NOVO : title: Breaking Bad season: 1 episode: 1 year: 2008 - format: BluRay + source: Blu-ray screen_size: 1080p audio_channels: '5.1' container: WMV @@ -1800,8 +1742,8 @@ : title: Cosmos A Space Time Odyssey season: 1 episode: 2 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 other: Proper proper_count: 1 release_group: LOL @@ -1811,8 +1753,8 @@ : title: Fear The Walking Dead season: 2 episode: 1 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 audio_codec: AAC container: mp4 release_group: k3n @@ -1824,8 +1766,8 @@ episode: 1 episode_details: Pilot episode_title: Pilot - format: DVD - video_codec: h264 + source: DVD + video_codec: H.264 other: [Screener, Preair] release_group: NoGRP type: episode @@ -1834,8 +1776,8 @@ : title: Once Upon a Time season: 5 episode: 19 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 other: Proper proper_count: 1 release_group: LOL[ettv] @@ -1845,49 +1787,49 @@ : title: Show Name season: 1 episode: 3 - format: WEB-DL - video_codec: h264 + source: Web + video_codec: H.264 language: hu release_group: nIk type: episode ? Game.of.Thrones.S6.Ep5.X265.Dolby.2.0.KTM3.mp4 : audio_channels: '2.0' - audio_codec: AC3 + audio_codec: Dolby Digital container: mp4 episode: 5 release_group: KTM3 season: 6 title: Game of Thrones type: episode - video_codec: h265 + video_codec: H.265 ? Fargo.-.Season.1.-.720p.BluRay.-.x264.-.ShAaNiG -: format: BluRay +: source: Blu-ray release_group: ShAaNiG screen_size: 720p season: 1 title: Fargo type: episode - video_codec: h264 + video_codec: H.264 ? Show.Name.S02E02.Episode.Title.1080p.WEB-DL.x264.5.1Ch.-.Group : audio_channels: '5.1' episode: 2 episode_title: Episode Title - format: WEB-DL + source: Web release_group: Group screen_size: 1080p season: 2 title: Show Name type: episode - video_codec: h264 + video_codec: H.264 ? Breaking.Bad.S01E01.2008.BluRay.VC1.1080P.5.1.WMV-NOVO : audio_channels: '5.1' container: wmv episode: 1 - format: BluRay + source: Blu-ray release_group: NOVO screen_size: 1080p season: 1 @@ -1897,20 +1839,20 @@ ? Cosmos.A.Space.Time.Odyssey.S01E02.HDTV.x264.PROPER-LOL : episode: 2 - format: HDTV + source: HDTV other: Proper proper_count: 1 release_group: LOL season: 1 title: Cosmos A Space Time Odyssey type: episode - video_codec: h264 + video_codec: H.264 ? Elementary.S01E01.Pilot.DVDSCR.x264.PREAiR-NoGRP : episode: 1 episode_details: Pilot episode_title: Pilot - format: DVD + source: DVD other: - Screener - Preair @@ -1918,25 +1860,24 @@ season: 1 title: Elementary type: episode - video_codec: h264 + video_codec: H.264 ? Fear.The.Walking.Dead.S02E01.HDTV.x264.AAC.MP4-k3n.mp4 : audio_codec: AAC container: mp4 episode: 1 - format: HDTV - mimetype: video/mp4 + source: HDTV release_group: k3n season: 2 title: Fear The Walking Dead type: episode - video_codec: h264 + video_codec: H.264 ? Game.of.Thrones.S03.1080p.BluRay.DTS-HD.MA.5.1.AVC.REMUX-FraMeSToR : audio_channels: '5.1' - audio_codec: DTS - audio_profile: HDMA - format: BluRay + audio_codec: DTS-HD + audio_profile: Master Audio + source: Blu-ray other: Remux release_group: FraMeSToR screen_size: 1080p @@ -1946,16 +1887,16 @@ ? Show.Name.S01E02.HDTV.x264.NL-subs-ABC : episode: 2 - format: HDTV + source: HDTV release_group: ABC season: 1 subtitle_language: nl title: Show Name type: episode - video_codec: h264 + video_codec: H.264 ? Friends.S01-S10.COMPLETE.720p.BluRay.x264-PtM -: format: BluRay +: source: Blu-ray other: Complete release_group: PtM screen_size: 720p @@ -1972,44 +1913,48 @@ - 10 title: Friends type: episode - video_codec: h264 + video_codec: H.264 ? Duck.Dynasty.S02E07.Streik.German.DOKU.DL.WS.DVDRiP.x264-CDP : episode: 7 - episode_title: Streik German DOKU - format: DVD - language: mul - other: WideScreen + episode_title: Streik + source: DVD + language: + - German + - Multi + other: [Documentary, Widescreen, Rip] release_group: CDP season: 2 title: Duck Dynasty type: episode - video_codec: h264 + video_codec: H.264 ? Family.Guy.S13E14.JOLO.German.AC3D.DL.720p.WebHD.x264-CDD -: audio_codec: AC3 +: audio_codec: Dolby Digital episode: 14 - episode_title: JOLO German - format: WEB-DL - language: mul + episode_title: JOLO + source: Web + language: + - German + - Multi release_group: CDD screen_size: 720p season: 13 title: Family Guy type: episode - video_codec: h264 + video_codec: H.264 ? How.I.Met.Your.Mother.COMPLETE.SERIES.DVDRip.XviD-AR : options: -L en -C us - format: DVD - other: Complete + source: DVD + other: [Complete, Rip] release_group: AR title: How I Met Your Mother type: movie # Should be episode - video_codec: XviD + video_codec: Xvid ? Show Name The Complete Seasons 1 to 5 720p BluRay x265 HEVC-SUJAIDR[UTR] -: format: BluRay +: source: Blu-ray other: Complete release_group: SUJAIDR[UTR] screen_size: 720p @@ -2021,12 +1966,12 @@ - 5 title: Show Name type: episode - video_codec: h265 + video_codec: H.265 ? Fear.the.Walking.Dead.-.Season.2.epi.02.XviD.Eng.Ac3-5.1.sub.ita.eng.iCV-MIRCrew : options: -t episode audio_channels: '5.1' - audio_codec: AC3 + audio_codec: Dolby Digital episode: 2 episode_title: epi language: en @@ -2035,11 +1980,11 @@ subtitle_language: it title: Fear the Walking Dead type: episode - video_codec: XviD + video_codec: Xvid ? Game.Of.Thrones.S06E04.720p.PROPER.HDTV.x264-HDD : episode: 4 - format: HDTV + source: HDTV other: Proper proper_count: 1 release_group: HDD @@ -2047,53 +1992,54 @@ season: 6 title: Game Of Thrones type: episode - video_codec: h264 + video_codec: H.264 ? Marvels.Daredevil.S02E04.WEBRip.x264-NF69.mkv : container: mkv episode: 4 - format: WEBRip + source: Web + other: Rip release_group: NF69 season: 2 title: Marvels Daredevil type: episode - video_codec: h264 + video_codec: H.264 ? The.Walking.Dead.S06E01.FRENCH.1080p.WEB-DL.DD5.1.HEVC.x265-GOLF68 : audio_channels: '5.1' - audio_codec: AC3 + audio_codec: Dolby Digital episode: 1 - format: WEB-DL + source: Web language: fr release_group: GOLF68 screen_size: 1080p season: 6 title: The Walking Dead type: episode - video_codec: h265 + video_codec: H.265 ? American.Crime.S01E03.FASTSUB.VOSTFR.720p.HDTV.x264-F4ST : episode: 3 - format: HDTV - other: Fastsub + source: HDTV + other: Fast Subtitled release_group: F4ST screen_size: 720p season: 1 subtitle_language: fr title: American Crime type: episode - video_codec: h264 + video_codec: H.264 ? Gotham.S02E12.FASTSUB.VOSTFR.HDTV.X264-F4ST3R : episode: 12 - format: HDTV - other: Fastsub + source: HDTV + other: Fast Subtitled release_group: F4ST3R season: 2 subtitle_language: fr title: Gotham type: episode - video_codec: h264 + video_codec: H.264 # WEBRip + LD ? Australian.Story.2016.05.23.Into.The.Fog.of.War.Part.1.360p.LDTV.WEBRIP.[MPup] @@ -2102,8 +2048,8 @@ episode_title: Into The Fog of War part: 1 screen_size: 360p - other: LD - format: WEBRip + other: [Low Definition, Rip] + source: Web release_group: MPup type: episode @@ -2113,8 +2059,8 @@ season: 4 episode: 6 language: fr - format: AHDTV - video_codec: XviD + source: Analog HDTV + video_codec: Xvid type: episode # WEBDLRip @@ -2122,10 +2068,10 @@ : title: Show Name season: 6 episode: 14 - format: WEBRip + source: Web + other: Rip release_group: qqss44 container: avi - mimetype: video/x-msvideo type: episode # WEBCap @@ -2135,8 +2081,9 @@ episode: 6 episode_title: Steven Floats screen_size: 720p - format: WEBRip - video_codec: h264 + source: Web + other: Rip + video_codec: H.264 release_group: SRS type: episode @@ -2146,9 +2093,9 @@ season: 5 episode: 9 episode_title: Some Episode Title - other: WideScreen - format: SATRip - video_codec: h264 + other: Widescreen + source: Satellite + video_codec: H.264 release_group: NY2 type: episode @@ -2157,9 +2104,9 @@ : title: Squidbillies season: 4 episode: 5 - other: WideScreen - format: SATRip - video_codec: XviD + other: [Widescreen, Rip] + source: Satellite + video_codec: Xvid release_group: aAF type: episode @@ -2167,14 +2114,13 @@ ? /series/The.B*.B*.T*.S10E01.1080p.HDTV.X264-DIMENSION[rarbg]/The.B*.B*.T*.S10E01.1080p.HDTV.X264-DIMENSION.mkv : container: mkv episode: 1 - format: HDTV - mimetype: video/x-matroska - release_group: DIMENSION[rarbg] + source: HDTV + release_group: DIMENSION screen_size: 1080p season: 10 title: The B B T type: episode - video_codec: h264 + video_codec: H.264 ? '[Y-F] Very long Show Name Here - 03 Vostfr HD 8bits' : release_group: Y-F @@ -2182,7 +2128,7 @@ episode: 3 subtitle_language: fr other: HD - video_profile: 8bit + color_depth: 8-bit type: episode ? '[.www.site.com.].-.Snooze.and.Go.Sleep.S03E02.1080p.HEVC.x265-MeGusta' @@ -2192,17 +2138,17 @@ season: 3 title: Snooze and Go Sleep type: episode - video_codec: h265 + video_codec: H.265 website: www.site.com ? Show.Name.S01.720p.HDTV.DD5.1.x264-Group/show.name.0106.720p-group.mkv : title: Show Name season: 1 screen_size: 720p - format: HDTV - audio_codec: AC3 + source: HDTV + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: Group episode: 6 container: mkv @@ -2211,8 +2157,8 @@ ? Coupling Season 1 - 4 Complete DVDRip/Coupling Season 4/Coupling - (4x03) - Bed Time.mkv : title: Coupling - other: Complete - format: DVD + other: [Complete, Rip] + source: DVD season: 4 episode: 3 episode_title: Bed Time @@ -2224,404 +2170,11 @@ : title: Vice News Tonight date: 2016-10-10 screen_size: 1080p - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Amazon -? Show.Name.S07E04.Service.1080p.AMZN.WEBRip.DD+5.1.x264 -: title: Show Name - season: 7 - episode: 4 - episode_title: Service - screen_size: 1080p - streaming_service: Amazon Prime - format: WEBRip - audio_codec: EAC3 - audio_channels: '5.1' - video_codec: h264 - type: episode - -# Streaming service: Comedy Central -? Show.Name.2016.09.28.Nice.Title.Extended.1080p.CC.WEBRip.AAC2.0.x264-monkee -: title: Show Name - date: 2016-09-28 - episode_title: Nice Title - edition: Extended - screen_size: 1080p - streaming_service: Comedy Central - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: The CW -? Show.Name.US.S12E20.Nice.Title.720p.CW.WEBRip.AAC2.0.x264-monkee -: title: Show Name - country: US - season: 12 - episode: 20 - episode_title: Nice Title - screen_size: 720p - streaming_service: The CW - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: AMBC -? Show.Name.2016.09.27.Nice.Title.720p.AMBC.WEBRip.AAC2.0.x264-monkee -: title: Show Name - date: 2016-09-27 - episode_title: Nice Title - screen_size: 720p - streaming_service: ABC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: HIST -? Show.Name.720p.HIST.WEBRip.AAC2.0.H.264-monkee -: options: -t episode - title: Show Name - screen_size: 720p - streaming_service: History - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: PBS -? Show.Name.2015.Nice.Title.1080p.PBS.WEBRip.AAC2.0.H264-monkee -: options: -t episode - title: Show Name - year: 2015 - episode_title: Nice Title - screen_size: 1080p - streaming_service: PBS - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: SeeSo -? Show.Name.2016.Nice.Title.1080p.SESO.WEBRip.AAC2.0.x264-monkee -: options: -t episode - title: Show Name - year: 2016 - episode_title: Nice Title - screen_size: 1080p - streaming_service: SeeSo - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Discovery -? Show.Name.S01E03.Nice.Title.720p.DISC.WEBRip.AAC2.0.x264-NTb -: title: Show Name - season: 1 - episode: 3 - episode_title: Nice Title - screen_size: 720p - streaming_service: Discovery - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: NTb - type: episode - -# Streaming service: BBC iPlayer -? Show.Name.2016.08.18.Nice.Title.720p.iP.WEBRip.AAC2.0.H.264-monkee -: title: Show Name - date: 2016-08-18 - episode_title: Nice Title - streaming_service: BBC iPlayer - screen_size: 720p - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: A&E -? Show.Name.S15E18.Nice.Title.720p.AE.WEBRip.AAC2.0.H.264-monkee -: title: Show Name - season: 15 - episode: 18 - episode_title: Nice Title - screen_size: 720p - streaming_service: A&E - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Adult Swim -? Show.Name.S04E01.Nice.Title.1080p.AS.WEBRip.AAC2.0.H.264-monkee -: title: Show Name - season: 4 - episode: 1 - episode_title: Nice Title - screen_size: 1080p - streaming_service: Adult Swim - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Netflix -? Show.Name.2013.S02E03.1080p.NF.WEBRip.DD5.1.x264-NTb.mkv -: title: Show Name - year: 2013 - season: 2 - episode: 3 - screen_size: 1080p - streaming_service: Netflix - format: WEBRip - audio_codec: AC3 - audio_channels: '5.1' - video_codec: h264 - release_group: NTb - container: mkv - mimetype: video/x-matroska - type: episode - -# Streaming service: CBS -? Show.Name.2016.05.10.Nice.Title.720p.CBS.WEBRip.AAC2.0.x264-monkee -: title: Show Name - date: 2016-05-10 - episode_title: Nice Title - screen_size: 720p - streaming_service: CBS - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: NBA TV -? NBA.2016.02.27.Team.A.vs.Team.B.720p.NBA.WEBRip.AAC2.0.H.264-monkee -: title: NBA - date: 2016-02-27 - episode_title: Team A vs Team B - screen_size: 720p - streaming_service: NBA TV - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: ePix -? Show.Name.S05E04.Nice.Title.Part4.720p.EPIX.WEBRip.AAC2.0.H.264-monkee -: title: Show Name - season: 5 - episode: 4 - episode_title: Nice Title - part: 4 - screen_size: 720p - streaming_service: ePix - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: NBC -? Show.Name.S41E03.Nice.Title.720p.NBC.WEBRip.AAC2.0.x264-monkee -: title: Show Name - season: 41 - episode: 3 - episode_title: Nice Title - screen_size: 720p - streaming_service: NBC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Syfy -? Show.Name.S01E02.Nice.Title.720p.SYFY.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: Syfy - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: Spike TV -? Show.Name.S01E02.Nice.Title.720p.SPKE.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: Spike TV - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: IFC -? Show.Name.S01E02.Nice.Title.720p.IFC.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: IFC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: NATG -? Show.Name.S01E02.Nice.Title.720p.NATG.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: National Geographic - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: NFL -? Show.Name.S01E02.Nice.Title.720p.NFL.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: NFL - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: UFC -? Show.Name.S01E02.Nice.Title.720p.UFC.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: UFC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: TV Land -? Show.Name.S01E02.Nice.Title.720p.TVL.WEBRip.AAC2.0.x264-group -: title: Show Name - season: 1 - episode: 2 - episode_title: Nice Title - screen_size: 720p - streaming_service: TV Land - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: group - type: episode - -# Streaming service: Crunchy Roll -? Show.Name.S01.1080p.CR.WEBRip.AAC.2.0.x264-monkee -: title: Show Name - season: 1 - screen_size: 1080p - streaming_service: Crunchy Roll - format: WEBRip + source: Web + other: Rip audio_codec: AAC audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Disney -? Show.Name.S01.1080p.DSNY.WEBRip.AAC.2.0.x264-monkee -: title: Show Name - season: 1 - screen_size: 1080p - streaming_service: Disney - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: Nickelodeon -? Show.Name.S01.1080p.NICK.WEBRip.AAC.2.0.x264-monkee -: title: Show Name - season: 1 - screen_size: 1080p - streaming_service: Nickelodeon - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: monkee - type: episode - -# Streaming service: TFou -? Show.Name.S01.1080p.TFOU.WEBRip.AAC.2.0.x264-monkee -: title: Show Name - season: 1 - screen_size: 1080p - streaming_service: TFou - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 + video_codec: H.264 release_group: monkee type: episode @@ -2641,8 +2194,8 @@ size: 177MB other: Proper proper_count: 1 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 type: episode ? Show.Name.S03E15.480p.4.8GB.Proper.HDTV.x264 @@ -2653,8 +2206,8 @@ size: 4.8GB other: Proper proper_count: 1 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 type: episode ? Show.Name.S03.1.1TB.Proper.HDTV.x264 @@ -2663,8 +2216,8 @@ size: 1.1TB other: Proper proper_count: 1 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 type: episode ? Some.Show.S02E14.1080p.HDTV.X264-reenc.GROUP @@ -2675,9 +2228,9 @@ season: 2 episode: 14 screen_size: 1080p - format: HDTV - video_codec: h264 - other: ReEncoded + source: HDTV + video_codec: H.264 + other: Reencoded release_group: GROUP type: episode @@ -2687,12 +2240,13 @@ year: 2016 season: 1 episode: 1 - screen_size: 4K + screen_size: 2160p streaming_service: Amazon Prime - format: WEBRip - audio_codec: EAC3 + source: Web + other: Rip + audio_codec: Dolby Digital Plus audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: Group type: episode @@ -2700,10 +2254,10 @@ : title: Show Name season: 2 episode: 19 - video_codec: h264 + video_codec: H.264 language: it audio_codec: AAC - format: WEB-DL + source: Web other: Mux release_group: UBi type: episode @@ -2713,11 +2267,11 @@ season: 1 episode: 10 screen_size: 1080p - video_codec: h264 + video_codec: H.264 language: [it, en] - format: WEB-DL + source: Web other: Mux - audio_codec: AC3 + audio_codec: Dolby Digital subtitle_language: [it, en] release_group: GiuseppeTnT Littlelinx type: episode @@ -2726,10 +2280,10 @@ : title: Show Name season: 4 episode: [7, 8] - video_codec: h264 + video_codec: H.264 language: it audio_codec: AAC - format: HDTV + source: HDTV other: Mux release_group: Group type: episode @@ -2740,9 +2294,9 @@ episode: 18 episode_title: Un Tuffo Nel Passato language: it - format: HDTV + source: HDTV other: Mux - video_codec: h264 + video_codec: H.264 release_group: Group type: episode @@ -2750,33 +2304,34 @@ : title: Show Name season: 3 screen_size: 1080p - format: BluRay + source: Blu-ray other: Mux - video_codec: h264 - audio_codec: DTS - audio_profile: HDMA + video_codec: H.264 + audio_codec: DTS-HD + audio_profile: Master Audio type: episode ? Show.Name.-.07.(2016).[RH].[English.Dubbed][WEBRip]..[HD.1080p] : options: -t episode episode: 7 - format: WEBRip + source: Web + other: Rip language: en - other: HD + other: [HD, Rip] screen_size: 1080p title: Show Name type: episode year: 2016 ? Show.Name.-.476-479.(2007).[HorribleSubs][WEBRip]..[HD.720p] -: options: -t episode -E +: options: -t episode episode: - 476 - 477 - 478 - 479 - format: WEBRip - other: HD + source: Web + other: [Rip, HD] release_group: HorribleSubs screen_size: 720p title: Show Name @@ -2788,128 +2343,19 @@ title: 11.22.63 season: 1 episode: 6 - format: HDTV + source: HDTV release_group: abc type: episode -# Streaming service: DIY Network -? Show.Name.S01.720p.DIY.WEBRip.AAC2.0.H.264-BTN -: title: Show Name - season: 1 - screen_size: 720p - streaming_service: DIY Network - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: BTN - type: episode - -# Streaming service: USA Network -? Show.Name.S01E02.Exfil.1080p.USAN.WEBRip.AAC2.0.x264-AJP69 -: title: Show Name - season: 1 - episode: 2 - screen_size: 1080p - streaming_service: USA Network - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: AJP69 - type: episode - -# Streaming service: TV3 Ireland -? Show.Name.S01E08.576p.TV3.WEBRip.AAC2.0.x264-HARiKEN -: title: Show Name - season: 1 - episode: 8 - screen_size: 576p - streaming_service: TV3 Ireland - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: HARiKEN - type: episode - -# Streaming service: TV4 Sweeden -? Show.Name.S05.720p.TV4.WEBRip.AAC2.0.H.264-BTW -: title: Show Name - season: 5 - screen_size: 720p - streaming_service: TV4 Sweeden - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: BTW - type: episode - -# Streaming service: TLC -? Show.Name.S02.720p.TLC.WEBRip.AAC2.0.x264-BTW -: title: Show Name - season: 2 - screen_size: 720p - streaming_service: TLC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: BTW - type: episode - -# Streaming service: Investigation Discovery -? Show.Name.S01E01.720p.ID.WEBRip.AAC2.0.x264-BTW -: title: Show Name - season: 1 - episode: 1 - screen_size: 720p - streaming_service: Investigation Discovery - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: BTW - type: episode - -# Streaming service: RTÉ One -? Show.Name.S10E01.576p.RTE.WEBRip.AAC2.0.H.264-RTN -: title: Show Name - season: 10 - episode: 1 - screen_size: 576p - streaming_service: RTÉ One - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: RTN - type: episode - -# Streaming service: AMC -? Show.Name.S01E01.1080p.AMC.WEBRip.H.264.AAC2.0-CasStudio -: title: Show Name - season: 1 - episode: 1 - screen_size: 1080p - streaming_service: AMC - format: WEBRip - audio_codec: AAC - audio_channels: '2.0' - video_codec: h264 - release_group: CasStudio - type: episode - ? Proof.2015.S01E10.1080p.WEB-DL.DD5.1.H.264-KINGS.mkv : title: Proof season: 1 episode: 10 screen_size: 1080p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: KINGS container: mkv type: episode @@ -2920,8 +2366,8 @@ season: 6 episode: 16 other: Hardcoded Subtitles - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 subtitle_language: sv type: episode @@ -2932,10 +2378,11 @@ episode: 8 screen_size: 1080p streaming_service: Netflix - format: WEBRip - audio_codec: AC3 + source: Web + other: Rip + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: ViSUM container: mkv type: episode @@ -2953,10 +2400,10 @@ season: 2 episode: 5 screen_size: 1080p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: HKD container: mkv type: episode @@ -2967,10 +2414,10 @@ episode: 14 episode_title: Brother Up screen_size: 1080p - format: WEB-DL + source: Web audio_codec: AAC audio_channels: '2.0' - video_codec: h264 + video_codec: H.264 release_group: TVSmash container: mkv type: episode @@ -2981,10 +2428,11 @@ episode: 2 episode_title: Tempus Fugit screen_size: 720p - format: WEBRip + source: Web + other: Rip audio_codec: AAC audio_channels: '2.0' - video_codec: h264 + video_codec: H.264 release_group: BTW container: mkv type: episode @@ -2995,10 +2443,10 @@ episode: 2 episode_title: The Brain In The Bot screen_size: 1080p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: R2D2 container: mkv type: episode @@ -3009,10 +2457,10 @@ season: 1 episode: 7 screen_size: 1080p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 subtitle_language: nl release_group: Q container: mkv @@ -3024,11 +2472,11 @@ episode: 1 episode_title: Love the Way You Lie screen_size: 1080p - format: WEB-DL + source: Web audio_codec: AAC audio_channels: '2.0' - video_codec: h264 - language: nl + video_codec: H.264 + release_group: NL container: mkv type: episode @@ -3037,8 +2485,8 @@ season: 2 episode: 12 screen_size: 1080p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' release_group: Het.Robot.Team.OYM type: episode @@ -3050,8 +2498,8 @@ season: 1 episode: 2 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3062,7 +2510,7 @@ : title: Show Name season: 4 episode: 8 - format: HDTV + source: HDTV language: ca type: episode @@ -3074,7 +2522,7 @@ # newpct ? Show.Name.-.Temporada1.[HDTV][Cap.105][Español.Castellano] : title: Show Name - format: HDTV + source: HDTV season: 1 episode: 5 language: ca @@ -3083,7 +2531,7 @@ # newpct ? Show.Name.-.Temporada1.[HDTV][Cap.105][Español] : title: Show Name - format: HDTV + source: HDTV season: 1 episode: 5 language: es @@ -3095,8 +2543,8 @@ season: 1 episode: [2, 3, 4] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3107,8 +2555,8 @@ season: 15 episode: 3 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3119,8 +2567,8 @@ season: 15 episode: [3, 4, 5, 6] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3131,8 +2579,8 @@ season: 1 episode: 2 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3143,8 +2591,8 @@ season: 1 episode: 2 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT type: episode @@ -3155,11 +2603,11 @@ season: 1 episode: [12, 13, 14] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 language: es release_group: NEWPCT - other: FINAL + episode_details: Final type: episode ? Mastercook Italia - Stagione 6 (2016) 720p ep13 spyro.mkv @@ -3189,23 +2637,22 @@ episode: 18 episode_title: Un Tuffo Nel Passato language: it - format: HDTV + source: HDTV other: Mux - video_codec: h264 + video_codec: H.264 release_group: NovaRip type: episode # Italian releases ? Show Name 3x18 Un Tuffo Nel Passato ITA HDTVMux x264 NovaRip -: options: --allowed-languages it - title: Show Name +: title: Show Name season: 3 episode: 18 episode_title: Un Tuffo Nel Passato language: it - format: HDTV + source: HDTV other: Mux - video_codec: h264 + video_codec: H.264 release_group: NovaRip type: episode @@ -3220,8 +2667,8 @@ episode: 9 subtitle_language: und screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: RRH type: episode @@ -3233,7 +2680,8 @@ season: 6 episode: 5 screen_size: 1080p - format: WEBRip + source: Web + other: Rip subtitle_language: pt-BR type: episode @@ -3242,7 +2690,7 @@ season: 1 episode: 7 episode_title: Super, Title - format: WEB-DL + source: Web screen_size: 720p subtitle_language: pt-BR container: srt @@ -3257,7 +2705,8 @@ season: 6 episode: 5 screen_size: 1080p - format: WEBRip + source: Web + other: Rip subtitle_language: pt type: episode @@ -3267,8 +2716,8 @@ episode: 1 subtitle_language: spa screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: sPHD type: episode @@ -3277,8 +2726,8 @@ season: 1 episode: 1 subtitle_language: deu - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: ASAP type: episode @@ -3289,15 +2738,15 @@ episode_title: Aint Nothing Like the Real Thing subtitle_language: deu screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 type: episode ? Show.Name.S01.Season.Complet.WEBRiP.Ro.Subbed.TM : title: Show Name season: 1 - other: Complete - format: WEBRip + other: [Complete, Rip] + source: Web subtitle_language: ro type: episode @@ -3307,10 +2756,11 @@ season: 3 subtitle_language: en screen_size: 720p - format: WEBRip - video_codec: h264 + source: Web + other: Rip + video_codec: H.264 container: mkv - audio_codec: AC3 + audio_codec: Dolby Digital audio_channels: '5.1' release_group: Ehhhh type: episode @@ -3321,10 +2771,10 @@ season: 2 episode: 3 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: Belex - other: DualAudio + other: Dual Audio language: und type: episode @@ -3333,8 +2783,8 @@ season: 6 episode: 10 screen_size: 1080p - format: WEB-DL - other: DualAudio + source: Web + other: Dual Audio language: und release_group: RK type: episode @@ -3344,8 +2794,8 @@ season: 6 episode: 12 screen_size: 720p - format: WEB-DL - other: DualAudio + source: Web + other: Dual Audio language: und type: episode @@ -3355,8 +2805,8 @@ episode: 7 screen_size: 720p language: und - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 0SEC-pia container: mkv type: episode @@ -3366,10 +2816,11 @@ season: 2 episode: 7 episode_title: Shiva - audio_codec: AC3 + audio_codec: Dolby Digital language: und - format: WEBRip - video_codec: h264 + source: Web + other: Rip + video_codec: H.264 type: episode # Legendas @@ -3377,23 +2828,22 @@ : title: Show Name season: 5 screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: Belex - other: DualAudio + other: Dual Audio subtitle_language: und type: episode # Legendas ? Show.Name.S05.1080p.BluRay.x264-Belex.-.Dual.Audio.+.Legendas -: options: --allowed-languages und - title: Show Name +: title: Show Name season: 5 screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: Belex - other: DualAudio + other: Dual Audio subtitle_language: und type: episode @@ -3401,12 +2851,10 @@ ? Show.Name.S01E03.HDTV.Subtitulado.Esp.SC ? Show.Name.S01E03.HDTV.Subtitulado.Espanol.SC ? Show.Name.S01E03.HDTV.Subtitulado.Español.SC -# SC is a release group, not a language. To be addressed in #296 -: options: --allowed-languages spa --allowed-countries ESP - title: Show Name +: title: Show Name season: 1 episode: 3 - format: HDTV + source: HDTV subtitle_language: es release_group: SC type: episode @@ -3418,7 +2866,7 @@ season: 2 episode: 8 screen_size: 720p - format: WEB-DL + source: Web subtitle_language: und type: episode @@ -3435,8 +2883,8 @@ season: 6 episode: 5 language: de - audio_codec: AC3 - format: HDTV + audio_codec: Dolby Digital + source: HDTV type: episode ? Show.Name.S01E01.Savage.Season.GERMAN.DUBBED.WS.HDTVRip.x264-TVP @@ -3445,9 +2893,9 @@ episode: 1 episode_title: Savage Season language: de - other: WideScreen - format: HDTV - video_codec: h264 + other: [Widescreen, Rip] + source: HDTV + video_codec: H.264 release_group: TVP type: episode @@ -3457,7 +2905,7 @@ episode: 3 language: en screen_size: 720p - format: WEB-DL + source: Web release_group: JRR type: episode @@ -3475,8 +2923,8 @@ : title: Show Name season: 5 episode: 5 - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: AFG subtitle_language: he type: episode @@ -3487,7 +2935,7 @@ episode: 31 episode_title: Episode 55 screen_size: 720p - format: HDTV + source: HDTV type: episode # Scenario: Removing invalid season and episode matches. Correct episode_title match @@ -3498,10 +2946,11 @@ episode_title: eps2 4 m4ster-s1ave aes screen_size: 1080p streaming_service: Amazon Prime - format: WEBRip - audio_codec: AC3 + source: Web + other: Rip + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: GROUP type: episode @@ -3511,9 +2960,9 @@ episode: 5 episode_title: 3xpl0its screen_size: 720p - format: WEB-DL + source: Web subtitle_language: en - video_codec: h264 + video_codec: H.264 type: episode # Regression: S4L release group detected as season 4 @@ -3522,8 +2971,8 @@ : title: Show Name season: 1 episode: 6 - format: DVD - video_codec: h264 + source: DVD + video_codec: H.264 release_group: S4L type: episode @@ -3532,8 +2981,8 @@ : title: The Show Name date: 2016-05-18 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: GROUP.VTV type: episode @@ -3547,7 +2996,7 @@ : title: Show Name episode: 6 screen_size: 720p - video_profile: 10bit + color_depth: 10-bit crc32: 1F5578AC release_group: SuperGroup type: episode @@ -3557,7 +3006,7 @@ : title: Show Name episode: 6 screen_size: 1080p - video_profile: 10bit + color_depth: 10-bit crc32: 1F5578AC release_group: SuperGroup type: episode @@ -3568,34 +3017,45 @@ title: Dimension W episode: 5 screen_size: 720p - video_profile: 10bit - other: DualAudio - format: TV + color_depth: 10-bit + other: Dual Audio + source: TV language: und crc32: EDA6E7F1 type: episode +? "[Zero-Raws].Show.Name.493-498.&.500-507.(CX.1280x720.VFR.x264.AAC)" +: release_group: Zero-Raws + title: Show Name + episode: [493, 494, 495, 496, 497, 498, 500, 501, 502, 503, 504, 505, 506, 507] + screen_size: 720p + other: Variable Frame Rate + video_codec: H.264 + audio_codec: AAC + type: episode + # NetflixUHD ? Show.Name.S01E06.NetflixUHD : title: Show Name season: 1 episode: 6 streaming_service: Netflix - other: UltraHD + other: Ultra HD type: episode ? Show.Name.S04E13.FINAL.MULTI.DD51.2160p.NetflixUHDRip.x265-TVS : title: Show Name season: 4 episode: 13 - other: FINAL + episode_details: Final language: mul - audio_codec: AC3 + audio_codec: Dolby Digital audio_channels: '5.1' - screen_size: 4K + screen_size: 2160p streaming_service: Netflix - format: UHDTV - video_codec: h265 + source: Ultra HDTV + other: Rip + video_codec: H.265 release_group: TVS type: episode @@ -3606,7 +3066,7 @@ episode_title: Of Late I Think of Rosewood streaming_service: iTunes other: HD - video_codec: h264 + video_codec: H.264 type: episode ? Show.Name.S01.720p.iTunes.h264-Group @@ -3614,7 +3074,7 @@ season: 1 screen_size: 720p streaming_service: iTunes - video_codec: h264 + video_codec: H.264 release_group: Group type: episode @@ -3625,7 +3085,7 @@ episode_title: eps1 0 hellofriend other: HD streaming_service: iTunes - audio_codec: AC3 + audio_codec: Dolby Digital language: spa year: 2015 container: avi @@ -3635,10 +3095,11 @@ : release_group: Hanamaru&LoliHouse title: The Dragon Dentist episode: 1 - format: WEBRip + source: Web + other: Rip screen_size: 1080p - video_codec: h265 - video_profile: 10bit + video_codec: H.265 + color_depth: 10-bit audio_codec: AAC container: mkv type: episode @@ -3653,7 +3114,7 @@ : title: Vikings season: 4 screen_size: 1080p - format: WEB-DL + source: Web subtitle_language: nl type: episode @@ -3663,8 +3124,8 @@ episode: 1 episode_title: Spark of Rebellion edition: Alternative Cut - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: W4F container: mp4 type: episode @@ -3673,8 +3134,8 @@ : title: DCs Legends of Tomorrow season: 2 episode: 12 - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: FUM type: episode @@ -3688,12 +3149,12 @@ ? Broadchurch.S01.DIRFIX.720p.BluRay.x264-SHORTBREHD : title: Broadchurch season: 1 - other: Proper + other: Fix screen_size: 720p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: SHORTBREHD - proper_count: 1 + -proper_count: 1 type: episode ? Simply Red - 2016-07-08 Montreux Jazz Festival 720p @@ -3708,8 +3169,8 @@ season: 7 episode: 14 other: Internal - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: YesTV type: episode @@ -3718,7 +3179,7 @@ date: 2016-05-25 episode_title: James McAvoy other: Internal - video_codec: XviD + video_codec: Xvid release_group: AFG type: episode @@ -3728,8 +3189,8 @@ episode: 13 other: [Internal, Read NFO] screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 2HD type: episode @@ -3739,8 +3200,8 @@ episode: 13 other: Read NFO screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 2HD type: episode @@ -3748,10 +3209,10 @@ : title: Dr Ken season: 1 episode: 21 - other: Proper + other: Fix screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: SVA type: episode @@ -3759,20 +3220,21 @@ : title: Rick and Morty season: 1 edition: Uncensored - format: BluRay + other: Rip + source: Blu-ray screen_size: 1080p - video_codec: h265 + video_codec: H.265 type: episode ? 12.Monkeys.S01E01.LiMiTED.FRENCH.1080p.WEB-DL.H264-AUTHORiTY : title: 12 Monkeys season: 1 episode: 1 - edition: Limited Edition + edition: Limited language: french screen_size: 1080p - format: WEB-DL - video_codec: h264 + source: Web + video_codec: H.264 release_group: AUTHORiTY type: episode @@ -3782,8 +3244,8 @@ season: 3 episode: 5 other: West Coast Feed - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 2HD type: episode @@ -3793,8 +3255,8 @@ season: 2 episode: [7, 8] other: West Coast Feed - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 2HD type: episode @@ -3804,8 +3266,8 @@ episode: [1, 2] other: East Coast Feed screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: KILLERS type: episode @@ -3815,8 +3277,8 @@ season: 2 episode: 7 other: East Coast Feed - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 2HD type: episode @@ -3827,10 +3289,10 @@ episode: 7 other: East Coast Feed screen_size: 720p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: NTb type: episode @@ -3839,8 +3301,8 @@ season: 2 episode: 4 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: 0SEC [GloDLS] container: mkv type: episode @@ -3851,8 +3313,8 @@ episode: 1 episode_title: Los Angeles screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: MiNDTHEGAP type: episode @@ -3866,10 +3328,11 @@ episode_title: and the winner is screen_size: 720p streaming_service: Amazon Prime - format: WEBRip - audio_codec: AC3 + source: Web + other: Rip + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: casstudio container: mkv type: episode @@ -3878,14 +3341,12 @@ : title: Adventure Time season: 8 episode: 16 - season: 8 - episode: 16 episode_title: Elements Part 1 Skyhooks screen_size: 720p - format: WEB-DL + source: Web audio_codec: AAC audio_channels: '2.0' - video_codec: h264 + video_codec: H.264 release_group: RTN container: mkv type: episode @@ -3895,9 +3356,7 @@ season: 7 episode: 22 episode_title: 2000 Light Years from Home - other: Classic container: mkv - mimetype: video/x-matroska type: episode ? Show.Name.S02E01.Super.Title.720p.WEB-DL.DD5.1.H.264-ABC.nzb @@ -3906,18 +3365,17 @@ episode: 1 episode_title: Super Title screen_size: 720p - format: WEB-DL - audio_codec: AC3 + source: Web + audio_codec: Dolby Digital audio_channels: '5.1' - video_codec: h264 + video_codec: H.264 release_group: ABC container: nzb type: episode ? "[SGKK] Bleach 312v1 [720p/mkv]-Group.mkv" : title: Bleach - season: 3 - episode: 12 + episode: 312 version: 1 screen_size: 720p release_group: Group @@ -3929,9 +3387,10 @@ season: 2 episode: 8 screen_size: 720p - format: WEBRip - video_codec: h264 - audio_codec: EAC3 + source: Web + other: Rip + video_codec: H.264 + audio_codec: Dolby Digital Plus release_group: KiNGS container: mkv type: episode @@ -3950,16 +3409,1285 @@ year: 2014 season: 2 episode: 8 - format: HDTV + source: HDTV release_group: lol[ettv] container: mkv type: episode ? "[Despair-Paradise].Kono.Subarashii.Sekai.ni.Shukufuku.wo!.2.-..09.vostfr.FHD" -: options: -E -t episode - release_group: Despair-Paradise +: release_group: Despair-Paradise title: Kono Subarashii Sekai ni Shukufuku wo! 2 episode: 9 subtitle_language: fr - other: FullHD + other: Full HD + type: episode + +? Whose Line is it anyway/Season 01/Whose.Line.is.it.Anyway.US.S13E01.720p.WEB.x264-TBS.mkv +: title: Whose Line is it Anyway + season: 13 + episode: 1 + country: US + screen_size: 720p + source: Web + video_codec: H.264 + release_group: TBS + container: mkv + type: episode + +? Planet.Earth.II.S01.2160p.UHD.BluRay.HDR.DTS-HD.MA5.1.x265-ULTRAHDCLUB +: title: Planet Earth II + season: 1 + screen_size: 2160p + source: Ultra HD Blu-ray + other: HDR10 + audio_codec: DTS-HD + audio_profile: Master Audio + audio_channels: '5.1' + video_codec: H.265 + release_group: ULTRAHDCLUB + type: episode + +? Reizen.Waes.S03.FLEMISH.1080p.HDTV.MP2.H.264-NOGRP/Reizen.Waes.S03E05.China.PART1.FLEMISH.1080p.HDTV.MP2.H.264-NOGRP.mkv +: title: Reizen Waes + season: 3 + episode: 5 + part: 1 + language: nl-BE + screen_size: 1080p + source: HDTV + video_codec: H.264 + audio_codec: MP2 + release_group: NOGRP + container: mkv + type: episode + +? "/folder/Marvels.Agent.Carter.S02E05.The.Atomic.Job.1080p.WEB-DL.DD5.1.H264-Coo7[rartv]/Marvel's.Agent.Carter.S02E05.The.Atomic.Job.1080p.WEB-DL.DD5.1.H.264-Coo7.mkv" +: title: Marvel's Agent Carter + season: 2 + episode: 5 + episode_title: The Atomic Job + release_group: Coo7 + type: episode + +? My.Name.Is.Earl.S01-S04.DVDRip.XviD-AR +: title: My Name Is Earl + season: [1, 2, 3, 4] + source: DVD + other: Rip + video_codec: Xvid + release_group: AR + type: episode + +? American.Dad.S01E01.Pilot.DVDRip.x264-CS +: title: American Dad + season: 1 + episode: 1 + episode_details: Pilot + source: DVD + other: Rip + video_codec: H.264 + release_group: CS + type: episode + +? Black.Sails.S01E01.HDTV.XviD.HebSubs-DR +: title: Black Sails + season: 1 + episode: 1 + source: HDTV + video_codec: Xvid + subtitle_language: he + release_group: DR + type: episode + +? The.West.Wing.S04E06.Game.On.720p.WEB-DL.AAC2.0.H.264-MC +: title: The West Wing + season: 4 + episode: 6 + episode_title: Game On + screen_size: 720p + source: Web + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: MC + type: episode + +? 12.Monkeys.S02E05.1080p.WEB-DL.DD5.1.H.264-NA +: title: 12 Monkeys + season: 2 + episode: 5 + screen_size: 1080p + source: Web + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: NA + type: episode + +? Fear.the.Walking.Dead.S03E07.1080p.AMZN.WEBRip.DD5.1.x264-VLAD[rarbg]/Fear.the.Walking.Dead.S03E07.1080p.AMZN.WEB-DL.DD+5.1.H.264-VLAD.mkv +: title: Fear the Walking Dead + season: 3 + episode: 7 + screen_size: 1080p + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + release_group: VLAD + container: mkv + type: episode + +? American.Crime.S01E02.1080p.WEB-DL.DD5.1.H.264-NL +: title: American Crime + season: 1 + episode: 2 + screen_size: 1080p + source: Web + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: NL + type: episode + +? Better.Call.Saul.S02.720p.HDTV.x264-TL +: title: Better Call Saul + season: 2 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: TL + type: episode + +? 60.Minutes.2008.12.14.HDTV.XviD-YT +: options: -T '60 Minutes' + title: 60 Minutes + date: 2008-12-14 + source: HDTV + video_codec: Xvid + release_group: YT + type: episode + +? Storm.Chasers.Season.1 +: title: Storm Chasers + season: 1 + type: episode + +? Faking.It.2014.S03E08.720p.HDTV.x264-AVS +: title: Faking It + year: 2014 + season: 3 + episode: 8 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: AVS + type: episode + +? /series/Marvel's Agents of S.H.I.E.L.D/Season 4/Marvels.Agents.of.S.H.I.E.L.D.S04E01.The.Ghost.1080p.WEB-DL.DD5.1.H.264-AG.mkv +: title: Marvels Agents of S.H.I.E.L.D. + season: 4 + episode: 1 + episode_title: The Ghost + screen_size: 1080p + source: Web + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: AG + container: mkv + type: episode + +? "[FASubs & TTF] Inuyasha - 099 [DVD] [B15AA1AC].mkv" +: release_group: FASubs & TTF + title: Inuyasha + episode: 99 + source: DVD + crc32: B15AA1AC + container: mkv + type: episode + +? Show.Name.S01E03.PL.SUBBED.480p.WEBRiP.x264 +: title: Show Name + season: 1 + episode: 3 + subtitle_language: pl + screen_size: 480p + source: Web + other: Rip + video_codec: H.264 + type: episode + +? Show.Name.s10e15(233).480p.BDRip-AVC.Ukr.hurtom +: title: Show Name + season: 10 + episode: 15 + screen_size: 480p + source: Blu-ray + other: Rip + video_codec: H.264 + language: uk + release_group: hurtom + type: episode + +? Goof.Troop.1x24.Waste.Makes.Haste.720p.HDTV.x264.CZ-SDTV +: title: Goof Troop + season: 1 + episode: 24 + episode_title: Waste Makes Haste + screen_size: 720p + source: HDTV + video_codec: H.264 + language: cs + release_group: SDTV + type: episode + +? Marvels.Daredevil.S02E11.German.DL.DUBBED.2160p.WebUHD.x264-UHDTV +: title: Marvels Daredevil + season: 2 + episode: 11 + language: [de, mul] + screen_size: 2160p + source: Web + video_codec: H.264 + release_group: UHDTV + type: episode + +? BBC The Story of China 1 of 6 - Ancestors CC HDTV x264 AC3 2.0 720p mkv +: title: BBC The Story of China + episode: 1 + episode_count: 6 + episode_title: Ancestors + source: HDTV + video_codec: H.264 + audio_codec: Dolby Digital + audio_channels: '2.0' + screen_size: 720p + container: mkv + type: episode + +? Duck.Dynasty.S09E04.Drone.Survivor.720p.AE.WEBRip.AAC2.0.H264-BTW[rartv] +: title: Duck Dynasty + season: 9 + episode: 4 + episode_title: Drone Survivor + screen_size: 720p + streaming_service: A&E + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTW[rartv] + type: episode + +? Mr.Selfridge.S04E03.720p.WEB-DL.AAC2.0.H264-MS[rartv] +: title: Mr Selfridge + season: 4 + episode: 3 + screen_size: 720p + source: Web + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: MS[rartv] + type: episode + +? Second.Chance.S01E02.One.More.Notch.1080p.WEB-DL.DD5.1.H264-SC[rartv] +: title: Second Chance + season: 1 + episode: 2 + episode_title: One More Notch + screen_size: 1080p + source: Web + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: rartv + type: episode + +? Total.Divas.S05E01.720p.HDTV.AAC2.0.H.264-SC-SDH +: title: Total Divas + season: 5 + episode: 1 + screen_size: 720p + source: HDTV + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + video_profile: Scalable Video Coding + release_group: SDH + type: episode + +? Marvel's Jessica Jones (2015) s01e09 - AKA Sin Bin.mkv +: title: Marvel's Jessica Jones + season: 1 + episode: 9 + episode_title: AKA Sin Bin + container: mkv + type: episode + +? Hotel.Hell.S01E01.720p.DD5.1.448kbps-ALANiS +: title: Hotel Hell + season: 1 + episode: 1 + screen_size: 720p + audio_codec: Dolby Digital + audio_channels: '5.1' + audio_bit_rate: 448Kbps + release_group: ALANiS + type: episode + +? Greys.Anatomy.S07D1.NTSC.DVDR-ToF +: title: Greys Anatomy + season: 7 + disc: 1 + other: NTSC + source: DVD + release_group: ToF + type: episode + +? Greys.Anatomy.S07D1.NTSC.DVDR-ToF +: title: Greys Anatomy + season: 7 + disc: 1 + other: NTSC + source: DVD + release_group: ToF + type: episode + +? Greys.Anatomy.S07D1-3&5.NTSC.DVDR-ToF +: title: Greys Anatomy + season: 7 + disc: [1, 2, 3, 5] + other: NTSC + source: DVD + release_group: ToF + type: episode + +? El.Principe.2014.S01D01.SPANiSH.COMPLETE.BLURAY-COJONUDO +: title: El Principe + year: 2014 + season: 1 + disc: 1 + language: spa + other: Complete + source: Blu-ray + release_group: COJONUDO + type: episode + +? The Simpsons - Season 2 Complete [DVDRIP VP7 KEGGERMAN +: title: The Simpsons + season: 2 + other: [Complete, Rip] + source: DVD + video_codec: VP7 + release_group: KEGGERMAN + type: episode + +? Barney & Friends_ Easy as ABC (Season 9_ Episode 15)_VP8_Vorbis_360p.webm +: title: Barney & Friends Easy as ABC + season: 9 + episode: 15 + video_codec: VP8 + audio_codec: Vorbis + screen_size: 360p + container: webm + type: episode + +? Victoria.S01.1080p.BluRay.HEVC.DTSMA.LPCM.PGS-OZM +: title: Victoria + season: 1 + screen_size: 1080p + source: Blu-ray + video_codec: H.265 + audio_codec: [DTS-HD, LPCM] + audio_profile: Master Audio + # Does it worth to add subtitle_format? Such rare case + # subtitle_format: PGS + # release_group: OZM + type: episode + +? The.Prisoners.S01E03.1080p.DM.AAC2.0.x264-BTN +: title: The Prisoners + season: 1 + episode: 3 + screen_size: 1080p + source: Digital Master + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTN + type: episode + +? Panorama.S2013E25.Broken.by.Battle.1080p.DM.AAC2.0.x264-BTN +: title: Panorama + season: 2013 + episode: 25 + episode_title: Broken by Battle + screen_size: 1080p + source: Digital Master + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTN + type: episode + +? Our.World.S2014E11.Chinas.Model.Army.720p.DM.AAC2.0.x264-BTN +: title: Our World + season: 2014 + episode: 11 + episode_title: Chinas Model Army + screen_size: 720p + source: Digital Master + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTN + type: episode + +? Storyville.S2016E08.My.Nazi.Legacy.1080p.DM.x264-BTN +: title: Storyville + season: 2016 + episode: 8 + episode_title: My Nazi Legacy + screen_size: 1080p + source: Digital Master + video_codec: H.264 + release_group: BTN + type: episode + +? Comedians.in.Cars.Getting.Coffee.S07E01.1080p.DM.FLAC2.0.x264-NTb +: title: Comedians in Cars Getting Coffee + season: 7 + episode: 1 + screen_size: 1080p + source: Digital Master + audio_codec: FLAC + audio_channels: '2.0' + video_codec: H.264 + release_group: NTb + type: episode + +? "[SomeGroup-Fansub]_Show_Name_727_[VOSTFR][HD_1280x720]" +: release_group: SomeGroup-Fansub + title: Show Name + episode: 727 + subtitle_language: fr + other: HD + screen_size: 720p type: episode + +? "[GROUP]Show_Name_726_[VOSTFR]_[V1]_[8bit]_[720p]_[2F7B3FA2]" +: release_group: GROUP + title: Show Name + episode: 726 + subtitle_language: fr + version: 1 + color_depth: 8-bit + screen_size: 720p + crc32: 2F7B3FA2 + type: episode + +? Show Name 445 VOSTFR par Fansub-Resistance (1280*720) - version MQ +: title: Show Name + episode: 445 + subtitle_language: fr + screen_size: 720p + type: episode + +? Anime Show Episode 159 v2 [VOSTFR][720p][AAC].mp4 +: title: Anime Show + episode: 159 + version: 2 + subtitle_language: fr + screen_size: 720p + audio_codec: AAC + container: mp4 + type: episode + +? "[Group] Anime Super Episode 161 [VOSTFR][720p].mp4" +: release_group: Group + title: Anime Super + episode: 161 + subtitle_language: fr + screen_size: 720p + container: mp4 + type: episode + +? Anime Show Episode 59 v2 [VOSTFR][720p][AAC].mp4 +: title: Anime Show + episode: 59 + version: 2 + subtitle_language: fr + screen_size: 720p + audio_codec: AAC + container: mp4 + type: episode + +? Show.Name.-.476-479.(2007).[HorribleSubs][WEBRip]..[HD.720p] +: title: Show Name + episode: [476, 477, 478, 479] + year: 2007 + release_group: HorribleSubs + source: Web + other: [Rip, HD] + screen_size: 720p + type: episode + +? Show Name - 722 [HD_1280x720].mp4 +: title: Show Name + episode: 722 + other: HD + screen_size: 720p + container: mp4 + type: episode + +? Show!.Name.2.-.10.(2016).[HorribleSubs][WEBRip]..[HD.720p] +: title: Show! Name 2 + episode: 10 + year: 2016 + release_group: HorribleSubs + source: Web + other: [Rip, HD] + screen_size: 720p + type: episode + +? 'C:\folder\[GROUP]_An_Anime_Show_100_-_10_[1080p]_mkv' +: options: -T 'An Anime Show 100' + release_group: GROUP + title: An Anime Show 100 + episode: 10 + screen_size: 1080p + container: mkv + type: episode + +? "[Group].Show.Name!.Super!!.-.05.[720p][AAC].mp4" +: release_group: Group + title: Show Name! Super!! + episode: 5 + screen_size: 720p + audio_codec: AAC + container: mp4 + type: episode + +? "[GROUP].Mobile.Suit.Gundam.Unicorn.RE.0096.-.14.[720p].mkv" +: options: -T 'Mobile Suit Gundam Unicorn RE 0096' + release_group: GROUP + title: Mobile Suit Gundam Unicorn RE 0096 + episode: 14 + screen_size: 720p + container: mkv + type: episode + +? Show.Name.-.Other Name.-.02.(1280x720.HEVC.AAC) +: title: Show Name + alternative_title: Other Name + episode: 2 + screen_size: 720p + video_codec: H.265 + audio_codec: AAC + type: episode + +? "[GroupName].Show.Name.-.02.5.(Special).[BD.1080p]" +: release_group: GroupName + title: Show Name + episode: 2 + episode_details: Special + screen_size: 1080p + source: Blu-ray + type: episode + +? "[Group].Show.Name.2.The.Big.Show.-.11.[1080p]" +: title: Show Name 2 The Big Show + episode: 11 + screen_size: 1080p + type: episode + +? "[SuperGroup].Show.Name.-.Still.Name.-.11.[1080p]" +: release_group: SuperGroup + title: Show Name + alternative_title: Still Name + episode: 11 + screen_size: 1080p + type: episode + +? "[SuperGroup].Show.Name.-.462" +: release_group: SuperGroup + title: Show Name + episode: 462 + type: episode + +? Show.Name.10.720p +: title: Show Name + episode: 10 + screen_size: 720p + type: episode + +? "[Group].Show.Name.G2.-.19.[1080p]" +: release_group: Group + title: Show Name G2 + episode: 19 + screen_size: 1080p + type: episode + +? "[Group].Show.Name.S2.-.19.[1080p]" +? /Show.Name.S2/[Group].Show.Name.S2.-.19.[1080p] +? /Show Name S2/[Group].Show.Name.S2.-.19.[1080p] +: options: -T 'Show Name S2' + release_group: Group + title: Show Name S2 + episode: 19 + screen_size: 1080p + type: episode + +? "[ABC]_Show_Name_001.mkv" +: release_group: ABC + title: Show Name + episode: 1 + container: mkv + type: episode + +? 003-005. Show Name - Ep Name.mkv +: episode: [3, 4, 5] + title: Show Name + episode_title: Ep Name + container: mkv + type: episode + +? 003. Show Name - Ep Name.mkv +: episode: 3 + title: Show Name + episode_title: Ep Name + container: mkv + type: episode + +? 165.Show Name.s08e014 +: absolute_episode: 165 + title: Show Name + season: 8 + episode: 14 + type: episode + +? Show Name - 16x03-05 - 313-315 +? Show.Name.16x03-05.313-315-GROUP +? Show Name 16x03-05 313-315 +? Show Name - 313-315 - s16e03-05 +? Show.Name.313-315.s16e03-05 +? Show Name 313-315 s16e03-05 +: title: Show Name + absolute_episode: [313, 314, 315] + season: 16 + episode: [3, 4, 5] + type: episode + +? Show Name 13-16 +: title: Show Name + episode: [13, 14, 15, 16] + type: episode + +? Show Name 804 vostfr HD +: options: --episode-prefer-number + title: Show Name + episode: 804 + subtitle_language: fr + other: HD + type: episode + +? "[Doki] Re Zero kara Hajimeru Isekai Seikatsu - 01 1920x1080 Hi10P BD FLAC [7F64383D].mkv" +: release_group: Doki + title: Re Zero kara Hajimeru Isekai Seikatsu + episode: 1 + screen_size: 1080p + aspect_ratio: 1.778 + video_profile: High 10 + color_depth: 10-bit + source: Blu-ray + audio_codec: FLAC + crc32: 7F64383D + container: mkv + type: episode + +? Shark Tank (AU) - S02E01 - HDTV-720p.mkv +: title: Shark Tank + country: AU + season: 2 + episode: 1 + source: HDTV + screen_size: 720p + container: mkv + type: episode + +? "[HorribleSubs] Garo - Vanishing Line - 01 [1080p].mkv" +: release_group: HorribleSubs + title: Garo + alternative_title: Vanishing Line + episode: 1 + screen_size: 1080p + container: mkv + type: episode + +? "[HorribleSubs] Yowamushi Pedal - Glory Line - 01 [1080p].mkv" +: release_group: HorribleSubs + title: Yowamushi Pedal + alternative_title: Glory Line + episode: 1 + screen_size: 1080p + container: mkv + type: episode + +? c:\Temp\autosubliminal\completed\2 Broke Girls\Season 01\2 Broke Girls - S01E01 - HDTV-720p Proper - x264 AC3 - IMMERSE - [2011-09-19].mkv +: title: 2 Broke Girls + season: 1 + episode: 1 + source: HDTV + screen_size: 720p + other: Proper + video_codec: H.264 + audio_codec: Dolby Digital + release_group: IMMERSE + date: 2011-09-19 + container: mkv + type: episode + +? c:\Temp\postprocessing\Marvels.Agents.of.S.H.I.E.L.D.s01e02.0.8.4.720p.WEB.DL.mkv +: title: Marvels Agents of S.H.I.E.L.D. + season: 1 + episode: 2 + episode_title: 0.8.4. + screen_size: 720p + source: Web + container: mkv + type: episode + +? Mind.Field.S02E06.The.Power.of.Suggestion.1440p.H264.WEBDL.Subtitles +: title: Mind Field + season: 2 + episode: 6 + episode_title: The Power of Suggestion + screen_size: 1440p + video_codec: H.264 + source: Web + subtitle_language: und + type: episode + +? The Power of Suggestion - Mind Field S2 (Ep 6) (1440p_24fps_H264-384kbit_AAC 6Ch).mp4 +: title: The Power of Suggestion + alternative_title: Mind Field + season: 2 + episode: 6 + screen_size: 1440p + frame_rate: 24fps + video_codec: H.264 + audio_bit_rate: 384Kbps + audio_codec: AAC + audio_channels: '5.1' + container: mp4 + type: episode + +? Mind.Field.S02E06.The.Power.of.Suggestion.1440p.H264.WEBDL.Subtitles/The Power of Suggestion - Mind Field S2 (Ep 6) (1440p_24fps_H264-384kbit_AAC 6Ch).mp4 +: season: 2 + episode: 6 + title: The Power of Suggestion + alternative_title: Mind Field + screen_size: 1440p + frame_rate: 24fps + video_codec: H.264 + source: Web + subtitle_language: und + audio_bit_rate: 384Kbps + audio_codec: AAC + audio_channels: '5.1' + container: mp4 + type: episode + +? Mind.Field.S02E06.The.Power.of.Suggestion.1440p.H264.WEBDL.Subtitles/The Power of Suggestion - Mind Field S2 (Ep 6) (English).srt +: title: Mind Field + season: 2 + episode: 6 + episode_title: The Power of Suggestion + screen_size: 1440p + video_codec: H.264 + source: Web + subtitle_language: en + container: srt + type: episode + +? Mind.Field.S02E06.The.Power.of.Suggestion.1440p.H264.WEBDL.Subtitles/The Power of Suggestion - Mind Field S2 (Ep 6) (Korean).srt +: title: Mind Field + season: 2 + episode: 6 + episode_title: The Power of Suggestion + screen_size: 1440p + video_codec: H.264 + source: Web + subtitle_language: ko + container: srt + type: episode + +? '[HorribleSubs] Overlord II - 01 [1080p] 19.1mbits - 120fps.mkv' +: release_group: HorribleSubs + title: Overlord II + episode: 1 + screen_size: 1080p + video_bit_rate: 19.1Mbps + frame_rate: 120fps + container: mkv + type: episode + +? One Piece - 720 +: title: One Piece + season: 7 + episode: 20 + type: episode + +? foobar.213.avi +: options: -E + title: foobar + episode: 213 + container: avi + type: episode + +? FooBar - 360 368p-Grp +: options: -E + title: FooBar + episode: 360 + screen_size: 368p + release_group: Grp + type: episode + +? wwiis.most.daring.raids.s01e04.storming.mussolinis.island.1080p.web.h.264-edhd-sample.mkv +: title: wwiis most daring raids + season: 1 + episode: 4 + episode_title: storming mussolinis island + screen_size: 1080p + source: Web + video_codec: H.264 + release_group: edhd + other: Sample + container: mkv + type: episode + +? WWIIs.Most.Daring.Raids.S01E04.Storming.Mussolinis.Island.1080p.WEB.h264-EDHD/wwiis.most.daring.raids.s01e04.storming.mussolinis.island.1080p.web.h.264-edhd-sample.mkv +: title: wwiis most daring raids + season: 1 + episode: 4 + episode_title: Storming Mussolinis Island + screen_size: 1080p + source: Web + video_codec: H.264 + release_group: edhd + other: Sample + container: mkv + type: episode + +? dcs.legends.of.tomorrow.s02e01.1080p.bluray.x264-rovers.proof +: title: dcs legends of tomorrow + season: 2 + episode: 1 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + release_group: rovers + other: Proof + type: episode + +? dcs.legends.of.tomorrow.s02e01.720p.bluray.x264-demand.sample.mkv +: title: dcs legends of tomorrow + season: 2 + episode: 1 + screen_size: 720p + source: Blu-ray + video_codec: H.264 + release_group: demand + other: Sample + container: mkv + type: episode + +? Season 06/e01.1080p.bluray.x264-wavey-obfuscated.mkv +: season: 6 + episode: 1 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + title: wavey + other: Obfuscated + container: mkv + type: episode + +? Hells.Kitchen.US.S17E08.1080p.HEVC.x265-MeGusta-Obfuscated/c48db7d2aeb040e8a920a9fd6effcbf4.mkv +: title: Hells Kitchen + country: US + season: 17 + episode: 8 + screen_size: 1080p + video_codec: H.265 + release_group: MeGusta + other: Obfuscated + uuid: c48db7d2aeb040e8a920a9fd6effcbf4 + container: mkv + type: episode + +? Blue.Bloods.S08E09.1080p.HEVC.x265-MeGusta-Obfuscated/afaae96ae7a140e0981ced2a79221751.mkv +: title: Blue Bloods + season: 8 + episode: 9 + screen_size: 1080p + video_codec: H.265 + release_group: MeGusta + other: Obfuscated + container: mkv + type: episode + +? MacGyver.2016.S02E09.CD-ROM.and.Hoagie.Foil.1080p.AMZN.WEBRip.DDP5.1.x264-NTb-Scrambled/c329b27187d44a94b4a25b21502db552.mkv +: title: MacGyver + year: 2016 + season: 2 + episode: 9 + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + other: [Rip, Obfuscated] + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + release_group: NTb + uuid: c329b27187d44a94b4a25b21502db552 + container: mkv + type: episode + +? The.Late.Late.Show.with.James.Corden.2017.11.27.Armie.Hammer.Juno.Temple.Charlie.Puth.1080p.AMZN.WEB-DL.DDP2.0.H.264-monkee-Scrambled/42e7e8a48eb7454aaebebcf49705ce41.mkv +: title: The Late Late Show with James Corden + date: 2017-11-27 + episode_title: Armie Hammer Juno Temple Charlie Puth + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + other: Obfuscated + uuid: 42e7e8a48eb7454aaebebcf49705ce41 + container: mkv + type: episode + +? Educating Greater Manchester S01E07 720p HDTV x264-PLUTONiUM-AsRequested +: title: Educating Greater Manchester + season: 1 + episode: 7 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: PLUTONiUM + other: Repost + type: episode + +? Im A Celebrity Get Me Out Of Here S17E14 HDTV x264-PLUTONiUM-xpost +: title: Im A Celebrity Get Me Out Of Here + season: 17 + episode: 14 + source: HDTV + video_codec: H.264 + release_group: PLUTONiUM + other: Repost + type: episode + +? Tales S01E08 All I Need Method Man Featuring Mary J Blige 720p BET WEBRip AAC2 0 x264-RTN-xpost +: title: Tales + season: 1 + episode: 8 + episode_title: All I Need Method Man Featuring Mary J Blige + screen_size: 720p + source: Web + other: [Rip, Repost] + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: RTN + type: episode + +? This is Us S01E11 Herzensangelegenheiten German DL WS DVDRip x264-CDP-xpost +: options: --exclude country + title: This is Us + season: 1 + episode: 11 + episode_title: Herzensangelegenheiten + language: + - de + - mul + other: + - Widescreen + - Rip + - Repost + source: DVD + video_codec: H.264 + release_group: CDP + type: episode + +? The Girlfriend Experience S02E10 1080p WEB H264-STRiFE-postbot +: title: The Girlfriend Experience + season: 2 + episode: 10 + screen_size: 1080p + source: Web + video_codec: H.264 + release_group: STRiFE + other: Repost + type: episode + +? The.Girlfriend.Experience.S02E10.1080p.WEB.H264-STRiFE-postbot/90550c1adaf44c47b60d24f59603bb98.mkv +: title: The Girlfriend Experience + season: 2 + episode: 10 + screen_size: 1080p + source: Web + video_codec: H.264 + release_group: STRiFE + other: Repost + uuid: 90550c1adaf44c47b60d24f59603bb98 + container: mkv + type: episode + +? 24.S01E02.1080p.BluRay.REMUX.AVC.DD.2.0-EPSiLON-xpost/eb518eaf33f641a1a8c6e0973a67aec2.mkv +: title: '24' + season: 1 + episode: 2 + screen_size: 1080p + source: Blu-ray + other: [Remux, Repost] + video_codec: H.264 + audio_codec: Dolby Digital + audio_channels: '2.0' + release_group: EPSiLON + uuid: eb518eaf33f641a1a8c6e0973a67aec2 + container: mkv + type: episode + +? Educating.Greater.Manchester.S01E02.720p.HDTV.x264-PLUTONiUM-AsRequested/47fbcb2393aa4b5cbbb340d3173ca1a9.mkv +: title: Educating Greater Manchester + season: 1 + episode: 2 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: PLUTONiUM + other: Repost + uuid: 47fbcb2393aa4b5cbbb340d3173ca1a9 + container: mkv + type: episode + +? Stranger.Things.S02E05.Chapter.Five.Dig.Dug.720p.NF.WEBRip.DD5.1.x264-PSYPHER-AsRequested-Obfuscated +: title: Stranger Things + season: 2 + episode: 5 + episode_title: Chapter Five Dig Dug + screen_size: 720p + streaming_service: Netflix + source: Web + other: [Rip, Repost, Obfuscated] + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: PSYPHER + type: episode + +? Show.Name.-.Season.1.3.4-.Mp4.1080p +: title: Show Name + season: [1, 3, 4] + container: mp4 + screen_size: 1080p + type: episode + +? Bones.S03.720p.HDTV.x264-SCENE +: title: Bones + season: 3 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: SCENE + type: episode + +? shes.gotta.have.it.s01e08.720p.web.x264-strife.mkv +: title: shes gotta have it + season: 1 + episode: 8 + screen_size: 720p + source: Web + video_codec: H.264 + release_group: strife + type: episode + +? DuckTales.2017.S01E10.The.Missing.Links.of.Moorshire.PDTV.H.264.MP2-KIDKAT +: title: DuckTales + year: 2017 + season: 1 + episode: 10 + episode_title: The Missing Links of Moorshire + source: Digital TV + video_codec: H.264 + audio_codec: MP2 + release_group: KIDKAT + type: episode + +? Por Trece Razones - Temporada 2 [HDTV 720p][Cap.201][AC3 5.1 Castellano]/Por Trece Razones 2x01 [des202].mkv +: title: Por Trece Razones + season: 2 + source: HDTV + screen_size: 720p + episode: 1 + audio_codec: Dolby Digital + audio_channels: '5.1' + language: Catalan + release_group: des202 + container: mkv + type: episode + +? Cuerpo de Elite - Temporada 1 [HDTV 720p][Cap.113][AC3 5.1 Esp Castellano]\CuerpoDeElite720p_113_desca202.mkv +: title: Cuerpo de Elite + season: 1 + source: HDTV + screen_size: 720p + episode: 13 + audio_codec: Dolby Digital + audio_channels: '5.1' + language: + - Spanish + - Catalan + container: mkv + type: episode + +? Show.Name.S01E01.St.Patricks.Day.1080p.mkv +: title: Show Name + season: 1 + episode: 1 + episode_title: St Patricks Day + screen_size: 1080p + container: mkv + type: episode + +? Show.Name.S01E01.St.Patricks.Day.1080p-grp.mkv +: title: Show Name + season: 1 + episode: 1 + episode_title: St Patricks Day + screen_size: 1080p + release_group: grp + container: mkv + type: episode + +? Titans.2018.S01E09.Hank.And.Dawn.720p.DCU.WEB-DL.AAC2.0.H264-NTb +: title: Titans + year: 2018 + season: 1 + episode: 9 + episode_title: Hank And Dawn + screen_size: 720p + streaming_service: DC Universe + source: Web + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: NTb + type: episode + +? S.W.A.T.2017.S01E21.Treibjagd.German.Dubbed.DL.AmazonHD.x264-TVS +: title: S.W.A.T. + year: 2017 + season: 1 + episode: 21 + episode_title: Treibjagd + language: + - German + - Multi + streaming_service: Amazon Prime + other: HD + video_codec: H.264 + release_group: TVS + type: episode + +? S.W.A.T.2017.S01E16.READNFO.720p.HDTV.x264-KILLERS +: title: S.W.A.T. + year: 2017 + season: 1 + episode: 16 + other: Read NFO + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: KILLERS + type: episode + +? /mnt/NAS/NoSubsTVShows/Babylon 5/Season 01/Ep. 02 - Soul Hunter +: title: Babylon 5 + season: 1 + episode: 2 + episode_title: Soul Hunter + type: episode + +? This.is.Us.S01E01.HDTV.x264-KILLERS.mkv +: title: This is Us + season: 1 + episode: 1 + source: HDTV + video_codec: H.264 + release_group: KILLERS + container: mkv + type: episode + +? Videos/Office1080/The Office (US) (2005) Season 2 S02 + Extras (1080p AMZN WEB-DL x265 HEVC 10bit AAC 2.0 LION)/The Office (US) (2005) - S02E12 - The Injury (1080p AMZN WEB-DL x265 LION).mkv +: title: The Office + country: US + year: 2005 + season: 2 + other: Extras + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + video_codec: H.265 + video_profile: High Efficiency Video Coding + color_depth: 10-bit + audio_codec: AAC + audio_channels: '2.0' + release_group: LION + episode: 12 + episode_title: The Injury + container: mkv + type: episode + +? Thumping.Spike.2.E01.DF.WEBRip.720p-DRAMATV.mp4 +: title: Thumping Spike 2 + episode: 1 + source: Web + other: Rip + screen_size: 720p + streaming_service: DramaFever + release_group: DRAMATV + container: mp4 + mimetype: video/mp4 + type: episode + +? About.Time.E01.1080p.VIKI.WEB-DL-BLUEBERRY.mp4 +: title: About Time + episode: 1 + screen_size: 1080p + streaming_service: Viki + source: Web + release_group: BLUEBERRY + container: mp4 + mimetype: video/mp4 + type: episode + +? Eyes.Of.Dawn.1991.E01.480p.MBCVOD.AAC.x264-NOGPR.mp4 +: title: Eyes Of Dawn + year: 1991 + season: 1991 + episode: 1 + screen_size: 480p + streaming_service: MBC + audio_codec: AAC + video_codec: H.264 + release_group: NOGPR + container: mp4 + mimetype: video/mp4 + type: episode
\ No newline at end of file diff --git a/libs/guessit/test/movies.yml b/libs/guessit/test/movies.yml index 610c3836c..a534ca0f2 100644 --- a/libs/guessit/test/movies.yml +++ b/libs/guessit/test/movies.yml @@ -5,16 +5,17 @@ : title: Fear and Loathing in Las Vegas year: 1998 screen_size: 720p - format: HD-DVD + source: HD-DVD audio_codec: DTS - video_codec: h264 + video_codec: H.264 container: mkv release_group: ESiR ? Movies/El Dia de la Bestia (1995)/El.dia.de.la.bestia.DVDrip.Spanish.DivX.by.Artik[SEDG].avi : title: El Dia de la Bestia year: 1995 - format: DVD + source: DVD + other: Rip language: spanish video_codec: DivX release_group: Artik[SEDG] @@ -23,37 +24,40 @@ ? Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv : title: Dark City year: 1998 - format: BluRay + source: Blu-ray + other: Rip screen_size: 720p audio_codec: DTS - video_codec: h264 + video_codec: H.264 release_group: CHD ? Movies/Sin City (BluRay) (2005)/Sin.City.2005.BDRip.720p.x264.AC3-SEPTiC.mkv : title: Sin City year: 2005 - format: BluRay + source: Blu-ray + other: Rip screen_size: 720p - video_codec: h264 - audio_codec: AC3 + video_codec: H.264 + audio_codec: Dolby Digital release_group: SEPTiC ? Movies/Borat (2006)/Borat.(2006).R5.PROPER.REPACK.DVDRip.XviD-PUKKA.avi : title: Borat year: 2006 proper_count: 2 - format: DVD - other: [ R5, Proper ] - video_codec: XviD + source: DVD + other: [ Region 5, Proper, Rip ] + video_codec: Xvid release_group: PUKKA ? "[XCT].Le.Prestige.(The.Prestige).DVDRip.[x264.HP.He-Aac.{Fr-Eng}.St{Fr-Eng}.Chaps].mkv" : title: Le Prestige - format: DVD - video_codec: h264 - video_profile: HP + source: DVD + other: Rip + video_codec: H.264 + video_profile: High audio_codec: AAC - audio_profile: HE + audio_profile: High Efficiency language: [ french, english ] subtitle_language: [ french, english ] release_group: Chaps @@ -61,23 +65,24 @@ ? Battle Royale (2000)/Battle.Royale.(Batoru.Rowaiaru).(2000).(Special.Edition).CD1of2.DVDRiP.XviD-[ZeaL].avi : title: Battle Royale year: 2000 - edition: Special Edition + edition: Special cd: 1 cd_count: 2 - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: ZeaL ? Movies/Brazil (1985)/Brazil_Criterion_Edition_(1985).CD2.avi : title: Brazil - edition: Criterion Edition + edition: Criterion year: 1985 cd: 2 ? Movies/Persepolis (2007)/[XCT] Persepolis [H264+Aac-128(Fr-Eng)+ST(Fr-Eng)+Ind].mkv : title: Persepolis year: 2007 - video_codec: h264 + video_codec: H.264 audio_codec: AAC language: [ French, English ] subtitle_language: [ French, English ] @@ -86,17 +91,18 @@ ? Movies/Toy Story (1995)/Toy Story [HDTV 720p English-Spanish].mkv : title: Toy Story year: 1995 - format: HDTV + source: HDTV screen_size: 720p language: [ english, spanish ] ? Movies/Office Space (1999)/Office.Space.[Dual-DVDRip].[Spanish-English].[XviD-AC3-AC3].[by.Oswald].avi : title: Office Space year: 1999 - format: DVD + other: [Dual Audio, Rip] + source: DVD language: [ english, spanish ] - video_codec: XviD - audio_codec: AC3 + video_codec: Xvid + audio_codec: Dolby Digital ? Movies/Wild Zero (2000)/Wild.Zero.DVDivX-EPiC.avi : title: Wild Zero @@ -106,30 +112,33 @@ ? movies/Baraka_Edition_Collector.avi : title: Baraka - edition: Collector Edition + edition: Collector ? Movies/Blade Runner (1982)/Blade.Runner.(1982).(Director's.Cut).CD1.DVDRip.XviD.AC3-WAF.avi : title: Blade Runner year: 1982 edition: Director's Cut cd: 1 - format: DVD - video_codec: XviD - audio_codec: AC3 + source: DVD + other: Rip + video_codec: Xvid + audio_codec: Dolby Digital release_group: WAF ? movies/American.The.Bill.Hicks.Story.2009.DVDRip.XviD-EPiSODE.[UsaBit.com]/UsaBit.com_esd-americanbh.avi : title: American The Bill Hicks Story year: 2009 - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: EPiSODE website: UsaBit.com ? movies/Charlie.And.Boots.DVDRip.XviD-TheWretched/wthd-cab.avi : title: Charlie And Boots - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: TheWretched ? movies/Steig Larsson Millenium Trilogy (2009) BRrip 720 AAC x264/(1)The Girl With The Dragon Tattoo (2009) BRrip 720 AAC x264.mkv @@ -137,18 +146,19 @@ #film_title: Steig Larsson Millenium Trilogy #film: 1 year: 2009 - format: BluRay + source: Blu-ray + other: [Reencoded, Rip] audio_codec: AAC - video_codec: h264 + video_codec: H.264 screen_size: 720p ? movies/Greenberg.REPACK.LiMiTED.DVDRip.XviD-ARROW/arw-repack-greenberg.dvdrip.xvid.avi : title: Greenberg - format: DVD - video_codec: XviD + source: DVD + video_codec: Xvid release_group: ARROW - other: Proper - edition: Limited Edition + other: [Proper, Rip] + edition: Limited proper_count: 1 ? Movies/Fr - Paris 2054, Renaissance (2005) - De Christian Volckman - (Film Divx Science Fiction Fantastique Thriller Policier N&B).avi @@ -161,14 +171,16 @@ : title: Avida year: 2006 language: french - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: PROD ? Movies/Alice in Wonderland DVDRip.XviD-DiAMOND/dmd-aw.avi : title: Alice in Wonderland - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: DiAMOND ? Movies/Ne.Le.Dis.A.Personne.Fr 2 cd/personnea_mp.avi @@ -180,49 +192,54 @@ : title: Bunker Palace Hôtel year: 1989 language: french - format: VHS + source: VHS + other: Rip ? Movies/21 (2008)/21.(2008).DVDRip.x264.AC3-FtS.[sharethefiles.com].mkv : title: "21" year: 2008 - format: DVD - video_codec: h264 - audio_codec: AC3 + source: DVD + other: Rip + video_codec: H.264 + audio_codec: Dolby Digital release_group: FtS website: sharethefiles.com ? Movies/9 (2009)/9.2009.Blu-ray.DTS.720p.x264.HDBRiSe.[sharethefiles.com].mkv : title: "9" year: 2009 - format: BluRay + source: Blu-ray audio_codec: DTS screen_size: 720p - video_codec: h264 + video_codec: H.264 release_group: HDBRiSe website: sharethefiles.com ? Movies/Mamma.Mia.2008.DVDRip.AC3.XviD-CrazyTeam/Mamma.Mia.2008.DVDRip.AC3.XviD-CrazyTeam.avi : title: Mamma Mia year: 2008 - format: DVD - audio_codec: AC3 - video_codec: XviD + source: DVD + other: Rip + audio_codec: Dolby Digital + video_codec: Xvid release_group: CrazyTeam ? Movies/M.A.S.H. (1970)/MASH.(1970).[Divx.5.02][Dual-Subtitulos][DVDRip].ogm : title: MASH year: 1970 video_codec: DivX - format: DVD + source: DVD + other: [Dual Audio, Rip] ? Movies/The Doors (1991)/09.03.08.The.Doors.(1991).BDRip.720p.AC3.X264-HiS@SiLUHD-English.[sharethefiles.com].mkv : title: The Doors year: 1991 date: 2008-03-09 - format: BluRay + source: Blu-ray + other: Rip screen_size: 720p - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 release_group: HiS@SiLUHD language: english website: sharethefiles.com @@ -232,17 +249,18 @@ title: The Doors year: 1991 date: 2008-03-09 - format: BluRay + source: Blu-ray + other: Rip screen_size: 720p - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 release_group: HiS@SiLUHD language: english website: sharethefiles.com ? Movies/Ratatouille/video_ts-ratatouille.srt : title: Ratatouille - format: DVD + source: DVD # Removing this one because 001 is guessed as an episode number. # ? Movies/001 __ A classer/Fantomas se déchaine - Louis de Funès.avi @@ -252,18 +270,20 @@ : title: Comme une Image year: 2004 language: french - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: NTK website: www.divx-overnet.com ? Movies/Fantastic Mr Fox/Fantastic.Mr.Fox.2009.DVDRip.{x264+LC-AAC.5.1}{Fr-Eng}{Sub.Fr-Eng}-™.[sharethefiles.com].mkv : title: Fantastic Mr Fox year: 2009 - format: DVD - video_codec: h264 + source: DVD + other: Rip + video_codec: H.264 audio_codec: AAC - audio_profile: LC + audio_profile: Low Complexity audio_channels: "5.1" language: [ french, english ] subtitle_language: [ french, english ] @@ -272,8 +292,9 @@ ? Movies/Somewhere.2010.DVDRip.XviD-iLG/i-smwhr.avi : title: Somewhere year: 2010 - format: DVD - video_codec: XviD + source: DVD + other: Rip + video_codec: Xvid release_group: iLG ? Movies/Moon_(2009).mkv @@ -339,21 +360,21 @@ ? /public/uTorrent/Downloads Finished/Movies/Indiana.Jones.and.the.Temple.of.Doom.1984.HDTV.720p.x264.AC3.5.1-REDµX/Indiana.Jones.and.the.Temple.of.Doom.1984.HDTV.720p.x264.AC3.5.1-REDµX.mkv : title: Indiana Jones and the Temple of Doom year: 1984 - format: HDTV + source: HDTV screen_size: 720p - video_codec: h264 - audio_codec: AC3 + video_codec: H.264 + audio_codec: Dolby Digital audio_channels: "5.1" release_group: REDµX ? The.Director’s.Notebook.2006.Blu-Ray.x264.DXVA.720p.AC3-de[42].mkv : title: The Director’s Notebook year: 2006 - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 video_api: DXVA screen_size: 720p - audio_codec: AC3 + audio_codec: Dolby Digital release_group: de[42] @@ -361,17 +382,18 @@ : title: Cosmopolis year: 2012 screen_size: 720p - video_codec: h264 + video_codec: H.264 release_group: AN0NYM0US[bb] - format: BluRay - edition: Limited Edition + source: Blu-ray + edition: Limited ? movies/La Science des Rêves (2006)/La.Science.Des.Reves.FRENCH.DVDRip.XviD-MP-AceBot.avi : title: La Science des Rêves year: 2006 - format: DVD - video_codec: XviD - video_profile: MP + source: DVD + other: Rip + video_codec: Xvid + video_profile: Main release_group: AceBot language: French @@ -382,8 +404,8 @@ : title: The Rum Diary year: 2011 screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 audio_codec: DTS release_group: D-Z0N3 @@ -391,8 +413,8 @@ : title: Life Of Pi year: 2012 screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 audio_codec: DTS release_group: D-Z0N3 @@ -400,28 +422,28 @@ : title: The Kings Speech year: 2010 screen_size: 1080p - format: BluRay + source: Blu-ray audio_codec: DTS - video_codec: h264 + video_codec: H.264 release_group: D Z0N3 ? Street.Kings.2008.BluRay.1080p.DTS.x264.dxva EuReKA.mkv : title: Street Kings year: 2008 - format: BluRay + source: Blu-ray screen_size: 1080p audio_codec: DTS - video_codec: h264 + video_codec: H.264 video_api: DXVA release_group: EuReKA ? 2001.A.Space.Odyssey.1968.HDDVD.1080p.DTS.x264.dxva EuReKA.mkv : title: 2001 A Space Odyssey year: 1968 - format: HD-DVD + source: HD-DVD screen_size: 1080p audio_codec: DTS - video_codec: h264 + video_codec: H.264 video_api: DXVA release_group: EuReKA @@ -429,24 +451,25 @@ : title: "2012" year: 2009 screen_size: 720p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 audio_codec: DTS release_group: WiKi ? /share/Download/movie/Dead Man Down (2013) BRRiP XViD DD5_1 Custom NLSubs =-_lt Q_o_Q gt-=_/XD607ebb-BRc59935-5155473f-1c5f49/XD607ebb-BRc59935-5155473f-1c5f49.avi : title: Dead Man Down year: 2013 - format: BluRay - video_codec: XviD + source: Blu-ray + other: [Reencoded, Rip] + video_codec: Xvid audio_channels: "5.1" - audio_codec: AC3 + audio_codec: Dolby Digital uuid: XD607ebb-BRc59935-5155473f-1c5f49 ? Pacific.Rim.3D.2013.COMPLETE.BLURAY-PCH.avi : title: Pacific Rim year: 2013 - format: BluRay + source: Blu-ray other: - Complete - 3D @@ -458,33 +481,33 @@ language: - French - English - format: DVD + source: DVD other: [Straight to Video, Read NFO, NTSC] ? Immersion.French.2011.STV.READNFO.QC.FRENCH.NTSC.DVDR.nfo : title: Immersion French year: 2011 language: French - format: DVD + source: DVD other: [Straight to Video, Read NFO, NTSC] ? Immersion.French.2011.STV.READNFO.QC.NTSC.DVDR.nfo : title: Immersion language: French year: 2011 - format: DVD + source: DVD other: [Straight to Video, Read NFO, NTSC] ? French.Immersion.2011.STV.READNFO.QC.ENGLISH.NTSC.DVDR.nfo : title: French Immersion year: 2011 language: ENGLISH - format: DVD + source: DVD other: [Straight to Video, Read NFO, NTSC] ? Howl's_Moving_Castle_(2004)_[720p,HDTV,x264,DTS]-FlexGet.avi -: video_codec: h264 - format: HDTV +: video_codec: H.264 + source: HDTV title: Howl's Moving Castle screen_size: 720p year: 2004 @@ -495,43 +518,42 @@ : screen_size: 1080p year: 2008 language: French - video_codec: h264 + video_codec: H.264 title: Pirates de langkasuka release_group: AsiaRa ? Masala (2013) Telugu Movie HD DVDScr XviD - Exclusive.avi : year: 2013 - video_codec: XviD + video_codec: Xvid title: Masala - format: HD-DVD + source: HD-DVD other: Screener - language: Telugu release_group: Exclusive ? Django Unchained 2012 DVDSCR X264 AAC-P2P.nfo : year: 2012 other: Screener - video_codec: h264 + video_codec: H.264 title: Django Unchained audio_codec: AAC - format: DVD + source: DVD release_group: P2P ? Ejecutiva.En.Apuros(2009).BLURAY.SCR.Xvid.Spanish.LanzamientosD.nfo : year: 2009 other: Screener - format: BluRay - video_codec: XviD + source: Blu-ray + video_codec: Xvid language: Spanish title: Ejecutiva En Apuros ? Die.Schluempfe.2.German.DL.1080p.BluRay.x264-EXQUiSiTE.mkv : title: Die Schluempfe 2 - format: BluRay + source: Blu-ray language: - Multiple languages - German - video_codec: h264 + video_codec: H.264 release_group: EXQUiSiTE screen_size: 1080p @@ -539,96 +561,99 @@ : title: Rocky year: 1976 subtitle_language: French - format: BluRay - video_codec: h264 - audio_codec: AC3 + source: Blu-ray + other: [Reencoded, Rip] + video_codec: H.264 + audio_codec: Dolby Digital release_group: FUNKY ? REDLINE (BD 1080p H264 10bit FLAC) [3xR].mkv : title: REDLINE - format: BluRay - video_codec: h264 - video_profile: 10bit + source: Blu-ray + video_codec: H.264 + color_depth: 10-bit audio_codec: FLAC screen_size: 1080p ? The.Lizzie.McGuire.Movie.(2003).HR.DVDRiP.avi : title: The Lizzie McGuire Movie year: 2003 - format: DVD - other: HR + source: DVD + other: [High Resolution, Rip] ? Hua.Mulan.BRRIP.MP4.x264.720p-HR.avi : title: Hua Mulan - video_codec: h264 - format: BluRay + video_codec: H.264 + source: Blu-ray screen_size: 720p - other: HR + other: [Reencoded, Rip] + release_group: HR ? Dr.Seuss.The.Lorax.2012.DVDRip.LiNE.XviD.AC3.HQ.Hive-CM8.mp4 -: video_codec: XviD +: video_codec: Xvid title: Dr Seuss The Lorax - format: DVD - other: LiNE + source: DVD + other: [Rip, Line Audio] year: 2012 - audio_codec: AC3 - audio_profile: HQ + audio_codec: Dolby Digital + audio_profile: High Quality release_group: Hive-CM8 ? "Star Wars: Episode IV - A New Hope (2004) Special Edition.MKV" : title: "Star Wars: Episode IV" alternative_title: A New Hope year: 2004 - edition: Special Edition + edition: Special ? Dr.LiNE.The.Lorax.2012.DVDRip.LiNE.XviD.AC3.HQ.Hive-CM8.mp4 -: video_codec: XviD +: video_codec: Xvid title: Dr LiNE The Lorax - format: DVD - other: LiNE + source: DVD + other: [Rip, Line Audio] year: 2012 - audio_codec: AC3 - audio_profile: HQ + audio_codec: Dolby Digital + audio_profile: High Quality release_group: Hive-CM8 ? Dr.LiNE.The.Lorax.2012.DVDRip.XviD.AC3.HQ.Hive-CM8.mp4 -: video_codec: XviD +: video_codec: Xvid title: Dr LiNE The Lorax - format: DVD + source: DVD + other: Rip year: 2012 - audio_codec: AC3 - audio_profile: HQ + audio_codec: Dolby Digital + audio_profile: High Quality release_group: Hive-CM8 ? Perfect [email protected] : release_group: h@mster title: Perfect Child - video_codec: XviD + video_codec: Xvid language: French - format: TV + source: TV + other: Rip year: 2007 ? entre.ciel.et.terre.(1994).dvdrip.h264.aac-psypeon.avi : audio_codec: AAC - format: DVD + source: DVD + other: Rip release_group: psypeon title: entre ciel et terre - video_codec: h264 + video_codec: H.264 year: 1994 ? Yves.Saint.Laurent.2013.FRENCH.DVDSCR.MD.XviD-ViVARiUM.avi -: format: DVD +: source: DVD language: French - other: - - MD - - Screener + other: [Screener, Mic Dubbed] release_group: ViVARiUM title: Yves Saint Laurent - video_codec: XviD + video_codec: Xvid year: 2013 ? Echec et Mort - Hard to Kill - Steven Seagal Multi 1080p BluRay x264 CCATS.avi -: format: BluRay +: source: Blu-ray language: Multiple languages release_group: CCATS screen_size: 1080p @@ -636,7 +661,7 @@ alternative_title: - Hard to Kill - Steven Seagal - video_codec: h264 + video_codec: H.264 ? Paparazzi - Timsit/Lindon (MKV 1080p tvripHD) : options: -n @@ -646,35 +671,36 @@ - Lindon screen_size: 1080p container: mkv - format: HDTV + source: HDTV + other: Rip ? some.movie.720p.bluray.x264-mind : title: some movie screen_size: 720p - video_codec: h264 + video_codec: H.264 release_group: mind - format: BluRay + source: Blu-ray ? Dr LiNE The Lorax 720p h264 BluRay : title: Dr LiNE The Lorax screen_size: 720p - video_codec: h264 - format: BluRay + video_codec: H.264 + source: Blu-ray #TODO: Camelcase implementation #? BeatdownFrenchDVDRip.mkv #: options: -c # title: Beatdown # language: French -# format: DVD +# source: DVD #? YvesSaintLaurent2013FrenchDVDScrXvid.avi #: options: -c -# format: DVD +# source: DVD # language: French # other: Screener # title: Yves saint laurent -# video_codec: XviD +# video_codec: Xvid # year: 2013 @@ -683,7 +709,7 @@ title: Elle s en va ? FooBar.7.PDTV-FlexGet -: format: DVB +: source: Digital TV release_group: FlexGet title: FooBar 7 @@ -693,7 +719,7 @@ language: fr screen_size: 1080p title: Riddick - video_codec: h265 + video_codec: H.265 ? "[h265 - HEVC] Riddick Unrated Director Cut French [1080p DTS].mkv" : audio_codec: DTS @@ -701,38 +727,36 @@ language: fr screen_size: 1080p title: Riddick - video_codec: h265 + video_codec: H.265 ? Barbecue-2014-French-mHD-1080p : language: fr - other: mHD + other: Micro HD screen_size: 1080p title: Barbecue year: 2014 ? Underworld Quadrilogie VO+VFF+VFQ 1080p HDlight.x264~Tonyk~Monde Infernal : language: fr - other: - - HDLight - - OV + other: [Original Video, Micro HD] screen_size: 1080p title: Underworld Quadrilogie - video_codec: h264 + video_codec: H.264 ? A Bout Portant (The Killers).PAL.Multi.DVD-R-KZ -: format: DVD +: source: DVD language: mul release_group: KZ title: A Bout Portant ? "Mise à Sac (Alain Cavalier, 1967) [Vhs.Rip.Vff]" -: format: VHS +: source: VHS language: fr title: "Mise à Sac" year: 1967 ? A Bout Portant (The Killers).PAL.Multi.DVD-R-KZ -: format: DVD +: source: DVD other: PAL language: mul release_group: KZ @@ -747,7 +771,8 @@ year: 2009 ? La Defense Lincoln (The Lincoln Lawyer) 2011 [DVDRIP][Vostfr] -: format: DVD +: source: DVD + other: Rip subtitle_language: fr title: La Defense Lincoln year: 2011 @@ -757,7 +782,7 @@ language: fr screen_size: 1080p title: Fight Club - video_codec: h265 + video_codec: H.265 ? Love Gourou (Mike Myers) - FR : language: fr @@ -765,11 +790,11 @@ ? '[h265 - hevc] transformers 2 1080p french ac3 6ch.' : audio_channels: '5.1' - audio_codec: AC3 + audio_codec: Dolby Digital language: fr screen_size: 1080p title: transformers 2 - video_codec: h265 + video_codec: H.265 ? 1.Angry.Man.1957.mkv : title: 1 Angry Man @@ -788,30 +813,31 @@ title: Looney Tunes ? Das.Appartement.German.AC3D.DL.720p.BluRay.x264-TVP -: audio_codec: AC3 - format: BluRay - language: mul +: audio_codec: Dolby Digital + source: Blu-ray + language: + - German + - Multi release_group: TVP screen_size: 720p - title: Das Appartement German + title: Das Appartement type: movie - video_codec: h264 + video_codec: H.264 ? Das.Appartement.GERMAN.AC3D.DL.720p.BluRay.x264-TVP -: audio_codec: AC3 - format: BluRay +: audio_codec: Dolby Digital + source: Blu-ray language: - de - mul release_group: TVP screen_size: 720p title: Das Appartement - video_codec: h264 + video_codec: H.264 ? Hyena.Road.2015.German.1080p.DL.DTSHD.Bluray.x264-pmHD -: audio_codec: DTS - audio_profile: HD - format: BluRay +: audio_codec: DTS-HD + source: Blu-ray language: - de - mul @@ -819,13 +845,12 @@ screen_size: 1080p title: Hyena Road type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? Hyena.Road.2015.German.1080p.DL.DTSHD.Bluray.x264-pmHD -: audio_codec: DTS - audio_profile: HD - format: BluRay +: audio_codec: DTS-HD + source: Blu-ray language: - de - mul @@ -833,16 +858,34 @@ screen_size: 1080p title: Hyena Road type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? Name.BDMux.720p -? Name.BDRipMux.720p +: title: Name + source: Blu-ray + other: Mux + screen_size: 720p + type: movie + ? Name.BRMux.720p +: title: Name + source: Blu-ray + other: [Reencoded, Mux] + screen_size: 720p + type: movie + +? Name.BDRipMux.720p +: title: Name + source: Blu-ray + other: [Rip, Mux] + screen_size: 720p + type: movie + ? Name.BRRipMux.720p : title: Name - format: BluRay - other: Mux + source: Blu-ray + other: [Reencoded, Rip, Mux] screen_size: 720p type: movie @@ -860,13 +903,11 @@ : title: Hacksaw Ridge year: 2016 language: mul - screen_size: 4K - other: UltraHD - format: BluRay - video_codec: h265 - video_profile: 10bit - audio_codec: [DTS, DolbyAtmos] - audio_profile: HD + screen_size: 2160p + source: Ultra HD Blu-ray + video_codec: H.265 + color_depth: 10-bit + audio_codec: [DTS-HD, Dolby Atmos] audio_channels: '7.1' release_group: DDR container: mkv @@ -876,9 +917,9 @@ : title: Special Correspondents year: 2016 language: [it, en] - screen_size: 4K + screen_size: 2160p streaming_service: Netflix - other: UltraHD + other: Ultra HD release_group: TeamPremium container: mp4 type: movie @@ -892,13 +933,12 @@ ? Suicide Squad EXTENDED (2016) 2160p 4K UltraHD Blu-Ray x265 (HEVC 10bit BT709) Dolby Atmos 7.1 -DDR : title: Suicide Squad edition: Extended - other: UltraHD year: 2016 - screen_size: 4K - format: BluRay - video_codec: h265 - video_profile: 10bit - audio_codec: DolbyAtmos + screen_size: 2160p + source: Ultra HD Blu-ray + video_codec: H.265 + color_depth: 10-bit + audio_codec: Dolby Atmos audio_channels: '7.1' release_group: DDR type: movie @@ -916,25 +956,25 @@ year: 1949 edition: Alternative Cut screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: SADPANDA[rarbg] ? The.Movie.CONVERT.720p.HDTV.x264-C4TV : title: The Movie other: Converted screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: C4TV type: movie ? Its.A.Wonderful.Life.1946.Colorized.720p.BRRip.999MB.MkvCage.com : title: Its A Wonderful Life year: 1946 - other: Colorized + other: [Colorized, Reencoded, Rip] screen_size: 720p - format: BluRay + source: Blu-ray size: 999MB website: MkvCage.com type: movie @@ -951,19 +991,19 @@ year: 2000 edition: Director's Cut screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: anoXmous type: movie ? Before.the.Flood.2016.DOCU.1080p.WEBRip.x264.DD5.1-FGT : title: Before the Flood year: 2016 - other: Documentary + other: [Documentary, Rip] screen_size: 1080p - format: WEBRip - video_codec: h264 - audio_codec: AC3 + source: Web + video_codec: H.264 + audio_codec: Dolby Digital audio_channels: '5.1' release_group: FGT type: movie @@ -971,7 +1011,7 @@ ? Zootopia.2016.HDRip.1.46Gb.Dub.MegaPeer : title: Zootopia year: 2016 - format: HDTV + other: [HD, Rip] size: 1.46GB language: und release_group: MegaPeer @@ -981,26 +1021,27 @@ : title: Suntan year: 2016 edition: Festival - format: DVD - video_codec: h264 + source: DVD + other: Rip + video_codec: H.264 release_group: IcHoR type: movie ? Hardwired.STV.NFOFiX.FRENCH.DVDRiP.XviD-SURViVAL : title: Hardwired - other: [Straight to Video, Proper] + other: [Straight to Video, Fix, Rip] language: french - format: DVD - video_codec: XviD + source: DVD + video_codec: Xvid release_group: SURViVAL - proper_count: 1 + -proper_count: 1 type: movie ? Maze.Runner.The.Scorch.Trials.OM.2015.WEB-DLRip.by.Seven : title: Maze Runner The Scorch Trials - other: Open Matte + other: [Open Matte, Rip] year: 2015 - format: WEBRip + source: Web release_group: Seven type: movie @@ -1008,40 +1049,41 @@ : title: Kampen Om Tungtvannet aka The Heavy Water War other: Complete screen_size: 720p - video_codec: h265 + video_codec: H.265 release_group: Lund type: movie ? All.Fall.Down.x264.PROOFFIX-OUTLAWS : title: All Fall Down - video_codec: h264 - other: Proper + video_codec: H.264 + other: Fix release_group: OUTLAWS - proper_count: 1 + -proper_count: 1 type: movie ? The.Last.Survivors.2014.PROOF.SAMPLE.FiX.BDRip.x264-TOPCAT : title: The Last Survivors year: 2014 - other: Proper - format: BluRay - video_codec: h264 + other: [Fix, Rip] + source: Blu-ray + video_codec: H.264 release_group: TOPCAT type: movie ? Bad Santa 2 2016 THEATRiCAL FRENCH BDRip XviD-EXTREME : title: Bad Santa 2 year: 2016 - edition: Theatrical Edition + edition: Theatrical language: french - format: BluRay - video_codec: XviD + source: Blu-ray + other: Rip + video_codec: Xvid release_group: EXTREME type: movie ? The Lord of the Rings The Fellowship of the Ring THEATRICAL EDITION (2001) [1080p] : title: The Lord of the Rings The Fellowship of the Ring - edition: Theatrical Edition + edition: Theatrical year: 2001 screen_size: 1080p type: movie @@ -1049,19 +1091,20 @@ ? World War Z (2013) Theatrical Cut 720p BluRay x264 : title: World War Z year: 2013 - edition: Theatrical Edition + edition: Theatrical screen_size: 720p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 type: movie ? The Heartbreak Kid (1993) UNCUT 720p WEBRip x264 : title: The Heartbreak Kid year: 1993 edition: Uncut + other: Rip screen_size: 720p - format: WEBRip - video_codec: h264 + source: Web + video_codec: H.264 type: movie ? Mrs.Doubtfire.1993.720p.OAR.Bluray.DTS.x264-CtrlHD @@ -1069,24 +1112,25 @@ year: 1993 screen_size: 720p other: Original Aspect Ratio - format: BluRay + source: Blu-ray audio_codec: DTS - video_codec: h264 + video_codec: H.264 release_group: CtrlHD type: movie ? Aliens.SE.1986.BDRip.1080p : title: Aliens - edition: Special Edition + edition: Special year: 1986 - format: BluRay + source: Blu-ray + other: Rip screen_size: 1080p type: movie ? 10 Cloverfield Lane.[Blu-Ray 1080p].[MULTI] : options: --type movie title: 10 Cloverfield Lane - format: BluRay + source: Blu-ray screen_size: 1080p language: Multiple languages type: movie @@ -1094,15 +1138,649 @@ ? 007.Spectre.[HDTC.MD].[TRUEFRENCH] : options: --type movie title: 007 Spectre - format: HDTC + source: HD Telecine language: French type: movie ? We.Are.X.2016.LIMITED.BDRip.x264-BiPOLAR : title: We Are X year: 2016 - edition: Limited Edition - format: BluRay - video_codec: h264 + edition: Limited + source: Blu-ray + other: Rip + video_codec: H.264 release_group: BiPOLAR type: movie + +? The Rack (VHS) [1956] Paul Newman +: title: The Rack + source: VHS + year: 1956 + type: movie + +? Les.Magiciens.1976.VHSRip.XViD.MKO +: title: Les Magiciens + year: 1976 + source: VHS + other: Rip + video_codec: Xvid + release_group: MKO + type: movie + +? The Boss Baby 2017 720p CAM x264 AC3 TiTAN +: title: The Boss Baby + year: 2017 + screen_size: 720p + source: Camera + video_codec: H.264 + audio_codec: Dolby Digital + release_group: TiTAN + type: movie + +? The.Boss.Baby.2017.HDCAM.XviD-MrGrey +: title: The Boss Baby + year: 2017 + source: HD Camera + video_codec: Xvid + release_group: MrGrey + type: movie + +? The Martian 2015 Multi 2160p 4K UHD Bluray HEVC10 SDR DTSHD 7.1 -Zeus +: title: The Martian + year: 2015 + language: mul + screen_size: 2160p + source: Ultra HD Blu-ray + video_codec: H.265 + color_depth: 10-bit + other: Standard Dynamic Range + audio_codec: DTS-HD + audio_channels: '7.1' + release_group: Zeus + type: movie + +? Fantastic Beasts and Where to Find Them 2016 Multi 2160p UHD BluRay HEVC HDR Atmos7.1-DDR +: title: Fantastic Beasts and Where to Find Them + year: 2016 + language: mul + screen_size: 2160p + source: Ultra HD Blu-ray + video_codec: H.265 + other: HDR10 + audio_codec: Dolby Atmos + audio_channels: '7.1' + release_group: DDR + type: movie + +? Life of Pi 2012 2160p 4K BluRay HDR10 HEVC BT2020 DTSHD 7.1 subs -DDR +: title: Life of Pi + year: 2012 + screen_size: 2160p + source: Ultra HD Blu-ray + other: [HDR10, BT.2020] + subtitle_language: und + release_group: DDR + +? Captain.America.Civil.War.HDR.1080p.HEVC.10bit.BT.2020.DTS-HD.MA.7.1-VISIONPLUSHDR +: title: Captain America Civil War + other: [HDR10, BT.2020] + screen_size: 1080p + video_codec: H.265 + color_depth: 10-bit + audio_codec: DTS-HD + audio_profile: Master Audio + audio_channels: '7.1' + release_group: VISIONPLUSHDR + type: movie + +? Deadpool.2016.4K.2160p.UHD.HQ.8bit.BluRay.8CH.x265.HEVC-MZABI.mkv +: title: Deadpool + year: 2016 + screen_size: 2160p + source: Ultra HD Blu-ray + other: High Quality + color_depth: 8-bit + audio_channels: '7.1' + video_codec: H.265 + release_group: MZABI + type: movie + +? Fantastic.Beasts.and.Where.to.Find.Them.2016.2160p.4K.UHD.10bit.HDR.BluRay.7.1.x265.HEVC-MZABI.mkv +: title: Fantastic Beasts and Where to Find Them + year: 2016 + screen_size: 2160p + source: Ultra HD Blu-ray + color_depth: 10-bit + other: HDR10 + audio_channels: '7.1' + video_codec: H.265 + release_group: MZABI + container: mkv + type: movie + +? The.Arrival.4K.HDR.HEVC.10bit.BT2020.DTS.HD-MA-MadVR.HDR10.Dolby.Vision-VISIONPLUSHDR1000 +: title: The Arrival + screen_size: 2160p + other: [HDR10, BT.2020, Dolby Vision] + video_codec: H.265 + color_depth: 10-bit + audio_codec: DTS-HD + audio_profile: Master Audio + release_group: VISIONPLUSHDR1000 + type: movie + +? How To Steal A Dog.2014.BluRay.1080p.12bit.HEVC.OPUS 5.1-Hn1Dr2.mkv +: title: How To Steal A Dog + year: 2014 + source: Blu-ray + screen_size: 1080p + color_depth: 12-bit + video_codec: H.265 + audio_codec: Opus + audio_channels: '5.1' + release_group: Hn1Dr2 + container: mkv + type: movie + +? Interstelar.2014.IMAX.RUS.BDRip.x264.-HELLYWOOD.mkv +: title: Interstelar + year: 2014 + edition: IMAX + language: ru + source: Blu-ray + other: Rip + video_codec: H.264 + release_group: HELLYWOOD + container: mkv + type: movie + +? The.Dark.Knight.IMAX.EDITION.HQ.BluRay.1080p.x264.AC3.Hindi.Eng.ETRG +: title: The Dark Knight + edition: IMAX + other: High Quality + source: Blu-ray + screen_size: 1080p + video_codec: H.264 + audio_codec: Dolby Digital + language: [hindi, english] + release_group: ETRG + type: movie + +? The.Martian.2015.4K.UHD.UPSCALED-ETRG +: title: The Martian + year: 2015 + screen_size: 2160p + other: [Ultra HD, Upscaled] + release_group: ETRG + type: movie + +? Delibal 2015 720p Upscale DVDRip x264 DD5.1 AC3 +: title: Delibal + year: 2015 + screen_size: 720p + other: [Upscaled, Rip] + source: DVD + video_codec: H.264 + audio_codec: Dolby Digital + audio_channels: '5.1' + type: movie + +? Casablanca [Ultimate Collector's Edition].1942.BRRip.XviD-VLiS +: title: Casablanca + edition: [Ultimate, Collector] + year: 1942 + source: Blu-ray + other: [Reencoded, Rip] + video_codec: Xvid + release_group: VLiS + type: movie + +? Batman V Superman Dawn of Justice 2016 Extended Cut Ultimate Edition HDRip x264 AC3-DaDDy +: title: Batman V Superman Dawn of Justice + year: 2016 + edition: [Extended, Ultimate] + other: [HD, Rip] + video_codec: H.264 + audio_codec: Dolby Digital + release_group: DaDDy + type: movie + +? Stargate SG1 Ultimate Fan Collection +: title: Stargate SG1 + edition: [Ultimate, Fan] + +? The.Jungle.Book.2016.MULTi.1080p.BluRay.x264.DTS-HD.MA.7.1.DTS-HD.HRA.5.1-LeRalou +: title: The Jungle Book + year: 2016 + language: mul + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + audio_codec: DTS-HD + audio_profile: [Master Audio, High Resolution Audio] + audio_channels: ['7.1', '5.1'] + release_group: LeRalou + type: movie + +? Terminus.2015.BluRay.1080p.x264.DTS-HD.HRA.5.1-LTT +: title: Terminus + year: 2015 + source: Blu-ray + screen_size: 1080p + video_codec: H.264 + audio_codec: DTS-HD + audio_profile: High Resolution Audio + audio_channels: '5.1' + release_group: LTT + type: movie + +? Ghost.in.the.Shell.1995.1080p.Bluray.DTSES.x264-SHiTSoNy +: title: Ghost in the Shell + year: 1995 + screen_size: 1080p + source: Blu-ray + audio_codec: DTS + audio_profile: Extended Surround + +? The.Boss.Baby.2017.BluRay.1080p.DTS-ES.x264-PRoDJi +: title: The Boss Baby + year: 2017 + source: Blu-ray + screen_size: 1080p + audio_codec: DTS + audio_profile: Extended Surround + video_codec: H.264 + release_group: PRoDJi + type: movie + +? Title.2000.720p.BluRay.DDEX.x264-HDClub.mkv +: title: Title + year: 2000 + screen_size: 720p + source: Blu-ray + audio_codec: Dolby Digital + audio_profile: EX + video_codec: H.264 + release_group: HDClub + container: mkv + type: movie + +? Jack Reacher Never Go Back 2016 720p Bluray DD-EX x264-BluPanther +: title: Jack Reacher Never Go Back + year: 2016 + screen_size: 720p + source: Blu-ray + audio_codec: Dolby Digital + audio_profile: EX + video_codec: H.264 + release_group: BluPanther + type: movie + +? How To Steal A Dog.2014.BluRay.1080p.12bit.HEVC.OPUS 5.1-Hn1Dr2.mkv +: title: How To Steal A Dog + year: 2014 + source: Blu-ray + screen_size: 1080p + color_depth: 12-bit + video_codec: H.265 + audio_codec: Opus + audio_channels: '5.1' + release_group: Hn1Dr2 + container: mkv + type: movie + +? How.To.Be.Single.2016.1080p.BluRay.x264-BLOW/blow-how.to.be.single.2016.1080p.bluray.x264.mkv +: title: How To Be Single + year: 2016 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + release_group: BLOW + container: mkv + type: movie + +? After.the.Storm.2016.720p.YIFY +: title: After the Storm + year: 2016 + screen_size: 720p + release_group: YIFY + type: movie + +? Battle Royale 2000 DC (1080p Bluray x265 HEVC 10bit AAC 7.1 Japanese Tigole) +: title: Battle Royale + year: 2000 + edition: Director's Cut + screen_size: 1080p + source: Blu-ray + video_codec: H.265 + color_depth: 10-bit + audio_codec: AAC + audio_channels: '7.1' + language: jp + release_group: Tigole + +? Congo.The.Grand.Inga.Project.2013.1080p.BluRay.x264-OBiTS +: title: Congo The Grand Inga Project + year: 2013 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + release_group: OBiTS + type: movie + +? Congo.The.Grand.Inga.Project.2013.BRRip.XviD.MP3-RARBG +: title: Congo The Grand Inga Project + year: 2013 + source: Blu-ray + other: [Reencoded, Rip] + video_codec: Xvid + audio_codec: MP3 + release_group: RARBG + type: movie + +? Congo.The.Grand.Inga.Project.2013.720p.BluRay.H264.AAC-RARBG +: title: Congo The Grand Inga Project + year: 2013 + screen_size: 720p + source: Blu-ray + video_codec: H.264 + audio_codec: AAC + release_group: RARBG + type: movie + +? Mit.dem.Bauch.durch.die.Wand.SWiSSGERMAN.DOKU.DVDRiP.x264-DEFLOW +: title: Mit dem Bauch durch die Wand + language: de-CH + other: [Documentary, Rip] + source: DVD + video_codec: H.264 + release_group: DEFLOW + type: movie + +? InDefinitely.Maybe.2008.1080p.EUR.BluRay.VC-1.DTS-HD.MA.5.1-FGT +: title: InDefinitely Maybe + year: 2008 + screen_size: 1080p + source: Blu-ray + video_codec: VC-1 + audio_codec: DTS-HD + audio_profile: Master Audio + audio_channels: '5.1' + release_group: FGT + type: movie + +? Bjyukujyo Kyoushi Kan XXX 720P WEBRIP MP4-GUSH +: title: Bjyukujyo Kyoushi Kan + other: [XXX, Rip] + screen_size: 720p + source: Web + container: mp4 + release_group: GUSH + type: movie + +? The.Man.With.The.Golden.Arm.1955.1080p.BluRay.x264.DTS-FGT +: title: The Man With The Golden Arm + year: 1955 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + audio_codec: DTS + release_group: FGT + type: movie + +? blow-how.to.be.single.2016.1080p.bluray.x264.mkv +: release_group: blow + title: how to be single + year: 2016 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + container: mkv + type: movie + +? ulshd-the.right.stuff.1983.multi.1080p.bluray.x264.mkv +: release_group: ulshd + title: the right stuff + year: 1983 + language: mul + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + container: mkv + type: movie + +? FROZEN [2010] LiMiTED DVDRip H262 AAC[ ENG SUBS]-MANTESH +: title: FROZEN + year: 2010 + edition: Limited + source: DVD + other: Rip + video_codec: MPEG-2 + audio_codec: AAC + subtitle_language: english + release_group: MANTESH + type: movie + +? Family.Katta.2016.1080p.WEB-DL.H263.DD5.1.ESub-DDR +: title: Family Katta + year: 2016 + screen_size: 1080p + source: Web + video_codec: H.263 + audio_codec: Dolby Digital + audio_channels: '5.1' + subtitle_language: und + release_group: DDR + type: movie + +? Bad Boys 2 1080i.mpg2.rus.eng.ts +: title: Bad Boys 2 + screen_size: 1080i + video_codec: MPEG-2 + language: [russian, english] + container: ts + type: movie + +? Alien.Director.Cut.Ita.Eng.VP9.Opus.AlphaBot.webm +: title: Alien + edition: Director's Cut + language: [english, italian] + video_codec: VP9 + audio_codec: Opus + release_group: AlphaBot + container: webm + type: movie + +? The.Stranger.1946.US.(Kino.Classics).Bluray.1080p.LPCM.DD-2.0.x264-Grym@BTNET +: title: The Stranger + year: 1946 + country: US + source: Blu-ray + screen_size: 1080p + audio_codec: [LPCM, Dolby Digital] + audio_channels: '2.0' + video_codec: H.264 + release_group: Grym@BTNET + type: movie + +? X-Men.Apocalypse.2016.complete.hdts.pcm.TrueFrench-Scarface45.avi +: title: X-Men Apocalypse + year: 2016 + other: Complete + source: HD Telesync + audio_codec: PCM + language: french + release_group: Scarface45 + container: avi + type: movie + +? Tears.of.Steel.2012.2160p.DMRip.Eng.HDCLUB.mkv +: title: Tears of Steel + year: 2012 + screen_size: 2160p + source: Digital Master + other: Rip + language: english + release_group: HDCLUB + container: mkv + type: movie + +? "/Movies/Open Season 2 (2008)/Open Season 2 (2008) - Bluray-1080p.x264.DTS.mkv" +: options: --type movie + title: Open Season 2 + year: 2008 + source: Blu-ray + screen_size: 1080p + video_codec: H.264 + audio_codec: DTS + container: mkv + type: movie + +? Re-Animator.1985.INTEGRAL VERSION LIMITED EDITION.1080p.BluRay.REMUX.AVC.DTS-HD MA 5.1-LAZY +: title: Re-Animator + year: 1985 + edition: Limited + screen_size: 1080p + source: Blu-ray + other: Remux + video_codec: H.264 + audio_codec: DTS-HD + audio_profile: Master Audio + audio_channels: '5.1' + release_group: LAZY + type: movie + +? Test (2013) [WEBDL-1080p] [x264 AC3] [ENG+RU+PT] [NTb].mkv +: title: Test + year: 2013 + source: Web + screen_size: 1080p + video_codec: H.264 + audio_codec: Dolby Digital + language: [en, ru, pt] + release_group: NTb + container: mkv + type: movie + +? "[nextorrent.org] Bienvenue.Au.Gondwana.2016.FRENCH.DVDRiP.XViD-AViTECH.avi" +: website: nextorrent.org + title: Bienvenue Au Gondwana + year: 2016 + language: french + source: DVD + other: Rip + video_codec: Xvid + release_group: AViTECH + container: avi + type: movie + +? Star Trek First Contact (1996) Blu-Ray 1080p24 H.264 TrueHD 5.1 CtrlHD +: title: Star Trek First Contact + year: 1996 + source: Blu-ray + screen_size: 1080p + frame_rate: 24fps + video_codec: H.264 + audio_codec: Dolby TrueHD + audio_channels: '5.1' + release_group: CtrlHD + type: movie + +? The.Hobbit.The.Desolation.of.Smaug.Extended.HFR.48fps.ITA.ENG.AC3.BDRip.1080p.x264_ZMachine.mkv +: title: The Hobbit The Desolation of Smaug + edition: Extended + other: [High Frame Rate, Rip] + frame_rate: 48fps + language: [it, en] + audio_codec: Dolby Digital + source: Blu-ray + screen_size: 1080p + video_codec: H.264 + release_group: ZMachine + container: mkv + type: movie + +? Test (2013) [WEBDL-1080p] [x264 AC3] [ENG+PT+DE] [STANDARD] +: title: Test + year: 2013 + source: Web + screen_size: 1080p + video_codec: H.264 + audio_codec: Dolby Digital + language: [en, pt, de] + release_group: STANDARD + type: movie + +? Test (2013) [WEBDL-1080p] [x264 AC3] [ENG+DE+IT] [STANDARD] +: title: Test + year: 2013 + source: Web + screen_size: 1080p + video_codec: H.264 + audio_codec: Dolby Digital + language: [en, de, it] + release_group: STANDARD + type: movie + +? Ant-Man.and.the.Wasp.2018.Digital.Extras.1080p.AMZN.WEB-DL.DDP5.1.H.264-NTG.mkv +: title: Ant-Man and the Wasp + year: 2018 + other: Extras + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + release_group: NTG + type: movie + +? Ant-Man.and.the.Wasp.2018.1080p.AMZN.WEB-DL.DDP5.1.H.264-NTG.mkv +: title: Ant-Man and the Wasp + year: 2018 + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + release_group: NTG + type: movie + +? Avengers.Infinity.War.2018.3D.Hybrid.REPACK.1080p.BluRay.REMUX.AVC.Atmos-EPSiLON.mk3d +: title: Avengers Infinity War + year: 2018 + other: + - 3D + - Proper + - Remux + proper_count: 1 + screen_size: 1080p + source: Blu-ray + video_codec: H.264 + audio_codec: Dolby Atmos + release_group: EPSiLON + container: mk3d + type: movie + +? Ouija.Seance.The.Final.Game.2018.1080p.WEB-DL.DD5.1.H264-CMRG +: title: Ouija Seance The Final Game + year: 2018 + screen_size: 1080p + source: Web + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: CMRG + type: movie + +? The.Girl.in.the.Spiders.Web.2019.1080p.WEB-DL.x264.AC3-EVO.mkv +: title: The Girl in the Spiders Web + year: 2019 + screen_size: 1080p + source: Web + video_codec: H.264 + audio_codec: Dolby Digital + release_group: EVO + container: mkv + type: movie diff --git a/libs/guessit/test/rules/audio_codec.yml b/libs/guessit/test/rules/audio_codec.yml index df5ac4dc9..9e381c343 100644 --- a/libs/guessit/test/rules/audio_codec.yml +++ b/libs/guessit/test/rules/audio_codec.yml @@ -8,22 +8,25 @@ ? +lame3.100 : audio_codec: MP3 +? +MP2 +: audio_codec: MP2 + ? +DolbyDigital ? +DD ? +Dolby Digital ? +AC3 -: audio_codec: AC3 +: audio_codec: Dolby Digital ? +DDP ? +DD+ ? +EAC3 -: audio_codec: EAC3 +: audio_codec: Dolby Digital Plus ? +DolbyAtmos ? +Dolby Atmos ? +Atmos ? -Atmosphere -: audio_codec: DolbyAtmos +: audio_codec: Dolby Atmos ? +AAC : audio_codec: AAC @@ -36,33 +39,34 @@ ? +True-HD ? +trueHD -: audio_codec: TrueHD +: audio_codec: Dolby TrueHD ? +True-HD51 ? +trueHD51 -: audio_codec: TrueHD +: audio_codec: Dolby TrueHD audio_channels: '5.1' - +? +DTSHD +? +DTS HD ? +DTS-HD -: audio_codec: DTS - audio_profile: HD +: audio_codec: DTS-HD ? +DTS-HDma -: audio_codec: DTS - audio_profile: HDMA +? +DTSMA +: audio_codec: DTS-HD + audio_profile: Master Audio ? +AC3-hq -: audio_codec: AC3 - audio_profile: HQ +: audio_codec: Dolby Digital + audio_profile: High Quality ? +AAC-HE : audio_codec: AAC - audio_profile: HE + audio_profile: High Efficiency ? +AAC-LC : audio_codec: AAC - audio_profile: LC + audio_profile: Low Complexity ? +AAC2.0 ? +AAC20 @@ -90,8 +94,41 @@ ? DD5.1 ? DD51 -: audio_codec: AC3 +: audio_codec: Dolby Digital audio_channels: '5.1' ? -51 : audio_channels: '5.1' + +? DTS-HD.HRA +? DTSHD.HRA +? DTS-HD.HR +? DTSHD.HR +? -HRA +? -HR +: audio_codec: DTS-HD + audio_profile: High Resolution Audio + +? DTSES +? DTS-ES +? -ES +: audio_codec: DTS + audio_profile: Extended Surround + +? DD-EX +? DDEX +? -EX +: audio_codec: Dolby Digital + audio_profile: EX + +? OPUS +: audio_codec: Opus + +? Vorbis +: audio_codec: Vorbis + +? PCM +: audio_codec: PCM + +? LPCM +: audio_codec: LPCM diff --git a/libs/guessit/test/rules/cds.yml b/libs/guessit/test/rules/cds.yml index cc63765e5..d76186c6b 100644 --- a/libs/guessit/test/rules/cds.yml +++ b/libs/guessit/test/rules/cds.yml @@ -7,4 +7,4 @@ ? Some.Title-DVDRIP-x264-CDP : cd: !!null release_group: CDP - video_codec: h264 + video_codec: H.264 diff --git a/libs/guessit/test/rules/common_words.yml b/libs/guessit/test/rules/common_words.yml new file mode 100644 index 000000000..d403a4571 --- /dev/null +++ b/libs/guessit/test/rules/common_words.yml @@ -0,0 +1,467 @@ +? is +: title: is + +? it +: title: it + +? am +: title: am + +? mad +: title: mad + +? men +: title: men + +? man +: title: man + +? run +: title: run + +? sin +: title: sin + +? st +: title: st + +? to +: title: to + +? 'no' +: title: 'no' + +? non +: title: non + +? war +: title: war + +? min +: title: min + +? new +: title: new + +? car +: title: car + +? day +: title: day + +? bad +: title: bad + +? bat +: title: bat + +? fan +: title: fan + +? fry +: title: fry + +? cop +: title: cop + +? zen +: title: zen + +? gay +: title: gay + +? fat +: title: fat + +? one +: title: one + +? cherokee +: title: cherokee + +? got +: title: got + +? an +: title: an + +? as +: title: as + +? cat +: title: cat + +? her +: title: her + +? be +: title: be + +? hat +: title: hat + +? sun +: title: sun + +? may +: title: may + +? my +: title: my + +? mr +: title: mr + +? rum +: title: rum + +? pi +: title: pi + +? bb +: title: bb + +? bt +: title: bt + +? tv +: title: tv + +? aw +: title: aw + +? by +: title: by + +? md +: other: Mic Dubbed + +? mp +: title: mp + +? cd +: title: cd + +? in +: title: in + +? ad +: title: ad + +? ice +: title: ice + +? ay +: title: ay + +? at +: title: at + +? star +: title: star + +? so +: title: so + +? he +: title: he + +? do +: title: do + +? ax +: title: ax + +? mx +: title: mx + +? bas +: title: bas + +? de +: title: de + +? le +: title: le + +? son +: title: son + +? ne +: title: ne + +? ca +: title: ca + +? ce +: title: ce + +? et +: title: et + +? que +: title: que + +? mal +: title: mal + +? est +: title: est + +? vol +: title: vol + +? or +: title: or + +? mon +: title: mon + +? se +: title: se + +? je +: title: je + +? tu +: title: tu + +? me +: title: me + +? ma +: title: ma + +? va +: title: va + +? au +: country: AU + +? lu +: title: lu + +? wa +: title: wa + +? ga +: title: ga + +? ao +: title: ao + +? la +: title: la + +? el +: title: el + +? del +: title: del + +? por +: title: por + +? mar +: title: mar + +? al +: title: al + +? un +: title: un + +? ind +: title: ind + +? arw +: title: arw + +? ts +: source: Telesync + +? ii +: title: ii + +? bin +: title: bin + +? chan +: title: chan + +? ss +: title: ss + +? san +: title: san + +? oss +: title: oss + +? iii +: title: iii + +? vi +: title: vi + +? ben +: title: ben + +? da +: title: da + +? lt +: title: lt + +? ch +: title: ch + +? sr +: title: sr + +? ps +: title: ps + +? cx +: title: cx + +? vo +: title: vo + +? mkv +: container: mkv + +? avi +: container: avi + +? dmd +: title: dmd + +? the +: title: the + +? dis +: title: dis + +? cut +: title: cut + +? stv +: title: stv + +? des +: title: des + +? dia +: title: dia + +? and +: title: and + +? cab +: title: cab + +? sub +: title: sub + +? mia +: title: mia + +? rim +: title: rim + +? las +: title: las + +? une +: title: une + +? par +: title: par + +? srt +: container: srt + +? ano +: title: ano + +? toy +: title: toy + +? job +: title: job + +? gag +: title: gag + +? reel +: title: reel + +? www +: title: www + +? for +: title: for + +? ayu +: title: ayu + +? csi +: title: csi + +? ren +: title: ren + +? moi +: title: moi + +? sur +: title: sur + +? fer +: title: fer + +? fun +: title: fun + +? two +: title: two + +? big +: title: big + +? psy +: title: psy + +? air +: title: air + +? brazil +: title: brazil + +? jordan +: title: jordan + +? bs +: title: bs + +? kz +: title: kz + +? gt +: title: gt + +? im +: title: im + +? pt +: language: pt + +? scr +: title: scr + +? sd +: title: sd + +? hr +: other: High Resolution diff --git a/libs/guessit/test/rules/country.yml b/libs/guessit/test/rules/country.yml index f2da1b205..b3d4d8f14 100644 --- a/libs/guessit/test/rules/country.yml +++ b/libs/guessit/test/rules/country.yml @@ -5,6 +5,9 @@ : country: US title: this is title -? This.is.us.title -: title: This is us title +? This.is.Us +: title: This is Us +? This.Is.Us +: options: --no-default-config + title: This Is Us diff --git a/libs/guessit/test/rules/edition.yml b/libs/guessit/test/rules/edition.yml index 4d96017b3..4b7fd9866 100644 --- a/libs/guessit/test/rules/edition.yml +++ b/libs/guessit/test/rules/edition.yml @@ -7,25 +7,57 @@ ? Collector ? Collector Edition ? Edition Collector -: edition: Collector Edition +: edition: Collector ? Special Edition ? Edition Special ? -Special -: edition: Special Edition +: edition: Special ? Criterion Edition ? Edition Criterion +? CC ? -Criterion -: edition: Criterion Edition +: edition: Criterion ? Deluxe ? Deluxe Edition ? Edition Deluxe -: edition: Deluxe Edition +: edition: Deluxe ? Super Movie Alternate XViD ? Super Movie Alternative XViD ? Super Movie Alternate Cut XViD ? Super Movie Alternative Cut XViD : edition: Alternative Cut + +? ddc +: edition: Director's Definitive Cut + +? IMAX +? IMAX Edition +: edition: IMAX + +? ultimate edition +? -ultimate +: edition: Ultimate + +? ultimate collector edition +? ultimate collector's edition +? ultimate collectors edition +? -collectors edition +? -ultimate edition +: edition: [Ultimate, Collector] + +? ultimate collectors edition dc +: edition: [Ultimate, Collector, Director's Cut] + +? fan edit +? fan edition +? fan collection +: edition: Fan + +? ultimate fan edit +? ultimate fan edition +? ultimate fan collection +: edition: [Ultimate, Fan] diff --git a/libs/guessit/test/rules/episodes.yml b/libs/guessit/test/rules/episodes.yml index 9c67c294a..44e06a3bb 100644 --- a/libs/guessit/test/rules/episodes.yml +++ b/libs/guessit/test/rules/episodes.yml @@ -32,8 +32,6 @@ ? +serie Season 2 other ? +serie Saisons 2 other ? +serie Seasons 2 other -? +serie Serie 2 other -? +serie Series 2 other ? +serie Season Two other ? +serie Season II other : season: 2 @@ -156,7 +154,7 @@ ? Show.Name.Season.1.3&5.HDTV.XviD-GoodGroup[SomeTrash] ? Show.Name.Season.1.3 and 5.HDTV.XviD-GoodGroup[SomeTrash] -: format: HDTV +: source: HDTV release_group: GoodGroup[SomeTrash] season: - 1 @@ -164,12 +162,12 @@ - 5 title: Show Name type: episode - video_codec: XviD + video_codec: Xvid ? Show.Name.Season.1.2.3-5.HDTV.XviD-GoodGroup[SomeTrash] ? Show.Name.Season.1.2.3~5.HDTV.XviD-GoodGroup[SomeTrash] ? Show.Name.Season.1.2.3 to 5.HDTV.XviD-GoodGroup[SomeTrash] -: format: HDTV +: source: HDTV release_group: GoodGroup[SomeTrash] season: - 1 @@ -179,18 +177,19 @@ - 5 title: Show Name type: episode - video_codec: XviD + video_codec: Xvid ? The.Get.Down.S01EP01.FRENCH.720p.WEBRIP.XVID-STR : episode: 1 - format: WEBRip + source: Web + other: Rip language: fr release_group: STR screen_size: 720p season: 1 title: The Get Down type: episode - video_codec: XviD + video_codec: Xvid ? My.Name.Is.Earl.S01E01-S01E21.SWE-SUB : episode: @@ -269,4 +268,64 @@ ? Episode71 ? Episode 71 -: episode: 71
\ No newline at end of file +: episode: 71 + +? S01D02.3-5-GROUP +: disc: [2, 3, 4, 5] + +? S01D02&4-6&8 +: disc: [2, 4, 5, 6, 8] + +? Something.4x05-06 +? Something - 4x05-06 +? Something:4x05-06 +? Something 4x05-06 +? Something-4x05-06 +: title: Something + season: 4 + episode: + - 5 + - 6 + +? Something.4x05-06 +? Something - 4x05-06 +? Something:4x05-06 +? Something 4x05-06 +? Something-4x05-06 +: options: -T something + title: something + season: 4 + episode: + - 5 + - 6 + +? Colony 23/S01E01.Some.title.mkv +: title: Colony 23 + season: 1 + episode: 1 + episode_title: Some title + +? Show.Name.E02.2010.mkv +: options: -t episode + title: Show Name + year: 2010 + episode: 2 + +? Show.Name.E02.S2010.mkv +: options: -t episode + title: Show Name + year: 2010 + season: 2010 + episode: 2 + + +? Show.Name.E02.2010.mkv +: title: Show Name + year: 2010 + episode: 2 + +? Show.Name.E02.S2010.mkv +: title: Show Name + year: 2010 + season: 2010 + episode: 2 diff --git a/libs/guessit/test/rules/format.yml b/libs/guessit/test/rules/format.yml deleted file mode 100644 index e983cfb1b..000000000 --- a/libs/guessit/test/rules/format.yml +++ /dev/null @@ -1,138 +0,0 @@ -# Multiple input strings having same expected results can be chained. -# Use - marker to check inputs that should not match results. -? +VHS -? +VHSRip -? +VHS-Rip -? +VhS_rip -? +VHS.RIP -? -VHSAnythingElse -? -SomeVHS stuff -? -VH -? -VHx -? -VHxRip -: format: VHS - -? +Cam -? +CamRip -? +CaM Rip -? +Cam_Rip -? +cam.rip -: format: Cam - -? +Telesync -? +TS -? +HD TS -? -Hd.Ts # ts file extension -? -HD.TS # ts file extension -? +Hd-Ts -: format: Telesync - -? +Workprint -? +workPrint -? +WorkPrint -? +WP -? -Work Print -: format: Workprint - -? +Telecine -? +teleCine -? +TC -? -Tele Cine -: format: Telecine - -? +PPV -? +ppv-rip -: format: PPV - -? -TV -? +SDTV -? +SDTVRIP -? +Rip sd tv -? +TvRip -? +Rip TV -: format: TV - -? +DVB -? +DVB-Rip -? +DvBRiP -? +pdTV -? +Pd Tv -: format: DVB - -? +DVD -? +DVD-RIP -? +video ts -? +DVDR -? +DVD 9 -? +dvd 5 -? -dvd ts -: format: DVD - -format: ts - -? +HDTV -? +tv rip hd -? +HDtv Rip -? +HdRip -: format: HDTV - -? +VOD -? +VodRip -? +vod rip -: format: VOD - -? +webrip -? +Web Rip -? +webdlrip -? +web dl rip -? +webcap -? +web cap -: format: WEBRip - -? +webdl -? +Web DL -? +webHD -? +WEB hd -? +web -: format: WEB-DL - -? +HDDVD -? +hd dvd -? +hdDvdRip -: format: HD-DVD - -? +BluRay -? +BluRay rip -? +BD -? +BR -? +BDRip -? +BR rip -? +BD5 -? +BD9 -? +BD25 -? +bd50 -: format: BluRay - -? XVID.NTSC.DVDR.nfo -: format: DVD - -? AHDTV -: format: AHDTV - -? dsr -? dsrip -? ds rip -? dsrrip -? dsr rip -? satrip -? sat rip -? dth -? dthrip -? dth rip -: format: SATRip - -? HDTC -: format: HDTC - -? UHDTV -? UHDRip -: format: UHDTV diff --git a/libs/guessit/test/rules/language.yml b/libs/guessit/test/rules/language.yml index 51bbd8da8..10e5b9c05 100644 --- a/libs/guessit/test/rules/language.yml +++ b/libs/guessit/test/rules/language.yml @@ -36,4 +36,12 @@ ? +ENG.-.SubSV ? +ENG.-.SVSUB : language: English - subtitle_language: Swedish
\ No newline at end of file + subtitle_language: Swedish + +? The English Patient (1996) +: title: The English Patient + -language: english + +? French.Kiss.1995.1080p +: title: French Kiss + -language: french diff --git a/libs/guessit/test/rules/other.yml b/libs/guessit/test/rules/other.yml index 3d3df706e..447f1787d 100644 --- a/libs/guessit/test/rules/other.yml +++ b/libs/guessit/test/rules/other.yml @@ -12,38 +12,35 @@ ? +AudioFixed ? +Audio Fix ? +Audio Fixed -: other: AudioFix +: other: Audio Fixed ? +SyncFix ? +SyncFixed ? +Sync Fix ? +Sync Fixed -: other: SyncFix +: other: Sync Fixed ? +DualAudio ? +Dual Audio -: other: DualAudio +: other: Dual Audio ? +ws ? +WideScreen ? +Wide Screen -: other: WideScreen +: other: Widescreen -# Fix and Real must be surround by others properties to be matched. -? DVD.Real.XViD +# Fix must be surround by others properties to be matched. ? DVD.fix.XViD -? -DVD.Real ? -DVD.Fix -? -Real.XViD ? -Fix.XViD -: other: Proper - proper_count: 1 +: other: Fix + -proper_count: 1 ? -DVD.BlablaBla.Fix.Blablabla.XVID ? -DVD.BlablaBla.Fix.XVID ? -DVD.Fix.Blablabla.XVID -: other: Proper - proper_count: 1 +: other: Fix + -proper_count: 1 ? DVD.Real.PROPER.REPACK @@ -51,25 +48,27 @@ proper_count: 3 -? Proper +? Proper.720p ? +Repack ? +Rerip : other: Proper proper_count: 1 ? XViD.Fansub -: other: Fansub +: other: Fan Subtitled ? XViD.Fastsub -: other: Fastsub +: other: Fast Subtitled ? +Season Complete ? -Complete : other: Complete ? R5 +: other: Region 5 + ? RC -: other: R5 +: other: Region C ? PreAir ? Pre Air @@ -81,7 +80,7 @@ ? Remux : other: Remux -? 3D +? 3D.2019 : other: 3D ? HD @@ -90,28 +89,23 @@ ? FHD ? FullHD ? Full HD -: other: FullHD +: other: Full HD ? UHD ? Ultra ? UltraHD ? Ultra HD -: other: UltraHD +: other: Ultra HD ? mHD # ?? -: other: mHD - ? HDLight -: other: HDLight +: other: Micro HD ? HQ -: other: HQ - -? ddc -: other: DDC +: other: High Quality ? hr -: other: HR +: other: High Resolution ? PAL : other: PAL @@ -122,15 +116,14 @@ ? NTSC : other: NTSC -? CC -: other: CC +? LDTV +: other: Low Definition ? LD -? LDTV -: other: LD +: other: Line Dubbed ? MD -: other: MD +: other: Mic Dubbed ? -The complete movie : other: Complete @@ -139,16 +132,38 @@ : title: The complete movie ? +AC3-HQ -: audio_profile: HQ +: audio_profile: High Quality ? Other-HQ -: other: HQ +: other: High Quality ? reenc ? re-enc ? re-encoded ? reencoded -: other: ReEncoded +: other: Reencoded ? CONVERT XViD -: other: Converted
\ No newline at end of file +: other: Converted + +? +HDRIP # it's a Rip from non specified HD source +: other: [HD, Rip] + +? SDR +: other: Standard Dynamic Range + +? HDR +? HDR10 +? -HDR100 +: other: HDR10 + +? BT2020 +? BT.2020 +? -BT.20200 +? -BT.2021 +: other: BT.2020 + +? Upscaled +? Upscale +: other: Upscaled + diff --git a/libs/guessit/test/rules/release_group.yml b/libs/guessit/test/rules/release_group.yml index f9d01e723..c96383e94 100644 --- a/libs/guessit/test/rules/release_group.yml +++ b/libs/guessit/test/rules/release_group.yml @@ -42,30 +42,30 @@ ? Show.Name.x264-byEMP : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: byEMP ? Show.Name.x264-NovaRip : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: NovaRip ? Show.Name.x264-PARTiCLE : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: PARTiCLE ? Show.Name.x264-POURMOi : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: POURMOi ? Show.Name.x264-RipPourBox : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: RipPourBox ? Show.Name.x264-RiPRG : title: Show Name - video_codec: h264 + video_codec: H.264 release_group: RiPRG diff --git a/libs/guessit/test/rules/screen_size.yml b/libs/guessit/test/rules/screen_size.yml index 1145dd7eb..25d8374fa 100644 --- a/libs/guessit/test/rules/screen_size.yml +++ b/libs/guessit/test/rules/screen_size.yml @@ -2,68 +2,279 @@ # Use - marker to check inputs that should not match results. ? +360p ? +360px -? +360i -? "+360" +? -360 ? +500x360 +? -250x360 +: screen_size: 360p + +? +640x360 +? -640x360i +? -684x360i : screen_size: 360p + aspect_ratio: 1.778 + +? +360i +: screen_size: 360i + +? +480x360i +? -480x360p +? -450x360 +: screen_size: 360i + aspect_ratio: 1.333 ? +368p ? +368px -? +368i -? "+368" +? -368i +? -368 ? +500x368 : screen_size: 368p +? -490x368 +? -700x368 +: screen_size: 368p + +? +492x368p +: screen_size: + aspect_ratio: 1.337 + +? +654x368 +: screen_size: 368p + aspect_ratio: 1.777 + +? +698x368 +: screen_size: 368p + aspect_ratio: 1.897 + +? +368i +: -screen_size: 368i + ? +480p ? +480px -? +480i -? "+480" -? +500x480 +? -480i +? -480 +? -500x480 +? -638x480 +? -920x480 +: screen_size: 480p + +? +640x480 +: screen_size: 480p + aspect_ratio: 1.333 + +? +852x480 +: screen_size: 480p + aspect_ratio: 1.775 + +? +910x480 : screen_size: 480p + aspect_ratio: 1.896 + +? +500x480 +? +500 x 480 +? +500 * 480 +? +500x480p +? +500X480i +: screen_size: 500x480 + aspect_ratio: 1.042 + +? +480i +? +852x480i +: screen_size: 480i ? +576p ? +576px -? +576i -? "+576" -? +500x576 +? -576i +? -576 +? -500x576 +? -766x576 +? -1094x576 +: screen_size: 576p + +? +768x576 : screen_size: 576p + aspect_ratio: 1.333 + +? +1024x576 +: screen_size: 576p + aspect_ratio: 1.778 + +? +1092x576 +: screen_size: 576p + aspect_ratio: 1.896 + +? +500x576 +: screen_size: 500x576 + aspect_ratio: 0.868 + +? +576i +: screen_size: 576i ? +720p ? +720px +? -720i ? 720hd ? 720pHD -? +720i -? "+720" -? +500x720 +? -720 +? -500x720 +? -950x720 +? -1368x720 : screen_size: 720p +? +960x720 +: screen_size: 720p + aspect_ratio: 1.333 + +? +1280x720 +: screen_size: 720p + aspect_ratio: 1.778 + +? +1366x720 +: screen_size: 720p + aspect_ratio: 1.897 + +? +500x720 +: screen_size: 500x720 + aspect_ratio: 0.694 + ? +900p ? +900px -? +900i -? "+900" -? +500x900 +? -900i +? -900 +? -500x900 +? -1198x900 +? -1710x900 +: screen_size: 900p + +? +1200x900 : screen_size: 900p + aspect_ratio: 1.333 + +? +1600x900 +: screen_size: 900p + aspect_ratio: 1.778 + +? +1708x900 +: screen_size: 900p + aspect_ratio: 1.898 + +? +500x900 +? +500x900p +? +500x900i +: screen_size: 500x900 + aspect_ratio: 0.556 + +? +900i +: screen_size: 900i ? +1080p ? +1080px ? +1080hd ? +1080pHD ? -1080i -? "+1080" -? +500x1080 +? -1080 +? -500x1080 +? -1438x1080 +? -2050x1080 : screen_size: 1080p +? +1440x1080 +: screen_size: 1080p + aspect_ratio: 1.333 + +? +1920x1080 +: screen_size: 1080p + aspect_ratio: 1.778 + +? +2048x1080 +: screen_size: 1080p + aspect_ratio: 1.896 + ? +1080i ? -1080p : screen_size: 1080i +? 1440p +: screen_size: 1440p + +? +500x1080 +: screen_size: 500x1080 + aspect_ratio: 0.463 + ? +2160p ? +2160px -? +2160i -? "+2160" +? -2160i +? -2160 ? +4096x2160 -: screen_size: 4K +? +4k +? -2878x2160 +? -4100x2160 +: screen_size: 2160p + +? +2880x2160 +: screen_size: 2160p + aspect_ratio: 1.333 + +? +3840x2160 +: screen_size: 2160p + aspect_ratio: 1.778 + +? +4098x2160 +: screen_size: 2160p + aspect_ratio: 1.897 + +? +500x2160 +: screen_size: 500x2160 + aspect_ratio: 0.231 + +? +4320p +? +4320px +? -4320i +? -4320 +? -5758x2160 +? -8198x2160 +: screen_size: 4320p + +? +5760x4320 +: screen_size: 4320p + aspect_ratio: 1.333 + +? +7680x4320 +: screen_size: 4320p + aspect_ratio: 1.778 + +? +8196x4320 +: screen_size: 4320p + aspect_ratio: 1.897 + +? +500x4320 +: screen_size: 500x4320 + aspect_ratio: 0.116 ? Test.File.720hd.bluray +? Test.File.720p24 +? Test.File.720p30 ? Test.File.720p50 +? Test.File.720p60 +? Test.File.720p120 : screen_size: 720p + +? Test.File.400p +: options: + advanced_config: + screen_size: + progressive: ["400"] + screen_size: 400p + +? Test.File2.400p +: options: + advanced_config: + screen_size: + progressive: ["400"] + screen_size: 400p + +? Test.File.720p +: options: + advanced_config: + screen_size: + progressive: ["400"] + screen_size: 720p diff --git a/libs/guessit/test/rules/source.yml b/libs/guessit/test/rules/source.yml new file mode 100644 index 000000000..cda8f1ac4 --- /dev/null +++ b/libs/guessit/test/rules/source.yml @@ -0,0 +1,323 @@ +# Multiple input strings having same expected results can be chained. +# Use - marker to check inputs that should not match results. +? +VHS +? -VHSAnythingElse +? -SomeVHS stuff +? -VH +? -VHx +: source: VHS + -other: Rip + +? +VHSRip +? +VHS-Rip +? +VhS_rip +? +VHS.RIP +? -VHS +? -VHxRip +: source: VHS + other: Rip + +? +Cam +: source: Camera + -other: Rip + +? +CamRip +? +CaM Rip +? +Cam_Rip +? +cam.rip +? -Cam +: source: Camera + other: Rip + +? +HDCam +? +HD-Cam +: source: HD Camera + -other: Rip + +? +HDCamRip +? +HD-Cam.rip +? -HDCam +? -HD-Cam +: source: HD Camera + other: Rip + +? +Telesync +? +TS +: source: Telesync + -other: Rip + +? +TelesyncRip +? +TSRip +? -Telesync +? -TS +: source: Telesync + other: Rip + +? +HD TS +? -Hd.Ts # ts file extension +? -HD.TS # ts file extension +? +Hd-Ts +: source: HD Telesync + -other: Rip + +? +HD TS Rip +? +Hd-Ts-Rip +? -HD TS +? -Hd-Ts +: source: HD Telesync + other: Rip + +? +Workprint +? +workPrint +? +WorkPrint +? +WP +? -Work Print +: source: Workprint + -other: Rip + +? +Telecine +? +teleCine +? +TC +? -Tele Cine +: source: Telecine + -other: Rip + +? +Telecine Rip +? +teleCine-Rip +? +TC-Rip +? -Telecine +? -TC +: source: Telecine + other: Rip + +? +HD-TELECINE +? +HDTC +: source: HD Telecine + -other: Rip + +? +HD-TCRip +? +HD TELECINE RIP +? -HD-TELECINE +? -HDTC +: source: HD Telecine + other: Rip + +? +PPV +: source: Pay-per-view + -other: Rip + +? +ppv-rip +? -PPV +: source: Pay-per-view + other: Rip + +? -TV +? +SDTV +? +TV-Dub +: source: TV + -other: Rip + +? +SDTVRIP +? +Rip sd tv +? +TvRip +? +Rip TV +? -TV +? -SDTV +: source: TV + other: Rip + +? +DVB +? +pdTV +? +Pd Tv +: source: Digital TV + -other: Rip + +? +DVB-Rip +? +DvBRiP +? +pdtvRiP +? +pd tv RiP +? -DVB +? -pdTV +? -Pd Tv +: source: Digital TV + other: Rip + +? +DVD +? +video ts +? +DVDR +? +DVD 9 +? +dvd 5 +? -dvd ts +: source: DVD + -source: Telesync + -other: Rip + +? +DVD-RIP +? -video ts +? -DVD +? -DVDR +? -DVD 9 +? -dvd 5 +: source: DVD + other: Rip + +? +HDTV +: source: HDTV + -other: Rip + +? +tv rip hd +? +HDtv Rip +? -HdRip # it's a Rip from non specified HD source +? -HDTV +: source: HDTV + other: Rip + +? +VOD +: source: Video on Demand + -other: Rip + +? +VodRip +? +vod rip +? -VOD +: source: Video on Demand + other: Rip + +? +webrip +? +Web Rip +? +webdlrip +? +web dl rip +? +webcap +? +web cap +? +webcaprip +? +web cap rip +: source: Web + other: Rip + +? +webdl +? +Web DL +? +webHD +? +WEB hd +? +web +: source: Web + -other: Rip + +? +HDDVD +? +hd dvd +: source: HD-DVD + -other: Rip + +? +hdDvdRip +? -HDDVD +? -hd dvd +: source: HD-DVD + other: Rip + +? +BluRay +? +BD +? +BD5 +? +BD9 +? +BD25 +? +bd50 +: source: Blu-ray + -other: Rip + +? +BR-Scr +? +BR.Screener +: source: Blu-ray + other: [Reencoded, Screener] + -language: pt-BR + +? +BR-Rip +? +BRRip +: source: Blu-ray + other: [Reencoded, Rip] + -language: pt-BR + +? +BluRay rip +? +BDRip +? -BluRay +? -BD +? -BR +? -BR rip +? -BD5 +? -BD9 +? -BD25 +? -bd50 +: source: Blu-ray + other: Rip + +? XVID.NTSC.DVDR.nfo +: source: DVD + -other: Rip + +? +AHDTV +: source: Analog HDTV + -other: Rip + +? +dsr +? +dth +: source: Satellite + -other: Rip + +? +dsrip +? +ds rip +? +dsrrip +? +dsr rip +? +satrip +? +sat rip +? +dthrip +? +dth rip +? -dsr +? -dth +: source: Satellite + other: Rip + +? +UHDTV +: source: Ultra HDTV + -other: Rip + +? +UHDRip +? +UHDTV Rip +? -UHDTV +: source: Ultra HDTV + other: Rip + +? UHD Bluray +? UHD 2160p Bluray +? UHD 8bit Bluray +? UHD HQ 8bit Bluray +? Ultra Bluray +? Ultra HD Bluray +? Bluray ULTRA +? Bluray Ultra HD +? Bluray UHD +? 4K Bluray +? 2160p Bluray +? UHD 10bit HDR Bluray +? UHD HDR10 Bluray +? -HD Bluray +? -AMERICAN ULTRA (2015) 1080p Bluray +? -American.Ultra.2015.BRRip +? -BRRip XviD AC3-ULTRAS +? -UHD Proper Bluray +: source: Ultra HD Blu-ray + +? UHD.BRRip +? UHD.2160p.BRRip +? BRRip.2160p.UHD +? BRRip.[4K-2160p-UHD] +: source: Ultra HD Blu-ray + other: [Reencoded, Rip] + +? UHD.2160p.BDRip +? BDRip.[4K-2160p-UHD] +: source: Ultra HD Blu-ray + other: Rip + +? DM +: source: Digital Master + +? DMRIP +? DM-RIP +: source: Digital Master + other: Rip diff --git a/libs/guessit/test/rules/title.yml b/libs/guessit/test/rules/title.yml index fffaf8a25..05c7f2086 100644 --- a/libs/guessit/test/rules/title.yml +++ b/libs/guessit/test/rules/title.yml @@ -30,3 +30,14 @@ ? Some.Other title/Some other title.mkv : title: Some Other title +? This T.I.T.L.E. has dots +? This.T.I.T.L.E..has.dots +: title: This T.I.T.L.E has dots + +? This.T.I.T.L.E..has.dots.S01E02.This E.P.T.I.T.L.E.has.dots +: title: This T.I.T.L.E has dots + season: 1 + episode: 2 + episode_title: This E.P.T.I.T.L.E has dots + type: episode + diff --git a/libs/guessit/test/rules/video_codec.yml b/libs/guessit/test/rules/video_codec.yml index a11991ecc..ae43bc436 100644 --- a/libs/guessit/test/rules/video_codec.yml +++ b/libs/guessit/test/rules/video_codec.yml @@ -6,15 +6,19 @@ ? Rv30 ? rv40 ? -xrv40 -: video_codec: Real +: video_codec: RealVideo ? mpeg2 ? MPEG2 +? MPEG-2 +? mpg2 +? H262 +? H.262 +? x262 ? -mpeg -? -mpeg 2 # Not sure if we should ignore this one ... ? -xmpeg2 ? -mpeg2x -: video_codec: Mpeg2 +: video_codec: MPEG-2 ? DivX ? -div X @@ -26,19 +30,25 @@ ? XviD ? xvid ? -x vid -: video_codec: XviD +: video_codec: Xvid + +? h263 +? x263 +? h.263 +: video_codec: H.263 ? h264 ? x264 ? h.264 ? x.264 -? mpeg4-AVC +? AVC +? AVCHD ? -MPEG-4 ? -mpeg4 ? -mpeg ? -h 265 ? -x265 -: video_codec: h264 +: video_codec: H.264 ? h265 ? x265 @@ -47,13 +57,42 @@ ? hevc ? -h 264 ? -x264 -: video_codec: h265 +: video_codec: H.265 ? hevc10 ? HEVC-YUV420P10 -: video_codec: h265 - video_profile: 10bit +: video_codec: H.265 + color_depth: 10-bit ? h265-HP -: video_codec: h265 - video_profile: HP
\ No newline at end of file +: video_codec: H.265 + video_profile: High + +? H.264-SC +: video_codec: H.264 + video_profile: Scalable Video Coding + +? mpeg4-AVC +: video_codec: H.264 + video_profile: Advanced Video Codec High Definition + +? AVCHD-SC +? H.264-AVCHD-SC +: video_codec: H.264 + video_profile: + - Scalable Video Coding + - Advanced Video Codec High Definition + +? VC1 +? VC-1 +: video_codec: VC-1 + +? VP7 +: video_codec: VP7 + +? VP8 +? VP80 +: video_codec: VP8 + +? VP9 +: video_codec: VP9 diff --git a/libs/guessit/test/streaming_services.yaml b/libs/guessit/test/streaming_services.yaml new file mode 100644 index 000000000..adf52e715 --- /dev/null +++ b/libs/guessit/test/streaming_services.yaml @@ -0,0 +1,1934 @@ +? 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 +: title: House of Cards + year: 2013 + season: 2 + episode: 3 + screen_size: 1080p + streaming_service: Netflix + source: Web + other: Rip + audio_channels: "5.1" + audio_codec: Dolby Digital + video_codec: H.264 + release_group: NTb + +? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.CC.WEBRip.AAC2.0.x264-BTW.mkv +? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.ComedyCentral.WEBRip.AAC2.0.x264-BTW.mkv +? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.Comedy.Central.WEBRip.AAC2.0.x264-BTW.mkv +: audio_channels: '2.0' + audio_codec: AAC + date: 2015-07-01 + edition: Extended + source: Web + other: Rip + release_group: BTW + screen_size: 720p + streaming_service: Comedy Central + title: The Daily Show + episode_title: Kirsten Gillibrand + video_codec: H.264 + +? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.Interview.720p.CC.WEBRip.AAC2.0.x264-BTW.mkv +: audio_channels: '2.0' + audio_codec: AAC + date: 2015-07-01 + source: Web + release_group: BTW + screen_size: 720p + streaming_service: Comedy Central + title: The Daily Show + episode_title: Kirsten Gillibrand Extended Interview + video_codec: H.264 + +? The.Daily.Show.2015.07.02.Sarah.Vowell.CC.WEBRip.AAC2.0.x264-BTW.mkv +: audio_channels: '2.0' + audio_codec: AAC + date: 2015-07-02 + source: Web + release_group: BTW + streaming_service: Comedy Central + title: The Daily Show + episode_title: Sarah Vowell + video_codec: H.264 + +# Streaming service: Amazon +? Show.Name.S07E04.Service.1080p.AMZN.WEBRip.DD+5.1.x264 +? Show.Name.S07E04.Service.1080p.AmazonPrime.WEBRip.DD+5.1.x264 +: title: Show Name + season: 7 + episode: 4 + episode_title: Service + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + other: Rip + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + video_codec: H.264 + type: episode + +# Streaming service: Comedy Central +? Show.Name.2016.09.28.Nice.Title.Extended.1080p.CC.WEBRip.AAC2.0.x264-monkee +: title: Show Name + date: 2016-09-28 + episode_title: Nice Title + edition: Extended + other: Rip + screen_size: 1080p + streaming_service: Comedy Central + source: Web + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: The CW +? Show.Name.US.S12E20.Nice.Title.720p.CW.WEBRip.AAC2.0.x264-monkee +? Show.Name.US.S12E20.Nice.Title.720p.TheCW.WEBRip.AAC2.0.x264-monkee +: title: Show Name + country: US + season: 12 + episode: 20 + episode_title: Nice Title + screen_size: 720p + streaming_service: The CW + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: AMBC +? Show.Name.2016.09.27.Nice.Title.720p.AMBC.WEBRip.AAC2.0.x264-monkee +: title: Show Name + date: 2016-09-27 + episode_title: Nice Title + screen_size: 720p + streaming_service: ABC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: HIST +? Show.Name.720p.HIST.WEBRip.AAC2.0.H.264-monkee +? Show.Name.720p.History.WEBRip.AAC2.0.H.264-monkee +: options: -t episode + title: Show Name + screen_size: 720p + streaming_service: History + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: PBS +? Show.Name.2015.Nice.Title.1080p.PBS.WEBRip.AAC2.0.H264-monkee +: options: -t episode + title: Show Name + year: 2015 + episode_title: Nice Title + screen_size: 1080p + streaming_service: PBS + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: SeeSo +? Show.Name.2016.Nice.Title.1080p.SESO.WEBRip.AAC2.0.x264-monkee +: options: -t episode + title: Show Name + year: 2016 + episode_title: Nice Title + screen_size: 1080p + streaming_service: SeeSo + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Discovery +? Show.Name.S01E03.Nice.Title.720p.DISC.WEBRip.AAC2.0.x264-NTb +? Show.Name.S01E03.Nice.Title.720p.Discovery.WEBRip.AAC2.0.x264-NTb +: title: Show Name + season: 1 + episode: 3 + episode_title: Nice Title + screen_size: 720p + streaming_service: Discovery + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: NTb + type: episode + +# Streaming service: BBC iPlayer +? Show.Name.2016.08.18.Nice.Title.720p.iP.WEBRip.AAC2.0.H.264-monkee +? Show.Name.2016.08.18.Nice.Title.720p.BBCiPlayer.WEBRip.AAC2.0.H.264-monkee +: title: Show Name + date: 2016-08-18 + episode_title: Nice Title + streaming_service: BBC iPlayer + screen_size: 720p + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: A&E +? Show.Name.S15E18.Nice.Title.720p.AE.WEBRip.AAC2.0.H.264-monkee +? Show.Name.S15E18.Nice.Title.720p.A&E.WEBRip.AAC2.0.H.264-monkee +: title: Show Name + season: 15 + episode: 18 + episode_title: Nice Title + screen_size: 720p + streaming_service: A&E + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Adult Swim +? Show.Name.S04E01.Nice.Title.1080p.AS.WEBRip.AAC2.0.H.264-monkee +? Show.Name.S04E01.Nice.Title.1080p.AdultSwim.WEBRip.AAC2.0.H.264-monkee +: title: Show Name + season: 4 + episode: 1 + episode_title: Nice Title + screen_size: 1080p + streaming_service: Adult Swim + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Netflix +? Show.Name.2013.S02E03.1080p.NF.WEBRip.DD5.1.x264-NTb.mkv +: title: Show Name + year: 2013 + season: 2 + episode: 3 + screen_size: 1080p + streaming_service: Netflix + source: Web + other: Rip + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: NTb + container: mkv + type: episode + +# Streaming service: CBS +? Show.Name.2016.05.10.Nice.Title.720p.CBS.WEBRip.AAC2.0.x264-monkee +: title: Show Name + date: 2016-05-10 + episode_title: Nice Title + screen_size: 720p + streaming_service: CBS + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: NBA TV +? NBA.2016.02.27.Team.A.vs.Team.B.720p.NBA.WEBRip.AAC2.0.H.264-monkee +? NBA.2016.02.27.Team.A.vs.Team.B.720p.NBATV.WEBRip.AAC2.0.H.264-monkee +: title: NBA + date: 2016-02-27 + episode_title: Team A vs Team B + screen_size: 720p + streaming_service: NBA TV + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: ePix +? Show.Name.S05E04.Nice.Title.Part4.720p.EPIX.WEBRip.AAC2.0.H.264-monkee +? Show.Name.S05E04.Nice.Title.Part4.720p.ePix.WEBRip.AAC2.0.H.264-monkee +: title: Show Name + season: 5 + episode: 4 + episode_title: Nice Title + part: 4 + screen_size: 720p + streaming_service: ePix + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: NBC +? Show.Name.S41E03.Nice.Title.720p.NBC.WEBRip.AAC2.0.x264-monkee +: title: Show Name + season: 41 + episode: 3 + episode_title: Nice Title + screen_size: 720p + streaming_service: NBC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Syfy +? Show.Name.S01E02.Nice.Title.720p.SYFY.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.Syfy.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: Syfy + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: Spike TV +? Show.Name.S01E02.Nice.Title.720p.SPKE.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.Spike TV.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.SpikeTV.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: Spike TV + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: IFC +? Show.Name.S01E02.Nice.Title.720p.IFC.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: IFC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: NATG +? Show.Name.S01E02.Nice.Title.720p.NATG.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.NationalGeographic.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: National Geographic + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: NFL +? Show.Name.S01E02.Nice.Title.720p.NFL.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: NFL + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: UFC +? Show.Name.S01E02.Nice.Title.720p.UFC.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: UFC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: TV Land +? Show.Name.S01E02.Nice.Title.720p.TVL.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.TVLand.WEBRip.AAC2.0.x264-group +? Show.Name.S01E02.Nice.Title.720p.TV Land.WEBRip.AAC2.0.x264-group +: title: Show Name + season: 1 + episode: 2 + episode_title: Nice Title + screen_size: 720p + streaming_service: TV Land + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: group + type: episode + +# Streaming service: Crunchy Roll +? Show.Name.S01.1080p.CR.WEBRip.AAC.2.0.x264-monkee +: title: Show Name + season: 1 + screen_size: 1080p + streaming_service: Crunchy Roll + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Disney +? Show.Name.S01.1080p.DSNY.WEBRip.AAC.2.0.x264-monkee +? Show.Name.S01.1080p.Disney.WEBRip.AAC.2.0.x264-monkee +: title: Show Name + season: 1 + screen_size: 1080p + streaming_service: Disney + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: Nickelodeon +? Show.Name.S01.1080p.NICK.WEBRip.AAC.2.0.x264-monkee +? Show.Name.S01.1080p.Nickelodeon.WEBRip.AAC.2.0.x264-monkee +: title: Show Name + season: 1 + screen_size: 1080p + streaming_service: Nickelodeon + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: TFou +? Show.Name.S01.1080p.TFOU.WEBRip.AAC.2.0.x264-monkee +? Show.Name.S01.1080p.TFou.WEBRip.AAC.2.0.x264-monkee +: title: Show Name + season: 1 + screen_size: 1080p + streaming_service: TFou + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: monkee + type: episode + +# Streaming service: DIY Network +? Show.Name.S01.720p.DIY.WEBRip.AAC2.0.H.264-BTN +: title: Show Name + season: 1 + screen_size: 720p + streaming_service: DIY Network + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTN + type: episode + +# Streaming service: USA Network +? Show.Name.S01E02.Exfil.1080p.USAN.WEBRip.AAC2.0.x264-AJP69 +: title: Show Name + season: 1 + episode: 2 + screen_size: 1080p + streaming_service: USA Network + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: AJP69 + type: episode + +# Streaming service: TV3 Ireland +? Show.Name.S01E08.576p.TV3.WEBRip.AAC2.0.x264-HARiKEN +: title: Show Name + season: 1 + episode: 8 + screen_size: 576p + streaming_service: TV3 Ireland + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: HARiKEN + type: episode + +# Streaming service: TV4 Sweeden +? Show.Name.S05.720p.TV4.WEBRip.AAC2.0.H.264-BTW +: title: Show Name + season: 5 + screen_size: 720p + streaming_service: TV4 Sweeden + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTW + type: episode + +# Streaming service: TLC +? Show.Name.S02.720p.TLC.WEBRip.AAC2.0.x264-BTW +: title: Show Name + season: 2 + screen_size: 720p + streaming_service: TLC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTW + type: episode + +# Streaming service: Investigation Discovery +? Show.Name.S01E01.720p.ID.WEBRip.AAC2.0.x264-BTW +: title: Show Name + season: 1 + episode: 1 + screen_size: 720p + streaming_service: Investigation Discovery + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: BTW + type: episode + +# Streaming service: RTÉ One +? Show.Name.S10E01.576p.RTE.WEBRip.AAC2.0.H.264-RTN +: title: Show Name + season: 10 + episode: 1 + screen_size: 576p + streaming_service: RTÉ One + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: RTN + type: episode + +# Streaming service: AMC +? Show.Name.S01E01.1080p.AMC.WEBRip.H.264.AAC2.0-CasStudio +: title: Show Name + season: 1 + episode: 1 + screen_size: 1080p + streaming_service: AMC + source: Web + other: Rip + audio_codec: AAC + audio_channels: '2.0' + video_codec: H.264 + release_group: CasStudio + type: episode + +? Suits.S07E01.1080p.iT.WEB-DL.DD5.1.H.264-VLAD.mkv +? Suits.S07E01.1080p.iTunes.WEB-DL.DD5.1.H.264-VLAD.mkv +: title: Suits + season: 7 + episode: 1 + screen_size: 1080p + source: Web + streaming_service: iTunes + audio_codec: Dolby Digital + audio_channels: '5.1' + video_codec: H.264 + release_group: VLAD + container: mkv + type: episode + +? UpFront.S01.720p.AJAZ.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + season: 1 + source: Web + streaming_service: Al Jazeera English + title: UpFront + type: episode + video_codec: H.264 + +? Smack.The.Pony.S01.4OD.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + season: 1 + source: Web + streaming_service: Channel 4 + title: Smack The Pony + type: episode + video_codec: H.264 + +? The.Toy.Box.S01E01.720p.AMBC.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: BTN + screen_size: 720p + season: 1 + source: Web + streaming_service: ABC + title: The Toy Box + type: episode + video_codec: H.264 + +? Gundam.Reconguista.in.G.S01.720p.ANLB.WEBRip.AAC2.0.x264-HorribleSubs +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: HorribleSubs + screen_size: 720p + season: 1 + source: Web + streaming_service: AnimeLab + title: Gundam Reconguista in G + type: episode + video_codec: H.264 + +? Animal.Nation.with.Anthony.Anderson.S01E01.1080p.ANPL.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: RTN + screen_size: 1080p + season: 1 + source: Web + streaming_service: Animal Planet + title: Animal Nation with Anthony Anderson + type: episode + video_codec: H.264 + +? Park.Bench.S01.1080p.AOL.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 1080p + season: 1 + source: Web + streaming_service: AOL + title: Park Bench + type: episode + video_codec: H.264 + +? Crime.Scene.Cleaner.S05.720p.ARD.WEBRip.AAC2.0.H.264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + screen_size: 720p + season: 5 + source: Web + streaming_service: ARD + title: Crime Scene Cleaner + type: episode + video_codec: H.264 + +? Decker.S03.720p.AS.WEB-DL.AAC2.0.H.264-RTN +: audio_channels: '2.0' + audio_codec: AAC + release_group: RTN + screen_size: 720p + season: 3 + source: Web + streaming_service: Adult Swim + title: Decker + type: episode + video_codec: H.264 + +? Southern.Charm.Savannah.S01E04.Hurricane.On.The.Horizon.1080p.BRAV.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + episode: 4 + episode_title: Hurricane On The Horizon + other: Rip + release_group: BTW + screen_size: 1080p + season: 1 + source: Web + streaming_service: BravoTV + title: Southern Charm Savannah + type: episode + video_codec: H.264 + +? Four.in.the.Morning.S01E01.Pig.RERip.720p.CBC.WEBRip.AAC2.0.H.264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + episode_title: Pig + other: + - Proper + - Rip + proper_count: 1 + release_group: RTN + screen_size: 720p + season: 1 + source: Web + streaming_service: CBC + title: Four in the Morning + type: episode + video_codec: H.264 + +? Rio.Olympics.2016.08.07.Mens.Football.Group.C.Germany.vs.South.Korea.720p.CBC.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + date: 2016-08-07 + episode_title: Mens Football Group C Germany vs South Korea + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: CBC + title: Rio Olympics + type: episode + video_codec: H.264 + +? Comedians.In.Cars.Getting.Coffee.S01.720p.CCGC.WEBRip.AAC2.0.x264-monkee +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: monkee + screen_size: 720p + season: 1 + source: Web + streaming_service: Comedians in Cars Getting Coffee + title: Comedians In Cars Getting Coffee + type: episode + video_codec: H.264 + +? Life.on.Top.S02.720p.CMAX.WEBRip.AAC2.0.x264-CMAX +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: CMAX + screen_size: 720p + season: 2 + source: Web + streaming_service: Cinemax + title: Life on Top + type: episode + video_codec: H.264 + +? Sun.Records.S01.720p.CMT.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + season: 1 + source: Web + streaming_service: Country Music Television + title: Sun Records + type: episode + video_codec: H.264 + +? Infinity.Train.S01E00.Pilot.REPACK.720p.CN.WEBRip.AAC2.0.H.264-monkee +: audio_channels: '2.0' + audio_codec: AAC + episode: 0 + episode_details: Pilot + episode_title: Pilot + language: zh + other: + - Proper + - Rip + proper_count: 1 + release_group: monkee + screen_size: 720p + season: 1 + source: Web + streaming_service: Cartoon Network + title: Infinity Train + type: episode + video_codec: H.264 + +? Jay.Lenos.Garage.2015.S03E02.1080p.CNBC.WEB-DL.x264-TOPKEK +: episode: 2 + release_group: TOPKEK + screen_size: 1080p + season: 3 + source: Web + streaming_service: CNBC + title: Jay Lenos Garage + type: episode + video_codec: H.264 + year: 2015 + +? US.Presidential.Debates.2015.10.28.Third.Republican.Debate.720p.CNBC.WEBRip.AAC2.0.H.264-monkee +: audio_channels: '2.0' + audio_codec: AAC + country: US + date: 2015-10-28 + episode_title: Third Republican Debate + other: Rip + release_group: monkee + screen_size: 720p + source: Web + streaming_service: CNBC + title: Presidential Debates + type: episode + video_codec: H.264 + +? What.The.Fuck.France.S01E01.Le.doublage.CNLP.WEBRip.AAC2.0.x264-TURTLE +: audio_channels: '2.0' + audio_codec: AAC + country: FR + episode: 1 + episode_title: Le doublage + other: Rip + release_group: TURTLE + season: 1 + source: Web + streaming_service: Canal+ + title: What The Fuck + type: episode + video_codec: H.264 + +? SuperMansion.S02.720p.CRKL.WEBRip.AAC2.0.x264-VLAD +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: VLAD + screen_size: 720p + season: 2 + source: Web + streaming_service: Crackle + title: SuperMansion + type: episode + video_codec: H.264 + +? Chosen.S02.1080p.CRKL.WEBRip.AAC2.0.x264-AJP69 +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: AJP69 + screen_size: 1080p + season: 2 + source: Web + streaming_service: Crackle + title: Chosen + type: episode + video_codec: H.264 + +? Chosen.S03.1080p.CRKL.WEBRip.AAC2.0.x264-AJP69 +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: AJP69 + screen_size: 1080p + season: 3 + source: Web + streaming_service: Crackle + title: Chosen + type: episode + video_codec: H.264 + +? Snatch.S01.1080p.CRKL.WEBRip.AAC2.0.x264-DEFLATE +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: DEFLATE + screen_size: 1080p + season: 1 + source: Web + streaming_service: Crackle + title: Snatch + type: episode + video_codec: H.264 + +? White.House.Correspondents.Dinner.2015.Complete.CSPN.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: + - Complete + - Rip + release_group: BTW + source: Web + streaming_service: CSpan + title: White House Correspondents Dinner + type: movie + video_codec: H.264 + year: 2015 + +? The.Amazing.Race.Canada.S03.720p.CTV.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + country: CA + other: Rip + release_group: BTW + screen_size: 720p + season: 3 + source: Web + streaming_service: CTV + title: The Amazing Race + type: episode + video_codec: H.264 + +? Miniverse.S01E01.Explore.the.Solar.System.2160p.CUR.WEB-DL.DDP2.0.x264-monkee +: audio_channels: '2.0' + audio_codec: Dolby Digital Plus + episode: 1 + episode_title: Explore the Solar System + release_group: monkee + screen_size: 2160p + season: 1 + source: Web + streaming_service: CuriosityStream + title: Miniverse + type: episode + video_codec: H.264 + +? Vixen.S02.720p.CWS.WEBRip.AAC2.0.x264-BMF +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BMF + screen_size: 720p + season: 2 + source: Web + streaming_service: CWSeed + title: Vixen + type: episode + video_codec: H.264 + +? Abidin.Dino.DDY.WEBRip.AAC2.0.H.264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + source: Web + streaming_service: Digiturk Diledigin Yerde + title: Abidin Dino + type: movie + video_codec: H.264 + +? Fast.N.Loud.S08.1080p.DISC.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + screen_size: 1080p + season: 8 + source: Web + streaming_service: Discovery + title: Fast N Loud + type: episode + video_codec: H.264 + +? Bake.Off.Italia.S04.1080p.DPLY.WEBRip.AAC2.0.x264-Threshold +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: Threshold + screen_size: 1080p + season: 4 + source: Web + streaming_service: DPlay + title: Bake Off Italia + type: episode + video_codec: H.264 + +? Long.Riders.S01.DSKI.WEBRip.AAC2.0.x264-HorribleSubs +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: HorribleSubs + season: 1 + source: Web + streaming_service: Daisuki + title: Long Riders + type: episode + video_codec: H.264 + +? Milo.Murphys.Law.S01.720p.DSNY.WEB-DL.AAC2.0.x264-TVSmash +: audio_channels: '2.0' + audio_codec: AAC + release_group: TVSmash + screen_size: 720p + season: 1 + source: Web + streaming_service: Disney + title: Milo Murphys Law + type: episode + video_codec: H.264 + +? 30.for.30.S03E15.Doc.and.Darryl.720p.ESPN.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + episode: 15 + episode_title: Doc and Darryl + other: Rip + release_group: BTW + screen_size: 720p + season: 3 + source: Web + streaming_service: ESPN + title: 30 for 30 + type: episode + video_codec: H.264 + +? Boundless.S03.720p.ESQ.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + screen_size: 720p + season: 3 + source: Web + streaming_service: Esquire + title: Boundless + type: episode + video_codec: H.264 + +? Periodismo.Para.Todos.S2016E01.720p.ETTV.WEBRip.AAC2.0.H.264-braggart74 +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: braggart74 + screen_size: 720p + season: 2016 + source: Web + streaming_service: El Trece + title: Periodismo Para Todos + type: episode + video_codec: H.264 + year: 2016 + +? Just.Jillian.S01E01.1080p.ETV.WEBRip.AAC2.0.x264-GoApe +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: GoApe + screen_size: 1080p + season: 1 + source: Web + streaming_service: E! + title: Just Jillian + type: episode + video_codec: H.264 + +? New.Money.S01.1080p.ETV.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 1080p + season: 1 + source: Web + streaming_service: E! + title: New Money + type: episode + video_codec: H.264 + +? Gaming.Show.In.My.Parents.Garage.S02E01.The.Power.Up1000.FAM.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + episode_title: The Power Up1000 + other: Rip + release_group: RTN + season: 2 + source: Web + streaming_service: Family + title: Gaming Show In My Parents Garage + type: episode + video_codec: H.264 + +? Little.People.2016.S01E03.Proud.to.Be.You.and.Me.720p.FJR.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 3 + episode_title: Proud to Be You and Me + other: Rip + release_group: RTN + screen_size: 720p + season: 1 + source: Web + streaming_service: Family Jr + title: Little People + type: episode + video_codec: H.264 + year: 2016 + +? The.Pioneer.Woman.S00E08.Summer.Summer.Summer.720p.FOOD.WEB-DL.AAC2.0.x264-AJP69 +: audio_channels: '2.0' + audio_codec: AAC + episode: 8 + episode_title: Summer Summer Summer + release_group: AJP69 + screen_size: 720p + season: 0 + source: Web + streaming_service: Food Network + title: The Pioneer Woman + type: episode + video_codec: H.264 + +? Prata.da.Casa.S01E01.720p.FOX.WEBRip.AAC2.0.H.264-BARRY +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: BARRY + screen_size: 720p + season: 1 + source: Web + streaming_service: Fox + title: Prata da Casa + type: episode + video_codec: H.264 + +? Grandfathered.S01.720p.FOX.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + season: 1 + source: Web + streaming_service: Fox + title: Grandfathered + type: episode + video_codec: H.264 + +? Truth.and.Iliza.S01E01.FREE.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: BTN + season: 1 + source: Web + streaming_service: Freeform + title: Truth and Iliza + type: episode + video_codec: H.264 + +? Seven.Year.Switch.S01.720p.FYI.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + season: 1 + source: Web + streaming_service: FYI Network + title: Seven Year Switch + type: episode + video_codec: H.264 + +? NHL.2015.10.09.Leafs.vs.Red.Wings.Condensed.Game.720p.Away.Feed.GC.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + date: 2015-10-09 + episode_title: Leafs vs Red Wings Condensed Game + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: NHL GameCenter + title: NHL + type: episode + video_codec: H.264 + +? NHL.2016.01.26.Maple.Leafs.vs.Panthers.720p.Home.Feed.GC.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + date: 2016-01-26 + episode_title: Maple Leafs vs Panthers + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: NHL GameCenter + title: NHL + type: episode + video_codec: H.264 + +? Big.Brother.Canada.S05.GLBL.WEBRip.AAC2.0.H.264-RTN +: audio_channels: '2.0' + audio_codec: AAC + country: CA + other: Rip + release_group: RTN + season: 5 + source: Web + streaming_service: Global + title: Big Brother + type: episode + video_codec: H.264 + +? Pornolandia.S01.720p.GLOB.WEBRip.AAC2.0.x264-GeneX +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: GeneX + screen_size: 720p + season: 1 + source: Web + streaming_service: GloboSat Play + title: Pornolandia + type: episode + video_codec: H.264 + +? Transando.com.Laerte.S01.720p.GLOB.WEBRip.AAC2.0.x264-GeneX +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: GeneX + screen_size: 720p + season: 1 + source: Web + streaming_service: GloboSat Play + title: Transando com Laerte + type: episode + video_codec: H.264 + +? Flip.or.Flop.S01.720p.HGTV.WEBRip.AAC2.0.H.264-AJP69 +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: AJP69 + screen_size: 720p + season: 1 + source: Web + streaming_service: HGTV + title: Flip or Flop + type: episode + video_codec: H.264 + +? Kitten.Bowl.2014.720p.HLMK.WEBRip.AAC2.0.x264-monkee +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: monkee + screen_size: 720p + source: Web + streaming_service: Hallmark + title: Kitten Bowl + type: movie + video_codec: H.264 + year: 2014 + +? Still.Star-Crossed.S01E05.720p.HULU.WEB-DL.AAC2.0.H.264-VLAD +: audio_channels: '2.0' + audio_codec: AAC + episode: 5 + release_group: VLAD + screen_size: 720p + season: 1 + source: Web + streaming_service: Hulu + title: Still Star-Crossed + type: episode + video_codec: H.264 + +? EastEnders.2017.07.17.720p.iP.WEB-DL.AAC2.0.H.264-BTN +: audio_channels: '2.0' + audio_codec: AAC + date: 2017-07-17 + release_group: BTN + screen_size: 720p + source: Web + streaming_service: BBC iPlayer + title: EastEnders + type: episode + video_codec: H.264 + +? Handmade.in.Japan.S01E01.720p.iP.WEBRip.AAC2.0.H.264-SUP +: audio_channels: '2.0' + audio_codec: AAC + country: JP + episode: 1 + other: Rip + release_group: SUP + screen_size: 720p + season: 1 + source: Web + streaming_service: BBC iPlayer + title: Handmade in + type: episode + video_codec: H.264 + +? The.Chillenden.Murders.S01.720p.iP.WEBRip.AAC2.0.H.264-HAX +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: HAX + screen_size: 720p + season: 1 + source: Web + streaming_service: BBC iPlayer + title: The Chillenden Murders + type: episode + video_codec: H.264 + +? The.Street.S01.ITV.WEB-DL.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + release_group: RTN + season: 1 + source: Web + streaming_service: ITV + title: The Street + type: episode + video_codec: H.264 + +? Hope.for.Wildlife.S04.1080p.KNOW.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 1080p + season: 4 + source: Web + streaming_service: Knowledge Network + title: Hope for Wildlife + type: episode + video_codec: H.264 + +? Kim.of.Queens.S02.720p.LIFE.WEBRip.AAC2.0.H.264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + screen_size: 720p + season: 2 + source: Web + streaming_service: Lifetime + title: Kim of Queens + type: episode + video_codec: H.264 + +? The.Rachel.Maddow.Show.2017.02.22.720p.MNBC.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + date: 2017-02-22 + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: MSNBC + title: The Rachel Maddow Show + type: episode + video_codec: H.264 + +? Ignition.S06E12.720p.MTOD.WEB-DL.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 12 + release_group: RTN + screen_size: 720p + season: 6 + source: Web + streaming_service: Motor Trend OnDemand + title: Ignition + type: episode + video_codec: H.264 + +? Teen.Mom.UK.S01E01.Life.as.a.Teen.Mum.1080p.MTV.WEB-DL.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + country: GB + episode: 1 + episode_title: Life as a Teen Mum + release_group: BTW + screen_size: 1080p + season: 1 + source: Web + streaming_service: MTV + title: Teen Mom + type: episode + video_codec: H.264 + +? Undrafted.S01.720p.NFLN.WEBRip.AAC2.0.H.264-TTYL +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: TTYL + screen_size: 720p + season: 1 + source: Web + streaming_service: NFL Now + title: Undrafted + type: episode + video_codec: H.264 + +? NFL.2016.08.25.PreSeason.Cowboys.vs.Seahawks.720p.NFL.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + date: 2016-08-25 + episode_title: PreSeason Cowboys vs Seahawks + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: NFL + title: NFL + type: episode + video_codec: H.264 + +? Bunsen.is.a.Beast.S01E23.Guinea.Some.Lovin.1080p.NICK.WEBRip.AAC2.0.x264-TVSmash +: audio_channels: '2.0' + audio_codec: AAC + country: GN + episode: 23 + episode_title: Some Lovin + other: Rip + release_group: TVSmash + screen_size: 1080p + season: 1 + source: Web + streaming_service: Nickelodeon + title: Bunsen is a Beast + type: episode + video_codec: H.264 + +? Valkyrie.S01.720p.NRK.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + screen_size: 720p + season: 1 + source: Web + streaming_service: Norsk Rikskringkasting + title: Valkyrie + type: episode + video_codec: H.264 + +? Food.Forward.S01.720p.PBS.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + screen_size: 720p + season: 1 + source: Web + streaming_service: PBS + title: Food Forward + type: episode + video_codec: H.264 + +? SciGirls.S01E01.Turtle.Mania.720p.PBSK.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + episode_title: Turtle Mania + other: Rip + release_group: RTN + screen_size: 720p + season: 1 + source: Web + streaming_service: PBS Kids + title: SciGirls + type: episode + video_codec: H.264 + +? Powers.2015.S01.1080p.PSN.WEBRip.DD5.1.x264-NTb +: audio_channels: '5.1' + audio_codec: Dolby Digital + other: Rip + release_group: NTb + screen_size: 1080p + season: 1 + source: Web + streaming_service: Playstation Network + title: Powers + type: episode + video_codec: H.264 + year: 2015 + +? Escape.The.Night.S02E02.The.Masquerade.Part.II.1080p.RED.WEBRip.AAC5.1.VP9-BTW +: audio_channels: '5.1' + audio_codec: AAC + episode: 2 + episode_title: The Masquerade + other: Rip + part: 2 + release_group: VP9-BTW + screen_size: 1080p + season: 2 + source: Web + streaming_service: YouTube Red + title: Escape The Night + type: episode + +? Escape.The.Night.S02E02.The.Masquerade.Part.II.2160p.RED.WEBRip.AAC5.1.VP9-BTW +: audio_channels: '5.1' + audio_codec: AAC + episode: 2 + episode_title: The Masquerade + other: Rip + part: 2 + release_group: VP9-BTW + screen_size: 2160p + season: 2 + source: Web + streaming_service: YouTube Red + title: Escape The Night + type: episode + +? Escape.The.Night.S02E02.The.Masquerade.Part.II.720p.RED.WEBRip.AAC5.1.VP9-BTW +: audio_channels: '5.1' + audio_codec: AAC + episode: 2 + episode_title: The Masquerade + other: Rip + part: 2 + release_group: VP9-BTW + screen_size: 720p + season: 2 + source: Web + streaming_service: YouTube Red + title: Escape The Night + type: episode + +? The.Family.Law.S02E01.720p.SBS.WEB-DL.AAC2.0.H.264-BTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + release_group: BTN + screen_size: 720p + season: 2 + source: Web + streaming_service: SBS (AU) + title: The Family Law + type: episode + video_codec: H.264 + +? Theres.No.Joy.In.Beachville.The.True.Story.of.Baseballs.Origin.720p.SNET.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + source: Web + streaming_service: Sportsnet + title: Theres No Joy In Beachville The True Story of Baseballs Origin + type: movie + video_codec: H.264 + +? One.Night.Only.Alec.Baldwin.720p.SPIK.WEB-DL.AAC2.0.x264-NOGRP +: audio_channels: '2.0' + audio_codec: AAC + release_group: NOGRP + screen_size: 720p + source: Web + streaming_service: Spike + title: One Night Only Alec Baldwin + type: movie + video_codec: H.264 + +? Ink.Master.S08.720p.SPIK.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 720p + season: 8 + source: Web + streaming_service: Spike + title: Ink Master + type: episode + video_codec: H.264 + +? Jungle.Bunch.S01E01.Deep.Chasm.1080p.SPRT.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + episode_title: Deep Chasm + other: Rip + release_group: RTN + screen_size: 1080p + season: 1 + source: Web + streaming_service: Sprout + title: Jungle Bunch + type: episode + video_codec: H.264 + +? Ash.vs.Evil.Dead.S01.720p.STZ.WEBRip.AAC2.0.x264-NTb +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: NTb + screen_size: 720p + season: 1 + source: Web + streaming_service: Starz + title: Ash vs Evil Dead + type: episode + video_codec: H.264 + +? WWE.Swerved.S01.720p.WWEN.WEBRip.AAC2.0.H.264-PPKORE +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: PPKORE + screen_size: 720p + season: 1 + source: Web + streaming_service: WWE Network + title: WWE Swerved + type: episode + video_codec: H.264 + +? Face.Off.S11.1080p.SYFY.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + screen_size: 1080p + season: 11 + source: Web + streaming_service: Syfy + title: Face Off + type: episode + video_codec: H.264 + +? Conan.2016.09.22.Jeff.Garlin.720p.TBS.WEBRip.AAC2.0.H.264-NOGRP +: audio_channels: '2.0' + audio_codec: AAC + date: 2016-09-22 + episode_title: Jeff Garlin + other: Rip + release_group: NOGRP + screen_size: 720p + source: Web + streaming_service: TBS + title: Conan + type: episode + video_codec: H.264 + +? Swans.Crossing.S01.TUBI.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + season: 1 + source: Web + streaming_service: TubiTV + title: Swans Crossing + type: episode + video_codec: H.264 + +? The.Joy.of.Techs.S01.UKTV.WEB-DL.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + release_group: RTN + season: 1 + source: Web + streaming_service: UKTV + title: The Joy of Techs + type: episode + video_codec: H.264 + +? Rock.Icons.S01.720p.VH1.WEB-DL.AAC2.0.H.264-RTN +: audio_channels: '2.0' + audio_codec: AAC + release_group: RTN + screen_size: 720p + season: 1 + source: Web + streaming_service: VH1 + title: Rock Icons + type: episode + video_codec: H.264 + +? Desus.and.Mero.S01E130.2017.07.18.1080p.VICE.WEB-DL.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + date: 2017-07-18 + episode: 130 + release_group: RTN + screen_size: 1080p + season: 1 + source: Web + streaming_service: Viceland + title: Desus and Mero + type: episode + video_codec: H.264 + +? Graveyard.Carz.S07.1080p.VLCT.WEBRip.AAC2.0.x264-RTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: RTN + screen_size: 1080p + season: 7 + source: Web + streaming_service: Velocity + title: Graveyard Carz + type: episode + video_codec: H.264 + +? Other.Space.S01E01.1080p.YHOO.WEBRip.AAC2.0.x264-BTW +: audio_channels: '2.0' + audio_codec: AAC + episode: 1 + other: Rip + release_group: BTW + screen_size: 1080p + season: 1 + source: Web + streaming_service: Yahoo + title: Other Space + type: episode + video_codec: H.264 + +? Americas.Test.Kitchen.S17.720p.ATK.WEB-DL.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + release_group: BTN + screen_size: 720p + season: 17 + source: Web + streaming_service: America's Test Kitchen + title: Americas Test Kitchen + type: episode + video_codec: H.264 + +? Bushwhacked.Bugs.S01.AUBC.WEBRip.AAC2.0.H.264-DAWN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: DAWN + season: 1 + source: Web + streaming_service: ABC Australia + title: Bushwhacked Bugs + type: episode + video_codec: H.264 + +? VICE.S05E12.1080p.HBO.WEB-DL.AAC2.0.H.264-monkee +? VICE.S05E12.1080p.HBO-Go.WEB-DL.AAC2.0.H.264-monkee +? VICE.S05E12.1080p.HBOGo.WEB-DL.AAC2.0.H.264-monkee +: audio_channels: '2.0' + audio_codec: AAC + episode: 12 + release_group: monkee + screen_size: 1080p + season: 5 + source: Web + streaming_service: HBO Go + title: VICE + type: episode + video_codec: H.264 + +? Dix.Pour.Cent.S02.PLUZ.WEBRip.AAC2.0.H.264-TURTLE +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: TURTLE + season: 2 + source: Web + streaming_service: Pluzz + title: Dix Pour Cent + type: episode + video_codec: H.264 + +? Ulveson.och.Herngren.S01.720p.SVT.WEBRip.AAC2.0.H.264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + screen_size: 720p + season: 1 + source: Web + streaming_service: Sveriges Television + title: Ulveson och Herngren + type: episode + video_codec: H.264 + +? Bravest.Warriors.S03.1080p.VRV.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + screen_size: 1080p + season: 3 + source: Web + streaming_service: VRV + title: Bravest Warriors + type: episode + video_codec: H.264 + +? The.Late.Night.Big.Breakfast.S02.WME.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + season: 2 + source: Web + streaming_service: WatchMe + title: The Late Night Big Breakfast + type: episode + video_codec: H.264 + +? Hockey.Wives.S02.WNET.WEBRip.AAC2.0.H.264-BTW +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTW + season: 2 + source: Web + streaming_service: W Network + title: Hockey Wives + type: episode + video_codec: H.264 + +? Sin.City.Saints.S01.1080p.YHOO.WEBRip.AAC2.0.x264-NTb +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: NTb + screen_size: 1080p + season: 1 + source: Web + streaming_service: Yahoo + title: Sin City Saints + type: episode + video_codec: H.264 + +? 555.S01.1080p.VMEO.WEBRip.AAC2.0.x264-BTN +: audio_channels: '2.0' + audio_codec: AAC + other: Rip + release_group: BTN + screen_size: 1080p + season: 1 + source: Web + streaming_service: Vimeo + title: '555' + type: episode + video_codec: H.264 + +# All this below shouldn't match any streaming services +? London.2012.Olympics.CTV.Preview.Show.HDTV.x264-2HD +: alternative_title: Olympics CTV Preview Show + release_group: 2HD + source: HDTV + title: London + type: movie + video_codec: H.264 + year: 2012 + +? UFC.on.FOX.24.1080p.HDTV.x264-VERUM +: episode: 24 + release_group: VERUM + screen_size: 1080p + source: HDTV + title: UFC on FOX + type: episode + video_codec: H.264 + +? ESPN.E.60.2016.10.04.HDTV.x264-LoTV +: date: 2016-10-04 + episode: 60 + release_group: LoTV + source: HDTV + title: ESPN E + type: episode + video_codec: H.264 + +? GTTV.E3.All.Access.Live.Day.1.Xbox.Showcase.Preshow.HDTV.x264-SYS +: episode: 3 + episode_title: All Access Live Day 1 Xbox Showcase Preshow + release_group: SYS + source: HDTV + title: GTTV + type: episode + video_codec: H.264 diff --git a/libs/guessit/test/suggested.json b/libs/guessit/test/suggested.json new file mode 100644 index 000000000..dc838ad01 --- /dev/null +++ b/libs/guessit/test/suggested.json @@ -0,0 +1,21 @@ +{ + "titles": [ + "13 Reasons Why", + "Star Wars: Episode VII - The Force Awakens", + "3%", + "The 100", + "3 Percent", + "This is Us", + "Open Season 2", + "Game of Thrones", + "The X-Files", + "11.22.63" + ], + "suggested": [ + "13 Reasons Why", + "Star Wars: Episode VII - The Force Awakens", + "The 100", + "Open Season 2", + "11.22.63" + ] +}
\ No newline at end of file diff --git a/libs/guessit/test/test_api.py b/libs/guessit/test/test_api.py index ca33df044..391dbced8 100644 --- a/libs/guessit/test/test_api.py +++ b/libs/guessit/test/test_api.py @@ -1,13 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement - +import json import os +import sys import pytest import six -from ..api import guessit, properties, GuessitException +from ..api import guessit, properties, suggested_expected, GuessitException __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -27,6 +28,18 @@ def test_forced_binary(): assert ret and 'title' in ret and isinstance(ret['title'], six.binary_type) [email protected](sys.version_info < (3, 4), reason="Path is not available") +def test_pathlike_object(): + try: + from pathlib import Path + + path = Path('Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') + ret = guessit(path) + assert ret and 'title' in ret + except ImportError: # pragma: no-cover + pass + + def test_unicode_japanese(): ret = guessit('[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi') assert ret and 'title' in ret @@ -61,3 +74,10 @@ def test_exception(): assert "An internal error has occured in guessit" in str(excinfo.value) assert "Guessit Exception Report" in str(excinfo.value) assert "Please report at https://github.com/guessit-io/guessit/issues" in str(excinfo.value) + + +def test_suggested_expected(): + with open(os.path.join(__location__, 'suggested.json'), 'r') as f: + content = json.load(f) + actual = suggested_expected(content['titles']) + assert actual == content['suggested'] diff --git a/libs/guessit/test/test_api_unicode_literals.py b/libs/guessit/test/test_api_unicode_literals.py index 3347a7d89..826f7cd16 100644 --- a/libs/guessit/test/test_api_unicode_literals.py +++ b/libs/guessit/test/test_api_unicode_literals.py @@ -53,6 +53,14 @@ if six.PY2: """ +def test_ensure_standard_string_class(): + class CustomStr(str): + pass + + ret = guessit(CustomStr('1080p'), options={'advanced': True}) + assert ret and 'screen_size' in ret and not isinstance(ret['screen_size'].input_string, CustomStr) + + def test_properties(): props = properties() assert 'video_codec' in props.keys() diff --git a/libs/guessit/test/test_options.py b/libs/guessit/test/test_options.py index 837497855..4f019b34a 100644 --- a/libs/guessit/test/test_options.py +++ b/libs/guessit/test/test_options.py @@ -5,7 +5,7 @@ import os import pytest -from ..options import get_config_file_locations, merge_configurations, load_config_file, ConfigurationException, \ +from ..options import get_options_file_locations, merge_options, load_config_file, ConfigurationException, \ load_config __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -15,7 +15,7 @@ def test_config_locations(): homedir = '/root' cwd = '/root/cwd' - locations = get_config_file_locations(homedir, cwd, True) + locations = get_options_file_locations(homedir, cwd, True) assert len(locations) == 9 assert '/root/.guessit/options.json' in locations @@ -34,12 +34,12 @@ def test_merge_configurations(): c2 = {'param1': False, 'param2': True, 'param3': False} c3 = {'param1': False, 'param2': True, 'param3': False} - merged = merge_configurations(c1, c2, c3) + merged = merge_options(c1, c2, c3) assert not merged['param1'] assert merged['param2'] assert not merged['param3'] - merged = merge_configurations(c3, c2, c1) + merged = merge_options(c3, c2, c1) assert merged['param1'] assert merged['param2'] assert not merged['param3'] @@ -50,28 +50,49 @@ def test_merge_configurations_lists(): c2 = {'param1': [2], 'param2': True, 'param3': False} c3 = {'param1': [3], 'param2': True, 'param3': False} - merged = merge_configurations(c1, c2, c3) + merged = merge_options(c1, c2, c3) assert merged['param1'] == [1, 2, 3] assert merged['param2'] assert not merged['param3'] - merged = merge_configurations(c3, c2, c1) + merged = merge_options(c3, c2, c1) assert merged['param1'] == [3, 2, 1] assert merged['param2'] assert not merged['param3'] +def test_merge_configurations_deep(): + c1 = {'param1': [1], 'param2': {'d1': [1]}, 'param3': False} + c2 = {'param1': [2], 'param2': {'d1': [2]}, 'param3': False} + c3 = {'param1': [3], 'param2': {'d3': [3]}, 'param3': False} + + merged = merge_options(c1, c2, c3) + assert merged['param1'] == [1, 2, 3] + assert merged['param2']['d1'] == [1, 2] + assert merged['param2']['d3'] == [3] + assert 'd2' not in merged['param2'] + assert not merged['param3'] + + merged = merge_options(c3, c2, c1) + assert merged['param1'] == [3, 2, 1] + assert merged['param2'] + assert merged['param2']['d1'] == [2, 1] + assert 'd2' not in merged['param2'] + assert merged['param2']['d3'] == [3] + assert not merged['param3'] + + def test_merge_configurations_pristine_all(): c1 = {'param1': [1], 'param2': True, 'param3': False} c2 = {'param1': [2], 'param2': True, 'param3': False, 'pristine': True} c3 = {'param1': [3], 'param2': True, 'param3': False} - merged = merge_configurations(c1, c2, c3) + merged = merge_options(c1, c2, c3) assert merged['param1'] == [2, 3] assert merged['param2'] assert not merged['param3'] - merged = merge_configurations(c3, c2, c1) + merged = merge_options(c3, c2, c1) assert merged['param1'] == [2, 1] assert merged['param2'] assert not merged['param3'] @@ -82,7 +103,18 @@ def test_merge_configurations_pristine_properties(): c2 = {'param1': [2], 'param2': True, 'param3': False, 'pristine': ['param2', 'param3']} c3 = {'param1': [3], 'param2': True, 'param3': False} - merged = merge_configurations(c1, c2, c3) + merged = merge_options(c1, c2, c3) + assert merged['param1'] == [1, 2, 3] + assert merged['param2'] + assert not merged['param3'] + + +def test_merge_configurations_pristine_properties_deep(): + c1 = {'param1': [1], 'param2': {'d1': False}, 'param3': True} + c2 = {'param1': [2], 'param2': {'d1': True}, 'param3': False, 'pristine': ['param2', 'param3']} + c3 = {'param1': [3], 'param2': {'d1': True}, 'param3': False} + + merged = merge_options(c1, c2, c3) assert merged['param1'] == [1, 2, 3] assert merged['param2'] assert not merged['param3'] @@ -93,7 +125,7 @@ def test_merge_configurations_pristine_properties2(): c2 = {'param1': [2], 'param2': True, 'param3': False, 'pristine': ['param1', 'param2', 'param3']} c3 = {'param1': [3], 'param2': True, 'param3': False} - merged = merge_configurations(c1, c2, c3) + merged = merge_options(c1, c2, c3) assert merged['param1'] == [2, 3] assert merged['param2'] assert not merged['param3'] @@ -119,24 +151,25 @@ def test_load_config_file(): def test_load_config(): - config = load_config({'no_embedded_config': True, 'param1': 'test', + config = load_config({'no_default_config': True, 'param1': 'test', 'config': [os.path.join(__location__, 'config', 'test.yml')]}) - assert config['param1'] == 'test' + assert not config.get('param1') + assert config.get('advanced_config') # advanced_config is still loaded from default assert config['expected_title'] == ['The 100', 'OSS 117'] assert config['yaml'] is True - config = load_config({'no_embedded_config': True, 'param1': 'test'}) + config = load_config({'no_default_config': True, 'param1': 'test'}) - assert config['param1'] == 'test' + assert not config.get('param1') assert 'expected_title' not in config assert 'yaml' not in config - config = load_config({'no_embedded_config': True, 'param1': 'test', 'config': ['false']}) + config = load_config({'no_default_config': True, 'param1': 'test', 'config': ['false']}) - assert config['param1'] == 'test' + assert not config.get('param1') assert 'expected_title' not in config assert 'yaml' not in config diff --git a/libs/guessit/test/test_yml.py b/libs/guessit/test/test_yml.py index 31aed6736..040796dea 100644 --- a/libs/guessit/test/test_yml.py +++ b/libs/guessit/test/test_yml.py @@ -2,36 +2,24 @@ # -*- coding: utf-8 -*- # pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name import logging - +import os # io.open supports encoding= in python 2.7 from io import open # pylint: disable=redefined-builtin -import os -import yaml - -import six import babelfish -import pytest - +import six # pylint:disable=wrong-import-order +import yaml # pylint:disable=wrong-import-order from rebulk.remodule import re from rebulk.utils import is_iterable -from ..options import parse_options, load_config -from ..yamlutils import OrderedDictYAMLLoader from .. import guessit - +from ..options import parse_options +from ..yamlutils import OrderedDictYAMLLoader logger = logging.getLogger(__name__) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) -filename_predicate = None -string_predicate = None - - -# filename_predicate = lambda filename: 'episode_title' in filename -# string_predicate = lambda string: '-DVD.BlablaBla.Fix.Blablabla.XVID' in string - class EntryResult(object): def __init__(self, string, negates=False): @@ -64,10 +52,10 @@ class EntryResult(object): def __repr__(self): if self.ok: return self.string + ': OK!' - elif self.warning: + if self.warning: return '%s%s: WARNING! (valid=%i, extra=%i)' % ('-' if self.negates else '', self.string, len(self.valid), len(self.extra)) - elif self.error: + if self.error: return '%s%s: ERROR! (valid=%i, missing=%i, different=%i, extra=%i, others=%i)' % \ ('-' if self.negates else '', self.string, len(self.valid), len(self.missing), len(self.different), len(self.extra), len(self.others)) @@ -136,9 +124,51 @@ class TestYml(object): Use $ marker to check inputs that should not match results. """ - options_re = re.compile(r'^([ \+-]+)(.*)') + options_re = re.compile(r'^([ +-]+)(.*)') + + def _get_unique_id(self, collection, base_id): + ret = base_id + i = 2 + while ret in collection: + suffix = "-" + str(i) + ret = base_id + suffix + i += 1 + return ret - files, ids = files_and_ids(filename_predicate) + def pytest_generate_tests(self, metafunc): + if 'yml_test_case' in metafunc.fixturenames: + entries = [] + entry_ids = [] + entry_set = set() + + for filename, _ in zip(*files_and_ids()): + with open(os.path.join(__location__, filename), 'r', encoding='utf-8') as infile: + data = yaml.load(infile, OrderedDictYAMLLoader) + + last_expected = None + for string, expected in reversed(list(data.items())): + if expected is None: + data[string] = last_expected + else: + last_expected = expected + + default = None + try: + default = data['__default__'] + del data['__default__'] + except KeyError: + pass + + for string, expected in data.items(): + TestYml.set_default(expected, default) + string = TestYml.fix_encoding(string, expected) + + entries.append((filename, string, expected)) + unique_id = self._get_unique_id(entry_set, '[' + filename + '] ' + str(string)) + entry_set.add(unique_id) + entry_ids.append(unique_id) + + metafunc.parametrize('yml_test_case', entries, ids=entry_ids) @staticmethod def set_default(expected, default): @@ -147,34 +177,8 @@ class TestYml(object): if k not in expected: expected[k] = v - @pytest.mark.parametrize('filename', files, ids=ids) - def test(self, filename, caplog): - caplog.setLevel(logging.INFO) - with open(os.path.join(__location__, filename), 'r', encoding='utf-8') as infile: - data = yaml.load(infile, OrderedDictYAMLLoader) - entries = Results() - - last_expected = None - for string, expected in reversed(list(data.items())): - if expected is None: - data[string] = last_expected - else: - last_expected = expected - - default = None - try: - default = data['__default__'] - del data['__default__'] - except KeyError: - pass - - for string, expected in data.items(): - TestYml.set_default(expected, default) - entry = self.check_data(filename, string, expected) - entries.append(entry) - entries.assert_ok() - - def check_data(self, filename, string, expected): + @classmethod + def fix_encoding(cls, string, expected): if six.PY2: if isinstance(string, six.text_type): string = string.encode('utf-8') @@ -187,16 +191,23 @@ class TestYml(object): expected[k] = v if not isinstance(string, str): string = str(string) - if not string_predicate or string_predicate(string): # pylint: disable=not-callable - entry = self.check(string, expected) - if entry.ok: - logger.debug('[' + filename + '] ' + str(entry)) - elif entry.warning: - logger.warning('[' + filename + '] ' + str(entry)) - elif entry.error: - logger.error('[' + filename + '] ' + str(entry)) - for line in entry.details: - logger.error('[' + filename + '] ' + ' ' * 4 + line) + return string + + def test_entry(self, yml_test_case): + filename, string, expected = yml_test_case + result = self.check_data(filename, string, expected) + assert not result.error + + def check_data(self, filename, string, expected): + entry = self.check(string, expected) + if entry.ok: + logger.debug('[%s] %s', filename, entry) + elif entry.warning: + logger.warning('[%s] %s', filename, entry) + elif entry.error: + logger.error('[%s] %s', filename, entry) + for line in entry.details: + logger.error('[%s] %s', filename, ' ' * 4 + line) return entry def check(self, string, expected): @@ -207,12 +218,10 @@ class TestYml(object): options = {} if not isinstance(options, dict): options = parse_options(options) - options['config'] = False - options = load_config(options) try: result = guessit(string, options) except Exception as exc: - logger.error('[' + string + '] Exception: ' + str(exc)) + logger.error('[%s] Exception: %s', string, exc) raise exc entry = EntryResult(string, negates) @@ -258,10 +267,10 @@ class TestYml(object): return False if isinstance(next(iter(values)), babelfish.Language): # pylint: disable=no-member - expecteds = set([babelfish.Language.fromguessit(expected) for expected in expecteds]) + expecteds = {babelfish.Language.fromguessit(expected) for expected in expecteds} elif isinstance(next(iter(values)), babelfish.Country): # pylint: disable=no-member - expecteds = set([babelfish.Country.fromguessit(expected) for expected in expecteds]) + expecteds = {babelfish.Country.fromguessit(expected) for expected in expecteds} return values == expecteds def check_expected(self, result, expected, entry): @@ -274,10 +283,10 @@ class TestYml(object): if negates_key: entry.valid.append((expected_key, expected_value)) else: - entry.different.append((expected_key, expected_value, result[expected_key])) + entry.different.append((expected_key, expected_value, result[result_key])) else: if negates_key: - entry.different.append((expected_key, expected_value, result[expected_key])) + entry.different.append((expected_key, expected_value, result[result_key])) else: entry.valid.append((expected_key, expected_value)) elif not negates_key: diff --git a/libs/guessit/test/various.yml b/libs/guessit/test/various.yml index 15964457e..6fb58deb6 100644 --- a/libs/guessit/test/various.yml +++ b/libs/guessit/test/various.yml @@ -3,9 +3,9 @@ title: Fear and Loathing in Las Vegas year: 1998 screen_size: 720p - format: HD-DVD + source: HD-DVD audio_codec: DTS - video_codec: h264 + video_codec: H.264 release_group: ESiR ? Series/Duckman/Duckman - 101 (01) - 20021107 - I, Duckman.avi @@ -36,8 +36,9 @@ episode_format: Minisode episode: 1 episode_title: Good Cop Bad Cop - format: WEBRip - video_codec: XviD + source: Web + other: Rip + video_codec: Xvid ? Series/Kaamelott/Kaamelott - Livre V - Ep 23 - Le Forfait.avi : type: episode @@ -50,10 +51,10 @@ title: The Doors year: 1991 date: 2008-03-09 - format: BluRay + source: Blu-ray screen_size: 720p - audio_codec: AC3 - video_codec: h264 + audio_codec: Dolby Digital + video_codec: H.264 release_group: HiS@SiLUHD language: english website: sharethefiles.com @@ -63,14 +64,15 @@ title: MASH year: 1970 video_codec: DivX - format: DVD + source: DVD + other: [Dual Audio, Rip] ? the.mentalist.501.hdtv-lol.mp4 : type: episode title: the mentalist season: 5 episode: 1 - format: HDTV + source: HDTV release_group: lol ? the.simpsons.2401.hdtv-lol.mp4 @@ -78,7 +80,7 @@ title: the simpsons season: 24 episode: 1 - format: HDTV + source: HDTV release_group: lol ? Homeland.S02E01.HDTV.x264-EVOLVE.mp4 @@ -86,8 +88,8 @@ title: Homeland season: 2 episode: 1 - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: EVOLVE ? /media/Band_of_Brothers-e01-Currahee.mkv @@ -115,7 +117,7 @@ title: new girl season: 1 episode: 17 - format: HDTV + source: HDTV release_group: lol ? The.Office.(US).1x03.Health.Care.HDTV.XviD-LOL.avi @@ -125,8 +127,8 @@ season: 1 episode: 3 episode_title: Health Care - format: HDTV - video_codec: XviD + source: HDTV + video_codec: Xvid release_group: LOL ? The_Insider-(1999)-x02-60_Minutes_Interview-1996.mp4 @@ -154,18 +156,18 @@ season: 56 episode: 6 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 ? White.House.Down.2013.1080p.BluRay.DTS-HD.MA.5.1.x264-PublicHD.mkv : type: movie title: White House Down year: 2013 screen_size: 1080p - format: BluRay - audio_codec: DTS - audio_profile: HDMA - video_codec: h264 + source: Blu-ray + audio_codec: DTS-HD + audio_profile: Master Audio + video_codec: H.264 release_group: PublicHD audio_channels: "5.1" @@ -174,10 +176,10 @@ title: White House Down year: 2013 screen_size: 1080p - format: BluRay - audio_codec: DTS - audio_profile: HDMA - video_codec: h264 + source: Blu-ray + audio_codec: DTS-HD + audio_profile: Master Audio + video_codec: H.264 release_group: PublicHD audio_channels: "5.1" @@ -188,10 +190,10 @@ season: 1 episode: 1 screen_size: 720p - format: WEB-DL + source: Web audio_channels: "5.1" - video_codec: h264 - audio_codec: AC3 + video_codec: H.264 + audio_codec: Dolby Digital release_group: NTb ? Despicable.Me.2.2013.1080p.BluRay.x264-VeDeTT.nfo @@ -199,37 +201,39 @@ title: Despicable Me 2 year: 2013 screen_size: 1080p - format: BluRay - video_codec: h264 + source: Blu-ray + video_codec: H.264 release_group: VeDeTT ? Le Cinquieme Commando 1971 SUBFORCED FRENCH DVDRiP XViD AC3 Bandix.mkv : type: movie - audio_codec: AC3 - format: DVD + audio_codec: Dolby Digital + source: DVD + other: Rip release_group: Bandix subtitle_language: French title: Le Cinquieme Commando - video_codec: XviD + video_codec: Xvid year: 1971 ? Le Seigneur des Anneaux - La Communauté de l'Anneau - Version Longue - BDRip.mkv : type: movie - format: BluRay title: Le Seigneur des Anneaux + source: Blu-ray + other: Rip ? La petite bande (Michel Deville - 1983) VF PAL MP4 x264 AAC.mkv : type: movie audio_codec: AAC language: French title: La petite bande - video_codec: h264 + video_codec: H.264 year: 1983 other: PAL ? Retour de Flammes (Gregor Schnitzler 2003) FULL DVD.iso : type: movie - format: DVD + source: DVD title: Retour de Flammes type: movie year: 2003 @@ -250,16 +254,16 @@ : type: movie year: 2014 title: A Common Title - edition: Special Edition + edition: Special ? Downton.Abbey.2013.Christmas.Special.HDTV.x264-FoV.mp4 : type: episode year: 2013 title: Downton Abbey episode_title: Christmas Special - video_codec: h264 + video_codec: H.264 release_group: FoV - format: HDTV + source: HDTV episode_details: Special ? Doctor_Who_2013_Christmas_Special.The_Time_of_The_Doctor.HD @@ -280,10 +284,10 @@ ? Robot Chicken S06-Born Again Virgin Christmas Special HDTV x264.avi : type: episode title: Robot Chicken - format: HDTV + source: HDTV season: 6 episode_title: Born Again Virgin Christmas Special - video_codec: h264 + video_codec: H.264 episode_details: Special ? Wicked.Tuna.S03E00.Head.To.Tail.Special.HDTV.x264-YesTV @@ -293,14 +297,14 @@ release_group: YesTV season: 3 episode: 0 - video_codec: h264 - format: HDTV + video_codec: H.264 + source: HDTV episode_details: Special ? The.Voice.UK.S03E12.HDTV.x264-C4TV : episode: 12 - video_codec: h264 - format: HDTV + video_codec: H.264 + source: HDTV title: The Voice release_group: C4TV season: 3 @@ -317,21 +321,21 @@ ? FlexGet.S01E02.TheName.HDTV.xvid : episode: 2 - format: HDTV + source: HDTV season: 1 title: FlexGet episode_title: TheName type: episode - video_codec: XviD + video_codec: Xvid ? FlexGet.S01E02.TheName.HDTV.xvid : episode: 2 - format: HDTV + source: HDTV season: 1 title: FlexGet episode_title: TheName type: episode - video_codec: XviD + video_codec: Xvid ? some.series.S03E14.Title.Here.720p : episode: 14 @@ -362,7 +366,7 @@ ? Something.Season.2.1of4.Ep.Title.HDTV.torrent : episode_count: 4 episode: 1 - format: HDTV + source: HDTV season: 2 title: Something episode_title: Title @@ -372,7 +376,7 @@ ? Show-A (US) - Episode Title S02E09 hdtv : country: US episode: 9 - format: HDTV + source: HDTV season: 2 title: Show-A type: episode @@ -402,23 +406,25 @@ type: movie ? Movies/El Bosque Animado (1987)/El.Bosque.Animado.[Jose.Luis.Cuerda.1987].[Xvid-Dvdrip-720 * 432].avi -: format: DVD +: source: DVD + other: Rip screen_size: 720x432 title: El Bosque Animado - video_codec: XviD + video_codec: Xvid year: 1987 type: movie ? Movies/El Bosque Animado (1987)/El.Bosque.Animado.[Jose.Luis.Cuerda.1987].[Xvid-Dvdrip-720x432].avi -: format: DVD +: source: DVD + other: Rip screen_size: 720x432 title: El Bosque Animado - video_codec: XviD + video_codec: Xvid year: 1987 type: movie ? 2009.shoot.fruit.chan.multi.dvd9.pal -: format: DVD +: source: DVD language: mul other: PAL title: shoot fruit chan @@ -426,7 +432,7 @@ year: 2009 ? 2009.shoot.fruit.chan.multi.dvd5.pal -: format: DVD +: source: DVD language: mul other: PAL title: shoot fruit chan @@ -435,25 +441,25 @@ ? The.Flash.2014.S01E01.PREAIR.WEBRip.XviD-EVO.avi : episode: 1 - format: WEBRip - other: Preair + source: Web + other: [Preair, Rip] release_group: EVO season: 1 title: The Flash type: episode - video_codec: XviD + video_codec: Xvid year: 2014 ? Ice.Lake.Rebels.S01E06.Ice.Lake.Games.720p.HDTV.x264-DHD : episode: 6 - format: HDTV + source: HDTV release_group: DHD screen_size: 720p season: 1 title: Ice Lake Rebels episode_title: Ice Lake Games type: episode - video_codec: h264 + video_codec: H.264 ? The League - S06E10 - Epi Sexy.mkv : episode: 10 @@ -463,23 +469,23 @@ type: episode ? Stay (2005) [1080p]/Stay.2005.1080p.BluRay.x264.YIFY.mp4 -: format: BluRay +: source: Blu-ray release_group: YIFY screen_size: 1080p title: Stay type: movie - video_codec: h264 + video_codec: H.264 year: 2005 ? /media/live/A/Anger.Management.S02E82.720p.HDTV.X264-DIMENSION.mkv -: format: HDTV +: source: HDTV release_group: DIMENSION screen_size: 720p title: Anger Management type: episode season: 2 episode: 82 - video_codec: h264 + video_codec: H.264 ? "[Figmentos] Monster 34 - At the End of Darkness [781219F1].mkv" : type: episode @@ -492,7 +498,7 @@ ? Game.of.Thrones.S05E07.720p.HDTV-KILLERS.mkv : type: episode episode: 7 - format: HDTV + source: HDTV release_group: KILLERS screen_size: 720p season: 5 @@ -501,7 +507,7 @@ ? Game.of.Thrones.S05E07.HDTV.720p-KILLERS.mkv : type: episode episode: 7 - format: HDTV + source: HDTV release_group: KILLERS screen_size: 720p season: 5 @@ -519,8 +525,8 @@ title: Star Trek Into Darkness year: 2013 screen_size: 720p - format: WEB-DL - video_codec: h264 + source: Web + video_codec: H.264 release_group: publichd ? /var/medias/series/The Originals/Season 02/The.Originals.S02E15.720p.HDTV.X264-DIMENSION.mkv @@ -529,8 +535,8 @@ season: 2 episode: 15 screen_size: 720p - format: HDTV - video_codec: h264 + source: HDTV + video_codec: H.264 release_group: DIMENSION ? Test.S01E01E07-FooBar-Group.avi @@ -539,202 +545,211 @@ - 1 - 7 episode_title: FooBar-Group # Make sure it doesn't conflict with uuid - mimetype: video/x-msvideo season: 1 title: Test type: episode ? TEST.S01E02.2160p.NF.WEBRip.x264.DD5.1-ABC : audio_channels: '5.1' - audio_codec: AC3 + audio_codec: Dolby Digital episode: 2 - format: WEBRip + source: Web + other: Rip release_group: ABC - screen_size: 4K + screen_size: 2160p season: 1 streaming_service: Netflix title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.2015.12.30.720p.WEBRip.h264-ABC : date: 2015-12-30 - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 720p title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S01E10.24.1080p.NF.WEBRip.AAC2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 10 episode_title: '24' - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 1080p season: 1 streaming_service: Netflix title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S01E10.24.1080p.NF.WEBRip.AAC2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 10 episode_title: '24' - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 1080p season: 1 streaming_service: Netflix title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S01E10.24.1080p.NF.WEBRip.AAC.2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 10 episode_title: '24' - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 1080p season: 1 streaming_service: Netflix title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S05E02.720p.iP.WEBRip.AAC2.0.H264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 2 - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 720p season: 5 title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S03E07.720p.WEBRip.AAC2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 7 - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 720p season: 3 title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S15E15.24.1080p.FREE.WEBRip.AAC2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 15 episode_title: '24' - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 1080p season: 15 title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.S11E11.24.720p.ETV.WEBRip.AAC2.0.x264-ABC : audio_channels: '2.0' audio_codec: AAC episode: 11 episode_title: '24' - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 720p season: 11 title: TEST type: episode - video_codec: h264 + video_codec: H.264 ? TEST.2015.1080p.HC.WEBRip.x264.AAC2.0-ABC : audio_channels: '2.0' audio_codec: AAC - format: WEBRip + source: Web + other: Rip release_group: ABC screen_size: 1080p title: TEST type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? TEST.2015.1080p.3D.BluRay.Half-SBS.x264.DTS-HD.MA.7.1-ABC : audio_channels: '7.1' - audio_codec: DTS - audio_profile: HDMA - format: BluRay + audio_codec: DTS-HD + audio_profile: Master Audio + source: Blu-ray other: 3D release_group: ABC screen_size: 1080p title: TEST type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? TEST.2015.1080p.3D.BluRay.Half-OU.x264.DTS-HD.MA.7.1-ABC : audio_channels: '7.1' - audio_codec: DTS - audio_profile: HDMA - format: BluRay + audio_codec: DTS-HD + audio_profile: Master Audio + source: Blu-ray other: 3D release_group: ABC screen_size: 1080p title: TEST type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? TEST.2015.1080p.3D.BluRay.Half-OU.x264.DTS-HD.MA.TrueHD.7.1.Atmos-ABC : audio_channels: '7.1' audio_codec: - - DTS - - TrueHD - - DolbyAtmos - audio_profile: HDMA - format: BluRay + - DTS-HD + - Dolby TrueHD + - Dolby Atmos + audio_profile: Master Audio + source: Blu-ray other: 3D release_group: ABC screen_size: 1080p title: TEST type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? TEST.2015.1080p.3D.BluRay.Half-SBS.x264.DTS-HD.MA.TrueHD.7.1.Atmos-ABC : audio_channels: '7.1' audio_codec: - - DTS - - TrueHD - - DolbyAtmos - audio_profile: HDMA - format: BluRay + - DTS-HD + - Dolby TrueHD + - Dolby Atmos + audio_profile: Master Audio + source: Blu-ray other: 3D release_group: ABC screen_size: 1080p title: TEST type: movie - video_codec: h264 + video_codec: H.264 year: 2015 ? TEST.2015.1080p.BluRay.REMUX.AVC.DTS-HD.MA.TrueHD.7.1.Atmos-ABC : audio_channels: '7.1' audio_codec: - - DTS - - TrueHD - - DolbyAtmos - audio_profile: HDMA - format: BluRay + - DTS-HD + - Dolby TrueHD + - Dolby Atmos + audio_profile: Master Audio + source: Blu-ray other: Remux release_group: ABC screen_size: 1080p @@ -743,23 +758,25 @@ year: 2015 ? Gangs of New York 2002 REMASTERED 1080p BluRay x264-AVCHD -: format: BluRay +: source: Blu-ray edition: Remastered screen_size: 1080p title: Gangs of New York type: movie - video_codec: h264 + video_codec: H.264 + video_profile: Advanced Video Codec High Definition year: 2002 ? Peep.Show.S06E02.DVDrip.x264-faks86.mkv : container: mkv episode: 2 - format: DVD + source: DVD + other: Rip release_group: faks86 season: 6 title: Peep Show type: episode - video_codec: h264 + video_codec: H.264 # Episode title is indeed 'October 8, 2014' # https://thetvdb.com/?tab=episode&seriesid=82483&seasonid=569935&id=4997362&lid=7 @@ -774,28 +791,409 @@ ? Red.Rock.S02E59.WEB-DLx264-JIVE : episode: 59 season: 2 - format: WEB-DL + source: Web release_group: JIVE title: Red Rock type: episode - video_codec: h264 + video_codec: H.264 ? Pawn.Stars.S12E31.Deals.On.Wheels.PDTVx264-JIVE : episode: 31 episode_title: Deals On Wheels season: 12 - format: DVB + source: Digital TV release_group: JIVE title: Pawn Stars type: episode - video_codec: h264 + video_codec: H.264 ? Duck.Dynasty.S09E09.Van.He-llsing.HDTVx264-JIVE : episode: 9 episode_title: Van He-llsing season: 9 - format: HDTV + source: HDTV release_group: JIVE title: Duck Dynasty type: episode - video_codec: h264
\ No newline at end of file + video_codec: H.264 + +? ATKExotics.16.01.24.Ava.Alba.Watersports.XXX.1080p.MP4-KTR +: title: ATKExotics + episode_title: Ava Alba Watersports + other: XXX + screen_size: 1080p + container: mp4 + release_group: KTR + type: episode + +? PutaLocura.15.12.22.Spanish.Luzzy.XXX.720p.MP4-oRo +: title: PutaLocura + episode_title: Spanish Luzzy + other: XXX + screen_size: 720p + container: mp4 + release_group: oRo + type: episode + +? French Maid Services - Lola At Your Service WEB-DL SPLIT SCENES MP4-RARBG +: title: French Maid Services + alternative_title: Lola At Your Service + source: Web + container: mp4 + release_group: RARBG + type: movie + +? French Maid Services - Lola At Your Service - Marc Dorcel WEB-DL SPLIT SCENES MP4-RARBG +: title: French Maid Services + alternative_title: [Lola At Your Service, Marc Dorcel] + source: Web + container: mp4 + release_group: RARBG + type: movie + +? PlayboyPlus.com_16.01.23.Eleni.Corfiate.Playboy.Romania.XXX.iMAGESET-OHRLY +: episode_title: Eleni Corfiate Playboy Romania + other: XXX + type: episode + +? TeenPornoPass - Anna - Beautiful Ass Deep Penetrated 720p mp4 +: title: TeenPornoPass + alternative_title: + - Anna + - Beautiful Ass Deep Penetrated + screen_size: 720p + container: mp4 + type: movie + +? SexInJeans.Gina.Gerson.Super.Nasty.Asshole.Pounding.With.Gina.In.Jeans.A.Devil.In.Denim.The.Finest.Ass.Fuck.Frolicking.mp4 +: title: SexInJeans Gina Gerson Super Nasty Asshole Pounding With Gina In Jeans A Devil In Denim The Finest Ass Fuck Frolicking + container: mp4 + type: movie + +? TNA Impact Wrestling HDTV 2017-06-22 720p H264 AVCHD-SC-SDH +: title: TNA Impact Wrestling + source: HDTV + date: 2017-06-22 + screen_size: 720p + video_codec: H.264 + video_profile: + - Advanced Video Codec High Definition + - Scalable Video Coding + release_group: SDH + type: episode + +? Katy Perry - Pepsi & Billboard Summer Beats Concert Series 2012 1080i HDTV 20 Mbps DD2.0 MPEG2-TrollHD.ts +: title: Katy Perry + alternative_title: Pepsi & Billboard Summer Beats Concert Series + year: 2012 + screen_size: 1080i + source: HDTV + video_bit_rate: 20Mbps + audio_codec: Dolby Digital + audio_channels: '2.0' + video_codec: MPEG-2 + release_group: TrollHD + container: ts + +? Justin Timberlake - MTV Video Music Awards 2013 1080i 32 Mbps DTS-HD 5.1.ts +: title: Justin Timberlake + alternative_title: MTV Video Music Awards + year: 2013 + screen_size: 1080i + video_bit_rate: 32Mbps + audio_codec: DTS-HD + audio_channels: '5.1' + container: ts + type: movie + +? Chuck Berry The Very Best Of Chuck Berry(2010)[320 Kbps] +: title: Chuck Berry The Very Best Of Chuck Berry + year: 2010 + audio_bit_rate: 320Kbps + type: movie + +? Title Name [480p][1.5Mbps][.mp4] +: title: Title Name + screen_size: 480p + video_bit_rate: 1.5Mbps + container: mp4 + type: movie + +? This.is.Us +: options: --no-default-config + title: This is Us + type: movie + +? This.is.Us +: options: --excludes country + title: This is Us + type: movie + +? MotoGP.2016x03.USA.Race.BTSportHD.1080p25 +: title: MotoGP + season: 2016 + year: 2016 + episode: 3 + screen_size: 1080p + frame_rate: 25fps + type: episode + +? BBC.Earth.South.Pacific.2010.D2.1080p.24p.BD25.DTS-HD +: title: BBC Earth South Pacific + year: 2010 + screen_size: 1080p + frame_rate: 24fps + source: Blu-ray + audio_codec: DTS-HD + type: movie + +? Mr Robot - S03E01 - eps3 0 power-saver-mode h (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 6.0 RCVR).mkv +: title: Mr Robot + season: 3 + episode: 1 + episode_title: eps3 0 power-saver-mode h + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + video_codec: H.265 + video_profile: High Efficiency Video Coding + color_depth: 10-bit + audio_codec: Dolby Digital Plus + audio_channels: '5.1' + release_group: RCVR + container: mkv + type: episode + +? Panorama.15-05-2018.Web-DL.540p.H264.AAC.Subs.mp4 +: title: Panorama + date: 2018-05-15 + source: Web + screen_size: 540p + video_codec: H.264 + audio_codec: AAC + subtitle_language: und + container: mp4 + type: episode + +? Shaolin 2011.720p.BluRay.x264-x0r.mkv +: title: Shaolin + year: 2011 + screen_size: 720p + source: Blu-ray + video_codec: H.264 + release_group: x0r + container: mkv + type: movie + +? '[ Engineering Catastrophes S02E10 1080p AMZN WEB-DL DD+ 2.0 x264-TrollHD ]' +: title: Engineering Catastrophes + season: 2 + episode: 10 + screen_size: 1080p + streaming_service: Amazon Prime + source: Web + audio_codec: Dolby Digital Plus + audio_channels: '2.0' + video_codec: H.264 + release_group: TrollHD + type: episode + +? A Very Harold & Kumar 3D Christmas (2011).mkv +: title: A Very Harold & Kumar 3D Christmas + year: 2011 + container: mkv + type: movie + +? Cleveland.Hustles.S01E03.Downward.Dogs.and.Proper.Pigs.720p.HDTV.x264-W4F +: title: Cleveland Hustles + season: 1 + episode: 3 + episode_title: Downward Dogs and Proper Pigs + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: W4F + type: episode + +? Pawn.Stars.S12E20.The.Pawn.Awakens.REAL.READ.NFO.720p.HDTV.x264-DHD +: title: Pawn Stars + season: 12 + episode: 20 + episode_title: The Pawn Awakens + other: + - Proper + - Read NFO + proper_count: 2 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: DHD + type: episode + +? Pawn.Stars.S12E22.Racing.Revolution.REAL.720p.HDTV.x264-DHD +: title: Pawn Stars + season: 12 + episode: 22 + episode_title: Racing Revolution + other: Proper + proper_count: 2 + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: DHD + type: episode + +? Luksusfellen.S18E02.REAL.NORWEGiAN.720p.WEB.h264-NORPiLT +: title: Luksusfellen + season: 18 + episode: 2 + other: Proper + proper_count: 2 + language: Norwegian + screen_size: 720p + source: Web + video_codec: H.264 + release_group: NORPiLT + type: episode + +? The.Exorcist.S02E07.REAL.FRENCH.720p.HDTV.x264-SH0W +: title: The Exorcist + season: 2 + episode: 7 + other: Proper + proper_count: 2 + language: fr + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: SH0W + type: episode + +? Outrageous.Acts.of.Science.S05E02.Is.This.for.Real.720p.HDTV.x264-DHD +: title: Outrageous Acts of Science + season: 5 + episode: 2 +# corner case +# episode_title: Is This for Real + screen_size: 720p + source: HDTV + video_codec: H.264 + release_group: DHD + type: episode + +? How.the.Universe.Works.S06E08.Strange.Lives.of.Dwarf.Planets.REAL.720p.WEB.x264-DHD +: title: How the Universe Works + season: 6 + episode: 8 + episode_title: Strange Lives of Dwarf Planets + other: Proper + proper_count: 2 + screen_size: 720p + source: Web + video_codec: H.264 + release_group: DHD + type: episode + +? Vampirina.S01E16.REAL.HDTV.x264-W4F +: title: Vampirina + season: 1 + episode: 16 + other: Proper + proper_count: 2 + source: HDTV + video_codec: H.264 + release_group: W4F + type: episode + +? Test.S01E16.Some Real Episode Title.HDTV.x264-W4F +: title: Test + season: 1 + episode: 16 + episode_title: Some Real Episode Title + source: HDTV + 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 + +? Star Trek DS9 Ep 2x03 The Siege (Part III) +: title: Star Trek DS9 + season: 2 + episode: 3 + episode_title: The Siege + part: 3 + type: episode + +? The.Red.Line.S01E01 +: title: The Red Line + season: 1 + episode: 1 + type: episode + +? Show.S01E01.WEB.x264-METCON.mkv +: title: Show + season: 1 + episode: 1 + source: Web + video_codec: H.264 + release_group: METCON + container: mkv + type: episode + +? Show.S01E01.WEB.x264-TCMEON.mkv +: title: Show + season: 1 + episode: 1 + source: Web + video_codec: H.264 + release_group: TCMEON + container: mkv + type: episode + +? Show.S01E01.WEB.x264-MEONTC.mkv +: title: Show + season: 1 + episode: 1 + source: Web + video_codec: H.264 + release_group: MEONTC + container: mkv + type: episode + +? '[TorrentCouch.com].Westworld.S02.Complete.720p.WEB-DL.x264.[MP4].[5.3GB].[Season.2.Full]/[TorrentCouch.com].Westworld.S02E03.720p.WEB-DL.x264.mp4' +: website: TorrentCouch.com + title: Westworld + season: 2 + other: Complete + screen_size: 720p + source: Web + video_codec: H.264 + container: mp4 + size: 5.3GB + episode: 3 + type: episode + +? Vita.&.Virginia.2018.720p.H.264.YTS.LT.mp4 +: title: Vita & Virginia + year: 2018 + screen_size: 720p + video_codec: H.264 + release_group: YTS.LT + container: mp4 + type: movie
\ No newline at end of file diff --git a/libs/guessit/yamlutils.py b/libs/guessit/yamlutils.py index 2824575da..d04be641b 100644 --- a/libs/guessit/yamlutils.py +++ b/libs/guessit/yamlutils.py @@ -3,23 +3,26 @@ """ Options """ + try: from collections import OrderedDict except ImportError: # pragma: no-cover from ordereddict import OrderedDict # pylint:disable=import-error import babelfish -import yaml +import yaml # pylint:disable=wrong-import-order + +from .rules.common.quantity import BitRate, FrameRate, Size -class OrderedDictYAMLLoader(yaml.Loader): +class OrderedDictYAMLLoader(yaml.SafeLoader): """ A YAML loader that loads mappings into ordered dictionaries. From https://gist.github.com/enaeseth/844388 """ def __init__(self, *args, **kwargs): - yaml.Loader.__init__(self, *args, **kwargs) + yaml.SafeLoader.__init__(self, *args, **kwargs) self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map) self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map) @@ -55,17 +58,24 @@ class CustomDumper(yaml.SafeDumper): """ Custom YAML Dumper. """ - pass + pass # pylint:disable=unnecessary-pass def default_representer(dumper, data): """Default representer""" return dumper.represent_str(str(data)) + + CustomDumper.add_representer(babelfish.Language, default_representer) CustomDumper.add_representer(babelfish.Country, default_representer) +CustomDumper.add_representer(BitRate, default_representer) +CustomDumper.add_representer(FrameRate, default_representer) +CustomDumper.add_representer(Size, default_representer) def ordered_dict_representer(dumper, data): """OrderedDict representer""" - return dumper.represent_dict(data) + return dumper.represent_mapping('tag:yaml.org,2002:map', data.items()) + + CustomDumper.add_representer(OrderedDict, ordered_dict_representer) |