def setValue(self, val): #from python to binary #we support writing unsigned (short, long, rational), ascii self.value = val if self.type in [3,4]: #short, long if type(val) not in [[], ()]: val = [val] self.valueFlat = ''.join([util.setNr(n, self.bytes, self.endian) for n in val]) self.count = len(val) elif self.type == 5: #rational self.valueFlat = ''.join([util.setNr(n, 4, self.endian) + util.setNr(d, 4, self.endian) for n,d in self.value]) self.count = 1 else: if self.type == 2: #ascii self.valueFlat = self.value + "\x00" #must be NULL terminated string self.count = len(self.value) + 1 if self.count < 4: self.valueFlat = self.valueFlat.rjust(4) #self.valueFlat + ("\x00" * (4-len(self.valueFlat))) self.count = 4 else: self.valueFlat = self.value self.count = len(self.value) if self.count < 4: self.valueFlat = self.valueFlat + ("\x00" * (4-len(self.valueFlat))) self.count = 4 self.len = self.bytes * self.count
def binary(self): #http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf (pag 15) res = self.marker + self.type val = self.value #only parse it this way but not write this way #if len(val) % 2 != 0: #Pad with a NULL if not even size but let length be as it was # val += "\x00" if len(self.value) < 32767: res += util.setNr(len(self.value), 2) + val else: #lengthOfValueLength + valueLength + value (4 should be enough for lengthOfValueLength) res += util.setNr(4, 2) + util.setNr(len(self.value), 4) + val return res
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 binary(self): res = "" for ds in self.datasets: res += ds.binary() length = util.setNr(len(res), 2) if len(res) % 2 != 0: # Pad with a blank if not even size but let length be as it was res += "\x00" return self.marker + self.type + self.padding + length + res
def create(self, name, value): if name in txt_datasets: ds = DataSet('\x1c\x02', util.setNr(txt_datasets[name], 1), value, self) self.datasets.append(ds) return ds else: s = ["Only the following are supported:"] for k in txt_datasets.keys(): s.append(k) raise NotImplementedError("\n".join(s))
def set(self, tag, value, targetIfd, type_): """ - Overwrite <tagID> tag with <value>. - If tag does not already exist, build it in the specified <targetIfd>. - The type (short, long, ascii, etc. as 3,4,2 etc.) need to be specified since this program will probably never be aware of all posible tags and their specification. You may need to do some research with exif.org to find the right type """ tagID = type(tag)==type(1) and util.setNr(tag)[2:] or tag #nr to str for ifd in self.ifds: for tag in ifd: if tag.id == tagID or (self.endian == "II" and tag.id == util.reverse(tagID)): tag.setValue(value) return True tag = FirstTag(tagID, value, type_, self) #don't have it yet, so create one targetIfd.append(tag)
def get(self, tags, value=True, parse_value=True): "return value for given <tags> (1 tag or a list of tags as number or string hex value in big endian)" if type(tags) in (type(""), type(1)): tags = [tags] #allow searching multiple tags at once tags = [type(t)==type(1) and util.setNr(t)[2:] or t for t in tags] #nr to str for each tag if self.endian == "II": tags = [t[1]+t[0] for t in tags] res = [] for ifd in self.ifds: for tag in ifd: if tag.id in tags: if value: res.append(tag.getValue(parse_value)) else: res.append(tag) if len(res) == len(tags): break if len(res) == 1: return res[0] if len(res) == 0: return None return res
def binary(self): """ return Exif as is needed to be written into the jpeg file """ exifPointer = interopPointer = gpsPointer= None #endian + fixed number 42 + 8 (ifd0 starts right after tiff header) fixed42 = self.endian == "MM" and "\x00\x2A" or "\x2A\x00" ifd0offset = self.endian == "MM" and "\x00\x00\x00\x08" or "\x08\x00\x00\x00" res = self.endian + fixed42 + ifd0offset offset = 8 #total bytes for endian, fixed, ifd0 offset for ifd in self.ifds: #this is a come back later to fill in a gap with offsets to ifd, when we know the offset if ifd is self.exif and self.exif: res = res[:exifPointer] + util.setNr(len(res), 4, self.endian) + res[exifPointer+4:] if ifd is self.interop and self.interop: res = res[:interopPointer] + util.setNr(len(res), 4, self.endian) + res[interopPointer+4:] if ifd is self.gps and self.gps: res = res[:gpsPointer] + util.setNr(len(res), 4, self.endian) + res[gpsPointer+4:] headLen = 2 #nr of tags nrTags = len(ifd) headLen += nrTags * 12 #all tags if ifd is self.ifd0: headLen += 4 #pointer to ifd1 valOffset = offset + headLen res += util.setNr(nrTags, 2, self.endian) tagValOffset = 0 for tag in ifd: res += tag.id #2 bytes res += util.setNr(tag.type, 2, self.endian) #4 res += util.setNr(tag.count, 4, self.endian) #8 if tag.id == "\x87\x69" or (self.endian=="II" and tag.id == "\x69\x87"): #exif exifPointer = len(res) res += util.setNr(0, 4, self.endian) #will set later, now just to have correct length elif tag.id == "\xA0\x05" or (self.endian=="II" and tag.id == "\x05\xA0"): #interopelability interopPointer = len(res) res += util.setNr(0, 4, self.endian) elif tag.id == "\x88\x25" or (self.endian=="II" and tag.id == "\x25\x88"): #gps gpsPointer = len(res) res += util.setNr(0, 4, self.endian) elif len(tag.valueFlat) <= 4: res += tag.valueFlat #12 else: res += util.setNr(valOffset + tagValOffset, 4, self.endian) #12 tagValOffset += tag.len if ifd is self.ifd0: ifd1Pointer = len(res) #pointer to ifd1 res += util.setNr(0, 4, self.endian) for tag in ifd: if len(tag.valueFlat) > 4: res += tag.valueFlat offset = len(res) res = res[:ifd1Pointer] + util.setNr(len(res), 4, self.endian) + res[ifd1Pointer+4:] res += self.ifd1 return "Exif\x00\x00" + res
'SubsecTimeDigitized': 0x9292, 'TimeZoneOffset': 0x882A, # TODO: I hope TimeZoneOffset is not a pointer to yet another ifd 'Copyright': 0x8298, 'Exif version': 0x9000, 'Exif IFD Pointer': 0x8769, 'GPS IFD Pointer': 0x8825, 'Interoperability IFD': 0xA005, 'FlashPixVersion': 0xA000, 'FNumber': 0x829D, 'CompressedBitsPerPixel': 0x9102, 'ISOSpeedRatings': 0x8827, 'InteroperabilityVersion': 0x0002, 'InteroperabilityIndex': 0x0001, 'RelatedImageWidth': 0x1001, 'RelatedImageLength': 0x1002, 'SensingMethod': 0xA217, 'FocalPlaneXResolution': 0xA20E, 'FocalPlaneYResolution': 0xA20F, 'FocalPlaneResolutionUnit': 0xA210 } exif_tags_description = {} for key in exif_tags: val = exif_tags[key] if type(val) == type(()): val = (util.setNr(val[0])[2:], val[1]) #MM int exif_tags_description[val[0]] = (key, val[1]) else: val = util.setNr(val, 4)[2:] exif_tags_description[val] = key exif_tags[key] = val