1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Photo is a part of Plinn - http://plinn.org #
4 # Copyright © 2008 Benoît PIN <benoit.pin@ensmp.fr> #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 # $Id: xmp.py 354 2008-02-13 13:30:53Z pin $
21 # $URL: http://svn.luxia.fr/svn/labo/projects/zope/Photo/trunk/xmp.py $
23 from types
import StringTypes
24 from logging
import getLogger
26 console
= getLogger('Photo.xmp')
29 XMP_HEADER
= u
'<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>'
30 XMP_HEADER_PATTERN
= u
'''<\?xpacket begin=['"]\ufeff['"] id=['"]W5M0MpCehiHzreSzNTczkc9d['"][^\?]*\?>'''
31 XMP_PADDING_LINE
= u
'\u0020' * 63 + u
'\n'
32 XMP_TRAILER
= u
'<?xpacket end="w"?>'
39 def __init__(self
, file, content_type
='image/jpeg', encoding
='utf-8') :
41 self
.reader
= self
._readers
[content_type
]
43 raise NotImplementedError, "%r content type not supported by XMP" % content_type
46 self
.writer
= self
._writers
[content_type
]
49 console
.info('XMP file opened on read-only mode.')
52 self
.encoding
= encoding
66 if type(self
.file) in StringTypes
:
67 self
.file = file(self
.file)
69 packet
= self
.reader(self
.file)
71 if packet
is not None :
73 reEncodedHeader
= re
.compile(self
.XMP_HEADER_PATTERN
.encode(self
.encoding
))
74 m
= reEncodedHeader
.match(packet
)
75 assert m
is not None, "No xmp header found"
76 xmp
= packet
[m
.end():]
78 trailer
= self
.XMP_TRAILER
[:-6].encode(self
.encoding
) # TODO handle read-only mode
79 trailerPos
= xmp
.find(trailer
)
80 assert trailerPos
!= -1, "No xmp trailer found"
82 xmp
= xmp
[:trailerPos
]
88 def save(self
, f
=None):
91 if type(f
) in StringTypes
:
98 self
.writer(original
, new
, self
.xmp
)
105 def setXMP(self
, xmp
) :
113 def getXmpPadding(size
) :
114 # size of trailer in kB
115 return (XMP
.XMP_PADDING_LINE
* 32 * size
)
119 def genXMPPacket(uXmpData
, paddingSize
):
122 packet
+= XMP
.XMP_HEADER
124 packet
+= XMP
.getXmpPadding(paddingSize
)
125 packet
+= XMP
.XMP_TRAILER
132 # content type registry stuff
137 def registerReader(cls
, content_type
, reader
) :
138 cls
._readers
[content_type
] = reader
141 def registerWriter(cls
, content_type
, writer
) :
142 cls
._writers
[content_type
] = writer
145 def registerWrapper(cls
, content_type
, wrapper
) :
146 """ Registers specific wrapper to prepare data
147 for embedding xmp into specific content_type file.
154 from xml
.dom
.minidom
import parse
155 data
= parse('new.xmp').documentElement
.toxml()
158 original
= 'original.jpg'
159 modified
= 'modified.jpg'
166 from cStringIO
import StringIO
168 sio
.write(file('modified.jpg').read())
175 f2
= open('modified2.jpg', 'w')
185 if __name__
== '__main__' :