--- /dev/null
+# -*- coding: utf-8 -*-
+#######################################################################################
+# Photo is a part of Plinn - http://plinn.org #
+# Copyright © 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. #
+#######################################################################################
+#
+#
+
+from types import StringTypes
+from logging import getLogger
+import re
+console = getLogger('Photo.xmp')
+
+class XMP(object) :
+ XMP_HEADER = u'<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>'
+ XMP_HEADER_PATTERN = u'''<\?xpacket begin=['"]\ufeff['"] id=['"]W5M0MpCehiHzreSzNTczkc9d['"][^\?]*\?>'''
+ XMP_PADDING_LINE = u'\u0020' * 63 + u'\n'
+ XMP_TRAILER = u'<?xpacket end="w"?>'
+
+ _readers = {}
+ _writers = {}
+
+
+
+ def __init__(self, file, content_type='image/jpeg', encoding='utf-8') :
+ try :
+ self.reader = self._readers[content_type]
+ except KeyError:
+ raise NotImplementedError, "%r content type not supported by XMP" % content_type
+
+ try :
+ self.writer = self._writers[content_type]
+ except KeyError :
+ self.writer = None
+ console.info('XMP file opened on read-only mode.')
+
+ self.file = file
+ self.encoding = encoding
+ self.xmp = None
+ self._open()
+
+
+ def __del__(self) :
+ try :
+ self.file.close()
+ except :
+ pass
+
+
+ def _open(self) :
+
+ if type(self.file) in StringTypes :
+ self.file = file(self.file)
+
+ packet = self.reader(self.file)
+
+ if packet is not None :
+ # tests / unwrap
+ reEncodedHeader = re.compile(self.XMP_HEADER_PATTERN.encode(self.encoding))
+ m = reEncodedHeader.match(packet)
+ if m is None :
+ console.warn('XMP packet wrapper not found')
+ self.xmp = packet
+ return
+
+ xmp = packet[m.end():]
+ trailer = self.XMP_TRAILER[:-6].encode(self.encoding) # TODO handle read-only mode
+ trailerPos = xmp.find(trailer)
+ assert trailerPos != -1, "No xmp trailer found"
+
+ xmp = xmp[:trailerPos]
+ xmp = xmp.strip()
+ self.xmp = xmp
+ else :
+ self.xmp = None
+
+ def save(self, f=None):
+ original = self.file
+ if f :
+ if type(f) in StringTypes :
+ new = file(f, 'w')
+ else :
+ new = f
+ elif f is None :
+ new = self.file
+
+ self.writer(original, new, self.xmp)
+
+
+ def getXMP(self) :
+ return self.xmp
+
+
+ def setXMP(self, xmp) :
+ self.xmp = xmp
+
+ #
+ # xmp utils
+ #
+
+ @staticmethod
+ def getXmpPadding(size) :
+ # size of trailer in kB
+ return (XMP.XMP_PADDING_LINE * 32 * size)
+
+
+ @staticmethod
+ def genXMPPacket(uXmpData, paddingSize):
+ packet = u''
+
+ packet += XMP.XMP_HEADER
+ packet += uXmpData
+ packet += XMP.getXmpPadding(paddingSize)
+ packet += XMP.XMP_TRAILER
+
+ return packet
+
+
+
+ #
+ # content type registry stuff
+ #
+
+
+ @classmethod
+ def registerReader(cls, content_type, reader) :
+ cls._readers[content_type] = reader
+
+ @classmethod
+ def registerWriter(cls, content_type, writer) :
+ cls._writers[content_type] = writer
+
+ @classmethod
+ def registerWrapper(cls, content_type, wrapper) :
+ """ Registers specific wrapper to prepare data
+ for embedding xmp into specific content_type file.
+ """
+ pass
+
+
+
+def test() :
+ from xml.dom.minidom import parse
+ data = parse('new.xmp').documentElement.toxml()
+
+ def test1() :
+ original = 'original.jpg'
+ modified = 'modified.jpg'
+
+ x = XMP(original)
+ x.setXMP(data)
+ x.save(modified)
+
+ def test2() :
+ from cStringIO import StringIO
+ sio = StringIO()
+ sio.write(file('modified.jpg').read())
+ sio.seek(0)
+
+ x = XMP(sio)
+ x.setXMP(data)
+ x.save()
+
+ f2 = open('modified2.jpg', 'w')
+ f2.write(sio.read())
+ f2.close()
+
+
+ test1()
+ test2()
+
+
+
+if __name__ == '__main__' :
+ test()