Subliminal update
This commit is contained in:
1 parent
4dfd8b4cd5
commit
67c87444de
163 files changed
+22377
-804
No files matched your search
@@ -18,12 +18,58 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
__version__ = '0.4.2'
|
||||
|
||||
__version__ = '0.5.2'
|
||||
__all__ = ['Guess', 'Language',
|
||||
'guess_file_info', 'guess_video_info',
|
||||
'guess_movie_info', 'guess_episode_info']
|
||||
|
||||
|
||||
# Do python3 detection before importing any other module, to be sure that
|
||||
# it will then always be available
|
||||
# with code from http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/
|
||||
import sys
|
||||
if sys.version_info[0] >= 3:
|
||||
PY3 = True
|
||||
unicode_text_type = str
|
||||
native_text_type = str
|
||||
base_text_type = str
|
||||
def u(x):
|
||||
return str(x)
|
||||
def s(x):
|
||||
return x
|
||||
class UnicodeMixin(object):
|
||||
__str__ = lambda x: x.__unicode__()
|
||||
import binascii
|
||||
def to_hex(x):
|
||||
return binascii.hexlify(x).decode('utf-8')
|
||||
|
||||
else:
|
||||
PY3 = False
|
||||
__all__ = [ str(s) for s in __all__ ] # fix imports for python2
|
||||
unicode_text_type = unicode
|
||||
native_text_type = str
|
||||
base_text_type = basestring
|
||||
def u(x):
|
||||
if isinstance(x, str):
|
||||
return x.decode('utf-8')
|
||||
return unicode(x)
|
||||
def s(x):
|
||||
if isinstance(x, unicode):
|
||||
return x.encode('utf-8')
|
||||
if isinstance(x, list):
|
||||
return [ s(y) for y in x ]
|
||||
if isinstance(x, tuple):
|
||||
return tuple(s(y) for y in x)
|
||||
if isinstance(x, dict):
|
||||
return dict((s(key), s(value)) for key, value in x.items())
|
||||
return x
|
||||
class UnicodeMixin(object):
|
||||
__str__ = lambda x: unicode(x).encode('utf-8')
|
||||
def to_hex(x):
|
||||
return x.encode('hex')
|
||||
|
||||
|
||||
from guessit.guess import Guess, merge_all
|
||||
from guessit.language import Language
|
||||
from guessit.matcher import IterativeMatcher
|
||||
@@ -32,6 +78,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
@@ -45,7 +92,7 @@ def guess_file_info(filename, filetype, info=None):
|
||||
"""info can contain the names of the various plugins, such as 'filename' to
|
||||
detect filename info, or 'hash_md5' to get the md5 hash of the file.
|
||||
|
||||
>>> guess_file_info('test/dummy.srt', 'autodetect', info = ['hash_md5', 'hash_sha1'])
|
||||
>>> guess_file_info('tests/dummy.srt', 'autodetect', info = ['hash_md5', 'hash_sha1'])
|
||||
{'hash_md5': 'e781de9b94ba2753a8e2945b2c0a123d', 'hash_sha1': 'bfd18e2f4e5d59775c2bc14d80f56971891ed620'}
|
||||
"""
|
||||
result = []
|
||||
@@ -54,7 +101,7 @@ def guess_file_info(filename, filetype, info=None):
|
||||
if info is None:
|
||||
info = ['filename']
|
||||
|
||||
if isinstance(info, basestring):
|
||||
if isinstance(info, base_text_type):
|
||||
info = [info]
|
||||
|
||||
for infotype in info:
|
||||
@@ -67,7 +114,7 @@ def guess_file_info(filename, filetype, info=None):
|
||||
try:
|
||||
result.append(Guess({'hash_mpc': hash_file(filename)},
|
||||
confidence=1.0))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
log.warning('Could not compute MPC-style hash because: %s' % e)
|
||||
|
||||
elif infotype == 'hash_ed2k':
|
||||
@@ -75,7 +122,7 @@ def guess_file_info(filename, filetype, info=None):
|
||||
try:
|
||||
result.append(Guess({'hash_ed2k': hash_file(filename)},
|
||||
confidence=1.0))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
log.warning('Could not compute ed2k hash because: %s' % e)
|
||||
|
||||
elif infotype.startswith('hash_'):
|
||||
@@ -97,17 +144,28 @@ def guess_file_info(filename, filetype, info=None):
|
||||
hasherobjs = dict(hashers).values()
|
||||
|
||||
with open(filename, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(blocksize), ''):
|
||||
chunk = f.read(blocksize)
|
||||
while chunk:
|
||||
for hasher in hasherobjs:
|
||||
hasher.update(chunk)
|
||||
chunk = f.read(blocksize)
|
||||
|
||||
for infotype, hasher in hashers:
|
||||
result.append(Guess({infotype: hasher.hexdigest()},
|
||||
confidence=1.0))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
log.warning('Could not compute hash because: %s' % e)
|
||||
|
||||
return merge_all(result)
|
||||
result = merge_all(result)
|
||||
|
||||
# last minute adjustments
|
||||
|
||||
# if country is in the guessed properties, make it part of the filename
|
||||
if 'country' in result:
|
||||
result['series'] += ' (%s)' % result['country'].alpha2.upper()
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def guess_video_info(filename, info=None):
|
||||
|
||||
+8
-11
@@ -19,10 +19,12 @@
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import fileutils
|
||||
from guessit.textutils import to_unicode
|
||||
from guessit import UnicodeMixin, base_text_type, u
|
||||
from guessit.fileutils import load_file_in_same_dir
|
||||
import logging
|
||||
|
||||
__all__ = [ 'Country' ]
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -32,8 +34,7 @@ log = logging.getLogger(__name__)
|
||||
# "An English name, an alpha-2 code (when given),
|
||||
# an alpha-3 code (when given), a numeric code, and an ISO 31666-2 code
|
||||
# are all separated by pipe (|) characters."
|
||||
_iso3166_contents = fileutils.load_file_in_same_dir(__file__,
|
||||
'ISO-3166-1_utf8.txt').decode('utf-8')
|
||||
_iso3166_contents = load_file_in_same_dir(__file__, 'ISO-3166-1_utf8.txt')
|
||||
|
||||
country_matrix = [ l.strip().split('|')
|
||||
for l in _iso3166_contents.strip().split('\n') ]
|
||||
@@ -59,7 +60,7 @@ country_alpha3_to_alpha2 = dict((c[2].lower(), c[1].lower()) for c in country_ma
|
||||
|
||||
|
||||
|
||||
class Country(object):
|
||||
class Country(UnicodeMixin):
|
||||
"""This class represents a country.
|
||||
|
||||
You can initialize it with pretty much anything, as it knows conversion
|
||||
@@ -67,7 +68,7 @@ class Country(object):
|
||||
"""
|
||||
|
||||
def __init__(self, country, strict=False):
|
||||
country = to_unicode(country.strip().lower())
|
||||
country = u(country.strip().lower())
|
||||
self.alpha3 = country_to_alpha3.get(country)
|
||||
|
||||
if self.alpha3 is None and strict:
|
||||
@@ -93,7 +94,7 @@ class Country(object):
|
||||
if isinstance(other, Country):
|
||||
return self.alpha3 == other.alpha3
|
||||
|
||||
if isinstance(other, basestring):
|
||||
if isinstance(other, base_text_type):
|
||||
try:
|
||||
return self == Country(other)
|
||||
except ValueError:
|
||||
@@ -107,9 +108,5 @@ class Country(object):
|
||||
def __unicode__(self):
|
||||
return self.english_name
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __repr__(self):
|
||||
return 'Country(%s)' % self.english_name
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import datetime
|
||||
import re
|
||||
|
||||
def valid_year(year):
|
||||
return 1920 < year < datetime.date.today().year + 5
|
||||
|
||||
def search_year(string):
|
||||
"""Looks for year patterns, and if found return the year and group span.
|
||||
@@ -40,7 +43,7 @@ def search_year(string):
|
||||
match = re.search(r'[^0-9]([0-9]{4})[^0-9]', string)
|
||||
if match:
|
||||
year = int(match.group(1))
|
||||
if 1920 < year < datetime.date.today().year + 5:
|
||||
if valid_year(year):
|
||||
return (year, match.span(1))
|
||||
|
||||
return (None, None)
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import s, u
|
||||
import os.path
|
||||
import zipfile
|
||||
|
||||
@@ -31,10 +33,10 @@ def split_path(path):
|
||||
- the drive letter on Windows systems (eg: r'C:\')
|
||||
- the mount point '\\' on Windows systems (eg: r'\\host\share')
|
||||
|
||||
>>> split_path('/usr/bin/smewt')
|
||||
>>> s(split_path('/usr/bin/smewt'))
|
||||
['/', 'usr', 'bin', 'smewt']
|
||||
|
||||
>>> split_path('relative_path/to/my_folder/')
|
||||
>>> s(split_path('relative_path/to/my_folder/'))
|
||||
['relative_path', 'to', 'my_folder']
|
||||
|
||||
"""
|
||||
@@ -65,7 +67,7 @@ def split_path(path):
|
||||
def file_in_same_dir(ref_file, desired_file):
|
||||
"""Return the path for a file in the same dir as a given reference file.
|
||||
|
||||
>>> file_in_same_dir('~/smewt/smewt.db', 'smewt.settings')
|
||||
>>> s(file_in_same_dir('~/smewt/smewt.db', 'smewt.settings'))
|
||||
'~/smewt/smewt.settings'
|
||||
|
||||
"""
|
||||
@@ -82,4 +84,4 @@ def load_file_in_same_dir(ref_file, filename):
|
||||
zfile = zipfile.ZipFile(zfilename)
|
||||
return zfile.read('/'.join(path[i + 1:]))
|
||||
|
||||
return open(os.path.join(*path)).read()
|
||||
return u(open(os.path.join(*path)).read())
|
||||
+22
-20
@@ -18,6 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import UnicodeMixin, s, u, base_text_type
|
||||
from guessit.language import Language
|
||||
from guessit.country import Country
|
||||
import json
|
||||
import datetime
|
||||
import logging
|
||||
@@ -25,7 +29,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Guess(dict):
|
||||
class Guess(UnicodeMixin, dict):
|
||||
"""A Guess is a dictionary which has an associated confidence for each of
|
||||
its values.
|
||||
|
||||
@@ -44,23 +48,21 @@ class Guess(dict):
|
||||
for prop in self:
|
||||
self._confidence[prop] = confidence
|
||||
|
||||
def to_utf8_dict(self):
|
||||
from guessit.language import Language
|
||||
|
||||
def to_dict(self):
|
||||
data = dict(self)
|
||||
for prop, value in data.items():
|
||||
if isinstance(value, datetime.date):
|
||||
data[prop] = value.isoformat()
|
||||
elif isinstance(value, Language):
|
||||
data[prop] = str(value)
|
||||
elif isinstance(value, unicode):
|
||||
data[prop] = value.encode('utf-8')
|
||||
elif isinstance(value, (Language, Country, base_text_type)):
|
||||
data[prop] = u(value)
|
||||
elif isinstance(value, list):
|
||||
data[prop] = [str(x) for x in value]
|
||||
data[prop] = [u(x) for x in value]
|
||||
|
||||
return data
|
||||
|
||||
def nice_string(self):
|
||||
data = self.to_utf8_dict()
|
||||
data = self.to_dict()
|
||||
|
||||
parts = json.dumps(data, indent=4).split('\n')
|
||||
for i, p in enumerate(parts):
|
||||
@@ -72,8 +74,8 @@ class Guess(dict):
|
||||
|
||||
return '\n'.join(parts)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.to_utf8_dict())
|
||||
def __unicode__(self):
|
||||
return u(self.to_dict())
|
||||
|
||||
def confidence(self, prop):
|
||||
return self._confidence.get(prop, -1)
|
||||
@@ -138,16 +140,16 @@ def choose_string(g1, g2):
|
||||
differ very little, such as one string being the other one with the 'the' word
|
||||
prepended to it.
|
||||
|
||||
>>> choose_string(('Hello', 0.75), ('World', 0.5))
|
||||
>>> s(choose_string(('Hello', 0.75), ('World', 0.5)))
|
||||
('Hello', 0.25)
|
||||
|
||||
>>> choose_string(('Hello', 0.5), ('hello', 0.5))
|
||||
>>> s(choose_string(('Hello', 0.5), ('hello', 0.5)))
|
||||
('Hello', 0.75)
|
||||
|
||||
>>> choose_string(('Hello', 0.4), ('Hello World', 0.4))
|
||||
>>> s(choose_string(('Hello', 0.4), ('Hello World', 0.4)))
|
||||
('Hello', 0.64)
|
||||
|
||||
>>> choose_string(('simpsons', 0.5), ('The Simpsons', 0.5))
|
||||
>>> s(choose_string(('simpsons', 0.5), ('The Simpsons', 0.5)))
|
||||
('The Simpsons', 0.75)
|
||||
|
||||
"""
|
||||
@@ -285,12 +287,12 @@ def merge_all(guesses, append=None):
|
||||
You can specify a list of properties that should be appended into a list
|
||||
instead of being merged.
|
||||
|
||||
>>> merge_all([ Guess({ 'season': 2 }, confidence = 0.6),
|
||||
... Guess({ 'episodeNumber': 13 }, confidence = 0.8) ])
|
||||
>>> s(merge_all([ Guess({ 'season': 2 }, confidence = 0.6),
|
||||
... Guess({ 'episodeNumber': 13 }, confidence = 0.8) ]))
|
||||
{'season': 2, 'episodeNumber': 13}
|
||||
|
||||
>>> merge_all([ Guess({ 'episodeNumber': 27 }, confidence = 0.02),
|
||||
... Guess({ 'season': 1 }, confidence = 0.2) ])
|
||||
>>> s(merge_all([ Guess({ 'episodeNumber': 27 }, confidence = 0.02),
|
||||
... Guess({ 'season': 1 }, confidence = 0.2) ]))
|
||||
{'season': 1}
|
||||
|
||||
"""
|
||||
@@ -320,7 +322,7 @@ def merge_all(guesses, append=None):
|
||||
result.update_highest_confidence(g)
|
||||
|
||||
# delete very unlikely values
|
||||
for p in result.keys():
|
||||
for p in list(result.keys()):
|
||||
if result.confidence(p) < 0.05:
|
||||
del result[p]
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import s, to_hex
|
||||
import hashlib
|
||||
import os.path
|
||||
|
||||
@@ -25,7 +27,7 @@ import os.path
|
||||
def hash_file(filename):
|
||||
"""Returns the ed2k hash of a given file.
|
||||
|
||||
>>> hash_file('test/dummy.srt')
|
||||
>>> s(hash_file('tests/dummy.srt'))
|
||||
'ed2k://|file|dummy.srt|44|1CA0B9DED3473B926AA93A0A546138BB|/'
|
||||
"""
|
||||
return 'ed2k://|file|%s|%d|%s|/' % (os.path.basename(filename),
|
||||
@@ -58,6 +60,6 @@ def hash_filehash(filename):
|
||||
a = gen(f)
|
||||
hashes = [md4_hash(data).digest() for data in a]
|
||||
if len(hashes) == 1:
|
||||
return hashes[0].encode("hex")
|
||||
return to_hex(hashes[0])
|
||||
else:
|
||||
return md4_hash(reduce(lambda a, d: a + d, hashes, "")).hexd
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import struct
|
||||
import os
|
||||
|
||||
|
||||
+17
-22
@@ -19,9 +19,9 @@
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import fileutils
|
||||
from guessit import UnicodeMixin, base_text_type, u, s
|
||||
from guessit.fileutils import load_file_in_same_dir
|
||||
from guessit.country import Country
|
||||
from guessit.textutils import to_unicode
|
||||
import re
|
||||
import logging
|
||||
|
||||
@@ -39,8 +39,7 @@ log = logging.getLogger(__name__)
|
||||
# "An alpha-3 (bibliographic) code, an alpha-3 (terminologic) code (when given),
|
||||
# an alpha-2 code (when given), an English name, and a French name of a language
|
||||
# are all separated by pipe (|) characters."
|
||||
_iso639_contents = fileutils.load_file_in_same_dir(__file__,
|
||||
'ISO-639-2_utf-8.txt').decode('utf-8')
|
||||
_iso639_contents = load_file_in_same_dir(__file__, 'ISO-639-2_utf-8.txt')
|
||||
|
||||
# drop the BOM from the beginning of the file
|
||||
_iso639_contents = _iso639_contents[1:]
|
||||
@@ -116,7 +115,6 @@ lng_exceptions = { 'unknown': ('und', None),
|
||||
'cn': ('chi', None),
|
||||
'chs': ('chi', None),
|
||||
'jp': ('jpn', None),
|
||||
'scc': ('srp', None),
|
||||
'scr': ('hrv', None)
|
||||
}
|
||||
|
||||
@@ -137,7 +135,7 @@ def lang_set(languages, strict=False):
|
||||
return set(Language(l, strict=strict) for l in languages)
|
||||
|
||||
|
||||
class Language(object):
|
||||
class Language(UnicodeMixin):
|
||||
"""This class represents a human language.
|
||||
|
||||
You can initialize it with pretty much anything, as it knows conversion
|
||||
@@ -154,30 +152,30 @@ class Language(object):
|
||||
>>> Language('fr')
|
||||
Language(French)
|
||||
|
||||
>>> Language('eng').french_name
|
||||
u'anglais'
|
||||
>>> s(Language('eng').french_name)
|
||||
'anglais'
|
||||
|
||||
>>> Language('pt(br)').country.english_name
|
||||
u'Brazil'
|
||||
>>> s(Language('pt(br)').country.english_name)
|
||||
'Brazil'
|
||||
|
||||
>>> Language('Español (Latinoamérica)').country.english_name
|
||||
u'Latin America'
|
||||
>>> s(Language('Español (Latinoamérica)').country.english_name)
|
||||
'Latin America'
|
||||
|
||||
>>> Language('Spanish (Latin America)') == Language('Español (Latinoamérica)')
|
||||
True
|
||||
|
||||
>>> Language('zz', strict=False).english_name
|
||||
u'Undetermined'
|
||||
>>> s(Language('zz', strict=False).english_name)
|
||||
'Undetermined'
|
||||
|
||||
>>> Language('pt(br)').opensubtitles
|
||||
u'pob'
|
||||
>>> s(Language('pt(br)').opensubtitles)
|
||||
'pob'
|
||||
"""
|
||||
|
||||
_with_country_regexp = re.compile('(.*)\((.*)\)')
|
||||
_with_country_regexp2 = re.compile('(.*)-(.*)')
|
||||
|
||||
def __init__(self, language, country=None, strict=False, scheme=None):
|
||||
language = to_unicode(language.strip().lower())
|
||||
language = u(language.strip().lower())
|
||||
with_country = (Language._with_country_regexp.match(language) or
|
||||
Language._with_country_regexp2.match(language))
|
||||
if with_country:
|
||||
@@ -249,7 +247,7 @@ class Language(object):
|
||||
def opensubtitles(self):
|
||||
if self.lang == 'por' and self.country and self.country.alpha2 == 'br':
|
||||
return 'pob'
|
||||
elif self.lang in ['gre', 'eus', 'ice', 'srp']:
|
||||
elif self.lang in ['gre', 'srp']:
|
||||
return self.alpha3term
|
||||
return self.alpha3
|
||||
|
||||
@@ -266,7 +264,7 @@ class Language(object):
|
||||
if isinstance(other, Language):
|
||||
return self.lang == other.lang
|
||||
|
||||
if isinstance(other, basestring):
|
||||
if isinstance(other, base_text_type):
|
||||
try:
|
||||
return self == Language(other)
|
||||
except ValueError:
|
||||
@@ -286,9 +284,6 @@ class Language(object):
|
||||
else:
|
||||
return self.english_name
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __repr__(self):
|
||||
if self.country:
|
||||
return 'Language(%s, country=%s)' % (self.english_name, self.country)
|
||||
|
||||
+24
-16
@@ -18,8 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import PY3, u
|
||||
from guessit.matchtree import MatchTree
|
||||
from guessit.textutils import to_utf8
|
||||
from guessit.guess import (merge_similar_guesses, merge_all,
|
||||
choose_int, choose_string)
|
||||
import copy
|
||||
@@ -69,12 +70,12 @@ class IterativeMatcher(object):
|
||||
"""
|
||||
|
||||
valid_filetypes = ('autodetect', 'subtitle', 'video',
|
||||
'movie', 'moviesubtitle',
|
||||
'episode', 'episodesubtitle')
|
||||
'movie', 'moviesubtitle',
|
||||
'episode', 'episodesubtitle')
|
||||
if filetype not in valid_filetypes:
|
||||
raise ValueError("filetype needs to be one of %s" % valid_filetypes)
|
||||
if not isinstance(filename, unicode):
|
||||
log.debug('WARNING: given filename to matcher is not unicode...')
|
||||
if not PY3 and not isinstance(filename, unicode):
|
||||
log.warning('Given filename to matcher is not unicode...')
|
||||
|
||||
self.match_tree = MatchTree(filename)
|
||||
mtree = self.match_tree
|
||||
@@ -99,21 +100,26 @@ class IterativeMatcher(object):
|
||||
apply_transfo('split_explicit_groups')
|
||||
|
||||
# 4- try to match information for specific patterns
|
||||
# NOTE: order needs to comply to the following:
|
||||
# - website before language (eg: tvu.org.ru vs russian)
|
||||
# - language before episodes_rexps
|
||||
# - properties before language (eg: he-aac vs hebrew)
|
||||
# - release_group before properties (eg: XviD-?? vs xvid)
|
||||
if mtree.guess['type'] in ('episode', 'episodesubtitle'):
|
||||
strategy = ['guess_date', 'guess_video_rexps',
|
||||
'guess_episodes_rexps', 'guess_website',
|
||||
'guess_release_group', 'guess_properties',
|
||||
'guess_weak_episodes_rexps', 'guess_language']
|
||||
strategy = [ 'guess_date', 'guess_website', 'guess_release_group',
|
||||
'guess_properties', 'guess_language',
|
||||
'guess_video_rexps',
|
||||
'guess_episodes_rexps', 'guess_weak_episodes_rexps' ]
|
||||
else:
|
||||
strategy = ['guess_date', 'guess_video_rexps',
|
||||
'guess_website', 'guess_release_group',
|
||||
'guess_properties', 'guess_language']
|
||||
strategy = [ 'guess_date', 'guess_website', 'guess_release_group',
|
||||
'guess_properties', 'guess_language',
|
||||
'guess_video_rexps' ]
|
||||
|
||||
for name in strategy:
|
||||
apply_transfo(name)
|
||||
|
||||
# more guessers for both movies and episodes
|
||||
for name in ['guess_bonus_features']:
|
||||
for name in ['guess_bonus_features', 'guess_year', 'guess_country']:
|
||||
apply_transfo(name)
|
||||
|
||||
# split into '-' separated subgroups (with required separator chars
|
||||
@@ -125,13 +131,12 @@ class IterativeMatcher(object):
|
||||
if mtree.guess['type'] in ('episode', 'episodesubtitle'):
|
||||
apply_transfo('guess_episode_info_from_position')
|
||||
else:
|
||||
apply_transfo('guess_year')
|
||||
apply_transfo('guess_movie_title_from_position')
|
||||
|
||||
# 6- perform some post-processing steps
|
||||
apply_transfo('post_process')
|
||||
|
||||
log.debug('Found match tree:\n%s' % (to_utf8(unicode(mtree))))
|
||||
log.debug('Found match tree:\n%s' % u(mtree))
|
||||
|
||||
def matched(self):
|
||||
# we need to make a copy here, as the merge functions work in place and
|
||||
@@ -147,9 +152,12 @@ class IterativeMatcher(object):
|
||||
|
||||
for string_part in ('title', 'series', 'container', 'format',
|
||||
'releaseGroup', 'website', 'audioCodec',
|
||||
'videoCodec', 'screenSize', 'episodeFormat'):
|
||||
'videoCodec', 'screenSize', 'episodeFormat',
|
||||
'audioChannels'):
|
||||
merge_similar_guesses(parts, string_part, choose_string)
|
||||
|
||||
# 2- merge the rest, potentially discarding information not properly
|
||||
# merged before
|
||||
result = merge_all(parts,
|
||||
append=['language', 'subtitleLanguage', 'other'])
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from guessit import Guess
|
||||
from guessit.textutils import clean_string, str_fill, to_utf8
|
||||
from __future__ import unicode_literals
|
||||
from guessit import UnicodeMixin, base_text_type, Guess
|
||||
from guessit.textutils import clean_string, str_fill
|
||||
from guessit.patterns import group_delimiters
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseMatchTree(object):
|
||||
class BaseMatchTree(UnicodeMixin):
|
||||
"""A MatchTree represents the hierarchical split of a string into its
|
||||
constituent semantic groups."""
|
||||
|
||||
@@ -154,6 +155,7 @@ class BaseMatchTree(object):
|
||||
'extension': 'e',
|
||||
'format': 'f',
|
||||
'language': 'l',
|
||||
'country': 'C',
|
||||
'videoCodec': 'v',
|
||||
'audioCodec': 'a',
|
||||
'website': 'w',
|
||||
@@ -198,9 +200,6 @@ class BaseMatchTree(object):
|
||||
def __unicode__(self):
|
||||
return self.to_string()
|
||||
|
||||
def __str__(self):
|
||||
return to_utf8(unicode(self))
|
||||
|
||||
|
||||
class MatchTree(BaseMatchTree):
|
||||
"""The MatchTree contains a few "utility" methods which are not necessary
|
||||
@@ -218,7 +217,7 @@ class MatchTree(BaseMatchTree):
|
||||
return list(self._unidentified_leaves(valid))
|
||||
|
||||
def _leaves_containing(self, property_name):
|
||||
if isinstance(property_name, basestring):
|
||||
if isinstance(property_name, base_text_type):
|
||||
property_name = [ property_name ]
|
||||
|
||||
for leaf in self._leaves():
|
||||
|
||||
+18
-11
@@ -19,6 +19,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
subtitle_exts = [ 'srt', 'idx', 'sub', 'ssa', 'txt' ]
|
||||
|
||||
@@ -40,7 +42,10 @@ episode_rexps = [ # ... Season 2 ...
|
||||
(r'saison (?P<season>[0-9]+)', 1.0, (0, 0)),
|
||||
|
||||
# ... s02e13 ...
|
||||
(r'[Ss](?P<season>[0-9]{1,2}).{,3}(?P<episodeNumber>(?:[EeXx][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
|
||||
(r'[Ss](?P<season>[0-9]{1,2}).{,3}(?P<episodeNumber>(?:[Ee][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
|
||||
|
||||
# ... s03-x02 ...
|
||||
(r'[Ss](?P<season>[0-9]{1,2}).{,3}(?P<bonusNumber>(?:[Xx][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
|
||||
|
||||
# ... 2x13 ...
|
||||
(r'[^0-9](?P<season>[0-9]{1,2})(?P<episodeNumber>(?:[xX][0-9]{1,2})+)[^0-9]', 0.8, (1, -1)),
|
||||
@@ -53,18 +58,16 @@ episode_rexps = [ # ... Season 2 ...
|
||||
(r'(?P<episodeNumber>[0-9]{1,3})v[23]' + sep, 0.6, (0, 0)),
|
||||
|
||||
# ... ep 23 ...
|
||||
('ep' + sep + r'(?P<episodeNumber>[0-9]{1,2})[^0-9]', 0.7, (0, -1))
|
||||
('ep' + sep + r'(?P<episodeNumber>[0-9]{1,2})[^0-9]', 0.7, (0, -1)),
|
||||
|
||||
# ... e13 ... for a mini-series without a season number
|
||||
(r'e(?P<episodeNumber>[0-9]{1,2})[^0-9]', 0.6, (0, -1))
|
||||
|
||||
]
|
||||
|
||||
|
||||
weak_episode_rexps = [ # ... 213 or 0106 ...
|
||||
(sep + r'(?P<episodeNumber>[0-9]{1,4})' + sep, (1, -1)),
|
||||
|
||||
# ... 2x13 ...
|
||||
(sep + r'[^0-9](?P<season>[0-9]{1,2})\.(?P<episodeNumber>[0-9]{1,2})[^0-9]' + sep, (1, -1)),
|
||||
|
||||
# ... e13 ... for a mini-series without a season number
|
||||
(r'e(?P<episodeNumber>[0-9]{1,4})[^0-9]', (0, -1)),
|
||||
(sep + r'(?P<episodeNumber>[0-9]{2,4})' + sep, (1, -1))
|
||||
]
|
||||
|
||||
non_episode_title = [ 'extras', 'rip' ]
|
||||
@@ -102,7 +105,7 @@ unlikely_series = ['series']
|
||||
|
||||
properties = { 'format': [ 'DVDRip', 'HD-DVD', 'HDDVD', 'HDDVDRip', 'BluRay', 'Blu-ray', 'BDRip', 'BRRip',
|
||||
'HDRip', 'DVD', 'DVDivX', 'HDTV', 'DVB', 'DVBRip', 'PDTV', 'WEBRip',
|
||||
'DVDSCR', 'Screener', 'VHS', 'VIDEO_TS' ],
|
||||
'DVDSCR', 'Screener', 'VHS', 'VIDEO_TS', 'WEB-DL', 'WEBDL' ],
|
||||
|
||||
'screenSize': [ '720p', '720', '1080p', '1080' ],
|
||||
|
||||
@@ -116,7 +119,10 @@ properties = { 'format': [ 'DVDRip', 'HD-DVD', 'HDDVD', 'HDDVDRip', 'BluRay', 'B
|
||||
'CHD', 'ViTE', 'TLF', 'DEiTY', 'FLAiTE',
|
||||
'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS', ' FiNaLe',
|
||||
'UnSeeN', 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL',
|
||||
'HDBRiSe' ],
|
||||
'SiNNERS', 'DiRTY', 'REWARD', 'ECI', 'KiNGS', 'CLUE',
|
||||
'CtrlHD', 'POD', 'WiKi', 'DIMENSION', 'IMMERSE', 'FQM',
|
||||
'2HD', 'REPTiLE', 'CTU', 'HALCYON', 'EbP', 'SiTV', 'SAiNTS',
|
||||
'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV' ],
|
||||
|
||||
'episodeFormat': [ 'Minisode', 'Minisodes' ],
|
||||
|
||||
@@ -149,6 +155,7 @@ def find_properties(filename):
|
||||
property_synonyms = { 'DVD': [ 'DVDRip', 'VIDEO_TS' ],
|
||||
'HD-DVD': [ 'HDDVD', 'HDDVDRip' ],
|
||||
'BluRay': [ 'BDRip', 'BRRip', 'Blu-ray' ],
|
||||
'WEB-DL': [ 'WEBDL' ],
|
||||
'DVB': [ 'DVBRip', 'PDTV' ],
|
||||
'Screener': [ 'DVDSCR' ],
|
||||
'DivX': [ 'DVDivX' ],
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
+13
-50
@@ -18,7 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import s
|
||||
from guessit.patterns import sep
|
||||
import functools
|
||||
import unicodedata
|
||||
import copy
|
||||
|
||||
@@ -61,45 +64,6 @@ def str_fill(string, region, c):
|
||||
return string[:start] + c * (end - start) + string[end:]
|
||||
|
||||
|
||||
def to_utf8(o):
|
||||
"""Convert all unicode strings found in the given object to utf-8
|
||||
strings."""
|
||||
|
||||
if isinstance(o, unicode):
|
||||
return o.encode('utf-8')
|
||||
elif isinstance(o, list):
|
||||
return [ to_utf8(i) for i in o ]
|
||||
elif isinstance(o, dict):
|
||||
# need to do it like that to handle Guess instances correctly
|
||||
# FIXME: why is that necessary?
|
||||
result = copy.deepcopy(o)
|
||||
for key, value in o.items():
|
||||
result[to_utf8(key)] = to_utf8(value)
|
||||
return result
|
||||
|
||||
else:
|
||||
return o
|
||||
|
||||
def to_unicode(o):
|
||||
"""Convert all strings found in the given object to normalized
|
||||
unicode strings, using the UTF-8 codec if needed."""
|
||||
|
||||
if isinstance(o, unicode):
|
||||
return unicodedata.normalize('NFC', o)
|
||||
if isinstance(o, str):
|
||||
return unicodedata.normalize('NFC', o.decode('utf-8'))
|
||||
elif isinstance(o, list):
|
||||
return [ to_unicode(i) for i in o ]
|
||||
elif isinstance(o, dict):
|
||||
# need to do it like that to handle Guess instances correctly
|
||||
#result = copy.deepcopy(o)
|
||||
for key, value in o.items():
|
||||
result[to_unicode(key)] = to_unicode(value)
|
||||
return result
|
||||
|
||||
else:
|
||||
return o
|
||||
|
||||
|
||||
def levenshtein(a, b):
|
||||
if not a:
|
||||
@@ -176,21 +140,20 @@ def find_first_level_groups_span(string, enclosing):
|
||||
|
||||
def split_on_groups(string, groups):
|
||||
"""Split the given string using the different known groups for boundaries.
|
||||
|
||||
>>> split_on_groups('0123456789', [ (2, 4) ])
|
||||
>>> s(split_on_groups('0123456789', [ (2, 4) ]))
|
||||
['01', '23', '456789']
|
||||
|
||||
>>> split_on_groups('0123456789', [ (2, 4), (4, 6) ])
|
||||
>>> s(split_on_groups('0123456789', [ (2, 4), (4, 6) ]))
|
||||
['01', '23', '45', '6789']
|
||||
|
||||
>>> split_on_groups('0123456789', [ (5, 7), (2, 4) ])
|
||||
>>> s(split_on_groups('0123456789', [ (5, 7), (2, 4) ]))
|
||||
['01', '23', '4', '56', '789']
|
||||
|
||||
"""
|
||||
if not groups:
|
||||
return [ string ]
|
||||
|
||||
boundaries = sorted(set(reduce(lambda l, x: l + list(x), groups, [])))
|
||||
boundaries = sorted(set(functools.reduce(lambda l, x: l + list(x), groups, [])))
|
||||
if boundaries[0] != 0:
|
||||
boundaries.insert(0, 0)
|
||||
if boundaries[-1] != len(string):
|
||||
@@ -212,22 +175,22 @@ def find_first_level_groups(string, enclosing, blank_sep=None):
|
||||
This does not return nested groups, ie: '(ab(c)(d))' will return a single group
|
||||
containing the whole string.
|
||||
|
||||
>>> find_first_level_groups('', '()')
|
||||
>>> s(find_first_level_groups('', '()'))
|
||||
['']
|
||||
|
||||
>>> find_first_level_groups('abcd', '()')
|
||||
>>> s(find_first_level_groups('abcd', '()'))
|
||||
['abcd']
|
||||
|
||||
>>> find_first_level_groups('abc(de)fgh', '()')
|
||||
>>> s(find_first_level_groups('abc(de)fgh', '()'))
|
||||
['abc', '(de)', 'fgh']
|
||||
|
||||
>>> find_first_level_groups('(ab(c)(d))', '()', blank_sep = '_')
|
||||
>>> s(find_first_level_groups('(ab(c)(d))', '()', blank_sep = '_'))
|
||||
['_ab(c)(d)_']
|
||||
|
||||
>>> find_first_level_groups('ab[c]de[f]gh(i)', '[]')
|
||||
>>> s(find_first_level_groups('ab[c]de[f]gh(i)', '[]'))
|
||||
['ab', '[c]', 'de', '[f]', 'gh(i)']
|
||||
|
||||
>>> find_first_level_groups('()[]()', '()', blank_sep = '-')
|
||||
>>> s(find_first_level_groups('()[]()', '()', blank_sep = '-'))
|
||||
['--', '[]', '--']
|
||||
|
||||
"""
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from guessit import Guess
|
||||
from __future__ import unicode_literals
|
||||
from guessit import base_text_type, Guess
|
||||
from guessit.patterns import canonical_form
|
||||
from guessit.textutils import clean_string
|
||||
import logging
|
||||
@@ -41,7 +42,7 @@ def format_guess(guess):
|
||||
if prop in ('season', 'episodeNumber', 'year', 'cdNumber',
|
||||
'cdNumberTotal', 'bonusNumber', 'filmNumber'):
|
||||
guess[prop] = int(guess[prop])
|
||||
elif isinstance(value, basestring):
|
||||
elif isinstance(value, base_text_type):
|
||||
if prop in ('edition',):
|
||||
value = clean_string(value)
|
||||
guess[prop] = canonical_form(value)
|
||||
@@ -63,7 +64,7 @@ def find_and_split_node(node, strategy, logger):
|
||||
|
||||
if isinstance(result, Guess):
|
||||
if confidence is None:
|
||||
confidence = result.confidence(result.keys()[0])
|
||||
confidence = result.confidence(list(result.keys())[0])
|
||||
else:
|
||||
if confidence is None:
|
||||
confidence = 1.0
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import found_property
|
||||
import logging
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GuessIt - A library for guessing information from filenames
|
||||
# Copyright (c) 2012 Nicolas Wack <wackou@gmail.com>
|
||||
#
|
||||
# GuessIt is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# GuessIt is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
#from guessit.transfo import SingleNodeGuesser
|
||||
#from guessit.date import search_year
|
||||
from guessit.country import Country
|
||||
from guessit import Guess
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def process(mtree):
|
||||
for node in mtree.unidentified_leaves():
|
||||
# only keep explicit groups (enclosed in parentheses/brackets)
|
||||
if len(node.node_idx) == 2:
|
||||
try:
|
||||
country = Country(node.value[1:-1], strict=True)
|
||||
if node.value[0] + node.value[-1] not in ['()', '[]', '{}']:
|
||||
continue
|
||||
node.guess = Guess(country=country, confidence=1.0)
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.date import search_date
|
||||
import logging
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import found_property
|
||||
from guessit.patterns import non_episode_title, unlikely_series
|
||||
import logging
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import episode_rexps
|
||||
@@ -27,30 +28,35 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def number_list(s):
|
||||
return re.sub('[^0-9]+', ' ', s).split()
|
||||
return list(re.sub('[^0-9]+', ' ', s).split())
|
||||
|
||||
def guess_episodes_rexps(string):
|
||||
for rexp, confidence, span_adjust in episode_rexps:
|
||||
match = re.search(rexp, string, re.IGNORECASE)
|
||||
if match:
|
||||
result = (Guess(match.groupdict(), confidence=confidence),
|
||||
(match.start() + span_adjust[0],
|
||||
match.end() + span_adjust[1]))
|
||||
guess = Guess(match.groupdict(), confidence=confidence)
|
||||
span = (match.start() + span_adjust[0],
|
||||
match.end() + span_adjust[1])
|
||||
|
||||
# episodes which have a season > 25 are most likely errors
|
||||
# (Simpsons is at 23!)
|
||||
if int(result[0].get('season', 0)) > 25:
|
||||
# (Simpsons is at 24!)
|
||||
if int(guess.get('season', 0)) > 25:
|
||||
continue
|
||||
|
||||
# decide whether we have only a single episode number or an
|
||||
# episode list
|
||||
if result[0].get('episodeNumber'):
|
||||
eplist = number_list(result[0]['episodeNumber'])
|
||||
result[0].set('episodeNumber', int(eplist[0]), confidence=confidence)
|
||||
if guess.get('episodeNumber'):
|
||||
eplist = number_list(guess['episodeNumber'])
|
||||
guess.set('episodeNumber', int(eplist[0]), confidence=confidence)
|
||||
|
||||
if len(eplist) > 1:
|
||||
result[0].set('episodeList', map(int, eplist), confidence=confidence)
|
||||
guess.set('episodeList', list(map(int, eplist)), confidence=confidence)
|
||||
|
||||
return result
|
||||
if guess.get('bonusNumber'):
|
||||
eplist = number_list(guess['bonusNumber'])
|
||||
guess.set('bonusNumber', int(eplist[0]), confidence=confidence)
|
||||
|
||||
return guess, span
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
from guessit.patterns import (subtitle_exts, video_exts, episode_rexps,
|
||||
find_properties, canonical_form)
|
||||
from guessit.date import valid_year
|
||||
from guessit.textutils import clean_string
|
||||
import os.path
|
||||
import re
|
||||
import mimetypes
|
||||
@@ -28,34 +31,22 @@ import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# List of well known movies and series, hardcoded because they cannot be
|
||||
# guessed appropriately otherwise
|
||||
MOVIES = [ 'OSS 117' ]
|
||||
SERIES = [ 'Band of Brothers' ]
|
||||
|
||||
def guess_filetype(filename, filetype):
|
||||
other = {}
|
||||
|
||||
# look at the extension first
|
||||
fileext = os.path.splitext(filename)[1][1:].lower()
|
||||
if fileext in subtitle_exts:
|
||||
if 'movie' in filetype:
|
||||
filetype = 'moviesubtitle'
|
||||
elif 'episode' in filetype:
|
||||
filetype = 'episodesubtitle'
|
||||
else:
|
||||
filetype = 'subtitle'
|
||||
other = { 'container': fileext }
|
||||
elif fileext in video_exts:
|
||||
if filetype == 'autodetect':
|
||||
filetype = 'video'
|
||||
other = { 'container': fileext }
|
||||
else:
|
||||
if filetype == 'autodetect':
|
||||
filetype = 'unknown'
|
||||
other = { 'extension': fileext }
|
||||
MOVIES = [ m.lower() for m in MOVIES ]
|
||||
SERIES = [ s.lower() for s in SERIES ]
|
||||
|
||||
def guess_filetype(mtree, filetype):
|
||||
# put the filetype inside a dummy container to be able to have the
|
||||
# following functions work correctly as closures
|
||||
# this is a workaround for python 2 which doesn't have the
|
||||
# 'nonlocal' keyword (python 3 does have it)
|
||||
filetype_container = [filetype]
|
||||
other = {}
|
||||
filename = mtree.string
|
||||
|
||||
def upgrade_episode():
|
||||
if filetype_container[0] == 'video':
|
||||
@@ -69,14 +60,82 @@ def guess_filetype(filename, filetype):
|
||||
elif filetype_container[0] == 'subtitle':
|
||||
filetype_container[0] = 'moviesubtitle'
|
||||
|
||||
def upgrade_subtitle():
|
||||
if 'movie' in filetype_container[0]:
|
||||
filetype_container[0] = 'moviesubtitle'
|
||||
elif 'episode' in filetype_container[0]:
|
||||
filetype_container[0] = 'episodesubtitle'
|
||||
else:
|
||||
filetype_container[0] = 'subtitle'
|
||||
|
||||
def upgrade(type='unknown'):
|
||||
if filetype_container[0] == 'autodetect':
|
||||
filetype_container[0] = type
|
||||
|
||||
|
||||
# look at the extension first
|
||||
fileext = os.path.splitext(filename)[1][1:].lower()
|
||||
if fileext in subtitle_exts:
|
||||
upgrade_subtitle()
|
||||
other = { 'container': fileext }
|
||||
elif fileext in video_exts:
|
||||
upgrade(type='video')
|
||||
other = { 'container': fileext }
|
||||
else:
|
||||
upgrade(type='unknown')
|
||||
other = { 'extension': fileext }
|
||||
|
||||
|
||||
|
||||
# check whether we are in a 'Movies', 'Tv Shows', ... folder
|
||||
folder_rexps = [ (r'Movies?', upgrade_movie),
|
||||
(r'Tv ?Shows?', upgrade_episode),
|
||||
(r'Series', upgrade_episode)
|
||||
]
|
||||
for frexp, upgrade_func in folder_rexps:
|
||||
frexp = re.compile(frexp, re.IGNORECASE)
|
||||
for pathgroup in mtree.children:
|
||||
if frexp.match(pathgroup.value):
|
||||
upgrade_func()
|
||||
|
||||
# check for a few specific cases which will unintentionally make the
|
||||
# following heuristics confused (eg: OSS 117 will look like an episode,
|
||||
# season 1, epnum 17, when it is in fact a movie)
|
||||
fname = clean_string(filename).lower()
|
||||
for m in MOVIES:
|
||||
if m in fname:
|
||||
upgrade_movie()
|
||||
for s in SERIES:
|
||||
if s in fname:
|
||||
upgrade_episode()
|
||||
|
||||
# now look whether there are some specific hints for episode vs movie
|
||||
if filetype in ('video', 'subtitle'):
|
||||
if filetype_container[0] in ('video', 'subtitle'):
|
||||
# if we have an episode_rexp (eg: s02e13), it is an episode
|
||||
for rexp, _, _ in episode_rexps:
|
||||
match = re.search(rexp, filename, re.IGNORECASE)
|
||||
if match:
|
||||
upgrade_episode()
|
||||
break
|
||||
|
||||
# if we have a 3-4 digit number that's not a year, maybe an episode
|
||||
match = re.search(r'[^0-9]([0-9]{3,4})[^0-9]', filename)
|
||||
if match:
|
||||
fullnumber = int(match.group()[1:-1])
|
||||
#season = fullnumber // 100
|
||||
epnumber = fullnumber % 100
|
||||
possible = True
|
||||
|
||||
# check for validity
|
||||
if epnumber > 40:
|
||||
possible = False
|
||||
if valid_year(fullnumber):
|
||||
possible = False
|
||||
|
||||
if possible:
|
||||
upgrade_episode()
|
||||
|
||||
# if we have certain properties characteristic of episodes, it is an ep
|
||||
for prop, value, _, _ in find_properties(filename):
|
||||
log.debug('prop: %s = %s' % (prop, value))
|
||||
if prop == 'episodeFormat':
|
||||
@@ -87,6 +146,7 @@ def guess_filetype(filename, filetype):
|
||||
upgrade_episode()
|
||||
break
|
||||
|
||||
# origin-specific type
|
||||
if 'tvu.org.ru' in filename:
|
||||
upgrade_episode()
|
||||
|
||||
@@ -98,7 +158,7 @@ def guess_filetype(filename, filetype):
|
||||
|
||||
|
||||
def process(mtree, filetype='autodetect'):
|
||||
filetype, other = guess_filetype(mtree.string, filetype)
|
||||
filetype, other = guess_filetype(mtree, filetype)
|
||||
|
||||
mtree.guess.set('type', filetype, confidence=1.0)
|
||||
log.debug('Found with confidence %.2f: %s' % (1.0, mtree.guess))
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.language import search_language
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
import logging
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import find_properties
|
||||
import logging
|
||||
|
||||
@@ -18,24 +18,53 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import properties, canonical_form
|
||||
import re
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CODECS = properties['videoCodec']
|
||||
FORMATS = properties['format']
|
||||
|
||||
def adjust_metadata(md):
|
||||
codec = canonical_form(md['videoCodec'])
|
||||
if codec in FORMATS:
|
||||
md['format'] = codec
|
||||
del md['videoCodec']
|
||||
return md
|
||||
|
||||
|
||||
def guess_release_group(string):
|
||||
group_names = [ r'\.(Xvid)-(?P<releaseGroup>.*?)[ \.]',
|
||||
r'\.(DivX)-(?P<releaseGroup>.*?)[\. ]',
|
||||
r'\.(DVDivX)-(?P<releaseGroup>.*?)[\. ]',
|
||||
]
|
||||
|
||||
# first try to see whether we have both a known codec and a known release group
|
||||
group_names = [ r'\.(?P<videoCodec>' + codec + r')-(?P<releaseGroup>.*?)[ \.]'
|
||||
for codec in (CODECS + FORMATS) ]
|
||||
|
||||
for rexp in group_names:
|
||||
match = re.search(rexp, string, re.IGNORECASE)
|
||||
if match:
|
||||
metadata = match.groupdict()
|
||||
metadata.update({ 'videoCodec': match.group(1) })
|
||||
return metadata, (match.start() + 1, match.end() - 1)
|
||||
if canonical_form(metadata['releaseGroup']) in properties['releaseGroup']:
|
||||
return adjust_metadata(metadata), (match.start(1), match.end(2))
|
||||
|
||||
# pick anything as releaseGroup as long as we have a codec in front
|
||||
# this doesn't include a potential dash ('-') ending the release group
|
||||
# eg: [...].X264-HiS@SiLUHD-English.[...]
|
||||
group_names = [ r'\.(?P<videoCodec>' + codec + r')-(?P<releaseGroup>.*?)(-(.*?))?[ \.]'
|
||||
for codec in (CODECS + FORMATS) ]
|
||||
|
||||
for rexp in group_names:
|
||||
match = re.search(rexp, string, re.IGNORECASE)
|
||||
if match:
|
||||
return adjust_metadata(match.groupdict()), (match.start(1), match.end(2))
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import video_rexps, sep
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import Guess
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import weak_episode_rexps
|
||||
@@ -40,8 +41,13 @@ def guess_weak_episodes_rexps(string, node):
|
||||
|
||||
epnum = int(metadata['episodeNumber'])
|
||||
if epnum > 100:
|
||||
return Guess({ 'season': epnum // 100,
|
||||
'episodeNumber': epnum % 100 },
|
||||
season, epnum = epnum // 100, epnum % 100
|
||||
# episodes which have a season > 25 are most likely errors
|
||||
# (Simpsons is at 23!)
|
||||
if season > 25:
|
||||
continue
|
||||
return Guess({ 'season': season,
|
||||
'episodeNumber': epnum },
|
||||
confidence=0.6), span
|
||||
else:
|
||||
return Guess(metadata, confidence=0.3), span
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.patterns import websites
|
||||
import logging
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.transfo import SingleNodeGuesser
|
||||
from guessit.date import search_year
|
||||
import logging
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.patterns import subtitle_exts
|
||||
import logging
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.textutils import find_first_level_groups
|
||||
from guessit.patterns import group_delimiters
|
||||
import functools
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -33,10 +35,10 @@ def process(mtree):
|
||||
groups = find_first_level_groups(c.value, group_delimiters[0])
|
||||
for delimiters in group_delimiters:
|
||||
flatten = lambda l, x: l + find_first_level_groups(x, delimiters)
|
||||
groups = reduce(flatten, groups, [])
|
||||
groups = functools.reduce(flatten, groups, [])
|
||||
|
||||
# do not do this at this moment, it is not strong enough and can break other
|
||||
# patterns, such as dates, etc...
|
||||
#groups = reduce(lambda l, x: l + x.split('-'), groups, [])
|
||||
#groups = functools.reduce(lambda l, x: l + x.split('-'), groups, [])
|
||||
|
||||
c.split_on_components(groups)
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit.patterns import sep
|
||||
import re
|
||||
import logging
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from guessit import fileutils
|
||||
import os.path
|
||||
import logging
|
||||
|
||||
Reference in new issue
Block a user