aboutsummaryrefslogtreecommitdiffhomepage
path: root/libs/guessit
diff options
context:
space:
mode:
authorLouis Vézina <[email protected]>2020-05-20 11:29:39 -0400
committerLouis Vézina <[email protected]>2020-05-20 11:29:39 -0400
commit376e13d7f1ab8e6d9202c1a51a96526de0f11163 (patch)
tree77e911bffc9a59ed2868a673f0733ad38f5b4ccd /libs/guessit
parent5b44007bbb7ef49bbf4087b43dbb948433639fbe (diff)
downloadbazarr-376e13d7f1ab8e6d9202c1a51a96526de0f11163.tar.gz
bazarr-376e13d7f1ab8e6d9202c1a51a96526de0f11163.zip
Upgraded GuessIt to 3.0.1
Diffstat (limited to 'libs/guessit')
-rw-r--r--libs/guessit/__init__.py5
-rw-r--r--libs/guessit/__main__.py28
-rw-r--r--libs/guessit/__version__.py2
-rw-r--r--libs/guessit/api.py180
-rw-r--r--libs/guessit/backports.py2
-rw-r--r--libs/guessit/config/options.json587
-rw-r--r--libs/guessit/jsonutils.py22
-rw-r--r--libs/guessit/monkeypatch.py34
-rw-r--r--libs/guessit/options.py163
-rw-r--r--libs/guessit/rules/__init__.py71
-rw-r--r--libs/guessit/rules/common/comparators.py14
-rw-r--r--libs/guessit/rules/common/date.py10
-rw-r--r--libs/guessit/rules/common/formatters.py2
-rw-r--r--libs/guessit/rules/common/pattern.py27
-rw-r--r--libs/guessit/rules/common/quantity.py106
-rw-r--r--libs/guessit/rules/common/validators.py25
-rw-r--r--libs/guessit/rules/common/words.py45
-rw-r--r--libs/guessit/rules/markers/groups.py9
-rw-r--r--libs/guessit/rules/markers/path.py6
-rw-r--r--libs/guessit/rules/match_processors.py20
-rw-r--r--libs/guessit/rules/processors.py25
-rw-r--r--libs/guessit/rules/properties/audio_codec.py127
-rw-r--r--libs/guessit/rules/properties/bit_rate.py74
-rw-r--r--libs/guessit/rules/properties/bonus.py16
-rw-r--r--libs/guessit/rules/properties/cds.py10
-rw-r--r--libs/guessit/rules/properties/container.py33
-rw-r--r--libs/guessit/rules/properties/country.py89
-rw-r--r--libs/guessit/rules/properties/crc.py11
-rw-r--r--libs/guessit/rules/properties/date.py18
-rw-r--r--libs/guessit/rules/properties/edition.py29
-rw-r--r--libs/guessit/rules/properties/episode_title.py70
-rw-r--r--libs/guessit/rules/properties/episodes.py582
-rw-r--r--libs/guessit/rules/properties/film.py11
-rw-r--r--libs/guessit/rules/properties/format.py72
-rw-r--r--libs/guessit/rules/properties/language.py260
-rw-r--r--libs/guessit/rules/properties/mimetype.py11
-rw-r--r--libs/guessit/rules/properties/other.py186
-rw-r--r--libs/guessit/rules/properties/part.py15
-rw-r--r--libs/guessit/rules/properties/release_group.py231
-rw-r--r--libs/guessit/rules/properties/screen_size.py151
-rw-r--r--libs/guessit/rules/properties/size.py19
-rw-r--r--libs/guessit/rules/properties/source.py235
-rw-r--r--libs/guessit/rules/properties/streaming_service.py92
-rw-r--r--libs/guessit/rules/properties/title.py76
-rw-r--r--libs/guessit/rules/properties/type.py14
-rw-r--r--libs/guessit/rules/properties/video_codec.py95
-rw-r--r--libs/guessit/rules/properties/website.py33
-rw-r--r--libs/guessit/test/enable_disable_properties.yml335
-rw-r--r--libs/guessit/test/episodes.yml2948
-rw-r--r--libs/guessit/test/movies.yml1178
-rw-r--r--libs/guessit/test/rules/audio_codec.yml67
-rw-r--r--libs/guessit/test/rules/cds.yml2
-rw-r--r--libs/guessit/test/rules/common_words.yml467
-rw-r--r--libs/guessit/test/rules/country.yml7
-rw-r--r--libs/guessit/test/rules/edition.yml40
-rw-r--r--libs/guessit/test/rules/episodes.yml77
-rw-r--r--libs/guessit/test/rules/format.yml138
-rw-r--r--libs/guessit/test/rules/language.yml10
-rw-r--r--libs/guessit/test/rules/other.yml87
-rw-r--r--libs/guessit/test/rules/release_group.yml12
-rw-r--r--libs/guessit/test/rules/screen_size.yml253
-rw-r--r--libs/guessit/test/rules/source.yml323
-rw-r--r--libs/guessit/test/rules/title.yml11
-rw-r--r--libs/guessit/test/rules/video_codec.yml61
-rw-r--r--libs/guessit/test/streaming_services.yaml1934
-rw-r--r--libs/guessit/test/suggested.json21
-rw-r--r--libs/guessit/test/test_api.py24
-rw-r--r--libs/guessit/test/test_api_unicode_literals.py8
-rw-r--r--libs/guessit/test/test_options.py65
-rw-r--r--libs/guessit/test/test_yml.py141
-rw-r--r--libs/guessit/test/various.yml658
-rw-r--r--libs/guessit/yamlutils.py20
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 &amp; 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
: 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)