X-Git-Url: https://scm.cri.ensmp.fr/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Products/Photo/xmp_jpeg.py diff --git a/Products/Photo/xmp_jpeg.py b/Products/Photo/xmp_jpeg.py new file mode 100755 index 0000000..e193114 --- /dev/null +++ b/Products/Photo/xmp_jpeg.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +####################################################################################### +# Photo is a part of Plinn - http://plinn.org # +# Copyright (C) 2008 Benoît PIN # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation; either version 2 # +# of the License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# 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, write to the Free Software # +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # +####################################################################################### +""" Jpeg plugin for xmp read/write support. + + +""" + +from xmp import XMP +from types import StringType + +class JpegXmpIO(object): + + JPEG_XMP_LEADIN = 'http://ns.adobe.com/xap/1.0/\x00' + JPEG_XMP_LEADIN_LENGTH = len(JPEG_XMP_LEADIN) + + MARKERS = { + 0xFFC0: ("SOF0", "Baseline DCT", True), + 0xFFC1: ("SOF1", "Extended Sequential DCT", True), + 0xFFC2: ("SOF2", "Progressive DCT", True), + 0xFFC3: ("SOF3", "Spatial lossless", True), + 0xFFC4: ("DHT", "Define Huffman table", True), + 0xFFC5: ("SOF5", "Differential sequential DCT", True), + 0xFFC6: ("SOF6", "Differential progressive DCT", True), + 0xFFC7: ("SOF7", "Differential spatial", True), + 0xFFC8: ("JPG", "Extension", False), + 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", True), + 0xFFCA: ("SOF10", "Progressive DCT (AC)", True), + 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", True), + 0xFFCC: ("DAC", "Define arithmetic coding conditioning", True), + 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", True), + 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", True), + 0xFFCF: ("SOF15", "Differential spatial (AC)", True), + 0xFFD0: ("RST0", "Restart 0", False), + 0xFFD1: ("RST1", "Restart 1", False), + 0xFFD2: ("RST2", "Restart 2", False), + 0xFFD3: ("RST3", "Restart 3", False), + 0xFFD4: ("RST4", "Restart 4", False), + 0xFFD5: ("RST5", "Restart 5", False), + 0xFFD6: ("RST6", "Restart 6", False), + 0xFFD7: ("RST7", "Restart 7", False), + 0xFFD8: ("SOI", "Start of image", False), + 0xFFD9: ("EOI", "End of image", False), + 0xFFDA: ("SOS", "Start of scan", True), + 0xFFDB: ("DQT", "Define quantization table", True), + 0xFFDC: ("DNL", "Define number of lines", True), + 0xFFDD: ("DRI", "Define restart interval", True), + 0xFFDE: ("DHP", "Define hierarchical progression", True), + 0xFFDF: ("EXP", "Expand reference component", True), + 0xFFE0: ("APP0", "Application segment 0", True), + 0xFFE1: ("APP1", "Application segment 1", True), + 0xFFE2: ("APP2", "Application segment 2", True), + 0xFFE3: ("APP3", "Application segment 3", True), + 0xFFE4: ("APP4", "Application segment 4", True), + 0xFFE5: ("APP5", "Application segment 5", True), + 0xFFE6: ("APP6", "Application segment 6", True), + 0xFFE7: ("APP7", "Application segment 7", True), + 0xFFE8: ("APP8", "Application segment 8", True), + 0xFFE9: ("APP9", "Application segment 9", True), + 0xFFEA: ("APP10", "Application segment 10", True), + 0xFFEB: ("APP11", "Application segment 11", True), + 0xFFEC: ("APP12", "Application segment 12", True), + 0xFFED: ("APP13", "Application segment 13", True), + 0xFFEE: ("APP14", "Application segment 14", True), + 0xFFEF: ("APP15", "Application segment 15", True), + 0xFFF0: ("JPG0", "Extension 0", False), + 0xFFF1: ("JPG1", "Extension 1", False), + 0xFFF2: ("JPG2", "Extension 2", False), + 0xFFF3: ("JPG3", "Extension 3", False), + 0xFFF4: ("JPG4", "Extension 4", False), + 0xFFF5: ("JPG5", "Extension 5", False), + 0xFFF6: ("JPG6", "Extension 6", False), + 0xFFF7: ("JPG7", "Extension 7", False), + 0xFFF8: ("JPG8", "Extension 8", False), + 0xFFF9: ("JPG9", "Extension 9", False), + 0xFFFA: ("JPG10", "Extension 10", False), + 0xFFFB: ("JPG11", "Extension 11", False), + 0xFFFC: ("JPG12", "Extension 12", False), + 0xFFFD: ("JPG13", "Extension 13", False), + 0xFFFE: ("COM", "Comment", True) + } + + + @staticmethod + def i16(c,o=0): + return ord(c[o+1]) + (ord(c[o])<<8) + + @staticmethod + def getBlockInfo(marker, f): + start = f.tell() + length = JpegXmpIO.i16(f.read(2)) + + markerInfo = JpegXmpIO.MARKERS[marker] + blockInfo = { 'name' : markerInfo[0] + , 'description' : markerInfo[1] + , 'start' : start + , 'length' : length} + + jump = start + length + f.seek(jump) + + return blockInfo + + @staticmethod + def getBlockInfos(f) : + f.seek(0) + s = f.read(1) + + blockInfos = [] + + while 1: + s = s + f.read(1) + i = JpegXmpIO.i16(s) + + if JpegXmpIO.MARKERS.has_key(i): + name, desciption, handle = JpegXmpIO.MARKERS[i] + + if handle: + blockInfo = JpegXmpIO.getBlockInfo(i, f) + blockInfos.append(blockInfo) + if i == 0xFFDA: # start of scan + break + s = f.read(1) + elif i == 0 or i == 65535: + # padded marker or junk; move on + s = "\xff" + + return blockInfos + + + @staticmethod + def genJpegXmpBlock(uXmpData, paddingSize=2) : + block = u'' + + block += JpegXmpIO.JPEG_XMP_LEADIN + block += XMP.genXMPPacket(uXmpData, paddingSize) + # utf-8 mandatory in jpeg files (xmp specification) + block = block.encode('utf-8') + + length = len(block) + 2 + + # TODO : reduce padding size if this assertion occurs + assert length <= 0xfffd, "Jpeg block too long: %d (max: 0xfffd)" % hex(length) + + chrlength = chr(length >> 8 & 0xff) + chr(length & 0xff) + + block = chrlength + block + + return block + + + + @staticmethod + def read(f) : + + blockInfos = JpegXmpIO.getBlockInfos(f) + app1BlockInfos = [b for b in blockInfos if b['name'] == 'APP1'] + + xmpBlocks = [] + + for info in app1BlockInfos : + f.seek(info['start']) + data = f.read(info['length'])[2:] + if data.startswith(JpegXmpIO.JPEG_XMP_LEADIN) : + xmpBlocks.append(data) + + assert len(xmpBlocks) <= 1, "Multiple xmp block data is not yet supported." + + if len(xmpBlocks) == 1 : + data = xmpBlocks[0] + packet = data[len(JpegXmpIO.JPEG_XMP_LEADIN):] + return packet + else : + return None + + @staticmethod + def write(original, new, uxmp) : + + blockInfos = JpegXmpIO.getBlockInfos(original) + app1BlockInfos = [b for b in blockInfos if b['name'] == 'APP1'] + + xmpBlockInfos = [] + + for info in app1BlockInfos : + original.seek(info['start']) + lead = original.read(JpegXmpIO.JPEG_XMP_LEADIN_LENGTH+2)[2:] + if lead == JpegXmpIO.JPEG_XMP_LEADIN : + xmpBlockInfos.append(info) + + + assert len(xmpBlockInfos) <= 1, "Multiple xmp block data is not yet supported." + + if isinstance(uxmp, StringType) : + uxmp = unicode(uxmp, 'utf-8') + + if len(xmpBlockInfos) == 0 : + blockInfo = [b for b in blockInfos if b['name'] == 'APP13'] + + if not blockInfo : + blockInfo = [b for b in blockInfos if b['name'] == 'APP1'] + + if not blockInfo : + blockInfo = [b for b in blockInfos if b['name'] == 'APP0'] + + if not blockInfo : raise ValueError, "No suitable place to write xmp segment" + + info = blockInfo[0] + print 'create xmp after: %s' % info['name'] + + original.seek(0) + before = original.read(info['start'] + info['length']) + after = original.read() + + jpegBlock = '\xFF\xE1' + JpegXmpIO.genJpegXmpBlock(uxmp) + + else : + info = xmpBlockInfos[0] + + original.seek(0) + before = original.read(info['start']) + + original.seek(info['start'] + info['length']) + after = original.read() + + jpegBlock = JpegXmpIO.genJpegXmpBlock(uxmp) + + new.seek(0) + new.write(before) + new.write(jpegBlock) + new.write(after) + + # if original == new : + # new.seek(0) + # else : + # new.close() + # original.close() + + +XMP.registerReader('image/jpeg', JpegXmpIO.read) +XMP.registerWriter('image/jpeg', JpegXmpIO.write)