X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Products/Photo/xmp.py diff --git a/Products/Photo/xmp.py b/Products/Photo/xmp.py new file mode 100755 index 0000000..8738e50 --- /dev/null +++ b/Products/Photo/xmp.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +####################################################################################### +# Photo is a part of Plinn - http://plinn.org # +# Copyright © 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. # +####################################################################################### +# +# + +from types import StringTypes +from logging import getLogger +import re +console = getLogger('Photo.xmp') + +class XMP(object) : + XMP_HEADER = u'' + XMP_HEADER_PATTERN = u'''<\?xpacket begin=['"]\ufeff['"] id=['"]W5M0MpCehiHzreSzNTczkc9d['"][^\?]*\?>''' + XMP_PADDING_LINE = u'\u0020' * 63 + u'\n' + XMP_TRAILER = u'' + + _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()