Subliminal update

This commit is contained in:
Ruud committed 2012-04-08 17:35:39 +02:00
1 parent 5321037f08
commit 148e1940ac
85 files changed
+4375 -3420

No files matched your search

+15 -8
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,14 +17,13 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
import mimetypes
import os
import sys
from exceptions import *
PARSERS = [('asf', ['video/asf'], ['asf', 'wmv', 'wma']),
('flv', ['video/flv'], ['flv']),
('mkv', ['video/x-matroska', 'application/mkv'], ['mkv', 'mka', 'webm']),
@@ -32,10 +31,18 @@ PARSERS = [('asf', ['video/asf'], ['asf', 'wmv', 'wma']),
('mpeg', ['video/mpeg'], ['mpeg', 'mpg', 'mp4', 'ts']),
('ogm', ['application/ogg'], ['ogm', 'ogg', 'ogv']),
('real', ['video/real'], ['rm', 'ra', 'ram']),
('riff', ['video/avi'], ['wav', 'avi'])]
('riff', ['video/avi'], ['wav', 'avi'])
]
def parse(path):
"""Parse metadata of the given video
:param string path: path to the video file to parse
:return: a parser corresponding to the video's mimetype or extension
:rtype: :class:`~enzyme.core.AVContainer`
"""
if not os.path.isfile(path):
raise ValueError('Invalid path')
extension = os.path.splitext(path)[1][1:]
@@ -47,7 +54,7 @@ def parse(path):
parser_mime = parser_name
if extension in parser_extensions:
parser_ext = parser_name
parser = parser_mime or parser_ext
parser = parser_mime or parser_ext
if not parser:
raise NoParserError()
mod = __import__(parser, globals=globals(), locals=locals(), fromlist=[], level=-1)
+62 -64
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,30 +17,29 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
from exceptions import ParseError
import core
import logging
import string
import struct
__all__ = ['Parser']
import struct
import string
import logging
from exceptions import *
import core
# get logging object
log = logging.getLogger(__name__)
def _guid(input):
# Remove any '-'
s = string.join(string.split(input,'-'), '')
s = string.join(string.split(input, '-'), '')
r = ''
if len(s) != 32:
return ''
x = ''
for i in range(0,16):
r+=chr(int(s[2*i:2*i+2],16))
guid = struct.unpack('>IHHBB6s',r)
for i in range(0, 16):
r += chr(int(s[2 * i:2 * i + 2], 16))
guid = struct.unpack('>IHHBB6s', r)
return guid
GUIDS = {
@@ -93,8 +92,7 @@ GUIDS = {
'ASF_Web_Stream_Format' : _guid('DA1E6B13-8359-4050-B398-388E965BF00C'),
'ASF_No_Error_Correction' : _guid('20FB5700-5B55-11CF-A8FD-00805F5C442B'),
'ASF_Audio_Spread' : _guid('BFC3CD50-618F-11CF-8BB2-00AA00B4E220'),
}
'ASF_Audio_Spread' : _guid('BFC3CD50-618F-11CF-8BB2-00AA00B4E220')}
class Asf(core.AVContainer):
@@ -114,7 +112,7 @@ class Asf(core.AVContainer):
raise ParseError()
(guidstr, objsize, objnum, reserved1, \
reserved2) = struct.unpack('<16sQIBB',h)
reserved2) = struct.unpack('<16sQIBB', h)
guid = self._parseguid(guidstr)
if (guid != GUIDS['ASF_Header_Object']):
@@ -122,9 +120,9 @@ class Asf(core.AVContainer):
if reserved1 != 0x01 or reserved2 != 0x02:
raise ParseError()
log.debug("asf header size: %d / %d objects" % (objsize,objnum))
header = file.read(objsize-30)
for i in range(0,objnum):
log.debug(u'Header size: %d / %d objects' % (objsize, objnum))
header = file.read(objsize - 30)
for _ in range(0, objnum):
h = self._getnextheader(header)
header = header[h[1]:]
@@ -148,19 +146,19 @@ class Asf(core.AVContainer):
stream._appendtable('ASFMETADATA', metadata)
def _parseguid(self,string):
def _parseguid(self, string):
return struct.unpack('<IHHBB6s', string[:16])
def _parsekv(self,s):
def _parsekv(self, s):
pos = 0
(descriptorlen,) = struct.unpack('<H', s[pos:pos+2])
(descriptorlen,) = struct.unpack('<H', s[pos:pos + 2])
pos += 2
descriptorname = s[pos:pos+descriptorlen]
descriptorname = s[pos:pos + descriptorlen]
pos += descriptorlen
descriptortype, valuelen = struct.unpack('<HH', s[pos:pos+4])
descriptortype, valuelen = struct.unpack('<HH', s[pos:pos + 4])
pos += 4
descriptorvalue = s[pos:pos+valuelen]
descriptorvalue = s[pos:pos + valuelen]
pos += valuelen
value = None
if descriptortype == 0x0000:
@@ -182,17 +180,17 @@ class Asf(core.AVContainer):
# WORD
value = struct.unpack('<H', descriptorvalue)[0]
else:
log.debug("Unknown Descriptor Type %d" % descriptortype)
return (pos,descriptorname,value)
log.debug(u'Unknown Descriptor Type %d' % descriptortype)
return (pos, descriptorname, value)
def _parsekv2(self,s):
def _parsekv2(self, s):
pos = 0
strno, descriptorlen, descriptortype, valuelen = struct.unpack('<2xHHHI', s[pos:pos+12])
strno, descriptorlen, descriptortype, valuelen = struct.unpack('<2xHHHI', s[pos:pos + 12])
pos += 12
descriptorname = s[pos:pos+descriptorlen]
descriptorname = s[pos:pos + descriptorlen]
pos += descriptorlen
descriptorvalue = s[pos:pos+valuelen]
descriptorvalue = s[pos:pos + valuelen]
pos += valuelen
value = None
@@ -216,25 +214,25 @@ class Asf(core.AVContainer):
# WORD
value = struct.unpack('<H', descriptorvalue)[0]
else:
log.debug("Unknown Descriptor Type %d" % descriptortype)
return (pos,descriptorname,value,strno)
log.debug(u'Unknown Descriptor Type %d' % descriptortype)
return (pos, descriptorname, value, strno)
def _getnextheader(self,s):
r = struct.unpack('<16sQ',s[:24])
(guidstr,objsize) = r
def _getnextheader(self, s):
r = struct.unpack('<16sQ', s[:24])
(guidstr, objsize) = r
guid = self._parseguid(guidstr)
if guid == GUIDS['ASF_File_Properties_Object']:
log.debug("File Properties Object")
val = struct.unpack('<16s6Q4I',s[24:24+80])
log.debug(u'File Properties Object')
val = struct.unpack('<16s6Q4I', s[24:24 + 80])
(fileid, size, date, packetcount, duration, \
senddur, preroll, flags, minpack, maxpack, maxbr) = \
val
# FIXME: parse date to timestamp
self.length = duration/10000000.0
self.length = duration / 10000000.0
elif guid == GUIDS['ASF_Stream_Properties_Object']:
log.debug("Stream Properties Object [%d]" % objsize)
log.debug(u'Stream Properties Object [%d]' % objsize)
streamtype = self._parseguid(s[24:40])
errortype = self._parseguid(s[40:56])
offset, typelen, errorlen, flags = struct.unpack('<QIIH', s[56:74])
@@ -244,15 +242,15 @@ class Asf(core.AVContainer):
self._set('encrypted', True)
if streamtype == GUIDS['ASF_Video_Media']:
vi = core.VideoStream()
vi.width, vi.height, depth, codec, = struct.unpack('<4xII2xH4s', s[89:89+20])
vi.width, vi.height, depth, codec, = struct.unpack('<4xII2xH4s', s[89:89 + 20])
vi.codec = codec
vi.id = strno
self.video.append(vi)
elif streamtype == GUIDS['ASF_Audio_Media']:
ai = core.AudioStream()
twocc, ai.channels, ai.samplerate, bitrate, block, \
ai.samplebits, = struct.unpack('<HHIIHH', s[78:78+16])
ai.bitrate = 8*bitrate
ai.samplebits, = struct.unpack('<HHIIHH', s[78:78 + 16])
ai.bitrate = 8 * bitrate
ai.codec = twocc
ai.id = strno
self.audio.append(ai)
@@ -260,8 +258,8 @@ class Asf(core.AVContainer):
self._apply_extinfo(strno)
elif guid == GUIDS['ASF_Extended_Stream_Properties_Object']:
streamid, langid, frametime = struct.unpack('<HHQ',s[72:84])
(bitrate,) = struct.unpack('<I', s[40:40+4])
streamid, langid, frametime = struct.unpack('<HHQ', s[72:84])
(bitrate,) = struct.unpack('<I', s[40:40 + 4])
if streamid not in self._extinfo:
self._extinfo[streamid] = [None, None, None, {}]
if frametime == 0:
@@ -271,31 +269,31 @@ class Asf(core.AVContainer):
self._apply_extinfo(streamid)
elif guid == GUIDS['ASF_Header_Extension_Object']:
log.debug("ASF_Header_Extension_Object %d" % objsize)
size = struct.unpack('<I',s[42:46])[0]
data = s[46:46+size]
log.debug(u'ASF_Header_Extension_Object %d' % objsize)
size = struct.unpack('<I', s[42:46])[0]
data = s[46:46 + size]
while len(data):
log.debug("Sub:")
log.debug(u'Sub:')
h = self._getnextheader(data)
data = data[h[1]:]
elif guid == GUIDS['ASF_Codec_List_Object']:
log.debug("List Object")
log.debug(u'List Object')
pass
elif guid == GUIDS['ASF_Error_Correction_Object']:
log.debug("Error Correction")
log.debug(u'Error Correction')
pass
elif guid == GUIDS['ASF_Content_Description_Object']:
log.debug("Content Description Object")
val = struct.unpack('<5H', s[24:24+10])
log.debug(u'Content Description Object')
val = struct.unpack('<5H', s[24:24 + 10])
pos = 34
strings = []
for i in val:
ss = s[pos:pos+i].replace('\0', '').lstrip().rstrip()
ss = s[pos:pos + i].replace('\0', '').lstrip().rstrip()
strings.append(ss)
pos+=i
pos += i
# Set empty strings to None
strings = [x or None for x in strings]
@@ -318,7 +316,7 @@ class Asf(core.AVContainer):
streams = {}
for i in range(0, count):
# Read additional content descriptors
size,key,value,strno = self._parsekv2(s[pos:])
size, key, value, strno = self._parsekv2(s[pos:])
if strno not in streams:
streams[strno] = {}
streams[strno][key] = value
@@ -334,12 +332,12 @@ class Asf(core.AVContainer):
count = struct.unpack('<H', s[24:26])[0]
pos = 26
for i in range(0, count):
idlen = struct.unpack('<B', s[pos:pos+1])[0]
idstring = s[pos+1:pos+1+idlen]
idlen = struct.unpack('<B', s[pos:pos + 1])[0]
idstring = s[pos + 1:pos + 1 + idlen]
idstring = unicode(idstring, 'utf-16').replace('\0', '')
log.debug("Language: %d/%d: %s" % (i+1, count, idstring))
log.debug(u'Language: %d/%d: %r' % (i + 1, count, idstring))
self._languages.append(idstring)
pos += 1+idlen
pos += 1 + idlen
elif guid == GUIDS['ASF_Stream_Bitrate_Properties_Object']:
# This record contains stream bitrate with payload overhead. For
@@ -356,11 +354,11 @@ class Asf(core.AVContainer):
# Just print the type:
for h in GUIDS.keys():
if GUIDS[h] == guid:
log.debug("Unparsed %s [%d]" % (h,objsize))
log.debug(u'Unparsed %r [%d]' % (h, objsize))
break
else:
u = "%.8X-%.4X-%.4X-%.2X%.2X-%s" % guid
log.debug("unknown: len=%d [%d]" % (len(u), objsize))
log.debug(u'unknown: len=%d [%d]' % (len(u), objsize))
return r
+26 -33
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,21 +17,14 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
import re
import logging
import fourcc
import language
from exceptions import *
from strutils import str_to_unicode, unicode_to_str
UNPRINTABLE_KEYS = ['thumbnail', 'url', 'codec_private']
EXTENSION_DEVICE = 'device'
EXTENSION_DIRECTORY = 'directory'
EXTENSION_STREAM = 'stream'
MEDIACORE = ['title', 'caption', 'comment', 'size', 'type', 'subtype', 'timestamp',
'keywords', 'country', 'language', 'langcode', 'url', 'artist',
'mime', 'datetime', 'tags', 'hash']
@@ -52,8 +45,6 @@ log = logging.getLogger(__name__)
class Media(object):
media = None
"""
Media is the base class to all Media Metadata Containers. It defines
the basic structures that handle metadata. Media and its derivates
@@ -61,10 +52,11 @@ class Media(object):
Specific derivates contain additional keys to the dublin core set that is
defined in Media.
"""
media = None
_keys = MEDIACORE
table_mapping = {}
def __init__(self, hash = None):
def __init__(self, hash=None):
if hash is not None:
# create Media based on dict
for key, value in hash.items():
@@ -139,20 +131,21 @@ class Media(object):
result += '| ' + re.sub(r'\n(.)', r'\n| \1', unicode(item))
# print tables
if log.level >= 10:
for name, table in self.tables.items():
result += '+-- Table %s\n' % str(name)
for key, value in table.items():
try:
value = unicode(value)
if len(value) > 50:
value = u'<unprintable data, size=%d>' % len(value)
except (UnicodeDecodeError, TypeError), e:
try:
value = u'<unprintable data, size=%d>' % len(value)
except AttributeError:
value = u'<unprintable data>'
result += u'| | %s: %s\n' % (unicode(key), value)
#FIXME: WTH?
# if log.level >= 10:
# for name, table in self.tables.items():
# result += '+-- Table %s\n' % str(name)
# for key, value in table.items():
# try:
# value = unicode(value)
# if len(value) > 50:
# value = u'<unprintable data, size=%d>' % len(value)
# except (UnicodeDecodeError, TypeError):
# try:
# value = u'<unprintable data, size=%d>' % len(value)
# except AttributeError:
# value = u'<unprintable data>'
# result += u'| | %s: %s\n' % (unicode(key), value)
return result
def __str__(self):
@@ -253,7 +246,7 @@ class Media(object):
"""
return hasattr(self, key)
def get(self, attr, default = None):
def get(self, attr, default=None):
"""
Returns the given attribute. If the attribute is not set by
the parser return 'default'.
@@ -315,7 +308,7 @@ class Tag(object):
Tag values are strings (for binary data), unicode objects, or datetime
objects for tags that represent dates or times.
"""
def __init__(self, value = None, langcode = 'und', binary = False):
def __init__(self, value=None, langcode='und', binary=False):
super(Tag, self).__init__()
self.value = value
self.langcode = langcode
@@ -363,7 +356,7 @@ class Tags(dict, Tag):
The attribute RATING has a value (PG), but it also has a child tag
COUNTRY that specifies the country code the rating belongs to.
"""
def __init__(self, value = None, langcode = 'und', binary = False):
def __init__(self, value=None, langcode='und', binary=False):
super(Tags, self).__init__()
self.value = value
self.langcode = langcode
@@ -410,7 +403,7 @@ class Chapter(Media):
"""
_keys = ['enabled', 'name', 'pos', 'id']
def __init__(self, name = None, pos = 0):
def __init__(self, name=None, pos=0):
Media.__init__(self)
self.name = name
self.pos = pos
@@ -424,7 +417,7 @@ class Subtitle(Media):
_keys = ['enabled', 'default', 'langcode', 'language', 'trackno', 'title',
'id', 'codec']
def __init__(self, language = None):
def __init__(self, language=None):
Media.__init__(self)
self.language = language
+2 -5
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
#
# This file is part of enzyme.
#
@@ -15,10 +15,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
class Error(Exception):
pass
+17 -18
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -16,16 +16,15 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__all__ = ['Parser']
from exceptions import *
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
from exceptions import ParseError
import core
import logging
import struct
__all__ = ['Parser']
# get logging object
log = logging.getLogger(__name__)
@@ -44,7 +43,7 @@ FLV_AUDIO_CODECID = (0x0001, 0x0002, 0x0055, 0x0001)
# video flags
FLV_VIDEO_CODECID_MASK = 0x0f
FLV_VIDEO_CODECID = ( 'FLV1', 'MSS1', 'VP60') # wild guess
FLV_VIDEO_CODECID = ('FLV1', 'MSS1', 'VP60') # wild guess
FLV_DATA_TYPE_NUMBER = 0x00
FLV_DATA_TYPE_BOOL = 0x01
@@ -70,7 +69,7 @@ class FlashVideo(core.AVContainer):
"""
table_mapping = { 'FLVINFO' : FLVINFO }
def __init__(self,file):
def __init__(self, file):
core.AVContainer.__init__(self)
self.mime = 'video/flv'
self.type = 'Flash Video'
@@ -78,7 +77,7 @@ class FlashVideo(core.AVContainer):
if len(data) < 13 or struct.unpack('>3sBBII', data)[0] != 'FLV':
raise ParseError()
for i in range(10):
for _ in range(10):
if self.audio and self.video:
break
data = file.read(11)
@@ -116,13 +115,13 @@ class FlashVideo(core.AVContainer):
file.seek(size - 1, 1)
elif chunk[0] == FLV_TAG_TYPE_META:
log.info('metadata %s', str(chunk))
log.info(u'metadata %r', str(chunk))
metadata = file.read(size)
try:
while metadata:
length, value = self._parse_value(metadata)
if isinstance(value, dict):
log.info('metadata: %s', value)
log.info(u'metadata: %r', value)
if value.get('creator'):
self.copyright = value.get('creator')
if value.get('width'):
@@ -139,7 +138,7 @@ class FlashVideo(core.AVContainer):
except (IndexError, struct.error, TypeError):
pass
else:
log.info('unkown %s', str(chunk))
log.info(u'unkown %r', str(chunk))
file.seek(size, 1)
file.seek(4, 1)
@@ -157,16 +156,16 @@ class FlashVideo(core.AVContainer):
if ord(data[0]) == FLV_DATA_TYPE_STRING:
length = (ord(data[1]) << 8) + ord(data[2])
return length + 3, data[3:length+3]
return length + 3, data[3:length + 3]
if ord(data[0]) == FLV_DATA_TYPE_ECMARRAY:
init_length = len(data)
num = struct.unpack('>I', data[1:5])[0]
data = data[5:]
result = {}
for i in range(num):
for _ in range(num):
length = (ord(data[0]) << 8) + ord(data[1])
key = data[2:length+2]
key = data[2:length + 2]
data = data[length + 2:]
length, value = self._parse_value(data)
if not length:
@@ -175,7 +174,7 @@ class FlashVideo(core.AVContainer):
data = data[length:]
return init_length - len(data), result
log.info('unknown code: %x. Stop metadata parser', ord(data[0]))
log.info(u'unknown code: %x. Stop metadata parser', ord(data[0]))
return 0, None
+3 -6
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -16,10 +16,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
import string
import re
import struct
+3 -10
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
#
# This file is part of enzyme.
#
@@ -15,12 +15,5 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__title__ = 'enzyme'
__description__ = 'Video metadata parser'
__version__ = '0.1'
__author__ = 'Antoine Bertin'
__email__ = 'diaoulael@gmail.com'
__license__ = 'GPLv3'
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__version__ = '0.2'
+4 -7
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -16,10 +16,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
import re
__all__ = ['resolve']
@@ -44,7 +41,7 @@ def resolve(code):
if code in spec[:-1]:
return code, spec[-1]
return code, u'Unknown (%s)' % code
return code, u'Unknown (%r)' % code
# Parsed from http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
+33 -34
View File
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright (C) 2003-2006 Jason Tackaberry <tack@urandom.ca>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2003-2006 Jason Tackaberry <tack@urandom.ca>
#
# This file is part of enzyme.
#
@@ -18,18 +18,17 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__all__ = ['Parser']
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
from exceptions import *
from exceptions import ParseError
from struct import unpack
import core
import logging
import re
__all__ = ['Parser']
# get logging object
log = logging.getLogger(__name__)
@@ -236,7 +235,7 @@ class EbmlEntity:
self.compute_id(inbuf)
if self.id_len == 0:
log.error("EBML entity not found, bad file format")
log.error(u'EBML entity not found, bad file format')
raise ParseError()
self.entity_len, self.len_size = self.compute_len(inbuf[self.id_len:])
@@ -247,7 +246,7 @@ class EbmlEntity:
# if the data size is 8 or less, it could be a numeric value
self.value = 0
if self.entity_len <= 8:
for pos, shift in zip(range(self.entity_len), range((self.entity_len-1)*8, -1, -8)):
for pos, shift in zip(range(self.entity_len), range((self.entity_len - 1) * 8, -1, -8)):
self.value |= ord(self.entity_data[pos]) << shift
@@ -271,19 +270,19 @@ class EbmlEntity:
if len(inbuf) < 2:
return 0
self.id_len = 2
self.entity_id = ord(inbuf[0])<<8 | ord(inbuf[1])
self.entity_id = ord(inbuf[0]) << 8 | ord(inbuf[1])
elif first & 0x20:
if len(inbuf) < 3:
return 0
self.id_len = 3
self.entity_id = (ord(inbuf[0])<<16) | (ord(inbuf[1])<<8) | \
self.entity_id = (ord(inbuf[0]) << 16) | (ord(inbuf[1]) << 8) | \
(ord(inbuf[2]))
elif first & 0x10:
if len(inbuf) < 4:
return 0
self.id_len = 4
self.entity_id = (ord(inbuf[0])<<24) | (ord(inbuf[1])<<16) | \
(ord(inbuf[2])<<8) | (ord(inbuf[3]))
self.entity_id = (ord(inbuf[0]) << 24) | (ord(inbuf[1]) << 16) | \
(ord(inbuf[2]) << 8) | (ord(inbuf[3]))
self.entity_str = inbuf[0:self.id_len]
@@ -381,7 +380,7 @@ class Matroska(core.AVContainer):
if header.get_id() != MATROSKA_HEADER_ID:
raise ParseError()
log.debug("HEADER ID found %08X" % header.get_id() )
log.debug(u'HEADER ID found %08X' % header.get_id())
self.mime = 'video/x-matroska'
self.type = 'Matroska'
self.has_idx = False
@@ -392,10 +391,10 @@ class Matroska(core.AVContainer):
# Record file offset of segment data for seekheads
self.segment.offset = header.get_total_len() + segment.get_header_len()
if segment.get_id() != MATROSKA_SEGMENT_ID:
log.debug("SEGMENT ID not found %08X" % segment.get_id())
log.debug(u'SEGMENT ID not found %08X' % segment.get_id())
return
log.debug("SEGMENT ID found %08X" % segment.get_id())
log.debug(u'SEGMENT ID found %08X' % segment.get_id())
try:
for elem in self.process_one_level(segment):
if elem.get_id() == MATROSKA_SEEKHEAD_ID:
@@ -404,12 +403,12 @@ class Matroska(core.AVContainer):
pass
if not self.has_idx:
log.warning('File has no index')
log.warning(u'File has no index')
self._set('corrupt', True)
def process_elem(self, elem):
elem_id = elem.get_id()
log.debug('BEGIN: process element %s' % hex(elem_id))
log.debug(u'BEGIN: process element %r' % hex(elem_id))
if elem_id == MATROSKA_SEGMENT_INFO_ID:
duration = 0
scalecode = 1000000.0
@@ -423,7 +422,7 @@ class Matroska(core.AVContainer):
elif ielem_id == MATROSKA_TITLE_ID:
self.title = ielem.get_utf8()
elif ielem_id == MATROSKA_DATE_UTC_ID:
timestamp = unpack('!q', ielem.get_data())[0] / 10.0**9
timestamp = unpack('!q', ielem.get_data())[0] / 10.0 ** 9
# Date is offset 2001-01-01 00:00:00 (timestamp 978307200.0)
self.timestamp = int(timestamp + 978307200)
@@ -447,7 +446,7 @@ class Matroska(core.AVContainer):
elif elem_id == MATROSKA_CUES_ID:
self.has_idx = True
log.debug('END: process element %s' % hex(elem_id))
log.debug(u'END: process element %r' % hex(elem_id))
return True
@@ -479,7 +478,7 @@ class Matroska(core.AVContainer):
index = 0
while index < tracks.get_len():
trackelem = EbmlEntity(tracksbuf[index:])
log.debug ("ELEMENT %X found" % trackelem.get_id())
log.debug (u'ELEMENT %X found' % trackelem.get_id())
self.process_track(trackelem)
index += trackelem.get_total_len() + trackelem.get_crc_len()
@@ -503,20 +502,20 @@ class Matroska(core.AVContainer):
elements = [x for x in self.process_one_level(track)]
track_type = [x.get_value() for x in elements if x.get_id() == MATROSKA_TRACK_TYPE_ID]
if not track_type:
log.debug('Bad track: no type id found')
log.debug(u'Bad track: no type id found')
return
track_type = track_type[0]
track = None
if track_type == MATROSKA_VIDEO_TRACK:
log.debug("Video track found")
log.debug(u'Video track found')
track = self.process_video_track(elements)
elif track_type == MATROSKA_AUDIO_TRACK:
log.debug("Audio track found")
log.debug(u'Audio track found')
track = self.process_audio_track(elements)
elif track_type == MATROSKA_SUBTITLES_TRACK:
log.debug("Subtitle track found")
log.debug(u'Subtitle track found')
track = core.Subtitle()
self.set_track_defaults(track)
track.id = len(self.subtitles)
@@ -529,7 +528,7 @@ class Matroska(core.AVContainer):
elem_id = elem.get_id()
if elem_id == MATROSKA_TRACK_LANGUAGE_ID:
track.language = elem.get_str()
log.debug("Track language found: %s" % track.language)
log.debug(u'Track language found: %r' % track.language)
elif elem_id == MATROSKA_NAME_ID:
track.title = elem.get_utf8()
elif elem_id == MATROSKA_TRACK_NUMBER_ID:
@@ -671,7 +670,7 @@ class Matroska(core.AVContainer):
elif elem_id == MATROSKA_CHAPTER_UID_ID:
self.objects_by_uid[elem.get_value()] = chap
log.debug('Chapter "%s" found', chap.name)
log.debug(u'Chapter %r found', chap.name)
chap.id = len(self.chapters)
self.chapters.append(chap)
@@ -704,10 +703,10 @@ class Matroska(core.AVContainer):
# Right now we only support attachments that could be cover images.
# Make a guess to see if this attachment is a cover image.
if mimetype.startswith("image/") and u"cover" in (name+desc).lower() and data:
if mimetype.startswith("image/") and u"cover" in (name + desc).lower() and data:
self.thumbnail = data
log.debug('Attachment "%s" found' % name)
log.debug(u'Attachment %r found' % name)
def process_tags(self, tags):
@@ -744,7 +743,7 @@ class Matroska(core.AVContainer):
self.objects_by_uid[target].tags.update(tags_dict)
self.tags_to_attributes(self.objects_by_uid[target], tags_dict)
except KeyError:
log.warning('Tags assigned to unknown/unsupported target uid %d', target)
log.warning(u'Tags assigned to unknown/unsupported target uid %d', target)
else:
self.tags.update(tags_dict)
self.tags_to_attributes(self, tags_dict)
@@ -816,7 +815,7 @@ class Matroska(core.AVContainer):
try:
value = [filter(item) for item in value] if isinstance(value, list) else filter(value)
except Exception, e:
log.warning('Failed to convert tag to core attribute: %s', e)
log.warning(u'Failed to convert tag to core attribute: %r', e)
# Special handling for tv series recordings. The 'title' tag
# can be used for both the series and the episode name. The
# same is true for trackno which may refer to the season
+64 -66
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2007 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2007 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2007 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2007 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,16 +17,14 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['Parser']
import zlib
import logging
import StringIO
import struct
from exceptions import *
from exceptions import ParseError
import core
# get logging object
@@ -169,7 +167,7 @@ class MPEG4(core.AVContainer):
self.type = 'Quicktime Video'
h = file.read(8)
try:
(size, type) = struct.unpack('>I4s',h)
(size, type) = struct.unpack('>I4s', h)
except struct.error:
# EOF.
raise ParseError()
@@ -183,18 +181,18 @@ class MPEG4(core.AVContainer):
self.mime = 'video/mp4'
self.type = 'MPEG-4 Video'
size -= 4
file.seek(size-8, 1)
file.seek(size - 8, 1)
h = file.read(8)
(size, type) = struct.unpack('>I4s',h)
(size, type) = struct.unpack('>I4s', h)
while type in ['mdat', 'skip']:
# movie data at the beginning, skip
file.seek(size-8, 1)
file.seek(size - 8, 1)
h = file.read(8)
(size, type) = struct.unpack('>I4s',h)
(size, type) = struct.unpack('>I4s', h)
if not type in ['moov', 'wide', 'free']:
log.debug('invalid header: %r' % type)
log.debug(u'invalid header: %r' % type)
raise ParseError()
# Extended size
@@ -216,37 +214,37 @@ class MPEG4(core.AVContainer):
if len(s) < 8:
return 0
atomsize,atomtype = struct.unpack('>I4s', s)
atomsize, atomtype = struct.unpack('>I4s', s)
if not str(atomtype).decode('latin1').isalnum():
# stop at nonsense data
return 0
log.debug('%s [%X]' % (atomtype,atomsize))
log.debug(u'%r [%X]' % (atomtype, atomsize))
if atomtype == 'udta':
# Userdata (Metadata)
pos = 0
tabl = {}
i18ntabl = {}
atomdata = file.read(atomsize-8)
while pos < atomsize-12:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos+8])
atomdata = file.read(atomsize - 8)
while pos < atomsize - 12:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos + 8])
if ord(datatype[0]) == 169:
# i18n Metadata...
mypos = 8+pos
while mypos + 4 < datasize+pos:
mypos = 8 + pos
while mypos + 4 < datasize + pos:
# first 4 Bytes are i18n header
(tlen, lang) = struct.unpack('>HH', atomdata[mypos:mypos+4])
(tlen, lang) = struct.unpack('>HH', atomdata[mypos:mypos + 4])
i18ntabl[lang] = i18ntabl.get(lang, {})
l = atomdata[mypos+4:mypos+tlen+4]
l = atomdata[mypos + 4:mypos + tlen + 4]
i18ntabl[lang][datatype[1:]] = l
mypos += tlen+4
mypos += tlen + 4
elif datatype == 'WLOC':
# Drop Window Location
pass
else:
if ord(atomdata[pos+8:pos+datasize][0]) > 1:
tabl[datatype] = atomdata[pos+8:pos+datasize]
if ord(atomdata[pos + 8:pos + datasize][0]) > 1:
tabl[datatype] = atomdata[pos + 8:pos + datasize]
pos += datasize
if len(i18ntabl.keys()) > 0:
for k in i18ntabl.keys():
@@ -254,19 +252,19 @@ class MPEG4(core.AVContainer):
self._appendtable('QTUDTA', i18ntabl[k])
self._appendtable('QTUDTA', tabl)
else:
log.debug('NO i18')
log.debug(u'NO i18')
self._appendtable('QTUDTA', tabl)
elif atomtype == 'trak':
atomdata = file.read(atomsize-8)
atomdata = file.read(atomsize - 8)
pos = 0
trackinfo = {}
tracktype = None
while pos < atomsize-8:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos+8])
while pos < atomsize - 8:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos + 8])
if datatype == 'tkhd':
tkhd = struct.unpack('>6I8x4H36xII', atomdata[pos+8:pos+datasize])
tkhd = struct.unpack('>6I8x4H36xII', atomdata[pos + 8:pos + datasize])
trackinfo['width'] = tkhd[10] >> 16
trackinfo['height'] = tkhd[11] >> 16
trackinfo['id'] = tkhd[3]
@@ -277,23 +275,23 @@ class MPEG4(core.AVContainer):
# XXX Apple time. FIXME to work on Apple, too
self.timestamp = int(tkhd[1]) - 2082844800
except Exception, e:
log.exception('There was trouble extracting timestamp')
log.exception(u'There was trouble extracting timestamp')
elif datatype == 'mdia':
pos += 8
datasize -= 8
log.debug('--> mdia information')
log.debug(u'--> mdia information')
while datasize:
mdia = struct.unpack('>I4s', atomdata[pos:pos+8])
mdia = struct.unpack('>I4s', atomdata[pos:pos + 8])
if mdia[1] == 'mdhd':
# Parse based on version of mdhd header. See
# http://wiki.multimedia.cx/index.php?title=QuickTime_container#mdhd
ver = ord(atomdata[pos + 8])
if ver == 0:
mdhd = struct.unpack('>IIIIIhh', atomdata[pos+8:pos+8+24])
mdhd = struct.unpack('>IIIIIhh', atomdata[pos + 8:pos + 8 + 24])
elif ver == 1:
mdhd = struct.unpack('>IQQIQhh', atomdata[pos+8:pos+8+36])
mdhd = struct.unpack('>IQQIQhh', atomdata[pos + 8:pos + 8 + 36])
else:
mdhd = None
@@ -313,34 +311,34 @@ class MPEG4(core.AVContainer):
pos -= (mdia[0] - 8)
datasize += (mdia[0] - 8)
elif mdia[1] == 'hdlr':
hdlr = struct.unpack('>I4s4s', atomdata[pos+8:pos+8+12])
hdlr = struct.unpack('>I4s4s', atomdata[pos + 8:pos + 8 + 12])
if hdlr[1] == 'mhlr':
if hdlr[2] == 'vide':
tracktype = 'video'
if hdlr[2] == 'soun':
tracktype = 'audio'
elif mdia[1] == 'stsd':
stsd = struct.unpack('>2I', atomdata[pos+8:pos+8+8])
stsd = struct.unpack('>2I', atomdata[pos + 8:pos + 8 + 8])
if stsd[1] > 0:
codec = atomdata[pos+16:pos+16+8]
codec = atomdata[pos + 16:pos + 16 + 8]
codec = struct.unpack('>I4s', codec)
trackinfo['codec'] = codec[1]
if codec[1] == 'jpeg':
tracktype = 'image'
elif mdia[1] == 'dinf':
dref = struct.unpack('>I4s', atomdata[pos+8:pos+8+8])
log.debug(' --> %s, %s (useless)' % mdia)
dref = struct.unpack('>I4s', atomdata[pos + 8:pos + 8 + 8])
log.debug(u' --> %r, %r (useless)' % mdia)
if dref[1] == 'dref':
num = struct.unpack('>I', atomdata[pos+20:pos+20+4])[0]
rpos = pos+20+4
num = struct.unpack('>I', atomdata[pos + 20:pos + 20 + 4])[0]
rpos = pos + 20 + 4
for ref in range(num):
# FIXME: do somthing if this references
ref = struct.unpack('>I3s', atomdata[rpos:rpos+7])
data = atomdata[rpos+7:rpos+ref[0]]
ref = struct.unpack('>I3s', atomdata[rpos:rpos + 7])
data = atomdata[rpos + 7:rpos + ref[0]]
rpos += ref[0]
else:
if mdia[1].startswith('st'):
log.debug(' --> %s, %s (sample)' % mdia)
log.debug(u' --> %r, %r (sample)' % mdia)
elif mdia[1] == 'vmhd' and not tracktype:
# indicates that this track is video
tracktype = 'video'
@@ -348,19 +346,19 @@ class MPEG4(core.AVContainer):
# indicates that this track is audio
tracktype = 'audio'
else:
log.debug(' --> %s, %s (unknown)' % mdia)
log.debug(u' --> %r, %r (unknown)' % mdia)
pos += mdia[0]
datasize -= mdia[0]
elif datatype == 'udta':
log.debug(struct.unpack('>I4s', atomdata[:8]))
log.debug(u'udta: %r' % struct.unpack('>I4s', atomdata[:8]))
else:
if datatype == 'edts':
log.debug('--> %s [%d] (edit list)' % \
log.debug(u'--> %r [%d] (edit list)' % \
(datatype, datasize))
else:
log.debug('--> %s [%d] (unknown)' % \
log.debug(u'--> %r [%d] (unknown)' % \
(datatype, datasize))
pos += datasize
@@ -380,7 +378,7 @@ class MPEG4(core.AVContainer):
mvhd = struct.unpack('>6I2h', file.read(28))
self.length = max(self.length, mvhd[4] / mvhd[3])
self.volume = mvhd[6]
file.seek(atomsize-8-28,1)
file.seek(atomsize - 8 - 28, 1)
elif atomtype == 'cmov':
@@ -389,21 +387,21 @@ class MPEG4(core.AVContainer):
if not atomtype == 'dcom':
return atomsize
method = struct.unpack('>4s', file.read(datasize-8))[0]
method = struct.unpack('>4s', file.read(datasize - 8))[0]
datasize, atomtype = struct.unpack('>I4s', file.read(8))
if not atomtype == 'cmvd':
return atomsize
if method == 'zlib':
data = file.read(datasize-8)
data = file.read(datasize - 8)
try:
decompressed = zlib.decompress(data)
except Exception, e:
try:
decompressed = zlib.decompress(data[4:])
except Exception, e:
log.exception('There was a proble decompressiong atom')
log.exception(u'There was a proble decompressiong atom')
return atomsize
decompressedIO = StringIO.StringIO(decompressed)
@@ -411,9 +409,9 @@ class MPEG4(core.AVContainer):
pass
else:
log.info('unknown compression %s' % method)
log.info(u'unknown compression %r' % method)
# unknown compression method
file.seek(datasize-8,1)
file.seek(datasize - 8, 1)
elif atomtype == 'moov':
# decompressed movie info
@@ -423,10 +421,10 @@ class MPEG4(core.AVContainer):
elif atomtype == 'mdat':
pos = file.tell() + atomsize - 8
# maybe there is data inside the mdat
log.info('parsing mdat')
log.info(u'parsing mdat')
while self._readatom(file):
pass
log.info('end of mdat')
log.info(u'end of mdat')
file.seek(pos, 0)
@@ -437,24 +435,24 @@ class MPEG4(core.AVContainer):
elif atomtype == 'rmda':
# reference
atomdata = file.read(atomsize-8)
atomdata = file.read(atomsize - 8)
pos = 0
url = ''
quality = 0
datarate = 0
while pos < atomsize-8:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos+8])
while pos < atomsize - 8:
(datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos + 8])
if datatype == 'rdrf':
rflags, rtype, rlen = struct.unpack('>I4sI', atomdata[pos+8:pos+20])
rflags, rtype, rlen = struct.unpack('>I4sI', atomdata[pos + 8:pos + 20])
if rtype == 'url ':
url = atomdata[pos+20:pos+20+rlen]
url = atomdata[pos + 20:pos + 20 + rlen]
if url.find('\0') > 0:
url = url[:url.find('\0')]
elif datatype == 'rmqu':
quality = struct.unpack('>I', atomdata[pos+8:pos+12])[0]
quality = struct.unpack('>I', atomdata[pos + 8:pos + 12])[0]
elif datatype == 'rmdr':
datarate = struct.unpack('>I', atomdata[pos+12:pos+16])[0]
datarate = struct.unpack('>I', atomdata[pos + 12:pos + 16])[0]
pos += datasize
if url:
@@ -462,11 +460,11 @@ class MPEG4(core.AVContainer):
else:
if not atomtype in ['wide', 'free']:
log.info('unhandled base atom %s' % atomtype)
log.info(u'unhandled base atom %r' % atomtype)
# Skip unknown atoms
try:
file.seek(atomsize-8,1)
file.seek(atomsize - 8, 1)
except IOError:
return 0
+89 -91
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,16 +17,14 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['Parser']
import os
import struct
import logging
import stat
from exceptions import *
from exceptions import ParseError
import core
# get logging object
@@ -49,7 +47,7 @@ START_CODE = {
0xB7 : 'sequence end',
0xB8 : 'group of pictures',
}
for i in range(0x01,0xAF):
for i in range(0x01, 0xAF):
START_CODE[i] = 'slice_start_code'
##------------------------------------------------------------------------
@@ -96,14 +94,14 @@ TS_SYNC = 0x47
##------------------------------------------------------------------------
FRAME_RATE = [
0,
24000.0/1001, ## 3-2 pulldown NTSC (CPB/Main Level)
24, ## Film (CPB/Main Level)
25, ## PAL/SECAM or 625/60 video
30000.0/1001, ## NTSC (CPB/Main Level)
30, ## drop-frame NTSC or component 525/60 (CPB/Main Level)
50, ## double-rate PAL
60000.0/1001, ## double-rate NTSC
60, ## double-rate, drop-frame NTSC/component 525/60 video
24000.0 / 1001, ## 3-2 pulldown NTSC (CPB/Main Level)
24, ## Film (CPB/Main Level)
25, ## PAL/SECAM or 625/60 video
30000.0 / 1001, ## NTSC (CPB/Main Level)
30, ## drop-frame NTSC or component 525/60 (CPB/Main Level)
50, ## double-rate PAL
60000.0 / 1001, ## double-rate NTSC
60, ## double-rate, drop-frame NTSC/component 525/60 video
]
##------------------------------------------------------------------------
@@ -114,9 +112,9 @@ FRAME_RATE = [
## it, a stream that doesn't adhere to one of these aspect ratios is
## technically considered non-compliant.
##------------------------------------------------------------------------
ASPECT_RATIO = ( None, # Forbidden
1.0, # 1/1 (VGA)
4.0 / 3, # 4/3 (TV)
ASPECT_RATIO = (None, # Forbidden
1.0, # 1/1 (VGA)
4.0 / 3, # 4/3 (TV)
16.0 / 9, # 16/9 (Widescreen)
2.21 # (Cinema)
)
@@ -131,7 +129,7 @@ class MPEG(core.AVContainer):
no additional metadata like title, etc; only codecs, length and
resolution is reported back.
"""
def __init__(self,file):
def __init__(self, file):
core.AVContainer.__init__(self)
self.sequence_header_offset = 0
self.mpeg_version = 2
@@ -190,29 +188,29 @@ class MPEG(core.AVContainer):
a.codec = ac
def dxy(self,file):
def dxy(self, file):
"""
get width and height of the video
"""
file.seek(self.sequence_header_offset+4,0)
file.seek(self.sequence_header_offset + 4, 0)
v = file.read(4)
x = struct.unpack('>H',v[:2])[0] >> 4
y = struct.unpack('>H',v[1:3])[0] & 0x0FFF
return (x,y)
x = struct.unpack('>H', v[:2])[0] >> 4
y = struct.unpack('>H', v[1:3])[0] & 0x0FFF
return (x, y)
def framerate_aspect(self,file):
def framerate_aspect(self, file):
"""
read framerate and aspect ratio
"""
file.seek(self.sequence_header_offset+7,0)
v = struct.unpack( '>B', file.read(1) )[0]
file.seek(self.sequence_header_offset + 7, 0)
v = struct.unpack('>B', file.read(1))[0]
try:
fps = FRAME_RATE[v&0xf]
fps = FRAME_RATE[v & 0xf]
except IndexError:
fps = None
if v>>4 < len(ASPECT_RATIO):
aspect = ASPECT_RATIO[v>>4]
if v >> 4 < len(ASPECT_RATIO):
aspect = ASPECT_RATIO[v >> 4]
else:
aspect = None
return (fps, aspect)
@@ -238,18 +236,18 @@ class MPEG(core.AVContainer):
if pos == -1 or len(buffer) - pos < 5:
buffer = buffer[-10:]
continue
ext = (ord(buffer[pos+4]) >> 4)
ext = (ord(buffer[pos + 4]) >> 4)
if ext == 8:
pass
elif ext == 1:
if (ord(buffer[pos+5]) >> 3) & 1:
if (ord(buffer[pos + 5]) >> 3) & 1:
self._set('progressive', True)
else:
self._set('interlaced', True)
return True
else:
log.debug('ext', ext)
buffer = buffer[pos+4:]
log.debug(u'ext: %r' % ext)
buffer = buffer[pos + 4:]
return False
@@ -292,12 +290,12 @@ class MPEG(core.AVContainer):
## Some parts in the code are based on mpgtx (mpgtx.sf.net)
def bitrate(self,file):
def bitrate(self, file):
"""
read the bitrate (most of the time broken)
"""
file.seek(self.sequence_header_offset+8,0)
t,b = struct.unpack( '>HB', file.read(3) )
file.seek(self.sequence_header_offset + 8, 0)
t, b = struct.unpack('>HB', file.read(3))
vrate = t << 2 | b >> 6
return vrate * 400
@@ -309,9 +307,9 @@ class MPEG(core.AVContainer):
if len(buffer) < 6:
return None
highbit = (ord(buffer[0])&0x20)>>5
highbit = (ord(buffer[0]) & 0x20) >> 5
low4Bytes= ((long(ord(buffer[0])) & 0x18) >> 3) << 30
low4Bytes = ((long(ord(buffer[0])) & 0x18) >> 3) << 30
low4Bytes |= (ord(buffer[0]) & 0x03) << 28
low4Bytes |= ord(buffer[1]) << 20
low4Bytes |= (ord(buffer[2]) & 0xF8) << 12
@@ -319,10 +317,10 @@ class MPEG(core.AVContainer):
low4Bytes |= ord(buffer[3]) << 5
low4Bytes |= (ord(buffer[4])) >> 3
sys_clock_ref=(ord(buffer[4]) & 0x3) << 7
sys_clock_ref|=(ord(buffer[5]) >> 1)
sys_clock_ref = (ord(buffer[4]) & 0x3) << 7
sys_clock_ref |= (ord(buffer[5]) >> 1)
return (long(highbit * (1<<16) * (1<<16)) + low4Bytes) / 90000
return (long(highbit * (1 << 16) * (1 << 16)) + low4Bytes) / 90000
def ReadSCRMpeg1(self, buffer):
@@ -340,7 +338,7 @@ class MPEG(core.AVContainer):
low4Bytes |= ord(buffer[3]) << 7;
low4Bytes |= ord(buffer[4]) >> 1;
return (long(highbit) * (1<<16) * (1<<16) + low4Bytes) / 90000;
return (long(highbit) * (1 << 16) * (1 << 16) + low4Bytes) / 90000;
def ReadPTS(self, buffer):
@@ -350,7 +348,7 @@ class MPEG(core.AVContainer):
high = ((ord(buffer[0]) & 0xF) >> 1)
med = (ord(buffer[1]) << 7) + (ord(buffer[2]) >> 1)
low = (ord(buffer[3]) << 7) + (ord(buffer[4]) >> 1)
return ((long(high) << 30 ) + (med << 15) + low) / 90000
return ((long(high) << 30) + (med << 15) + low) / 90000
def ReadHeader(self, buffer, offset):
@@ -358,25 +356,25 @@ class MPEG(core.AVContainer):
Handle MPEG header in buffer on position offset
Return None on error, new offset or 0 if the new offset can't be scanned
"""
if buffer[offset:offset+3] != '\x00\x00\x01':
if buffer[offset:offset + 3] != '\x00\x00\x01':
return None
id = ord(buffer[offset+3])
id = ord(buffer[offset + 3])
if id == PADDING_PKT:
return offset + (ord(buffer[offset+4]) << 8) + \
ord(buffer[offset+5]) + 6
return offset + (ord(buffer[offset + 4]) << 8) + \
ord(buffer[offset + 5]) + 6
if id == PACK_PKT:
if ord(buffer[offset+4]) & 0xF0 == 0x20:
if ord(buffer[offset + 4]) & 0xF0 == 0x20:
self.type = 'MPEG-1 Video'
self.get_time = self.ReadSCRMpeg1
self.mpeg_version = 1
return offset + 12
elif (ord(buffer[offset+4]) & 0xC0) == 0x40:
elif (ord(buffer[offset + 4]) & 0xC0) == 0x40:
self.type = 'MPEG-2 Video'
self.get_time = self.ReadSCRMpeg2
return offset + (ord(buffer[offset+13]) & 0x07) + 14
return offset + (ord(buffer[offset + 13]) & 0x07) + 14
else:
# I have no idea what just happened, but for some DVB
# recordings done with mencoder this points to a
@@ -412,10 +410,10 @@ class MPEG(core.AVContainer):
if id in [PRIVATE_STREAM1, PRIVATE_STREAM2]:
# private stream. we don't know, but maybe we can guess later
add = ord(buffer[offset+8])
add = ord(buffer[offset + 8])
# if (ord(buffer[offset+6]) & 4) or 1:
# id = ord(buffer[offset+10+add])
if buffer[offset+11+add:offset+15+add].find('\x0b\x77') != -1:
if buffer[offset + 11 + add:offset + 15 + add].find('\x0b\x77') != -1:
# AC3 stream
for a in self.audio:
if a.id == id:
@@ -442,18 +440,18 @@ class MPEG(core.AVContainer):
This MPEG starts with a sequence of 0x00 followed by a PACK Header
http://dvd.sourceforge.net/dvdinfo/packhdr.html
"""
file.seek(0,0)
file.seek(0, 0)
buffer = file.read(10000)
offset = 0
# seek until the 0 byte stop
while offset < len(buffer)-100 and buffer[offset] == '\0':
while offset < len(buffer) - 100 and buffer[offset] == '\0':
offset += 1
offset -= 2
# test for mpeg header 0x00 0x00 0x01
header = '\x00\x00\x01%s' % chr(PACK_PKT)
if offset < 0 or not buffer[offset:offset+4] == header:
if offset < 0 or not buffer[offset:offset + 4] == header:
if not force:
return 0
# brute force and try to find the pack header in the first
@@ -470,9 +468,9 @@ class MPEG(core.AVContainer):
self.ReadHeader(buffer, offset)
# store first timestamp
self.start = self.get_time(buffer[offset+4:])
self.start = self.get_time(buffer[offset + 4:])
while len(buffer) > offset + 1000 and \
buffer[offset:offset+3] == '\x00\x00\x01':
buffer[offset:offset + 3] == '\x00\x00\x01':
# read the mpeg header
new_offset = self.ReadHeader(buffer, offset)
@@ -486,12 +484,12 @@ class MPEG(core.AVContainer):
# skip padding 0 before a new header
while len(buffer) > offset + 10 and \
not ord(buffer[offset+2]):
not ord(buffer[offset + 2]):
offset += 1
else:
# seek to new header by brute force
offset += buffer[offset+4:].find('\x00\x00\x01') + 4
offset += buffer[offset + 4:].find('\x00\x00\x01') + 4
# fill in values for support functions:
self.__seek_size__ = 1000000
@@ -555,15 +553,15 @@ class MPEG(core.AVContainer):
self.video[-1]._set('id', id)
# new mpeg starting
if buffer[header_length+9:header_length+13] == \
if buffer[header_length + 9:header_length + 13] == \
'\x00\x00\x01\xB3' and not self.sequence_header_offset:
# yes, remember offset for later use
self.sequence_header_offset = offset + header_length+9
self.sequence_header_offset = offset + header_length + 9
elif ord(buffer[3]) == 189 or ord(buffer[3]) == 191:
# private stream. we don't know, but maybe we can guess later
id = id or ord(buffer[3]) & 0xF
if align and \
buffer[header_length+9:header_length+11] == '\x0b\x77':
buffer[header_length + 9:header_length + 11] == '\x0b\x77':
# AC3 stream
for a in self.audio:
if a.id == id:
@@ -581,7 +579,7 @@ class MPEG(core.AVContainer):
if ptsdts and ptsdts == ord(buffer[9]) >> 4:
if ord(buffer[9]) >> 4 != ptsdts:
log.warning('WARNING: bad PTS/DTS, please contact us')
log.warning(u'WARNING: bad PTS/DTS, please contact us')
return packet_length, None
# timestamp = self.ReadPTS(buffer[9:14])
@@ -595,8 +593,8 @@ class MPEG(core.AVContainer):
def isPES(self, file):
log.info('trying mpeg-pes scan')
file.seek(0,0)
log.info(u'trying mpeg-pes scan')
file.seek(0, 0)
buffer = file.read(3)
# header (also valid for all mpegs)
@@ -613,7 +611,7 @@ class MPEG(core.AVContainer):
return 0
if timestamp != None and not hasattr(self, 'start'):
self.get_time = self.ReadPTS
bpos = buffer[offset+timestamp:offset+timestamp+5]
bpos = buffer[offset + timestamp:offset + timestamp + 5]
self.start = self.get_time(bpos)
if self.sequence_header_offset and hasattr(self, 'start'):
# we have all informations we need
@@ -702,13 +700,13 @@ class MPEG(core.AVContainer):
# Transport Stream ===============================================
def isTS(self, file):
file.seek(0,0)
file.seek(0, 0)
buffer = file.read(TS_PACKET_LENGTH * 2)
c = 0
while c + TS_PACKET_LENGTH < len(buffer):
if ord(buffer[c]) == ord(buffer[c+TS_PACKET_LENGTH]) == TS_SYNC:
if ord(buffer[c]) == ord(buffer[c + TS_PACKET_LENGTH]) == TS_SYNC:
break
c += 1
else:
@@ -718,7 +716,7 @@ class MPEG(core.AVContainer):
self.type = 'MPEG-TS'
while c + TS_PACKET_LENGTH < len(buffer):
start = ord(buffer[c+1]) & 0x40
start = ord(buffer[c + 1]) & 0x40
# maybe load more into the buffer
if c + 2 * TS_PACKET_LENGTH > len(buffer) and c < 500000:
buffer += file.read(10000)
@@ -728,30 +726,30 @@ class MPEG(core.AVContainer):
c += TS_PACKET_LENGTH
continue
tsid = ((ord(buffer[c+1]) & 0x3F) << 8) + ord(buffer[c+2])
adapt = (ord(buffer[c+3]) & 0x30) >> 4
tsid = ((ord(buffer[c + 1]) & 0x3F) << 8) + ord(buffer[c + 2])
adapt = (ord(buffer[c + 3]) & 0x30) >> 4
offset = 4
if adapt & 0x02:
# meta info present, skip it for now
adapt_len = ord(buffer[c+offset])
adapt_len = ord(buffer[c + offset])
offset += adapt_len + 1
if not ord(buffer[c+1]) & 0x40:
if not ord(buffer[c + 1]) & 0x40:
# no new pes or psi in stream payload starting
pass
elif adapt & 0x01:
# PES
timestamp = self.ReadPESHeader(c+offset, buffer[c+offset:],
timestamp = self.ReadPESHeader(c + offset, buffer[c + offset:],
tsid)[1]
if timestamp != None:
if not hasattr(self, 'start'):
self.get_time = self.ReadPTS
timestamp = c + offset + timestamp
self.start = self.get_time(buffer[timestamp:timestamp+5])
self.start = self.get_time(buffer[timestamp:timestamp + 5])
elif not hasattr(self, 'audio_ok'):
timestamp = c + offset + timestamp
start = self.get_time(buffer[timestamp:timestamp+5])
start = self.get_time(buffer[timestamp:timestamp + 5])
if start is not None and self.start is not None and \
abs(start - self.start) < 10:
# looks ok
@@ -759,7 +757,7 @@ class MPEG(core.AVContainer):
else:
# timestamp broken
del self.start
log.warning('Timestamp error, correcting')
log.warning(u'Timestamp error, correcting')
if hasattr(self, 'start') and self.start and \
self.sequence_header_offset and self.video and self.audio:
@@ -786,31 +784,31 @@ class MPEG(core.AVContainer):
c = 0
while c + TS_PACKET_LENGTH < len(buffer):
if ord(buffer[c]) == ord(buffer[c+TS_PACKET_LENGTH]) == TS_SYNC:
if ord(buffer[c]) == ord(buffer[c + TS_PACKET_LENGTH]) == TS_SYNC:
break
c += 1
else:
return None
while c + TS_PACKET_LENGTH < len(buffer):
start = ord(buffer[c+1]) & 0x40
start = ord(buffer[c + 1]) & 0x40
if not start:
c += TS_PACKET_LENGTH
continue
tsid = ((ord(buffer[c+1]) & 0x3F) << 8) + ord(buffer[c+2])
adapt = (ord(buffer[c+3]) & 0x30) >> 4
tsid = ((ord(buffer[c + 1]) & 0x3F) << 8) + ord(buffer[c + 2])
adapt = (ord(buffer[c + 3]) & 0x30) >> 4
offset = 4
if adapt & 0x02:
# meta info present, skip it for now
offset += ord(buffer[c+offset]) + 1
offset += ord(buffer[c + offset]) + 1
if adapt & 0x01:
timestamp = self.ReadPESHeader(c+offset, buffer[c+offset:], tsid)[1]
timestamp = self.ReadPESHeader(c + offset, buffer[c + offset:], tsid)[1]
if timestamp is None:
# this should not happen
log.error('bad TS')
log.error(u'bad TS')
return None
return c + offset + timestamp
c += TS_PACKET_LENGTH
@@ -841,7 +839,7 @@ class MPEG(core.AVContainer):
if pos == None:
break
end = self.get_time(buffer[pos:]) or end
buffer = buffer[pos+100:]
buffer = buffer[pos + 100:]
file.close()
return end
@@ -855,7 +853,7 @@ class MPEG(core.AVContainer):
if end == None or self.start == None:
return None
if self.start > end:
return int(((long(1) << 33) - 1 ) / 90000) - self.start + end
return int(((long(1) << 33) - 1) / 90000) - self.start + end
return end - self.start
@@ -897,7 +895,7 @@ class MPEG(core.AVContainer):
return 0
file = open(self.filename)
log.debug('scanning file...')
log.debug(u'scanning file...')
while 1:
file.seek(self.__seek_size__ * 10, 1)
buffer = file.read(self.__sample_size__)
@@ -906,10 +904,10 @@ class MPEG(core.AVContainer):
pos = self.__search__(buffer)
if pos == None:
continue
log.debug('buffer position: %s' % self.get_time(buffer[pos:]))
log.debug(u'buffer position: %r' % self.get_time(buffer[pos:]))
file.close()
log.debug('done scanning file')
log.debug(u'done scanning file')
Parser = MPEG
+29 -31
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,9 +17,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['Parser']
import struct
@@ -27,7 +25,7 @@ import re
import stat
import os
import logging
from exceptions import *
from exceptions import ParseError
import core
# get logging object
@@ -82,7 +80,7 @@ class Ogm(core.AVContainer):
# seek to the end of the stream, to avoid scanning the whole file
if (os.stat(file.name)[stat.ST_SIZE] > 50000):
file.seek(os.stat(file.name)[stat.ST_SIZE]-49000)
file.seek(os.stat(file.name)[stat.ST_SIZE] - 49000)
# read the rest of the file into a buffer
h = file.read()
@@ -147,21 +145,21 @@ class Ogm(core.AVContainer):
self._appendtable('VORBISCOMMENT', header)
def _parseOGGS(self,file):
def _parseOGGS(self, file):
h = file.read(27)
if len(h) == 0:
# Regular File end
return None, None
elif len(h) < 27:
log.debug("%d Bytes of Garbage found after End." % len(h))
log.debug(u'%d Bytes of Garbage found after End.' % len(h))
return None, None
if h[:4] != "OggS":
log.debug("Invalid Ogg")
log.debug(u'Invalid Ogg')
raise ParseError()
version = ord(h[4])
if version != 0:
log.debug("Unsupported OGG/OGM Version %d." % version)
log.debug(u'Unsupported OGG/OGM Version %d' % version)
return None, None
head = struct.unpack('<BQIIIB', h[5:])
@@ -178,13 +176,13 @@ class Ogm(core.AVContainer):
h = file.read(1)
packettype = ord(h[0]) & PACKET_TYPE_BITS
if packettype == PACKET_TYPE_HEADER:
h += file.read(nextlen-1)
h += file.read(nextlen - 1)
self._parseHeader(h, granulepos)
elif packettype == PACKED_TYPE_METADATA:
h += file.read(nextlen-1)
h += file.read(nextlen - 1)
self._parseMeta(h)
else:
file.seek(nextlen-1,1)
file.seek(nextlen - 1, 1)
if len(self.all_streams) > serial:
stream = self.all_streams[serial]
if hasattr(stream, 'samplerate') and \
@@ -197,27 +195,27 @@ class Ogm(core.AVContainer):
return granulepos, nextlen + 27 + pageSegCount
def _parseMeta(self,h):
def _parseMeta(self, h):
flags = ord(h[0])
headerlen = len(h)
if headerlen >= 7 and h[1:7] == 'vorbis':
header = {}
nextlen, self.encoder = self._extractHeaderString(h[7:])
numItems = struct.unpack('<I',h[7+nextlen:7+nextlen+4])[0]
start = 7+4+nextlen
for i in range(numItems):
numItems = struct.unpack('<I', h[7 + nextlen:7 + nextlen + 4])[0]
start = 7 + 4 + nextlen
for _ in range(numItems):
(nextlen, s) = self._extractHeaderString(h[start:])
start += nextlen
if s:
a = re.split('=',s)
header[(a[0]).upper()]=a[1]
a = re.split('=', s)
header[(a[0]).upper()] = a[1]
# Put Header fields into info fields
self.type = 'OGG Vorbis'
self.subtype = ''
self.all_header.append(header)
def _parseHeader(self,header,granule):
def _parseHeader(self, header, granule):
headerlen = len(header)
flags = ord(header[0])
@@ -225,7 +223,7 @@ class Ogm(core.AVContainer):
ai = core.AudioStream()
ai.version, ai.channels, ai.samplerate, bitrate_max, ai.bitrate, \
bitrate_min, blocksize, framing = \
struct.unpack('<IBIiiiBB',header[7:7+23])
struct.unpack('<IBIiiiBB', header[7:7 + 23])
ai.codec = 'Vorbis'
#ai.granule = granule
#ai.length = granule / ai.samplerate
@@ -250,12 +248,12 @@ class Ogm(core.AVContainer):
self.all_streams.append(vi)
elif flags & PACKET_TYPE_BITS == PACKET_TYPE_HEADER and \
headerlen >= struct.calcsize(STREAM_HEADER_VIDEO)+1:
headerlen >= struct.calcsize(STREAM_HEADER_VIDEO) + 1:
# New Directshow Format
htype = header[1:9]
if htype[:5] == 'video':
sh = header[9:struct.calcsize(STREAM_HEADER_VIDEO)+9]
sh = header[9:struct.calcsize(STREAM_HEADER_VIDEO) + 9]
streamheader = struct.unpack(STREAM_HEADER_VIDEO, sh)
vi = core.VideoStream()
(type, ssize, timeunit, samplerate, vi.length, buffersize, \
@@ -270,13 +268,13 @@ class Ogm(core.AVContainer):
self.all_streams.append(vi)
elif htype[:5] == 'audio':
sha = header[9:struct.calcsize(STREAM_HEADER_AUDIO)+9]
sha = header[9:struct.calcsize(STREAM_HEADER_AUDIO) + 9]
streamheader = struct.unpack(STREAM_HEADER_AUDIO, sha)
ai = core.AudioStream()
(type, ssize, timeunit, ai.samplerate, ai.length, buffersize, \
ai.bitrate, ai.channels, bloc, ai.bitrate) = streamheader
self.samplerate = ai.samplerate
log.debug("Samplerate %d" % self.samplerate)
log.debug(u'Samplerate %d' % self.samplerate)
self.audio.append(ai)
self.all_streams.append(ai)
@@ -287,15 +285,15 @@ class Ogm(core.AVContainer):
self.all_streams.append(subtitle)
else:
log.debug("Unknown Header")
log.debug(u'Unknown Header')
def _extractHeaderString(self,header):
def _extractHeaderString(self, header):
len = struct.unpack('<I', header[:4])[0]
try:
return (len+4,unicode(header[4:4+len], 'utf-8'))
return (len + 4, unicode(header[4:4 + len], 'utf-8'))
except (KeyError, IndexError, UnicodeDecodeError):
return (len+4,None)
return (len + 4, None)
Parser = Ogm
+39 -41
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,14 +17,12 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['Parser']
import struct
import logging
from exceptions import *
from exceptions import ParseError
import core
# http://www.pcisys.net/~melanson/codecs/rmff.htm
@@ -34,13 +32,13 @@ import core
log = logging.getLogger(__name__)
class RealVideo(core.AVContainer):
def __init__(self,file):
def __init__(self, file):
core.AVContainer.__init__(self)
self.mime = 'video/real'
self.type = 'Real Video'
h = file.read(10)
try:
(object_id,object_size,object_version) = struct.unpack('>4sIH',h)
(object_id, object_size, object_version) = struct.unpack('>4sIH', h)
except struct.error:
# EOF.
raise ParseError()
@@ -49,47 +47,47 @@ class RealVideo(core.AVContainer):
raise ParseError()
file_version, num_headers = struct.unpack('>II', file.read(8))
log.debug("size: %d, ver: %d, headers: %d" % \
(object_size, file_version,num_headers))
for i in range(0,num_headers):
log.debug(u'size: %d, ver: %d, headers: %d' % \
(object_size, file_version, num_headers))
for _ in range(0, num_headers):
try:
oi = struct.unpack('>4sIH',file.read(10))
oi = struct.unpack('>4sIH', file.read(10))
except (struct.error, IOError):
# Header data we expected wasn't there. File may be
# only partially complete.
break
if object_id == 'DATA' and oi[0] != 'INDX':
log.debug('INDX chunk expected after DATA but not found -- file corrupt')
log.debug(u'INDX chunk expected after DATA but not found -- file corrupt')
break
(object_id,object_size,object_version) = oi
(object_id, object_size, object_version) = oi
if object_id == 'DATA':
# Seek over the data chunk rather than reading it in.
file.seek(object_size - 10, 1)
else:
self._read_header(object_id, file.read(object_size-10))
log.debug("%s [%d]" % (object_id,object_size-10))
self._read_header(object_id, file.read(object_size - 10))
log.debug(u'%r [%d]' % (object_id, object_size - 10))
# Read all the following headers
def _read_header(self,object_id,s):
def _read_header(self, object_id, s):
if object_id == 'PROP':
prop = struct.unpack('>9IHH', s)
log.debug(prop)
log.debug(u'PROP: %r' % prop)
if object_id == 'MDPR':
mdpr = struct.unpack('>H7I', s[:30])
log.debug(mdpr)
self.length = mdpr[7]/1000.0
log.debug(u'MDPR: %r' % mdpr)
self.length = mdpr[7] / 1000.0
(stream_name_size,) = struct.unpack('>B', s[30:31])
stream_name = s[31:31+stream_name_size]
pos = 31+stream_name_size
(mime_type_size,) = struct.unpack('>B', s[pos:pos+1])
mime = s[pos+1:pos+1+mime_type_size]
pos += mime_type_size+1
(type_specific_len,) = struct.unpack('>I', s[pos:pos+4])
type_specific = s[pos+4:pos+4+type_specific_len]
pos += 4+type_specific_len
stream_name = s[31:31 + stream_name_size]
pos = 31 + stream_name_size
(mime_type_size,) = struct.unpack('>B', s[pos:pos + 1])
mime = s[pos + 1:pos + 1 + mime_type_size]
pos += mime_type_size + 1
(type_specific_len,) = struct.unpack('>I', s[pos:pos + 4])
type_specific = s[pos + 4:pos + 4 + type_specific_len]
pos += 4 + type_specific_len
if mime[:5] == 'audio':
ai = core.AudioStream()
ai.id = mdpr[0]
@@ -101,20 +99,20 @@ class RealVideo(core.AVContainer):
vi.bitrate = mdpr[2]
self.video.append(vi)
else:
log.debug("Unknown: %s" % mime)
log.debug(u'Unknown: %r' % mime)
if object_id == 'CONT':
pos = 0
(title_len,) = struct.unpack('>H', s[pos:pos+2])
self.title = s[2:title_len+2]
pos += title_len+2
(author_len,) = struct.unpack('>H', s[pos:pos+2])
self.artist = s[pos+2:pos+author_len+2]
pos += author_len+2
(copyright_len,) = struct.unpack('>H', s[pos:pos+2])
self.copyright = s[pos+2:pos+copyright_len+2]
pos += copyright_len+2
(comment_len,) = struct.unpack('>H', s[pos:pos+2])
self.comment = s[pos+2:pos+comment_len+2]
(title_len,) = struct.unpack('>H', s[pos:pos + 2])
self.title = s[2:title_len + 2]
pos += title_len + 2
(author_len,) = struct.unpack('>H', s[pos:pos + 2])
self.artist = s[pos + 2:pos + author_len + 2]
pos += author_len + 2
(copyright_len,) = struct.unpack('>H', s[pos:pos + 2])
self.copyright = s[pos + 2:pos + copyright_len + 2]
pos += copyright_len + 2
(comment_len,) = struct.unpack('>H', s[pos:pos + 2])
self.comment = s[pos + 2:pos + comment_len + 2]
Parser = RealVideo
+85 -87
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright (C) 2003-2006 Dirk Meyer <dischi@freevo.org>
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2003-2006 Thomas Schueppel <stain@acm.org>
# Copyright 2003-2006 Dirk Meyer <dischi@freevo.org>
#
# This file is part of enzyme.
#
@@ -17,9 +17,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['Parser']
import os
@@ -27,7 +25,7 @@ import struct
import string
import logging
import time
from exceptions import *
from exceptions import ParseError
import core
# get logging object
@@ -70,7 +68,7 @@ class Riff(core.AVContainer):
"""
table_mapping = { 'AVIINFO' : AVIINFO }
def __init__(self,file):
def __init__(self, file):
core.AVContainer.__init__(self)
# read the header
h = file.read(12)
@@ -90,12 +88,12 @@ class Riff(core.AVContainer):
while self._parseRIFFChunk(file):
pass
except IOError:
log.exception('error in file, stop parsing')
log.exception(u'error in file, stop parsing')
self._find_subtitles(file.name)
if not self.has_idx and isinstance(self, core.AVContainer):
log.debug('WARNING: avi has no index')
log.debug(u'WARNING: avi has no index')
self._set('corrupt', True)
@@ -104,9 +102,9 @@ class Riff(core.AVContainer):
Search for subtitle files. Right now only VobSub is supported
"""
base = os.path.splitext(filename)[0]
if os.path.isfile(base+'.idx') and \
(os.path.isfile(base+'.sub') or os.path.isfile(base+'.rar')):
file = open(base+'.idx')
if os.path.isfile(base + '.idx') and \
(os.path.isfile(base + '.sub') or os.path.isfile(base + '.rar')):
file = open(base + '.idx')
if file.readline().find('VobSub index file') > 0:
for line in file.readlines():
if line.find('id') == 0:
@@ -117,10 +115,10 @@ class Riff(core.AVContainer):
file.close()
def _parseAVIH(self,t):
def _parseAVIH(self, t):
retval = {}
v = struct.unpack('<IIIIIIIIIIIIII',t[0:56])
( retval['dwMicroSecPerFrame'],
v = struct.unpack('<IIIIIIIIIIIIII', t[0:56])
(retval['dwMicroSecPerFrame'],
retval['dwMaxBytesPerSec'],
retval['dwPaddingGranularity'],
retval['dwFlags'],
@@ -133,22 +131,22 @@ class Riff(core.AVContainer):
retval['dwScale'],
retval['dwRate'],
retval['dwStart'],
retval['dwLength'] ) = v
retval['dwLength']) = v
if retval['dwMicroSecPerFrame'] == 0:
log.warning("ERROR: Corrupt AVI")
log.warning(u'ERROR: Corrupt AVI')
raise ParseError()
return retval
def _parseSTRH(self,t):
def _parseSTRH(self, t):
retval = {}
retval['fccType'] = t[0:4]
log.debug("_parseSTRH(%s) : %d bytes" % (retval['fccType'], len(t)))
log.debug(u'_parseSTRH(%r) : %d bytes' % (retval['fccType'], len(t)))
if retval['fccType'] != 'auds':
retval['fccHandler'] = t[4:8]
v = struct.unpack('<IHHIIIIIIIII',t[8:52])
( retval['dwFlags'],
v = struct.unpack('<IHHIIIIIIIII', t[8:52])
(retval['dwFlags'],
retval['wPriority'],
retval['wLanguage'],
retval['dwInitialFrames'],
@@ -159,11 +157,11 @@ class Riff(core.AVContainer):
retval['dwSuggestedBufferSize'],
retval['dwQuality'],
retval['dwSampleSize'],
retval['rcFrame'] ) = v
retval['rcFrame']) = v
else:
try:
v = struct.unpack('<IHHIIIIIIIII',t[8:52])
( retval['dwFlags'],
v = struct.unpack('<IHHIIIIIIIII', t[8:52])
(retval['dwFlags'],
retval['wPriority'],
retval['wLanguage'],
retval['dwInitialFrames'],
@@ -174,7 +172,7 @@ class Riff(core.AVContainer):
retval['dwSuggestedBufferSize'],
retval['dwQuality'],
retval['dwSampleSize'],
retval['rcFrame'] ) = v
retval['rcFrame']) = v
self.delay = float(retval['dwStart']) / \
(float(retval['dwRate']) / retval['dwScale'])
except (KeyError, IndexError, ValueError, ZeroDivisionError):
@@ -183,12 +181,12 @@ class Riff(core.AVContainer):
return retval
def _parseSTRF(self,t,strh):
def _parseSTRF(self, t, strh):
fccType = strh['fccType']
retval = {}
if fccType == 'auds':
v = struct.unpack('<HHHHHH',t[0:12])
( retval['wFormatTag'],
v = struct.unpack('<HHHHHH', t[0:12])
(retval['wFormatTag'],
retval['nChannels'],
retval['nSamplesPerSec'],
retval['nAvgBytesPerSec'],
@@ -209,18 +207,18 @@ class Riff(core.AVContainer):
ai.codec = retval['wFormatTag']
self.audio.append(ai)
elif fccType == 'vids':
v = struct.unpack('<IIIHH',t[0:16])
( retval['biSize'],
v = struct.unpack('<IIIHH', t[0:16])
(retval['biSize'],
retval['biWidth'],
retval['biHeight'],
retval['biPlanes'],
retval['biBitCount'] ) = v
v = struct.unpack('IIIII',t[20:40])
( retval['biSizeImage'],
retval['biBitCount']) = v
v = struct.unpack('IIIII', t[20:40])
(retval['biSizeImage'],
retval['biXPelsPerMeter'],
retval['biYPelsPerMeter'],
retval['biClrUsed'],
retval['biClrImportant'] ) = v
retval['biClrImportant']) = v
vi = core.VideoStream()
vi.codec = t[16:20]
vi.width = retval['biWidth']
@@ -233,15 +231,15 @@ class Riff(core.AVContainer):
return retval
def _parseSTRL(self,t):
def _parseSTRL(self, t):
retval = {}
size = len(t)
i = 0
while i < len(t) - 8:
key = t[i:i+4]
sz = struct.unpack('<I',t[i+4:i+8])[0]
i+=8
key = t[i:i + 4]
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += 8
value = t[i:]
if key == 'strh':
@@ -249,40 +247,40 @@ class Riff(core.AVContainer):
elif key == 'strf':
retval[key] = self._parseSTRF(value, retval['strh'])
else:
log.debug("_parseSTRL: unsupported stream tag '%s'", key)
log.debug(u'_parseSTRL: unsupported stream tag %r', key)
i += sz
return retval, i
def _parseODML(self,t):
def _parseODML(self, t):
retval = {}
size = len(t)
i = 0
key = t[i:i+4]
sz = struct.unpack('<I',t[i+4:i+8])[0]
key = t[i:i + 4]
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += 8
value = t[i:]
if key != 'dmlh':
log.debug("_parseODML: Error")
log.debug(u'_parseODML: Error')
i += sz - 8
return (retval, i)
def _parseVPRP(self,t):
def _parseVPRP(self, t):
retval = {}
v = struct.unpack('<IIIIIIIIII',t[:4*10])
v = struct.unpack('<IIIIIIIIII', t[:4 * 10])
( retval['VideoFormat'],
(retval['VideoFormat'],
retval['VideoStandard'],
retval['RefreshRate'],
retval['HTotalIn'],
retval['VTotalIn'],
retval['FrameAspectRatio'],
retval['wPixel'],
retval['hPixel'] ) = v[1:-1]
retval['hPixel']) = v[1:-1]
# I need an avi with more informations
# enum {FORMAT_UNKNOWN, FORMAT_PAL_SQUARE, FORMAT_PAL_CCIR_601,
@@ -309,7 +307,7 @@ class Riff(core.AVContainer):
# If the VOL header doesn't appear within 5MB or 5 video chunks,
# give up. The 5MB limit is not likely to apply except in
# pathological cases.
while i < min(1024*1024*5, size - 8) and n_dc < 5:
while i < min(1024 * 1024 * 5, size - 8) and n_dc < 5:
data = file.read(8)
if ord(data[0]) == 0:
# Eat leading nulls.
@@ -317,7 +315,7 @@ class Riff(core.AVContainer):
i += 1
key, sz = struct.unpack('<4sI', data)
if key[2:] != 'dc' or sz > 1024*500:
if key[2:] != 'dc' or sz > 1024 * 500:
# This chunk is not video or is unusually big (> 500KB);
# skip it.
file.seek(sz, 1)
@@ -338,7 +336,7 @@ class Riff(core.AVContainer):
startcode = 0xff
def bits(v, o, n):
# Returns n bits in v, offset o bits.
return (v & 2**n-1 << (64-n-o)) >> 64-n-o
return (v & 2 ** n - 1 << (64 - n - o)) >> 64 - n - o
while pos < sz:
startcode = ((startcode << 8) | ord(data[pos])) & 0xffffffff
@@ -350,7 +348,7 @@ class Riff(core.AVContainer):
if startcode >= 0x120 and startcode <= 0x12F:
# We have the VOL startcode. Pull 64 bits of it and treat
# as a bitstream
v = struct.unpack(">Q", data[pos : pos+8])[0]
v = struct.unpack(">Q", data[pos : pos + 8])[0]
offset = 10
if bits(v, 9, 1):
# is_ol_id, skip over vo_ver_id and vo_priority
@@ -385,36 +383,36 @@ class Riff(core.AVContainer):
if i < size:
# Seek past whatever might be remaining of the movi list.
file.seek(size-i,1)
file.seek(size - i, 1)
def _parseLIST(self,t):
def _parseLIST(self, t):
retval = {}
i = 0
size = len(t)
while i < size-8:
while i < size - 8:
# skip zero
if ord(t[i]) == 0: i += 1
key = t[i:i+4]
key = t[i:i + 4]
sz = 0
if key == 'LIST':
sz = struct.unpack('<I',t[i+4:i+8])[0]
i+=8
key = "LIST:"+t[i:i+4]
value = self._parseLIST(t[i:i+sz])
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += 8
key = "LIST:" + t[i:i + 4]
value = self._parseLIST(t[i:i + sz])
if key == 'strl':
for k in value.keys():
retval[k] = value[k]
else:
retval[key] = value
i+=sz
i += sz
elif key == 'avih':
sz = struct.unpack('<I',t[i+4:i+8])[0]
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += 8
value = self._parseAVIH(t[i:i+sz])
value = self._parseAVIH(t[i:i + sz])
i += sz
retval[key] = value
elif key == 'strl':
@@ -433,15 +431,15 @@ class Riff(core.AVContainer):
retval[key] = value
i += sz
elif key == 'JUNK':
sz = struct.unpack('<I',t[i+4:i+8])[0]
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += sz + 8
else:
sz = struct.unpack('<I',t[i+4:i+8])[0]
i+=8
sz = struct.unpack('<I', t[i + 4:i + 8])[0]
i += 8
# in most cases this is some info stuff
if not key in AVIINFO.keys() and key != 'IDIT':
log.debug("Unknown Key: %s, len: %d" % (key,sz))
value = t[i:i+sz]
log.debug(u'Unknown Key: %r, len: %d' % (key, sz))
value = t[i:i + sz]
if key == 'ISFT':
# product information
if value.find('\0') > 0:
@@ -466,17 +464,17 @@ class Riff(core.AVContainer):
except ValueError:
pass
else:
log.debug('no support for time format %s', value)
i+=sz
log.debug(u'no support for time format %r', value)
i += sz
return retval
def _parseRIFFChunk(self,file):
def _parseRIFFChunk(self, file):
h = file.read(8)
if len(h) < 8:
return False
name = h[:4]
size = struct.unpack('<I',h[4:8])[0]
size = struct.unpack('<I', h[4:8])[0]
if name == 'LIST':
pos = file.tell() - 8
@@ -488,18 +486,18 @@ class Riff(core.AVContainer):
# header), but we do know the video's dimensions, and
# we're dealing with an mpeg4 stream, try to get the aspect
# from the VOL header in the mpeg4 stream.
self._parseLISTmovi(size-4, file)
self._parseLISTmovi(size - 4, file)
return True
elif size > 80000:
log.debug('RIFF LIST "%s" too long to parse: %s bytes' % (key, size))
t = file.seek(size-4,1)
log.debug(u'RIFF LIST %r too long to parse: %r bytes' % (key, size))
t = file.seek(size - 4, 1)
return True
elif size < 5:
log.debug('RIFF LIST "%s" too short: %s bytes' % (key, size))
log.debug(u'RIFF LIST %r too short: %r bytes' % (key, size))
return True
t = file.read(size-4)
log.debug('parse RIFF LIST "%s": %d bytes' % (key, size))
t = file.read(size - 4)
log.debug(u'parse RIFF LIST %r: %d bytes' % (key, size))
value = self._parseLIST(t)
self.header[key] = value
if key == 'INFO':
@@ -511,7 +509,7 @@ class Riff(core.AVContainer):
# no need to add this info to a table
pass
else:
log.debug('Skipping table info %s' % key)
log.debug(u'Skipping table info %r' % key)
elif name == 'JUNK':
self.junkStart = file.tell() - 8
@@ -519,15 +517,15 @@ class Riff(core.AVContainer):
file.seek(size, 1)
elif name == 'idx1':
self.has_idx = True
log.debug('idx1: %s bytes' % size)
log.debug(u'idx1: %r bytes' % size)
# no need to parse this
t = file.seek(size,1)
t = file.seek(size, 1)
elif name == 'RIFF':
log.debug("New RIFF chunk, extended avi [%i]" % size)
log.debug(u'New RIFF chunk, extended avi [%i]' % size)
type = file.read(4)
if type != 'AVIX':
log.debug("Second RIFF chunk is %s, not AVIX, skipping", type)
file.seek(size-4, 1)
log.debug(u'Second RIFF chunk is %r, not AVIX, skipping', type)
file.seek(size - 4, 1)
# that's it, no new informations should be in AVIX
return False
elif name == 'fmt ' and size <= 50:
@@ -556,11 +554,11 @@ class Riff(core.AVContainer):
elif not name.strip(string.printable + string.whitespace):
# check if name is something usefull at all, maybe it is no
# avi or broken
t = file.seek(size,1)
log.debug("Skipping %s [%i]" % (name,size))
t = file.seek(size, 1)
log.debug(u'Skipping %r [%i]' % (name, size))
else:
# bad avi
log.debug("Bad or broken avi")
log.debug(u'Bad or broken avi')
return False
return True
+4 -6
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# enzyme - Video metadata parser
# Copyright (C) 2011 Antoine Bertin <diaoulael@gmail.com>
# Copyright (C) 2006-2009 Dirk Meyer <dischi@freevo.org>
# Copyright (C) 2006-2009 Jason Tackaberry
# Copyright 2011-2012 Antoine Bertin <diaoulael@gmail.com>
# Copyright 2006-2009 Dirk Meyer <dischi@freevo.org>
# Copyright 2006-2009 Jason Tackaberry
#
# This file is part of enzyme.
#
@@ -17,9 +17,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# along with enzyme. If not, see <http://www.gnu.org/licenses/>.
__all__ = ['ENCODING', 'str_to_unicode', 'unicode_to_str']
import locale