def __init__(self, file): core.AVContainer.__init__(self) self.mime = 'video/x-ms-asf' self.type = 'asf format' self._languages = [] self._extinfo = {} h = file.read(30) if len(h) < 30: raise core.ParseError() (guidstr, objsize, objnum, reserved1, \ reserved2) = struct.unpack('<16sQIBB',h) guid = self._parseguid(guidstr) if (guid != GUIDS['ASF_Header_Object']): raise core.ParseError() if reserved1 != 0x01 or reserved2 != 0x02: raise core.ParseError() log.debug("asf header size: %d / %d objects" % (objsize, objnum)) header = file.read(objsize - 30) for i in range(0, objnum): h = self._getnextheader(header) header = header[h[1]:] del self._languages del self._extinfo
def parseDisc(self, device): type = None if self.is_disc(device) != 2: raise core.ParseError() f = open(device) try: # read CD-XA001 at byte 1024 in sector 16 f.seek(2048 * 16 + 1024, 0) if f.read(8) != 'CD-XA001': raise core.ParseError() # read VIDEO_CD at sector 150 f.seek(2048 * 150, 0) typebuffer = f.read(8) if typebuffer != 'VIDEO_CD' and typebuffer != 'SUPERVCD': raise core.ParseError() # Read some bytes of the ISO9660 part to better guess VCD or SVCD. # Maybe this code is not needed and VIDEO_CD and SUPERVCD are enough. f.seek(2048 * 16, 0) iso9660 = f.read(2048 * 16) if typebuffer == 'VIDEO_CD' and iso9660.find('MPEGAV') > 0: type = 'VCD' elif typebuffer == 'SUPERVCD' and \ (iso9660.find('SVCD') > 0 or iso9660.find('MPEG2') > 0): type = 'SVCD' else: raise core.ParseError() finally: f.close() # read the tracks to generate the title list device = open(device) (first, last) = cdrom.audiocd_toc_header(device) lmin = 0 lsec = 0 num = 0 for i in range(first, last + 2): if i == last + 1: min, sec, frames = cdrom.audiocd_leadout(device) else: min, sec, frames = cdrom.audiocd_toc_entry(device, i) if num: vi = core.VideoStream() # XXX add more static information here, it's also possible # XXX to scan for more informations like fps # XXX Settings to MPEG1/2 is a wild guess, maybe the track # XXX isn't playable at all (e.g. the menu) if type == 'VCD': vi.codec = 'MPEG1' else: vi.codec = 'MPEG2' vi.length = (min - lmin) * 60 + (sec - lsec) self.tracks.append(vi) num += 1 lmin, lsec = min, sec device.close()
def __init__(self, file): core.AVContainer.__init__(self) self.sequence_header_offset = 0 self.mpeg_version = 2 # detect TS (fast scan) if not self.isTS(file): # detect system mpeg (many infos) if not self.isMPEG(file): # detect PES if not self.isPES(file): # Maybe it's MPEG-ES if self.isES(file): # If isES() succeeds, we needn't do anything further. return if file.name.lower().endswith('mpeg') or \ file.name.lower().endswith('mpg'): # This has to be an mpeg file. It could be a bad # recording from an ivtv based hardware encoder with # same bytes missing at the beginning. # Do some more digging... if not self.isMPEG(file, force=True) or \ not self.video or not self.audio: # does not look like an mpeg at all raise core.ParseError() else: # no mpeg at all raise core.ParseError() self.mime = 'video/mpeg' if not self.video: self.video.append(core.VideoStream()) if self.sequence_header_offset <= 0: return self.progressive(file) for vi in self.video: vi.width, vi.height = self.dxy(file) vi.fps, vi.aspect = self.framerate_aspect(file) vi.bitrate = self.bitrate(file) if self.length: vi.length = self.length if not self.type: self.type = 'MPEG Video' # set fourcc codec for video and audio vc, ac = 'MP2V', 'MP2A' if self.mpeg_version == 1: vc, ac = 'MPEG', 0x0050 for v in self.video: v.codec = vc for a in self.audio: if not a.codec: a.codec = ac
def __init__(self,file): core.Music.__init__(self) t = self._what(file) if not t: raise core.ParseError() (self.type, self.samplerate, self.channels, self.bitrate, \ self.samplebits) = t if self.bitrate == -1: # doesn't look right raise core.ParseError() self.mime = "audio/%s" % self.type
def _parse(self, device): if not _ifoparser: log.debug("kaa.metadata not compiled with DVD support") raise core.ParseError() info = _ifoparser.parse(device) if not info: raise core.ParseError() for pos, title in enumerate(info): ti = DVDTitle(title) ti.trackno = pos + 1 ti.trackof = len(info) self.tracks.append(ti)
def __init__(self, file): core.AVContainer.__init__(self) self._references = [] self.mime = 'video/quicktime' self.type = 'Quicktime Video' h = file.read(8) try: (size, type) = struct.unpack('>I4s', h) except struct.error: # EOF. raise core.ParseError() if type == 'ftyp': # file type information if size >= 12: # this should always happen if file.read(4) != 'qt ': # not a quicktime movie, it is a mpeg4 container self.mime = 'video/mp4' self.type = 'MPEG-4 Video' size -= 4 file.seek(size - 8, 1) h = file.read(8) (size, type) = struct.unpack('>I4s', h) while type in ('mdat', 'skip'): # movie data at the beginning, skip file.seek(size - 8, 1) h = file.read(8) (size, type) = struct.unpack('>I4s', h) if not type in ('moov', 'wide', 'free'): log.debug('invalid header: %r' % type) raise core.ParseError() # Extended size if size == 1: size = struct.unpack('>Q', file.read(8)) # Back over the atom header we just read, since _readatom expects the # file position to be at the start of an atom. file.seek(-8, 1) while self._readatom(file): pass if self._references: self._set('references', self._references)
def __init__(self, file): core.AVContainer.__init__(self) # read the header h = file.read(12) if h[:4] != "RIFF" and h[:4] != 'SDSS': raise core.ParseError() self.has_idx = False self.header = {} self.junkStart = None self.infoStart = None self.type = h[8:12] if self.type == 'AVI ': self.mime = 'video/avi' elif self.type == 'WAVE': self.mime = 'audio/wav' try: while self._parseRIFFChunk(file): pass except IOError: log.exception('error in file, stop parsing') self._find_subtitles(file.name) if not self.has_idx and self.media == core.MEDIA_AV: log.debug('WARNING: avi has no index') self._set('corrupt', True)
def __init__(self, file): core.Image.__init__(self) self.mime = 'image/bmp' self.type = 'windows bitmap image' try: (bfType, bfSize, bfZero, bfOffset, biSize, self.width, self.height) = struct.unpack('<2sIIIIII', file.read(26)) except struct.error: raise core.ParseError() # seek to the end to test length file.seek(0, 2) if bfType != 'BM' or bfSize != file.tell(): raise core.ParseError()
def __init__(self,file): core.Image.__init__(self) self.mime = 'image/gif' try: header = struct.unpack('<6sHH', file.read(10)) except struct.error: # EOF. raise core.ParseError() gifType, self.width, self.height = header if not gifType.startswith('GIF'): raise core.ParseError() self.type = gifType.lower()
def __init__(self, file): core.Image.__init__(self) self.iptc = None self.mime = 'image/tiff' self.type = 'TIFF image' self.intel = 0 iptc = {} header = file.read(8) if header[:4] == MOTOROLASIGNATURE: self.intel = 0 (offset, ) = struct.unpack(">I", header[4:8]) file.seek(offset) (len, ) = struct.unpack(">H", file.read(2)) app = file.read(len * 12) for i in range(len): (tag, type, length, value, offset) = \ struct.unpack('>HHIHH', app[i*12:i*12+12]) if tag == 0x8649: file.seek(offset, 0) iptc = IPTC.parseiptc(file.read(1000)) elif tag == 0x0100: if value != 0: self.width = value else: self.width = offset elif tag == 0x0101: if value != 0: self.height = value else: self.height = offset elif header[:4] == INTELSIGNATURE: self.intel = 1 (offset, ) = struct.unpack("<I", header[4:8]) file.seek(offset, 0) (len, ) = struct.unpack("<H", file.read(2)) app = file.read(len * 12) for i in range(len): (tag, type, length, offset, value) = \ struct.unpack('<HHIHH', app[i*12:i*12+12]) if tag == 0x8649: file.seek(offset) iptc = IPTC.parseiptc(file.read(1000)) elif tag == 0x0100: if value != 0: self.width = value else: self.width = offset elif tag == 0x0101: if value != 0: self.height = value else: self.height = offset else: raise core.ParseError() if iptc: self._appendtable('IPTC', iptc)
def __init__(self, device): core.Disc.__init__(self) if self.is_disc(device) != 2: raise core.ParseError() self.offset = 0 self.mime = 'unknown/unknown' self.type = 'CD' self.subtype = 'data'
def __init__(self, file): core.Game.__init__(self) # Determine if the ROM is a Gameboy Advance ROM. # Compare the Logo Code. All GBA Roms have this code. file.seek(4) if file.read(156) != GBA_LOGOCODE: # Determine if the ROM is a Standard Gameboy ROM # Compare the Logo Code. All GB Roms have this code. file.seek(260) if file.read(len(GB_LOGOCODE)) != GB_LOGOCODE: raise core.ParseError() # Retrieve the ROM Title game_title = file.read(15) self.title = game_title # Retrieve the Rom Type (GB Colour or GB)j if file.read(1) == '\x80': self.mime = 'games/gbc' self.type = 'GameBoyColour game' else: self.mime = 'games/gb' self.type = 'GameBoy game' else: self.mime = 'games/gba' self.type = 'GameBoyAdvance game' # Retrieve the ROM Title game_title = file.read(12) self.title = game_title # Retrieve the Game Code game_code = file.read(4) # Retrieve the Manufacturer Code maker_code = file.read(2) log.debug("MAKER CODE: %s" % maker_code) # Check that the Fized Value is 0x96, if not then error. if file.read(1) != '\x96': raise core.ParseError()
def parseDVDiso(self, f): # brute force reading of the device to find out if it is a DVD f.seek(32768, 0) buffer = f.read(60000) if buffer.find('UDF') == -1: raise core.ParseError() # seems to be a DVD, read a little bit more buffer += f.read(550000) if buffer.find('VIDEO_TS') == -1 and \ buffer.find('VIDEO_TS.IFO') == -1 and \ buffer.find('OSTA UDF Compliant') == -1: raise core.ParseError() # OK, try libdvdread self._parse(f.name)
def __init__(self, url): core.Music.__init__(self) tup = urlparse.urlsplit(url) scheme, location, path, query, fragment = tup if scheme != 'http': raise core.ParseError() # Open an URL Connection fi = urllib.urlopen(url) # grab the statusline self.statusline = fi.readline() try: statuslist = string.split(self.statusline) except ValueError: # assume it is okay since so many servers are badly configured statuslist = ["ICY", "200"] if statuslist[1] != "200": if fi: fi.close() raise core.ParseError() self.type = 'audio' self.subtype = 'mp3' # grab any headers for a max of 10 lines linecnt = 0 tab = {} lines = fi.readlines(512) for linecnt in range(0, 11): icyline = lines[linecnt] icyline = icyline.rstrip('\r\n') if len(icyline) < 4: break cidx = icyline.find(':') if cidx != -1: # break on short line (ie. really should be a blank line) # strip leading and trailing whitespace tab[icyline[:cidx].strip()] = icyline[cidx + 2:].strip() if fi: fi.close() self._appendtable('ICY', tab)
def __init__(self,device): core.Disc.__init__(self) self.offset = 0 # check disc if self.is_disc(device) != 1: raise core.ParseError() self.query(device) self.mime = 'audio/cd' self.type = 'CD' self.subtype = 'audio'
def __init__(self, file): core.AVContainer.__init__(self) self.samplerate = 1 self.file = file # Read enough that we're likely to get the full seekhead (FIXME: kludge) buffer = file.read(2000) if len(buffer) == 0: # Regular File end raise core.ParseError() # Check the Matroska header header = EbmlEntity(buffer) if header.get_id() != MATROSKA_HEADER_ID: raise core.ParseError() log.debug("HEADER ID found %08X" % header.get_id()) self.mime = 'application/mkv' self.type = 'Matroska' self.has_idx = False self.objects_by_uid = {} # Now get the segment self.segment = segment = EbmlEntity(buffer[header.get_total_len():]) # 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()) return log.debug("SEGMENT ID found %08X" % segment.get_id()) try: for elem in self.process_one_level(segment): if elem.get_id() == MATROSKA_SEEKHEAD_ID: self.process_elem(elem) except core.ParseError: pass if not self.has_idx: log.debug('WARNING: file has no index') self._set('corrupt', True)
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) except struct.error: # EOF. raise core.ParseError() if not object_id == '.RMF': raise core.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): try: 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' ) break (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))
def __init__(self, inbuf): # Compute the EBML id # Set the CRC len to zero self.crc_len = 0 # Now loop until we find an entity without CRC try: self.build_entity(inbuf) except IndexError: raise core.ParseError() while self.get_id() == MATROSKA_CRC32_ID: self.crc_len += self.get_total_len() inbuf = inbuf[self.get_total_len():] self.build_entity(inbuf)
def parseDVDdir(self, dirname): def iglob(path, ifile): # Case insensitive glob to find video_ts dir/file. Python 2.5 has # glob.iglob but this doesn't exist in 2.4. file_glob = ''.join(['[%s%s]' % (c, c.upper()) for c in ifile]) return glob.glob(os.path.join(path, file_glob)) if True not in [ os.path.isdir(x) for x in iglob(dirname, 'video_ts') ] + \ [ os.path.isfile(x) for x in iglob(dirname, 'video_ts.vob') ]: raise core.ParseError() # OK, try libdvdread self._parse(dirname) return 1
def _parseAVIH(self, t): retval = {} v = struct.unpack('<IIIIIIIIIIIIII', t[0:56]) (retval['dwMicroSecPerFrame'], retval['dwMaxBytesPerSec'], retval['dwPaddingGranularity'], retval['dwFlags'], retval['dwTotalFrames'], retval['dwInitialFrames'], retval['dwStreams'], retval['dwSuggestedBufferSize'], retval['dwWidth'], retval['dwHeight'], retval['dwScale'], retval['dwRate'], retval['dwStart'], retval['dwLength']) = v if retval['dwMicroSecPerFrame'] == 0: log.warning("ERROR: Corrupt AVI") raise core.ParseError() return retval
def execute(cls, uri, http_verb, extra_headers=None, batch=False, **kw): """ if batch == False, execute a command with the given parameters and return the response JSON. If batch == True, return the dictionary that would be used in a batch command. """ if batch: ret = {"method": http_verb, "path": uri.split("parse.com")[1]} if kw: ret["body"] = kw return ret if not ('app_id' in ACCESS_KEYS and 'rest_key' in ACCESS_KEYS): raise core.ParseError('Missing connection credentials') app_id = ACCESS_KEYS.get('app_id') rest_key = ACCESS_KEYS.get('rest_key') master_key = ACCESS_KEYS.get('master_key') headers = extra_headers or {} url = uri if uri.startswith(API_ROOT) else cls.ENDPOINT_ROOT + uri data = kw and json.dumps(kw) or "{}" if http_verb == 'GET' and data: url += '?%s' % urlencode(kw) data = None request = Request(url, data, headers) request.add_header('Content-type', 'application/json') request.add_header('X-Parse-Application-Id', app_id) request.add_header('X-Parse-REST-API-Key', rest_key) if master_key and 'X-Parse-Session-Token' not in headers.keys(): request.add_header('X-Parse-Master-Key', master_key) request.get_method = lambda: http_verb try: response = urlopen(request) except HTTPError as e: exc = { 400: core.ResourceRequestBadRequest, 401: core.ResourceRequestLoginRequired, 403: core.ResourceRequestForbidden, 404: core.ResourceRequestNotFound }.get(e.code, core.ParseError) raise exc(e.read()) return json.loads(response.read())
def parseDisc(self, device): if self.is_disc(device) != 2: raise core.ParseError() # brute force reading of the device to find out if it is a DVD f = open(device, 'rb') f.seek(32768, 0) buffer = f.read(60000) if buffer.find('UDF') == -1: f.close() raise core.ParseError() # seems to be a DVD, read a little bit more buffer += f.read(550000) f.close() if buffer.find('VIDEO_TS') == -1 and \ buffer.find('VIDEO_TS.IFO') == -1 and \ buffer.find('OSTA UDF Compliant') == -1: raise core.ParseError() # OK, try libdvdread self._parse(device)
def __init__(self, file): core.Music.__init__(self) if file.name.endswith('.ac3'): # when the ending is ac3, force the detection. It may not be # necessary the the header is at the beginning but in the first # 2000 bytes check_length = 1000 else: check_length = 1 for i in range(check_length): if file.read(2) == '\x0b\x77': break else: raise core.ParseError() info = struct.unpack('<HBBBB', file.read(6)) self.samplerate = FSCOD[info[1] >> 6] self.bitrate = FRMSIZCOD[(info[1] & 0x3F) >> 1] * 1000 bsmod = info[2] & 0x7 channels = ACMOD[info[3] >> 5] acmod = info[3] >> 5 self.channels = ACMOD[acmod][1] bits = 0 if acmod & 0x01 and not acmod == 0x01: bits += 2 if acmod & 0x04: bits += 2 if acmod == 0x02: bits += 2 # info is now 5 bits of info[3] and all bits of info[4] ( == 13 bits) # 'bits' bits (0-6) bits are information we don't need, after that, # the bit we need is lfeon. info = (((info[3] & 0x1F) << 8) + info[4]) # now we create the mask we need (based on 'bits') # the bit number 13 - 'bits' is what we want to read for i in range(13 - bits - 1): info = info >> 1 if info & 1: # LFE channel self.channels += 1 file.seek(-1, 2) size = file.tell() self.length = size * 8.0 / self.bitrate self.codec = 0x2000 # fourcc code of ac3 self.mime = 'audio/ac3'
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)) return None, None if h[:4] != "OggS": log.debug("Invalid Ogg") raise core.ParseError() version = ord(h[4]) if version != 0: log.debug("Unsupported OGG/OGM Version %d." % version) return None, None head = struct.unpack('<BQIIIB', h[5:]) headertype, granulepos, serial, pageseqno, checksum, \ pageSegCount = head self.mime = 'application/ogm' self.type = 'OGG Media' tab = file.read(pageSegCount) nextlen = 0 for i in range(len(tab)): nextlen += ord(tab[i]) else: h = file.read(1) packettype = ord(h[0]) & PACKET_TYPE_BITS if packettype == PACKET_TYPE_HEADER: h += file.read(nextlen - 1) self._parseHeader(h, granulepos) elif packettype == PACKED_TYPE_METADATA: h += file.read(nextlen - 1) self._parseMeta(h) else: file.seek(nextlen - 1, 1) if len(self.all_streams) > serial: stream = self.all_streams[serial] if hasattr(stream, 'samplerate') and \ stream.samplerate: stream.length = granulepos / stream.samplerate elif hasattr(stream, 'bitrate') and \ stream.bitrate: stream.length = granulepos / stream.bitrate return granulepos, nextlen + 27 + pageSegCount
def __init__(self, file): core.Image.__init__(self) self.mime = 'image/png' self.type = 'PNG image' signature = file.read(8) if (signature != PNGSIGNATURE): raise core.ParseError() self.meta = {} while self._readChunk(file): pass if len(self.meta.keys()): self._appendtable('PNGMETA', self.meta) for key, value in self.meta.items(): if key.startswith('Thumb:') or key == 'Software': self._set(key, value)
def build_entity(self, inbuf): self.compute_id(inbuf) if self.id_len == 0: log.debug("EBML entity not found, bad file format") raise core.ParseError() self.entity_len, self.len_size = self.compute_len(inbuf[self.id_len:]) self.entity_data = inbuf[self.get_header_len() : self.get_total_len()] self.ebml_length = self.entity_len self.entity_len = min(len(self.entity_data), self.entity_len) # 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)): self.value |= ord(self.entity_data[pos]) << shift
def __init__(self, file): core.Music.__init__(self) tags = M4ATags(file) if tags.get('FileType') != 'M4A ': raise core.ParseError() self.valid = True self.mime = 'audio/mp4' self.filename = getattr(file, 'name', None) # Initialize core attributes from available tag values. self.title = tags.get('Title') self.artist = tags.get('Artist') self.album = tags.get('Album') self.trackno = tags.get('Track') self.year = tags.get('Year') self.encoder = tags.get('Tool') self.length = tags.get('Length') self.samplerate = tags.get('SampleRate')
def ret(obj, *args, **kw): conn = ACCESS_KEYS if not (conn and conn.get('master_key')): message = '%s requires the master key' % func.__name__ raise core.ParseError(message) func(obj, *args, **kw)
def __init__(self, file): core.Music.__init__(self) h = file.read(4 + 1 + 1 + 20 + 1) if h[:5] != "OggS\00": log.info("Invalid header") raise core.ParseError() if ord(h[5]) != 2: log.info("Invalid header type flag (trying to go ahead anyway)") self.pageSegCount = ord(h[-1]) # Skip the PageSegCount file.seek(self.pageSegCount, 1) h = file.read(7) if h != VORBIS_PACKET_INFO: log.info("Wrong vorbis header type, giving up.") raise core.ParseError() # http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions self.mime = 'audio/x-vorbis+ogg' header = {} info = file.read(23) self.version, self.channels, self.samplerate, bitrate_max, \ self.bitrate, bitrate_min, blocksize, \ framing = struct.unpack('<IBIiiiBB',info[:23]) self.bitrate = self.bitrate / 1000 # INFO Header, read Oggs and skip 10 bytes h = file.read(4 + 10 + 13) if h[:4] == 'OggS': (serial, pagesequence, checksum, numEntries) = \ struct.unpack( '<14xIIIB', h ) # skip past numEntries file.seek(numEntries, 1) h = file.read(7) if h != VORBIS_PACKET_HEADER: # Not a corrent info header return self.encoder = self._extractHeaderString(file) numItems = struct.unpack('<I', file.read(4))[0] for i in range(numItems): s = self._extractHeaderString(file) a = re.split('=', s) header[(a[0]).upper()] = a[1] # Put Header fields into info fields if header.has_key('TITLE'): self.title = header['TITLE'] if header.has_key('ALBUM'): self.album = header['ALBUM'] if header.has_key('ARTIST'): self.artist = header['ARTIST'] if header.has_key('COMMENT'): self.comment = header['COMMENT'] if header.has_key('DATE'): # FIXME: try to convert to timestamp self.userdate = header['DATE'] if header.has_key('ENCODER'): self.encoder = header['ENCODER'] if header.has_key('TRACKNUMBER'): self.trackno = header['TRACKNUMBER'] self.type = 'OGG Vorbis' self.subtype = '' self.length = self._calculateTrackLength(file) self._appendtable('VORBISCOMMENT', header)
def __init__(self, file): core.Image.__init__(self) self.mime = 'image/jpeg' self.type = 'jpeg image' if file.read(2) != '\xff\xd8': raise core.ParseError() file.seek(-2, 2) if file.read(2) != '\xff\xd9': # Normally an JPEG should end in ffd9. This does not however # we assume it's an jpeg for now log.info("Wrong encode found for jpeg") file.seek(2) app = file.read(4) self.meta = {} while (len(app) == 4): (ff, segtype, seglen) = struct.unpack(">BBH", app) if ff != 0xff: break if segtype == 0xd9: break elif SOF.has_key(segtype): data = file.read(seglen - 2) (precision,self.height,self.width,\ num_comp) = struct.unpack('>BHHB', data[:6]) elif segtype == 0xe1: data = file.read(seglen - 2) type = data[:data.find('\0')] if type == 'Exif': # create a fake file from the data we have to # pass it to the EXIF parser fakefile = cStringIO.StringIO() fakefile.write('\xFF\xD8') fakefile.write(app) fakefile.write(data) fakefile.seek(0) exif = EXIF.process_file(fakefile) fakefile.close() if exif: self.thumbnail = exif.get('JPEGThumbnail', None) if self.thumbnail: self.thumbnail = str(self.thumbnail) self._appendtable('EXIF', exif) if 'Image Orientation' in exif: orientation = str(exif['Image Orientation']) if orientation.find('90 CW') > 0: self.rotation = 90 elif orientation.find('90') > 0: self.rotation = 270 elif orientation.find('180') > 0: self.rotation = 180 t = exif.get('Image DateTimeOriginal') if not t: # sometimes it is called this way t = exif.get('EXIF DateTimeOriginal') if not t: t = exif.get('Image DateTime') if t: try: t = time.strptime(str(t), '%Y:%m:%d %H:%M:%S') self.timestamp = int(time.mktime(t)) except ValueError: # Malformed time string. pass elif type == 'http://ns.adobe.com/xap/1.0/': # FIXME: parse XMP data (xml) doc = data[data.find('\0') + 1:] else: pass elif segtype == 0xed: iptc = IPTC.parseiptc(file.read(seglen - 2)) if iptc: self._appendtable('IPTC', iptc) elif segtype == 0xe7: # information created by libs like epeg data = file.read(seglen - 2) if data.count('\n') == 1: key, value = data.split('\n') self.meta[key] = value elif segtype == 0xfe: self.comment = file.read(seglen - 2) if self.comment.startswith('<?xml'): # This could be a comment based on # http://www.w3.org/TR/photo-rdf/ log.error('xml comment parser not integrated') self.comment = '' else: # Huffman table marker (FFC4) # Start of Scan marker (FFDA) # Quantization table marker (FFDB) # Restart Interval (FFDD) ??? if not segtype in (0xc4, 0xda, 0xdb, 0xdd): log.info("SEGMENT: 0x%x%x, len=%d" % (ff, segtype, seglen)) file.seek(seglen - 2, 1) app = file.read(4) if len(self.meta.keys()): self._appendtable('JPGMETA', self.meta) for key, value in self.meta.items(): if key.startswith('Thumb:') or key == 'Software': self._set(key, value)