def __init__(self, tag, parent): if DEBUG: print repr(tag) self.parent = parent self.endian = parent.endian tiff = parent.tiff self.id = tag[:2] self.type = util.getNr(tag[2:4], self.endian) self.count = util.getNr(tag[4:8], self.endian) try: self.bytes = self.typeMap[self.type] self.len = self.bytes * self.count except: raise ValueError, "Value type %s is not supported for %s - %s" % (self.type, self.niceID(), repr(tag)) if self.len <= 4: self.valueFlat = tag[8:] #as it needs to be written back into the file self.value = self._getVal(tag[8:]) else: offset = util.getNr(tag[8:], self.endian) self.valueFlat = tiff[offset: offset + self.len] self.value = self._getVal(tiff[offset: offset + self.len]) if DEBUG: self.display()
def niceID(self): "return tag hex value and also a description if available" id = exif_tags_description.get(self.endian == "MM" and self.id or util.reverse(self.id), None) if id == None: id = hex(util.getNr(self.id, self.endian)) else: id = hex(util.getNr(self.id, self.endian)) + " - " + (type(id) == type(()) and id[0] or id) return id
def _getVal(self, val): #from binary to python if self.type in [1,3,4]: #byte, sort, long if len(val)> self.len: val = self.endian == "II" and val[:self.bytes] or val[self.bytes:] r = [util.getNr(''.join(t), self.endian) for t in util.unzip(val, self.bytes)] if len(r)==1: return r[0] return r if self.type == 5: #rational r = util.unzip([util.getNr(''.join(t), self.endian) for t in util.unzip(val, 4)], 2) if len(r)==1: return r[0] return r if self.type == 2: #string return val[:-1] #strip NULL from NULL terminated string return val # unknown
def _write(value, file, marker): """-Overwrights <marker> segment with given <value> -if <marker> segment does not already exist then it will write it right before the image segment (SOF - FFC0) """ markerSeg, sof, length, im = _process(file, marker) if markerSeg or sof: lenHex = util.setNr(len(value) + 2, "short") #the length on 2 bytes segment = "\xFF" + marker + lenHex + value #segment = marker + value length + value pos = im.tell() - 4 im.seek(0) before = im.read(pos) if markerSeg: im.seek(util.getNr(length) + 2, 1) #skip over existing segment, including marker after = im.read() im.reset() im.write(before) im.write(segment) im.write(after) im.close() return im else: im.close() raise NoImageFound, "There is no image in this image file ?"
def _write(value, file, marker): """-Overwrights <marker> segment with given <value> -if <marker> segment does not already exist then it will write it right before the image segment (SOF - FFC0) """ markerSeg, sof, length, im = _process(file, marker) if markerSeg or sof: lenHex = util.setNr(len(value)+2, "short") #the length on 2 bytes segment = "\xFF" + marker + lenHex + value #segment = marker + value length + value pos = im.tell() - 4 im.seek(0) before = im.read(pos) if markerSeg: im.seek(util.getNr(length) + 2, 1) #skip over existing segment, including marker after = im.read() im.reset() im.write(before) im.write(segment) im.write(after) im.close() return im else: im.close() raise NoImageFound, "There is no image in this image file ?"
def parse_ifd(self, ifdOffset, context, next=None): """ Construct a modifiable Exif. Get nr of tags and parse each tag. If a pointer tags to other ifd is found, recurse inside that ifd as well """ ifd = self.tiff[ifdOffset:] nrTags = util.getNr(ifd[:2], self.endian) if DEBUG: print self.ifdName(context), "nr tags", nrTags offset = 2 #position after nr tags bytes for i in range(nrTags): try: tag = Tag(ifd[offset : offset+12], self) except Exception, e: #mainly unsuported tag types, etc # TODO: if a tag is buggy is it ok to just ignore it and go on or should we crash alltoghether if DEBUG: print "Can't create tag", str(e) offset += 12 continue context.append(tag) if next is not None: # and tag.value is not None: for ix in range(len(next)): nxtId = next[ix][0] if (tag.id == nxtId) or (self.endian=="II" and tag.id == util.reverse(nxtId)): next[ix][1] = tag.value offset += 12
def __init__(self, marker, type, value, record): self.marker = marker self.type = type self.value = value self.record = record self.nrType = util.getNr(type) #nr_datasets dictionary key self.name = nr_datasets.get(self.nrType, None)
def dict(self): "return a dictionary {tag description: (ifd, tag id, value)} of all parsed tags" d = {} for ifd in self.ifds: for tag in ifd: id = exif_tags_description.get(self.endian == "MM" and tag.id or util.reverse(tag.id), repr(tag.id)) id = type(id) == type(()) and id[0] or id d[id] = (self.ifdName(ifd), hex(util.getNr(tag.id, self.endian)), tag.value) return d
def _read(file, marker): "return value of <marker> segment if exists, or None otherwise" segmentValue=None try: markerSeg, sof, length, im = _process(file, marker) except: return "" if markerSeg: im.seek(-2, 1) #retract right after marker ID segmentValue = im.read(util.getNr(length))[2:] im.close() if not markerSeg and not sof: raise NoImageFound, "There is no image in this image file ?" return segmentValue
def parse(self, value): # 8BIM Records if value: marker = value[:4] type = value[4:6] padding = value[6:10] length = util.getNr(value[10:12]) rValue = value[12:12 + length] self.records.append(Record(marker, type, padding, rValue, self)) #Skip a NULL (\x00 terminated value) if not even size if length % 2 != 0: length += 1 self.parse(value[12 + length:])
def parse(self, value): # 8BIM Records if value: marker = value[:4] type = value[4:6] padding = value[6:10] length = util.getNr(value[10:12]) rValue = value[12:12+length] self.records.append(Record(marker, type, padding, rValue, self)) #Skip a NULL (\x00 terminated value) if not even size if length % 2 != 0: length += 1 self.parse(value[12+length:])
def _process(file, target): """seek target marker in JPEG file, and return tuple: (found marker segment boolean, reached image segment boolean, marker segment length, the open file object positioned at the begining of value) """ comment = image = False im = ImgObj(file) marker = im.read(2) if marker != "\xFF\xD8": raise NoJPEGFound, "Not a JPEG image" l = 2 while im.read(1) == "\xFF": markerType = im.read(1) length = im.read(2) l += util.getNr(length) + 2 if markerType == "\xC0": #SOF - got to the image, stop return (False, True, length, im) if markerType == target: return (True, False, length, im) #skip over current segment #-2 to move <length> positions starting right after marker im.seek(util.getNr(length) - 2, 1) return (False, False, length, im)
def _process(file, target): """seek target marker in JPEG file, and return tuple: (found marker segment boolean, reached image segment boolean, marker segment length, the open file object positioned at the begining of value) """ comment = image = False im = ImgObj(file) marker = im.read(2) if marker != "\xFF\xD8": raise NoJPEGFound, "Not a JPEG image" l=2 while im.read(1) == "\xFF": markerType = im.read(1) length = im.read(2) l += util.getNr(length) + 2 if markerType == "\xC0": #SOF - got to the image, stop return (False, True, length, im) if markerType == target: return (True, False, length, im) #skip over current segment #-2 to move <length> positions starting right after marker im.seek(util.getNr(length) - 2, 1) return (False, False, length, im)
def _read(file, marker): "return value of <marker> segment if exists, or None otherwise" segmentValue = None try: markerSeg, sof, length, im = _process(file, marker) except: return "" if markerSeg: im.seek(-2, 1) #retract right after marker ID segmentValue = im.read(util.getNr(length))[2:] im.close() if not markerSeg and not sof: raise NoImageFound, "There is no image in this image file ?" return segmentValue
def parse(self, value): # sub-segments for 8BIM segment if value: marker = value[:2] type = value[2:3] length = util.getNr(value[3:5]) rValue = value[5:5 + length] self.datasets.append(DataSet(marker, type, rValue, self)) # Skip a NULL (\x00 terminated value) if not even size try: if length % 2 != 0 and value[5 + length + 1] == '\x00': length += 1 except IndexError, e: pass self.parse(value[5 + length:])
def parse(self, value): # sub-segments for 8BIM segment if value: marker = value[:2] type = value[2:3] length = util.getNr(value[3:5]) rValue = value[5:5+length] self.datasets.append(DataSet(marker, type, rValue, self)) # Skip a NULL (\x00 terminated value) if not even size try: if length % 2 != 0 and value[5+length+1]=='\x00': length += 1 except IndexError, e: pass self.parse(value[5+length:])
def __init__(self, value=None): if value is None: raise "EXIF must not be empty" self.origValue = value self.tiff = value[6:] self.endian = value[6:8] self.fixed42 = "\x00\x2A" #42 value[8:10] #IFD's data structure self.ifd0 = [] self.exif = [] self.gps = [] self.interop = [] self.ifd1 = None #we are not messing with ifd1 (thumbnail etc.) in this app at all self.ifds = [self.ifd0, self.exif, self.gps, self.interop] if DEBUG: print "endian", self.endian #parse IFD's into above data structure: self.parse_ifd(util.getNr(value[10:14], self.endian), self.ifd0, [["\x87\x69", None, self.exif, # EXIFOffset [["\xA0\x05", None, self.interop, None]]], # Interopelability IFD offset ["\x88\x25", None, self.gps, None]]) # GPSOffset