+++ /dev/null
-# -*- coding: utf-8 -*-
-#######################################################################################
-# Photo is a part of Plinn - http://plinn.org #
-# Copyright (C) 2008 BenoƮt PIN <benoit.pin@ensmp.fr> #
-# #
-# 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)