mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-03 01:43:37 +00:00
d0ab6db706
Change avi metadata extraction is more fault tolerant and the chance of hanging due to corrupt avi files is reduced.
295 lines
9.1 KiB
Python
295 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
from hachoir_core.compatibility import any, sorted
|
|
from hachoir_core.endian import endian_name
|
|
from hachoir_core.tools import makePrintable, makeUnicode
|
|
from hachoir_core.dict import Dict
|
|
from hachoir_core.error import error, HACHOIR_ERRORS
|
|
from hachoir_core.i18n import _
|
|
from hachoir_core.log import Logger
|
|
from hachoir_metadata.metadata_item import (
|
|
MIN_PRIORITY, MAX_PRIORITY, QUALITY_NORMAL)
|
|
from hachoir_metadata.register import registerAllItems
|
|
|
|
extractors = {}
|
|
|
|
class Metadata(Logger):
|
|
header = u"Metadata"
|
|
|
|
def __init__(self, parent, quality=QUALITY_NORMAL):
|
|
assert isinstance(self.header, unicode)
|
|
|
|
# Limit to 0.0 .. 1.0
|
|
if parent:
|
|
quality = parent.quality
|
|
else:
|
|
quality = min(max(0.0, quality), 1.0)
|
|
|
|
object.__init__(self)
|
|
object.__setattr__(self, "_Metadata__data", {})
|
|
object.__setattr__(self, "quality", quality)
|
|
header = self.__class__.header
|
|
object.__setattr__(self, "_Metadata__header", header)
|
|
|
|
registerAllItems(self)
|
|
|
|
def _logger(self):
|
|
pass
|
|
|
|
def __setattr__(self, key, value):
|
|
"""
|
|
Add a new value to data with name 'key'. Skip duplicates.
|
|
"""
|
|
# Invalid key?
|
|
if key not in self.__data:
|
|
raise KeyError(_("%s has no metadata '%s'") % (self.__class__.__name__, key))
|
|
|
|
# Skip duplicates
|
|
self.__data[key].add(value)
|
|
|
|
def setHeader(self, text):
|
|
object.__setattr__(self, "header", text)
|
|
|
|
def getItems(self, key):
|
|
try:
|
|
return self.__data[key]
|
|
except LookupError:
|
|
raise ValueError("Metadata has no value '%s'" % key)
|
|
|
|
def getItem(self, key, index):
|
|
try:
|
|
return self.getItems(key)[index]
|
|
except (LookupError, ValueError):
|
|
return None
|
|
|
|
def has(self, key):
|
|
return 1 <= len(self.getItems(key))
|
|
|
|
def get(self, key, default=None, index=0):
|
|
"""
|
|
Read first value of tag with name 'key'.
|
|
|
|
>>> from datetime import timedelta
|
|
>>> a = RootMetadata()
|
|
>>> a.duration = timedelta(seconds=2300)
|
|
>>> a.get('duration')
|
|
datetime.timedelta(0, 2300)
|
|
>>> a.get('author', u'Anonymous')
|
|
u'Anonymous'
|
|
"""
|
|
item = self.getItem(key, index)
|
|
if item is None:
|
|
if default is None:
|
|
raise ValueError("Metadata has no value '%s' (index %s)" % (key, index))
|
|
else:
|
|
return default
|
|
return item.value
|
|
|
|
def getValues(self, key):
|
|
try:
|
|
data = self.__data[key]
|
|
except LookupError:
|
|
raise ValueError("Metadata has no value '%s'" % key)
|
|
return [ item.value for item in data ]
|
|
|
|
def getText(self, key, default=None, index=0):
|
|
"""
|
|
Read first value, as unicode string, of tag with name 'key'.
|
|
|
|
>>> from datetime import timedelta
|
|
>>> a = RootMetadata()
|
|
>>> a.duration = timedelta(seconds=2300)
|
|
>>> a.getText('duration')
|
|
u'38 min 20 sec'
|
|
>>> a.getText('titre', u'Unknown')
|
|
u'Unknown'
|
|
"""
|
|
item = self.getItem(key, index)
|
|
if item is not None:
|
|
return item.text
|
|
else:
|
|
return default
|
|
|
|
def register(self, data):
|
|
assert data.key not in self.__data
|
|
data.metadata = self
|
|
self.__data[data.key] = data
|
|
|
|
def __iter__(self):
|
|
return self.__data.itervalues()
|
|
|
|
def __str__(self):
|
|
r"""
|
|
Create a multi-line ASCII string (end of line is "\n") which
|
|
represents all datas.
|
|
|
|
>>> a = RootMetadata()
|
|
>>> a.author = "haypo"
|
|
>>> a.copyright = unicode("© Hachoir", "UTF-8")
|
|
>>> print a
|
|
Metadata:
|
|
- Author: haypo
|
|
- Copyright: \xa9 Hachoir
|
|
|
|
@see __unicode__() and exportPlaintext()
|
|
"""
|
|
text = self.exportPlaintext()
|
|
return "\n".join( makePrintable(line, "ASCII") for line in text )
|
|
|
|
def __unicode__(self):
|
|
r"""
|
|
Create a multi-line Unicode string (end of line is "\n") which
|
|
represents all datas.
|
|
|
|
>>> a = RootMetadata()
|
|
>>> a.copyright = unicode("© Hachoir", "UTF-8")
|
|
>>> print repr(unicode(a))
|
|
u'Metadata:\n- Copyright: \xa9 Hachoir'
|
|
|
|
@see __str__() and exportPlaintext()
|
|
"""
|
|
return "\n".join(self.exportPlaintext())
|
|
|
|
def exportPlaintext(self, priority=None, human=True, line_prefix=u"- ", title=None):
|
|
r"""
|
|
Convert metadata to multi-line Unicode string and skip datas
|
|
with priority lower than specified priority.
|
|
|
|
Default priority is Metadata.MAX_PRIORITY. If human flag is True, data
|
|
key are translated to better human name (eg. "bit_rate" becomes
|
|
"Bit rate") which may be translated using gettext.
|
|
|
|
If priority is too small, metadata are empty and so None is returned.
|
|
|
|
>>> print RootMetadata().exportPlaintext()
|
|
None
|
|
>>> meta = RootMetadata()
|
|
>>> meta.copyright = unicode("© Hachoir", "UTF-8")
|
|
>>> print repr(meta.exportPlaintext())
|
|
[u'Metadata:', u'- Copyright: \xa9 Hachoir']
|
|
|
|
@see __str__() and __unicode__()
|
|
"""
|
|
if priority is not None:
|
|
priority = max(priority, MIN_PRIORITY)
|
|
priority = min(priority, MAX_PRIORITY)
|
|
else:
|
|
priority = MAX_PRIORITY
|
|
if not title:
|
|
title = self.header
|
|
text = ["%s:" % title]
|
|
for data in sorted(self):
|
|
if priority < data.priority:
|
|
break
|
|
if not data.values:
|
|
continue
|
|
if human:
|
|
title = data.description
|
|
else:
|
|
title = data.key
|
|
for item in data.values:
|
|
if human:
|
|
value = item.text
|
|
else:
|
|
value = makeUnicode(item.value)
|
|
text.append("%s%s: %s" % (line_prefix, title, value))
|
|
if 1 < len(text):
|
|
return text
|
|
else:
|
|
return None
|
|
|
|
def __nonzero__(self):
|
|
return any(item for item in self.__data.itervalues())
|
|
|
|
class RootMetadata(Metadata):
|
|
def __init__(self, quality=QUALITY_NORMAL):
|
|
Metadata.__init__(self, None, quality)
|
|
|
|
class MultipleMetadata(RootMetadata):
|
|
header = _("Common")
|
|
def __init__(self, quality=QUALITY_NORMAL):
|
|
RootMetadata.__init__(self, quality)
|
|
object.__setattr__(self, "_MultipleMetadata__groups", Dict())
|
|
object.__setattr__(self, "_MultipleMetadata__key_counter", {})
|
|
|
|
def __contains__(self, key):
|
|
return key in self.__groups
|
|
|
|
def __getitem__(self, key):
|
|
return self.__groups[key]
|
|
|
|
def iterGroups(self):
|
|
return self.__groups.itervalues()
|
|
|
|
def __nonzero__(self):
|
|
if RootMetadata.__nonzero__(self):
|
|
return True
|
|
return any(bool(group) for group in self.__groups)
|
|
|
|
def addGroup(self, key, metadata, header=None):
|
|
"""
|
|
Add a new group (metadata of a sub-document).
|
|
|
|
Returns False if the group is skipped, True if it has been added.
|
|
"""
|
|
if not metadata:
|
|
self.warning("Skip empty group %s" % key)
|
|
return False
|
|
if key.endswith("[]"):
|
|
key = key[:-2]
|
|
if key in self.__key_counter:
|
|
self.__key_counter[key] += 1
|
|
else:
|
|
self.__key_counter[key] = 1
|
|
key += "[%u]" % self.__key_counter[key]
|
|
if header:
|
|
metadata.setHeader(header)
|
|
self.__groups.append(key, metadata)
|
|
return True
|
|
|
|
def exportPlaintext(self, priority=None, human=True, line_prefix=u"- "):
|
|
common = Metadata.exportPlaintext(self, priority, human, line_prefix)
|
|
if common:
|
|
text = common
|
|
else:
|
|
text = []
|
|
for key, metadata in self.__groups.iteritems():
|
|
if not human:
|
|
title = key
|
|
else:
|
|
title = None
|
|
value = metadata.exportPlaintext(priority, human, line_prefix, title=title)
|
|
if value:
|
|
text.extend(value)
|
|
if len(text):
|
|
return text
|
|
else:
|
|
return None
|
|
|
|
def registerExtractor(parser, extractor):
|
|
assert parser not in extractors
|
|
assert issubclass(extractor, RootMetadata)
|
|
extractors[parser] = extractor
|
|
|
|
def extractMetadata(parser, quality=QUALITY_NORMAL, scan_index=True):
|
|
"""
|
|
Create a Metadata class from a parser. Returns None if no metadata
|
|
extractor does exist for the parser class.
|
|
"""
|
|
try:
|
|
extractor = extractors[parser.__class__]
|
|
except KeyError:
|
|
return None
|
|
metadata = extractor(quality)
|
|
try:
|
|
metadata.extract(parser, scan_index)
|
|
except HACHOIR_ERRORS, err:
|
|
error("Error during metadata extraction: %s" % unicode(err))
|
|
return None
|
|
except Exception, err:
|
|
error("Error during metadata extraction: %s" % unicode(err))
|
|
return None
|
|
if metadata:
|
|
metadata.mime_type = parser.mime_type
|
|
metadata.endian = endian_name[parser.endian]
|
|
return metadata
|
|
|