]> CRI, Mines Paris - PSL - Photo.git/blobdiff - Products/Photo/xmp.py
eggification
[Photo.git] / Products / Photo / xmp.py
diff --git a/Products/Photo/xmp.py b/Products/Photo/xmp.py
new file mode 100755 (executable)
index 0000000..8738e50
--- /dev/null
@@ -0,0 +1,189 @@
+# -*- 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()