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 #######################################################################################
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
)
76 console
.warn('XMP packet wrapper not found')
80 xmp
= packet
[m
.end():]
81 trailer
= self
.XMP_TRAILER
[:-6].encode(self
.encoding
) # TODO handle read-only mode
82 trailerPos
= xmp
.find(trailer
)
83 assert trailerPos
!= -1, "No xmp trailer found"
85 xmp
= xmp
[:trailerPos
]
91 def save(self
, f
=None):
94 if type(f
) in StringTypes
:
101 self
.writer(original
, new
, self
.xmp
)
108 def setXMP(self
, xmp
) :
116 def getXmpPadding(size
) :
117 # size of trailer in kB
118 return (XMP
.XMP_PADDING_LINE
* 32 * size
)
122 def genXMPPacket(uXmpData
, paddingSize
):
125 packet
+= XMP
.XMP_HEADER
127 packet
+= XMP
.getXmpPadding(paddingSize
)
128 packet
+= XMP
.XMP_TRAILER
135 # content type registry stuff
140 def registerReader(cls
, content_type
, reader
) :
141 cls
._readers
[content_type
] = reader
144 def registerWriter(cls
, content_type
, writer
) :
145 cls
._writers
[content_type
] = writer
148 def registerWrapper(cls
, content_type
, wrapper
) :
149 """ Registers specific wrapper to prepare data
150 for embedding xmp into specific content_type file.
157 from xml
.dom
.minidom
import parse
158 data
= parse('new.xmp').documentElement
.toxml()
161 original
= 'original.jpg'
162 modified
= 'modified.jpg'
169 from cStringIO
import StringIO
171 sio
.write(file('modified.jpg').read())
178 f2
= open('modified2.jpg', 'w')
188 if __name__
== '__main__' :