├── lib ├── dateutil │ ├── requires.txt │ ├── __init__.py │ ├── MANIFEST.in │ ├── NEWS │ ├── parser.py │ ├── zoneinfo │ │ ├── dateutil-zoneinfo.tar.gz │ │ └── __init__.py │ ├── PKG-INFO │ ├── LICENSE │ ├── easter.py │ ├── README.rst │ └── tzwin.py ├── guessit │ ├── config │ │ └── options.json │ ├── rules │ │ ├── markers │ │ │ ├── __init__.py │ │ │ ├── path.py │ │ │ └── groups.py │ │ ├── properties │ │ │ ├── __init__.py │ │ │ ├── size.py │ │ │ ├── mimetype.py │ │ │ ├── cds.py │ │ │ ├── part.py │ │ │ ├── film.py │ │ │ ├── bonus.py │ │ │ ├── edition.py │ │ │ ├── type.py │ │ │ ├── container.py │ │ │ ├── crc.py │ │ │ ├── date.py │ │ │ ├── format.py │ │ │ ├── video_codec.py │ │ │ ├── country.py │ │ │ ├── website.py │ │ │ ├── screen_size.py │ │ │ ├── streaming_service.py │ │ │ ├── audio_codec.py │ │ │ └── release_group.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ ├── validators.py │ │ │ ├── expected.py │ │ │ ├── comparators.py │ │ │ ├── words.py │ │ │ ├── formatters.py │ │ │ ├── date.py │ │ │ └── numeral.py │ │ ├── __init__.py │ │ └── processors.py │ ├── __version__.py │ ├── __init__.py │ ├── backports.py │ ├── reutils.py │ ├── jsonutils.py │ ├── yamlutils.py │ ├── tlds-alpha-by-domain.txt │ ├── api.py │ └── __main__.py ├── rebulk │ ├── __version__.py │ ├── remodule.py │ ├── __init__.py │ ├── formatters.py │ ├── debug.py │ ├── validators.py │ ├── toposort.py │ ├── processors.py │ ├── introspector.py │ ├── utils.py │ └── loose.py └── babelfish │ ├── converters │ ├── name.py │ ├── alpha2.py │ ├── alpha3b.py │ ├── alpha3t.py │ ├── scope.py │ ├── type.py │ ├── countryname.py │ └── opensubtitles.py │ ├── __init__.py │ ├── script.py │ ├── exceptions.py │ ├── country.py │ ├── data │ └── iso-3166-1.txt │ └── language.py ├── .gitignore ├── README.txt ├── README.md ├── ChangeLog.txt ├── testsort.py └── testdata.json /lib/dateutil/requires.txt: -------------------------------------------------------------------------------- 1 | six 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | .vscode 5 | __ 6 | -------------------------------------------------------------------------------- /lib/dateutil/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = "2.3" 3 | -------------------------------------------------------------------------------- /lib/dateutil/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE NEWS zonefile_metadata.json updatezinfo.py 2 | -------------------------------------------------------------------------------- /lib/dateutil/NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbget/VideoSort/HEAD/lib/dateutil/NEWS -------------------------------------------------------------------------------- /lib/guessit/config/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "expected_title": [ 3 | "OSS 117" 4 | ] 5 | } -------------------------------------------------------------------------------- /lib/dateutil/parser.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbget/VideoSort/HEAD/lib/dateutil/parser.py -------------------------------------------------------------------------------- /lib/guessit/rules/markers/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Markers 5 | """ 6 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Properties 5 | """ 6 | -------------------------------------------------------------------------------- /lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbget/VideoSort/HEAD/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz -------------------------------------------------------------------------------- /lib/rebulk/__version__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Version module 5 | """ 6 | # pragma: no cover 7 | __version__ = '0.9.0' 8 | -------------------------------------------------------------------------------- /lib/guessit/__version__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Version module 5 | """ 6 | # pragma: no cover 7 | __version__ = '2.1.4' 8 | -------------------------------------------------------------------------------- /lib/guessit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Extracts as much information as possible from a video file. 5 | """ 6 | from .api import guessit, GuessItApi 7 | from .options import ConfigurationException 8 | 9 | from .__version__ import __version__ 10 | -------------------------------------------------------------------------------- /lib/rebulk/remodule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Uniform re module 5 | """ 6 | # pylint: disable-all 7 | import os 8 | 9 | REGEX_AVAILABLE = False 10 | if os.environ.get('REGEX_DISABLED') in ["1", "true", "True", "Y"]: 11 | import re 12 | else: 13 | try: 14 | import regex as re 15 | REGEX_AVAILABLE = True 16 | except ImportError: 17 | import re 18 | -------------------------------------------------------------------------------- /lib/rebulk/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Define simple search patterns in bulk to perform advanced matching on any string. 5 | """ 6 | # pylint:disable=import-self 7 | from .rebulk import Rebulk 8 | from .rules import Rule, CustomRule, AppendMatch, RemoveMatch, RenameMatch, AppendTags, RemoveTags 9 | from .processors import ConflictSolver, PrivateRemover, POST_PROCESS, PRE_PROCESS 10 | from .pattern import REGEX_AVAILABLE 11 | -------------------------------------------------------------------------------- /lib/guessit/rules/common/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Common module 5 | """ 6 | import re 7 | 8 | seps = r' [](){}+*|=-_~#/\\.,;:' # list of tags/words separators 9 | seps_no_groups = seps.replace('[](){}', '') 10 | seps_no_fs = seps.replace('/', '').replace('\\', '') 11 | 12 | title_seps = r'-+/\|' # separators for title 13 | 14 | dash = (r'-', r'['+re.escape(seps_no_fs)+']') # abbreviation used by many rebulk objects. 15 | alt_dash = (r'@', r'['+re.escape(seps_no_fs)+']') # abbreviation used by many rebulk objects. 16 | -------------------------------------------------------------------------------- /lib/babelfish/converters/name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageEquivalenceConverter 9 | from ..language import LANGUAGE_MATRIX 10 | 11 | 12 | class NameConverter(LanguageEquivalenceConverter): 13 | CASE_SENSITIVE = False 14 | SYMBOLS = {} 15 | for iso_language in LANGUAGE_MATRIX: 16 | if iso_language.name: 17 | SYMBOLS[iso_language.alpha3] = iso_language.name 18 | -------------------------------------------------------------------------------- /lib/babelfish/converters/alpha2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageEquivalenceConverter 9 | from ..language import LANGUAGE_MATRIX 10 | 11 | 12 | class Alpha2Converter(LanguageEquivalenceConverter): 13 | CASE_SENSITIVE = True 14 | SYMBOLS = {} 15 | for iso_language in LANGUAGE_MATRIX: 16 | if iso_language.alpha2: 17 | SYMBOLS[iso_language.alpha3] = iso_language.alpha2 18 | -------------------------------------------------------------------------------- /lib/babelfish/converters/alpha3b.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageEquivalenceConverter 9 | from ..language import LANGUAGE_MATRIX 10 | 11 | 12 | class Alpha3BConverter(LanguageEquivalenceConverter): 13 | CASE_SENSITIVE = True 14 | SYMBOLS = {} 15 | for iso_language in LANGUAGE_MATRIX: 16 | if iso_language.alpha3b: 17 | SYMBOLS[iso_language.alpha3] = iso_language.alpha3b 18 | -------------------------------------------------------------------------------- /lib/babelfish/converters/alpha3t.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageEquivalenceConverter 9 | from ..language import LANGUAGE_MATRIX 10 | 11 | 12 | class Alpha3TConverter(LanguageEquivalenceConverter): 13 | CASE_SENSITIVE = True 14 | SYMBOLS = {} 15 | for iso_language in LANGUAGE_MATRIX: 16 | if iso_language.alpha3t: 17 | SYMBOLS[iso_language.alpha3] = iso_language.alpha3t 18 | -------------------------------------------------------------------------------- /lib/rebulk/formatters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Formatter functions to use in patterns. 5 | 6 | All those function have last argument as match.value (str). 7 | """ 8 | 9 | 10 | def formatters(*chained_formatters): 11 | """ 12 | Chain formatter functions. 13 | :param chained_formatters: 14 | :type chained_formatters: 15 | :return: 16 | :rtype: 17 | """ 18 | def formatters_chain(input_string): # pylint:disable=missing-docstring 19 | for chained_formatter in chained_formatters: 20 | input_string = chained_formatter(input_string) 21 | return input_string 22 | 23 | return formatters_chain 24 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/size.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | size property 5 | """ 6 | import re 7 | 8 | from rebulk import Rebulk 9 | 10 | from ..common.validators import seps_surround 11 | from ..common import dash 12 | 13 | 14 | def size(): 15 | """ 16 | Builder for rebulk object. 17 | :return: Created Rebulk object 18 | :rtype: Rebulk 19 | """ 20 | 21 | def format_size(value): 22 | """Format size using uppercase and no space.""" 23 | return re.sub(r'(?<=\d)[.](?=[^\d])', '', value.upper()) 24 | 25 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) 26 | rebulk.defaults(name='size', validator=seps_surround) 27 | rebulk.regex(r'\d+\.?[mgt]b', r'\d+\.\d+[mgt]b', formatter=format_size, tags=['release-group-prefix']) 28 | 29 | return rebulk 30 | -------------------------------------------------------------------------------- /lib/babelfish/converters/scope.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageConverter 9 | from ..exceptions import LanguageConvertError 10 | from ..language import LANGUAGE_MATRIX 11 | 12 | 13 | class ScopeConverter(LanguageConverter): 14 | FULLNAME = {'I': 'individual', 'M': 'macrolanguage', 'S': 'special'} 15 | SYMBOLS = {} 16 | for iso_language in LANGUAGE_MATRIX: 17 | SYMBOLS[iso_language.alpha3] = iso_language.scope 18 | codes = set(SYMBOLS.values()) 19 | 20 | def convert(self, alpha3, country=None, script=None): 21 | if self.SYMBOLS[alpha3] in self.FULLNAME: 22 | return self.FULLNAME[self.SYMBOLS[alpha3]] 23 | raise LanguageConvertError(alpha3, country, script) 24 | -------------------------------------------------------------------------------- /lib/guessit/backports.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Backports 5 | """ 6 | # pragma: no-cover 7 | # pylint: disabled 8 | 9 | def cmp_to_key(mycmp): 10 | """functools.cmp_to_key backport""" 11 | class KeyClass(object): 12 | """Key class""" 13 | def __init__(self, obj, *args): # pylint: disable=unused-argument 14 | self.obj = obj 15 | def __lt__(self, other): 16 | return mycmp(self.obj, other.obj) < 0 17 | def __gt__(self, other): 18 | return mycmp(self.obj, other.obj) > 0 19 | def __eq__(self, other): 20 | return mycmp(self.obj, other.obj) == 0 21 | def __le__(self, other): 22 | return mycmp(self.obj, other.obj) <= 0 23 | def __ge__(self, other): 24 | return mycmp(self.obj, other.obj) >= 0 25 | def __ne__(self, other): 26 | return mycmp(self.obj, other.obj) != 0 27 | return KeyClass 28 | -------------------------------------------------------------------------------- /lib/guessit/reutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Utils for re module 5 | """ 6 | 7 | from rebulk.remodule import re 8 | 9 | 10 | def build_or_pattern(patterns, name=None, escape=False): 11 | """ 12 | Build a or pattern string from a list of possible patterns 13 | 14 | :param patterns: 15 | :type patterns: 16 | :param name: 17 | :type name: 18 | :param escape: 19 | :type escape: 20 | :return: 21 | :rtype: 22 | """ 23 | or_pattern = [] 24 | for pattern in patterns: 25 | if not or_pattern: 26 | or_pattern.append('(?') 27 | if name: 28 | or_pattern.append('P<' + name + '>') 29 | else: 30 | or_pattern.append(':') 31 | else: 32 | or_pattern.append('|') 33 | or_pattern.append('(?:%s)' % re.escape(pattern) if escape else pattern) 34 | or_pattern.append(')') 35 | return ''.join(or_pattern) 36 | -------------------------------------------------------------------------------- /lib/guessit/jsonutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | JSON Utils 5 | """ 6 | import json 7 | try: 8 | from collections import OrderedDict 9 | except ImportError: # pragma: no-cover 10 | from ordereddict import OrderedDict # pylint:disable=import-error 11 | 12 | from rebulk.match import Match 13 | 14 | 15 | class GuessitEncoder(json.JSONEncoder): 16 | """ 17 | JSON Encoder for guessit response 18 | """ 19 | 20 | def default(self, o): # pylint:disable=method-hidden 21 | if isinstance(o, Match): 22 | ret = OrderedDict() 23 | ret['value'] = o.value 24 | if o.raw: 25 | ret['raw'] = o.raw 26 | ret['start'] = o.start 27 | ret['end'] = o.end 28 | return ret 29 | elif hasattr(o, 'name'): # Babelfish languages/countries long name 30 | return str(o.name) 31 | else: # pragma: no cover 32 | return str(o) 33 | -------------------------------------------------------------------------------- /lib/babelfish/converters/type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageConverter 9 | from ..exceptions import LanguageConvertError 10 | from ..language import LANGUAGE_MATRIX 11 | 12 | 13 | class LanguageTypeConverter(LanguageConverter): 14 | FULLNAME = {'A': 'ancient', 'C': 'constructed', 'E': 'extinct', 'H': 'historical', 'L': 'living', 'S': 'special'} 15 | SYMBOLS = {} 16 | for iso_language in LANGUAGE_MATRIX: 17 | SYMBOLS[iso_language.alpha3] = iso_language.type 18 | codes = set(SYMBOLS.values()) 19 | 20 | def convert(self, alpha3, country=None, script=None): 21 | if self.SYMBOLS[alpha3] in self.FULLNAME: 22 | return self.FULLNAME[self.SYMBOLS[alpha3]] 23 | raise LanguageConvertError(alpha3, country, script) 24 | -------------------------------------------------------------------------------- /lib/babelfish/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | __title__ = 'babelfish' 8 | __version__ = '0.5.5-dev' 9 | __author__ = 'Antoine Bertin' 10 | __license__ = 'BSD' 11 | __copyright__ = 'Copyright 2015 the BabelFish authors' 12 | 13 | import sys 14 | 15 | if sys.version_info[0] >= 3: 16 | basestr = str 17 | else: 18 | basestr = basestring 19 | 20 | from .converters import (LanguageConverter, LanguageReverseConverter, LanguageEquivalenceConverter, CountryConverter, 21 | CountryReverseConverter) 22 | from .country import country_converters, COUNTRIES, COUNTRY_MATRIX, Country 23 | from .exceptions import Error, LanguageConvertError, LanguageReverseError, CountryConvertError, CountryReverseError 24 | from .language import language_converters, LANGUAGES, LANGUAGE_MATRIX, Language 25 | from .script import SCRIPTS, SCRIPT_MATRIX, Script 26 | -------------------------------------------------------------------------------- /lib/dateutil/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: python-dateutil 3 | Version: 2.3 4 | Summary: Extensions to the standard Python datetime module 5 | Home-page: https://dateutil.readthedocs.org 6 | Author: Yaron de Leeuw 7 | Author-email: me@jarondl.net 8 | License: Simplified BSD 9 | Description: 10 | The dateutil module provides powerful extensions to the 11 | datetime module available in the Python standard library. 12 | 13 | Platform: UNKNOWN 14 | Classifier: Development Status :: 5 - Production/Stable 15 | Classifier: Intended Audience :: Developers 16 | Classifier: License :: OSI Approved :: BSD License 17 | Classifier: Programming Language :: Python 18 | Classifier: Programming Language :: Python :: 2 19 | Classifier: Programming Language :: Python :: 2.6 20 | Classifier: Programming Language :: Python :: 2.7 21 | Classifier: Programming Language :: Python :: 3 22 | Classifier: Programming Language :: Python :: 3.2 23 | Classifier: Programming Language :: Python :: 3.3 24 | Classifier: Programming Language :: Python :: 3.4 25 | Classifier: Topic :: Software Development :: Libraries 26 | Requires: six 27 | -------------------------------------------------------------------------------- /lib/guessit/rules/markers/path.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Path markers 5 | """ 6 | from rebulk import Rebulk 7 | 8 | from rebulk.utils import find_all 9 | 10 | 11 | def path(): 12 | """ 13 | Builder for rebulk object. 14 | :return: Created Rebulk object 15 | :rtype: Rebulk 16 | """ 17 | rebulk = Rebulk() 18 | rebulk.defaults(name="path", marker=True) 19 | 20 | def mark_path(input_string, context): 21 | """ 22 | Functional pattern to mark path elements. 23 | 24 | :param input_string: 25 | :return: 26 | """ 27 | ret = [] 28 | if context.get('name_only', False): 29 | ret.append((0, len(input_string))) 30 | else: 31 | indices = list(find_all(input_string, '/')) 32 | indices += list(find_all(input_string, '\\')) 33 | indices += [-1, len(input_string)] 34 | 35 | indices.sort() 36 | 37 | for i in range(0, len(indices) - 1): 38 | ret.append((indices[i] + 1, indices[i + 1])) 39 | 40 | return ret 41 | 42 | rebulk.functional(mark_path) 43 | return rebulk 44 | -------------------------------------------------------------------------------- /lib/babelfish/converters/countryname.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import CountryReverseConverter, CaseInsensitiveDict 9 | from ..country import COUNTRY_MATRIX 10 | from ..exceptions import CountryConvertError, CountryReverseError 11 | 12 | 13 | class CountryNameConverter(CountryReverseConverter): 14 | def __init__(self): 15 | self.codes = set() 16 | self.to_name = {} 17 | self.from_name = CaseInsensitiveDict() 18 | for country in COUNTRY_MATRIX: 19 | self.codes.add(country.name) 20 | self.to_name[country.alpha2] = country.name 21 | self.from_name[country.name] = country.alpha2 22 | 23 | def convert(self, alpha2): 24 | if alpha2 not in self.to_name: 25 | raise CountryConvertError(alpha2) 26 | return self.to_name[alpha2] 27 | 28 | def reverse(self, name): 29 | if name not in self.from_name: 30 | raise CountryReverseError(name) 31 | return self.from_name[name] 32 | -------------------------------------------------------------------------------- /lib/guessit/rules/common/validators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Validators 5 | """ 6 | from functools import partial 7 | 8 | from rebulk.validators import chars_before, chars_after, chars_surround 9 | from . import seps 10 | 11 | seps_before = partial(chars_before, seps) 12 | seps_after = partial(chars_after, seps) 13 | seps_surround = partial(chars_surround, seps) 14 | 15 | 16 | def int_coercable(string): 17 | """ 18 | Check if string can be coerced to int 19 | :param string: 20 | :type string: 21 | :return: 22 | :rtype: 23 | """ 24 | try: 25 | int(string) 26 | return True 27 | except ValueError: 28 | return False 29 | 30 | 31 | def compose(*validators): 32 | """ 33 | Compose validators functions 34 | :param validators: 35 | :type validators: 36 | :return: 37 | :rtype: 38 | """ 39 | def composed(string): 40 | """ 41 | Composed validators function 42 | :param string: 43 | :type string: 44 | :return: 45 | :rtype: 46 | """ 47 | for validator in validators: 48 | if not validator(string): 49 | return False 50 | return True 51 | return composed 52 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/mimetype.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | mimetype property 5 | """ 6 | import mimetypes 7 | 8 | from rebulk import Rebulk, CustomRule, POST_PROCESS 9 | from rebulk.match import Match 10 | 11 | from ...rules.processors import Processors 12 | 13 | 14 | def mimetype(): 15 | """ 16 | Builder for rebulk object. 17 | :return: Created Rebulk object 18 | :rtype: Rebulk 19 | """ 20 | return Rebulk().rules(Mimetype) 21 | 22 | 23 | class Mimetype(CustomRule): 24 | """ 25 | Mimetype post processor 26 | :param matches: 27 | :type matches: 28 | :return: 29 | :rtype: 30 | """ 31 | priority = POST_PROCESS 32 | 33 | dependency = Processors 34 | 35 | def when(self, matches, context): 36 | mime, _ = mimetypes.guess_type(matches.input_string, strict=False) 37 | return mime 38 | 39 | def then(self, matches, when_response, context): 40 | mime = when_response 41 | matches.append(Match(len(matches.input_string), len(matches.input_string), name='mimetype', value=mime)) 42 | 43 | @property 44 | def properties(self): 45 | """ 46 | Properties for this rule. 47 | """ 48 | return {'mimetype': [None]} 49 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/cds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | cd and cd_count properties 5 | """ 6 | from rebulk.remodule import re 7 | 8 | from rebulk import Rebulk 9 | from ..common import dash 10 | 11 | 12 | def cds(): 13 | """ 14 | Builder for rebulk object. 15 | :return: Created Rebulk object 16 | :rtype: Rebulk 17 | """ 18 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) 19 | 20 | rebulk.regex(r'cd-?(?P\d+)(?:-?of-?(?P\d+))?', 21 | validator={'cd': lambda match: 0 < match.value < 100, 22 | 'cd_count': lambda match: 0 < match.value < 100}, 23 | formatter={'cd': int, 'cd_count': int}, 24 | children=True, 25 | private_parent=True, 26 | properties={'cd': [None], 'cd_count': [None]}) 27 | rebulk.regex(r'(?P\d+)-?cds?', 28 | validator={'cd': lambda match: 0 < match.value < 100, 29 | 'cd_count': lambda match: 0 < match.value < 100}, 30 | formatter={'cd_count': int}, 31 | children=True, 32 | private_parent=True, 33 | properties={'cd': [None], 'cd_count': [None]}) 34 | 35 | return rebulk 36 | -------------------------------------------------------------------------------- /lib/guessit/rules/markers/groups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Groups markers (...), [...] and {...} 5 | """ 6 | from rebulk import Rebulk 7 | 8 | 9 | def groups(): 10 | """ 11 | Builder for rebulk object. 12 | :return: Created Rebulk object 13 | :rtype: Rebulk 14 | """ 15 | rebulk = Rebulk() 16 | rebulk.defaults(name="group", marker=True) 17 | 18 | starting = '([{' 19 | ending = ')]}' 20 | 21 | def mark_groups(input_string): 22 | """ 23 | Functional pattern to mark groups (...), [...] and {...}. 24 | 25 | :param input_string: 26 | :return: 27 | """ 28 | openings = ([], [], []) 29 | i = 0 30 | 31 | ret = [] 32 | for char in input_string: 33 | start_type = starting.find(char) 34 | if start_type > -1: 35 | openings[start_type].append(i) 36 | 37 | i += 1 38 | 39 | end_type = ending.find(char) 40 | if end_type > -1: 41 | try: 42 | start_index = openings[end_type].pop() 43 | ret.append((start_index, i)) 44 | except IndexError: 45 | pass 46 | return ret 47 | 48 | rebulk.functional(mark_groups) 49 | return rebulk 50 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/part.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | part property 5 | """ 6 | from rebulk.remodule import re 7 | 8 | from rebulk import Rebulk 9 | from ..common import dash 10 | from ..common.validators import seps_surround, int_coercable, compose 11 | from ..common.numeral import numeral, parse_numeral 12 | from ...reutils import build_or_pattern 13 | 14 | 15 | def part(): 16 | """ 17 | Builder for rebulk object. 18 | :return: Created Rebulk object 19 | :rtype: Rebulk 20 | """ 21 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash], validator={'__parent__': seps_surround}) 22 | 23 | prefixes = ['pt', 'part'] 24 | 25 | def validate_roman(match): 26 | """ 27 | Validate a roman match if surrounded by separators 28 | :param match: 29 | :type match: 30 | :return: 31 | :rtype: 32 | """ 33 | if int_coercable(match.raw): 34 | return True 35 | return seps_surround(match) 36 | 37 | rebulk.regex(build_or_pattern(prefixes) + r'-?(?P' + numeral + r')', 38 | prefixes=prefixes, validate_all=True, private_parent=True, children=True, formatter=parse_numeral, 39 | validator={'part': compose(validate_roman, lambda m: 0 < m.value < 100)}) 40 | 41 | return rebulk 42 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/film.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | film property 5 | """ 6 | from rebulk import Rebulk, AppendMatch, Rule 7 | from rebulk.remodule import re 8 | 9 | from ..common.formatters import cleanup 10 | from ..common.validators import seps_surround 11 | 12 | 13 | def film(): 14 | """ 15 | Builder for rebulk object. 16 | :return: Created Rebulk object 17 | :rtype: Rebulk 18 | """ 19 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, validate_all=True, validator={'__parent__': seps_surround}) 20 | 21 | rebulk.regex(r'f(\d{1,2})', name='film', private_parent=True, children=True, formatter=int) 22 | 23 | rebulk.rules(FilmTitleRule) 24 | 25 | return rebulk 26 | 27 | 28 | class FilmTitleRule(Rule): 29 | """ 30 | Rule to find out film_title (hole after film property 31 | """ 32 | consequence = AppendMatch 33 | 34 | properties = {'film_title': [None]} 35 | 36 | def when(self, matches, context): 37 | bonus_number = matches.named('film', lambda match: not match.private, index=0) 38 | if bonus_number: 39 | filepath = matches.markers.at_match(bonus_number, lambda marker: marker.name == 'path', 0) 40 | hole = matches.holes(filepath.start, bonus_number.start + 1, formatter=cleanup, index=0) 41 | if hole and hole.value: 42 | hole.name = 'film_title' 43 | return hole 44 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | # VideoSort post-processing script for NZBGet. 2 | # 3 | # Copyright (C) 2013-2020 Andrey Prygunkov 4 | # 5 | # This program is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with subliminal. If not, see . 17 | # 18 | 19 | # Sort movies and tv shows. 20 | # 21 | # This is a script for downloaded TV shows and movies. It uses scene-standard 22 | # naming conventions to match TV shows and movies and rename/move/sort/organize 23 | # them as you like. 24 | # 25 | # The script relies on python library "guessit" (http://guessit.readthedocs.org) 26 | # to extract information from file names and includes portions of code from 27 | # "SABnzbd+" (http://sabnzbd.org). 28 | # 29 | # Info about pp-script: 30 | # Author: Andrey Prygunkov (nzbget@gmail.com). 31 | # Web-site: https://github.com/nzbget/VideoSort. 32 | # PP-Script Version: see . 33 | -------------------------------------------------------------------------------- /lib/rebulk/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Debug tools. 5 | 6 | Can be configured by changing values of those variable. 7 | 8 | DEBUG = False 9 | Enable this variable to activate debug features (like defined_at parameters). It can slow down Rebulk 10 | 11 | LOG_LEVEL = 0 12 | Default log level of generated rebulk logs. 13 | """ 14 | 15 | import inspect 16 | import logging 17 | import os 18 | from collections import namedtuple 19 | 20 | 21 | DEBUG = False 22 | LOG_LEVEL = logging.DEBUG 23 | 24 | 25 | class Frame(namedtuple('Frame', ['lineno', 'package', 'name', 'filename'])): 26 | """ 27 | Stack frame representation. 28 | """ 29 | __slots__ = () 30 | 31 | def __repr__(self): 32 | return "%s#L%s" % (os.path.basename(self.filename), self.lineno) 33 | 34 | 35 | def defined_at(): 36 | """ 37 | Get definition location of a pattern or a match (outside of rebulk package). 38 | :return: 39 | :rtype: 40 | """ 41 | if DEBUG: 42 | frame = inspect.currentframe() 43 | while frame: 44 | try: 45 | if frame.f_globals['__package__'] != __package__: 46 | break 47 | except KeyError: # pragma:no cover 48 | # If package is missing, consider we are in. Workaround for python 3.3. 49 | break 50 | frame = frame.f_back 51 | ret = Frame(frame.f_lineno, 52 | frame.f_globals.get('__package__'), 53 | frame.f_globals.get('__name__'), 54 | frame.f_code.co_filename) 55 | del frame 56 | return ret 57 | -------------------------------------------------------------------------------- /lib/guessit/rules/common/expected.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Expected property factory 5 | """ 6 | import re 7 | 8 | from rebulk import Rebulk 9 | from rebulk.utils import find_all 10 | 11 | from . import dash, seps 12 | 13 | 14 | def build_expected_function(context_key): 15 | """ 16 | Creates a expected property function 17 | :param context_key: 18 | :type context_key: 19 | :param cleanup: 20 | :type cleanup: 21 | :return: 22 | :rtype: 23 | """ 24 | 25 | def expected(input_string, context): 26 | """ 27 | Expected property functional pattern. 28 | :param input_string: 29 | :type input_string: 30 | :param context: 31 | :type context: 32 | :return: 33 | :rtype: 34 | """ 35 | ret = [] 36 | for search in context.get(context_key): 37 | if search.startswith('re:'): 38 | search = search[3:] 39 | search = search.replace(' ', '-') 40 | matches = Rebulk().regex(search, abbreviations=[dash], flags=re.IGNORECASE) \ 41 | .matches(input_string, context) 42 | for match in matches: 43 | ret.append(match.span) 44 | else: 45 | value = search 46 | for sep in seps: 47 | input_string = input_string.replace(sep, ' ') 48 | search = search.replace(sep, ' ') 49 | for start in find_all(input_string, search, ignore_case=True): 50 | ret.append({'start': start, 'end': start + len(search), 'value': value}) 51 | return ret 52 | 53 | return expected 54 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/bonus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | bonus property 5 | """ 6 | from rebulk.remodule import re 7 | 8 | from rebulk import Rebulk, AppendMatch, Rule 9 | 10 | from .title import TitleFromPosition 11 | from ..common.formatters import cleanup 12 | from ..common.validators import seps_surround 13 | 14 | 15 | def bonus(): 16 | """ 17 | Builder for rebulk object. 18 | :return: Created Rebulk object 19 | :rtype: Rebulk 20 | """ 21 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE) 22 | 23 | rebulk.regex(r'x(\d+)', name='bonus', private_parent=True, children=True, formatter=int, 24 | validator={'__parent__': lambda match: seps_surround}, 25 | conflict_solver=lambda match, conflicting: match 26 | if conflicting.name in ['video_codec', 'episode'] and 'bonus-conflict' not in conflicting.tags 27 | else '__default__') 28 | 29 | rebulk.rules(BonusTitleRule) 30 | 31 | return rebulk 32 | 33 | 34 | class BonusTitleRule(Rule): 35 | """ 36 | Find bonus title after bonus. 37 | """ 38 | dependency = TitleFromPosition 39 | consequence = AppendMatch 40 | 41 | properties = {'bonus_title': [None]} 42 | 43 | def when(self, matches, context): 44 | bonus_number = matches.named('bonus', lambda match: not match.private, index=0) 45 | if bonus_number: 46 | filepath = matches.markers.at_match(bonus_number, lambda marker: marker.name == 'path', 0) 47 | hole = matches.holes(bonus_number.end, filepath.end + 1, formatter=cleanup, index=0) 48 | if hole and hole.value: 49 | hole.name = 'bonus_title' 50 | return hole 51 | -------------------------------------------------------------------------------- /lib/dateutil/LICENSE: -------------------------------------------------------------------------------- 1 | dateutil - Extensions to the standard Python datetime module. 2 | 3 | Copyright (c) 2003-2011 - Gustavo Niemeyer 4 | Copyright (c) 2012-2014 - Tomi Pieviläinen 5 | Copyright (c) 2014 - Yaron de Leeuw 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 25 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /lib/babelfish/converters/opensubtitles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from . import LanguageReverseConverter, CaseInsensitiveDict 9 | from ..exceptions import LanguageReverseError 10 | from ..language import language_converters 11 | 12 | 13 | class OpenSubtitlesConverter(LanguageReverseConverter): 14 | def __init__(self): 15 | self.alpha3b_converter = language_converters['alpha3b'] 16 | self.alpha2_converter = language_converters['alpha2'] 17 | self.to_opensubtitles = {('por', 'BR'): 'pob', ('gre', None): 'ell', ('srp', None): 'scc', ('srp', 'ME'): 'mne'} 18 | self.from_opensubtitles = CaseInsensitiveDict({'pob': ('por', 'BR'), 'pb': ('por', 'BR'), 'ell': ('ell', None), 19 | 'scc': ('srp', None), 'mne': ('srp', 'ME')}) 20 | self.codes = (self.alpha2_converter.codes | self.alpha3b_converter.codes | set(['pob', 'pb', 'scc', 'mne'])) 21 | 22 | def convert(self, alpha3, country=None, script=None): 23 | alpha3b = self.alpha3b_converter.convert(alpha3, country, script) 24 | if (alpha3b, country) in self.to_opensubtitles: 25 | return self.to_opensubtitles[(alpha3b, country)] 26 | return alpha3b 27 | 28 | def reverse(self, opensubtitles): 29 | if opensubtitles in self.from_opensubtitles: 30 | return self.from_opensubtitles[opensubtitles] 31 | for conv in [self.alpha3b_converter, self.alpha2_converter]: 32 | try: 33 | return conv.reverse(opensubtitles) 34 | except LanguageReverseError: 35 | pass 36 | raise LanguageReverseError(opensubtitles) 37 | -------------------------------------------------------------------------------- /lib/rebulk/validators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Validator functions to use in patterns. 5 | 6 | All those function have last argument as match, so it's possible to use functools.partial to bind previous arguments. 7 | """ 8 | 9 | 10 | def chars_before(chars, match): 11 | """ 12 | Validate the match if left character is in a given sequence. 13 | 14 | :param chars: 15 | :type chars: 16 | :param match: 17 | :type match: 18 | :return: 19 | :rtype: 20 | """ 21 | if match.start <= 0: 22 | return True 23 | return match.input_string[match.start - 1] in chars 24 | 25 | 26 | def chars_after(chars, match): 27 | """ 28 | Validate the match if right character is in a given sequence. 29 | 30 | :param chars: 31 | :type chars: 32 | :param match: 33 | :type match: 34 | :return: 35 | :rtype: 36 | """ 37 | if match.end >= len(match.input_string): 38 | return True 39 | return match.input_string[match.end] in chars 40 | 41 | 42 | def chars_surround(chars, match): 43 | """ 44 | Validate the match if surrounding characters are in a given sequence. 45 | 46 | :param chars: 47 | :type chars: 48 | :param match: 49 | :type match: 50 | :return: 51 | :rtype: 52 | """ 53 | return chars_before(chars, match) and chars_after(chars, match) 54 | 55 | 56 | def validators(*chained_validators): 57 | """ 58 | Creates a validator chain from several validator functions. 59 | 60 | :param chained_validators: 61 | :type chained_validators: 62 | :return: 63 | :rtype: 64 | """ 65 | def validator_chain(match): # pylint:disable=missing-docstring 66 | for chained_validator in chained_validators: 67 | if not chained_validator(match): 68 | return False 69 | return True 70 | return validator_chain 71 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/edition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | edition property 5 | """ 6 | from rebulk.remodule import re 7 | 8 | from rebulk import Rebulk 9 | from ..common import dash 10 | from ..common.validators import seps_surround 11 | 12 | 13 | def edition(): 14 | """ 15 | Builder for rebulk object. 16 | :return: Created Rebulk object 17 | :rtype: Rebulk 18 | """ 19 | rebulk = Rebulk().regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]).string_defaults(ignore_case=True) 20 | rebulk.defaults(name='edition', validator=seps_surround) 21 | 22 | rebulk.regex('collector', 'collector-edition', 'edition-collector', value='Collector Edition') 23 | rebulk.regex('special-edition', 'edition-special', value='Special Edition', 24 | conflict_solver=lambda match, other: other 25 | if other.name == 'episode_details' and other.value == 'Special' 26 | else '__default__') 27 | rebulk.string('se', value='Special Edition', tags='has-neighbor') 28 | rebulk.regex('criterion-edition', 'edition-criterion', value='Criterion Edition') 29 | rebulk.regex('deluxe', 'deluxe-edition', 'edition-deluxe', value='Deluxe Edition') 30 | rebulk.regex('limited', 'limited-edition', value='Limited Edition', tags=['has-neighbor', 'release-group-prefix']) 31 | rebulk.regex(r'theatrical-cut', r'theatrical-edition', r'theatrical', value='Theatrical Edition') 32 | rebulk.regex(r"director'?s?-cut", r"director'?s?-cut-edition", r"edition-director'?s?-cut", 'DC', 33 | value="Director's Cut") 34 | rebulk.regex('extended', 'extended-?cut', 'extended-?version', 35 | value='Extended', tags=['has-neighbor', 'release-group-prefix']) 36 | rebulk.regex('alternat(e|ive)(?:-?Cut)?', value='Alternative Cut', tags=['has-neighbor', 'release-group-prefix']) 37 | for value in ('Remastered', 'Uncensored', 'Uncut', 'Unrated'): 38 | rebulk.string(value, value=value, tags=['has-neighbor', 'release-group-prefix']) 39 | rebulk.string('Festival', value='Festival', tags=['has-neighbor-before', 'has-neighbor-after']) 40 | 41 | return rebulk 42 | -------------------------------------------------------------------------------- /lib/guessit/rules/properties/type.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | type property 5 | """ 6 | from rebulk import CustomRule, Rebulk, POST_PROCESS 7 | from rebulk.match import Match 8 | 9 | from ...rules.processors import Processors 10 | 11 | 12 | def _type(matches, value): 13 | """ 14 | Define type match with given value. 15 | :param matches: 16 | :param value: 17 | :return: 18 | """ 19 | matches.append(Match(len(matches.input_string), len(matches.input_string), name='type', value=value)) 20 | 21 | 22 | def type_(): 23 | """ 24 | Builder for rebulk object. 25 | :return: Created Rebulk object 26 | :rtype: Rebulk 27 | """ 28 | return Rebulk().rules(TypeProcessor) 29 | 30 | 31 | class TypeProcessor(CustomRule): 32 | """ 33 | Post processor to find file type based on all others found matches. 34 | """ 35 | priority = POST_PROCESS 36 | 37 | dependency = Processors 38 | 39 | properties = {'type': ['episode', 'movie']} 40 | 41 | def when(self, matches, context): # pylint:disable=too-many-return-statements 42 | option_type = context.get('type', None) 43 | if option_type: 44 | return option_type 45 | 46 | episode = matches.named('episode') 47 | season = matches.named('season') 48 | episode_details = matches.named('episode_details') 49 | 50 | if episode or season or episode_details: 51 | return 'episode' 52 | 53 | film = matches.named('film') 54 | if film: 55 | return 'movie' 56 | 57 | year = matches.named('year') 58 | date = matches.named('date') 59 | 60 | if date and not year: 61 | return 'episode' 62 | 63 | bonus = matches.named('bonus') 64 | if bonus and not year: 65 | return 'episode' 66 | 67 | crc32 = matches.named('crc32') 68 | anime_release_group = matches.named('release_group', lambda match: 'anime' in match.tags) 69 | if crc32 and anime_release_group: 70 | return 'episode' 71 | 72 | return 'movie' 73 | 74 | def then(self, matches, when_response, context): 75 | _type(matches, when_response) 76 | -------------------------------------------------------------------------------- /lib/guessit/rules/common/comparators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Comparators 5 | """ 6 | try: 7 | from functools import cmp_to_key 8 | except ImportError: 9 | from ...backports import cmp_to_key 10 | 11 | 12 | def marker_comparator_predicate(match): 13 | """ 14 | Match predicate used in comparator 15 | """ 16 | return not match.private and \ 17 | match.name not in ['proper_count', 'title', 'episode_title', 'alternative_title'] and \ 18 | not (match.name == 'container' and 'extension' in match.tags) 19 | 20 | 21 | def marker_weight(matches, marker, predicate): 22 | """ 23 | Compute the comparator weight of a marker 24 | :param matches: 25 | :param marker: 26 | :param predicate: 27 | :return: 28 | """ 29 | return len(set(match.name for match in matches.range(*marker.span, predicate=predicate))) 30 | 31 | 32 | def marker_comparator(matches, markers, predicate): 33 | """ 34 | Builds a comparator that returns markers sorted from the most valuable to the less. 35 | 36 | Take the parts where matches count is higher, then when length is higher, then when position is at left. 37 | 38 | :param matches: 39 | :type matches: 40 | :param markers: 41 | :param predicate: 42 | :return: 43 | :rtype: 44 | """ 45 | 46 | def comparator(marker1, marker2): 47 | """ 48 | The actual comparator function. 49 | """ 50 | matches_count = marker_weight(matches, marker2, predicate) - marker_weight(matches, marker1, predicate) 51 | if matches_count: 52 | return matches_count 53 | len_diff = len(marker2) - len(marker1) 54 | if len_diff: 55 | return len_diff 56 | return markers.index(marker2) - markers.index(marker1) 57 | 58 | return comparator 59 | 60 | 61 | def marker_sorted(markers, matches, predicate=marker_comparator_predicate): 62 | """ 63 | Sort markers from matches, from the most valuable to the less. 64 | 65 | :param markers: 66 | :type markers: 67 | :param matches: 68 | :type matches: 69 | :param predicate: 70 | :return: 71 | :rtype: 72 | """ 73 | return sorted(markers, key=cmp_to_key(marker_comparator(matches, markers, predicate=predicate))) 74 | -------------------------------------------------------------------------------- /lib/babelfish/script.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2013 the BabelFish authors. All rights reserved. 4 | # Use of this source code is governed by the 3-clause BSD license 5 | # that can be found in the LICENSE file. 6 | # 7 | from __future__ import unicode_literals 8 | from collections import namedtuple 9 | from pkg_resources import resource_stream # @UnresolvedImport 10 | from . import basestr 11 | 12 | #: Script code to script name mapping 13 | SCRIPTS = {} 14 | 15 | #: List of countries in the ISO-15924 as namedtuple of code, number, name, french_name, pva and date 16 | SCRIPT_MATRIX = [] 17 | 18 | #: The namedtuple used in the :data:`SCRIPT_MATRIX` 19 | IsoScript = namedtuple('IsoScript', ['code', 'number', 'name', 'french_name', 'pva', 'date']) 20 | 21 | f = resource_stream('babelfish', 'data/iso15924-utf8-20131012.txt') 22 | f.readline() 23 | for l in f: 24 | l = l.decode('utf-8').strip() 25 | if not l or l.startswith('#'): 26 | continue 27 | script = IsoScript._make(l.split(';')) 28 | SCRIPT_MATRIX.append(script) 29 | SCRIPTS[script.code] = script.name 30 | f.close() 31 | 32 | 33 | class Script(object): 34 | """A human writing system 35 | 36 | A script is represented by a 4-letter code from the ISO-15924 standard 37 | 38 | :param string script: 4-letter ISO-15924 script code 39 | 40 | """ 41 | def __init__(self, script): 42 | if script not in SCRIPTS: 43 | raise ValueError('%r is not a valid script' % script) 44 | 45 | #: ISO-15924 4-letter script code 46 | self.code = script 47 | 48 | @property 49 | def name(self): 50 | """English name of the script""" 51 | return SCRIPTS[self.code] 52 | 53 | def __getstate__(self): 54 | return self.code 55 | 56 | def __setstate__(self, state): 57 | self.code = state 58 | 59 | def __hash__(self): 60 | return hash(self.code) 61 | 62 | def __eq__(self, other): 63 | if isinstance(other, basestr): 64 | return self.code == other 65 | if not isinstance(other, Script): 66 | return False 67 | return self.code == other.code 68 | 69 | def __ne__(self, other): 70 | return not self == other 71 | 72 | def __repr__(self): 73 | return '