2
.. warning:: Many methods in this module are not thread safe since they do in place sort for :attr:`qualities`
9
log = logging.getLogger('utils.qualities')
14
def __init__(self, value, name, all_of=None, none_of=None):
17
numerical value for quality, used for determining order
19
commonly used name for the quality
21
list of regexps that all need to match when testing
22
whether or not given text matches this quality
24
list of regexps that cannot match to this quality
35
self.regexps.append(re.compile('(?<![^\W_])' + r + '(?![^\W_])', re.IGNORECASE))
38
self.not_regexps.append(re.compile('(?<![^\W_])' + r + '(?![^\W_])', re.IGNORECASE))
40
def matches(self, text):
41
"""Test if quality matches to text.
43
:param string text: data te be tested against
44
:returns: tuple (matches, remaining text without quality data)
47
#log.debug('testing for quality %s --->' % self.name)
48
# none of these regexps can match
49
for regexp in self.not_regexps:
50
match = regexp.search(text)
52
#log.debug('`%s` matches to `%s`, cannot be `%s`' % (regexp.pattern, text, self.name))
54
#log.debug('`%s` missed `%s`' % (regexp.pattern, text))
55
# all of the regexps must match
56
for regexp in self.regexps:
57
match = regexp.search(text)
59
#log.debug('`%s` did not match to `%s`, cannot be `%s`' % (regexp.pattern, text, self.name))
62
# remove matching part from the text
63
text = text[:match.start()] + text[match.end():]
64
#log.debug('passed: ' + regexp.pattern)
65
#log.debug('`%s` seems to be `%s`' % (text, self.name))
71
def __eq__(self, other):
72
if isinstance(other, basestring):
73
other = get(other, None)
74
if hasattr(other, 'value'):
75
return self.value == other.value
79
def __ne__(self, other):
80
return not self.__eq__(other)
82
def __lt__(self, other):
83
if isinstance(other, basestring):
84
other = get(other, other)
85
if not hasattr(other, 'value'):
86
raise TypeError('%r is not a valid quality' % other)
87
return self.value < other.value
89
def __ge__(self, other):
90
return not self.__lt__(other)
92
def __le__(self, other):
93
return self.__lt__(other) or self.__eq__(other)
95
def __gt__(self, other):
96
return not self.__le__(other)
99
return '<Quality(name=%s,value=%s)>' % (self.name, self.value)
104
def __deepcopy__(self, memo=None):
105
# No mutable attributes, return a regular copy
106
return copy.copy(self)
108
UNKNOWN = Quality(0, 'unknown')
110
# Reminder, Quality regexps are automatically surrounded!
112
re_webdl = 'web[\W_]?dl'
113
re_720p = '(?:1280x)?720p?'
114
re_1080p = '(?:1920x)?1080p?'
115
re_bluray = '(?:b[dr][\W_]?rip|bluray(?:[\W_]?rip)?)'
116
re_10bit = '(10.?bit|hi10p)'
118
# TODO: this should be marked as private (_qualities), not sure if it used from other places though
119
qualities = [Quality(1200, '1080p bluray 10bit', [re_1080p, re_bluray, re_10bit], none_of=[re_rc_or_r5]),
120
Quality(1100, '1080p bluray', [re_1080p, re_bluray], none_of=[re_rc_or_r5]),
121
Quality(1000, '1080p web-dl', [re_1080p, re_webdl]),
122
Quality(850, '1080p 10bit', [re_1080p, re_10bit], none_of=[re_bluray, re_rc_or_r5]),
123
Quality(800, '1080p', [re_1080p], none_of=[re_bluray, re_rc_or_r5]),
124
Quality(750, '1080i'),
125
Quality(670, '720p bluray 10bit', [re_720p, re_bluray, re_10bit], none_of=[re_rc_or_r5]),
126
Quality(650, '720p bluray', [re_720p, re_bluray], none_of=[re_rc_or_r5, re_10bit]),
127
Quality(600, '720p web-dl', [re_720p, re_webdl]),
128
Quality(520, '720p 10bit', [re_720p, re_10bit], none_of=[re_bluray, re_rc_or_r5]),
129
Quality(500, '720p', [re_720p], none_of=[re_bluray, re_rc_or_r5, re_10bit]),
130
Quality(450, '720i'),
131
Quality(430, '1080p bluray rc', [re_1080p, re_bluray, re_rc_or_r5]),
132
Quality(420, '720p bluray rc', [re_720p, re_bluray, re_rc_or_r5]),
134
Quality(380, 'bdrip', [re_bluray], none_of=[re_rc_or_r5]),
135
Quality(350, 'dvdrip', ['dvd(?:[\W_]?rip)?'], none_of=[re_rc_or_r5]),
136
Quality(320, 'web-dl', [re_webdl]),
137
Quality(315, '576p', ['576p?']),
138
Quality(310, '480p 10bit', ['480p?', re_10bit]),
139
Quality(300, '480p', ['480p?'], none_of=[re_10bit]),
140
Quality(290, '368p', ['368p?']),
141
Quality(280, '360p'), # I don't think we want to make trailing p optional here (ie. xbox 360)
142
Quality(270, 'hdtv', ['hdtv(?:[\W_]?rip)?']),
143
Quality(260, 'dvdrip r5', ['dvd(?:[\W_]?rip)?', re_rc_or_r5]),
144
Quality(250, 'bdscr'),
145
Quality(240, 'dvdscr'),
146
Quality(100, 'sdtv', ['(?:[sp]dtv|dvb)(?:[\W_]?rip)?|(?:t|pp)v[\W_]?rip']),
147
Quality(80, 'dsr', ['dsr|(?:ds|web)[\W_]?rip']),
150
Quality(30, 'preair'),
151
Quality(25, 'ts', ['ts|telesync']),
153
Quality(10, 'workprint')]
155
registry = dict([(qual.name.lower(), qual) for qual in qualities])
156
registry['unknown'] = UNKNOWN
160
"""Return all Qualities in order of best to worst"""
161
return sorted(qualities, reverse=True) + [UNKNOWN]
164
def get(name, default=None):
166
Return Quality object for :name: (case insensitive)
167
:param name: Quality name
168
:return: Found :class:`Quality` / UNKNOWN or *default* if given and nothing was found.
172
return registry[name]
173
q = parse_quality(name)
176
return default if default is not None else UNKNOWN
181
:param str name: case insensitive quality name
182
:return: Return value of quality with given *name* or 0 if unknown
184
return get(name).value
188
"""Return lowest known Quality excluding unknown."""
194
"""Return highest known Quality."""
195
qualities.sort(reverse=True)
199
def common_name(name):
200
"""Return `common name` for *name* (case insensitive).
202
:param string name: Name to be converted in the common form.
203
:returns: common name, eg. 1280x720, 720 and 720p will all return 720p
206
return get(name).name
209
def quality_match(title):
210
"""Search best quality from title
212
:param string title: text to search from
213
:returns: tuple (:class:`Quality` which can be unknown, remaining title without quality)
215
qualities.sort(reverse=True)
216
for quality in qualities:
217
result, remaining = quality.matches(title)
219
return quality, remaining
220
return UNKNOWN, title
223
def parse_quality(title):
224
"""Find the highest know quality in a given string :title:
226
:returns: :class:`Quality` object or False
228
return quality_match(title)[0]