1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Photo is a part of Plinn - http://plinn.org #
4 # Copyright (C) 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 """ Jpeg plugin for xmp read/write support.
21 $Id: xmp_jpeg.py 999 2009-05-11 14:43:44Z pin $
22 $URL: http://svn.luxia.fr/svn/labo/projects/zope/Photo/trunk/xmp_jpeg.py $
26 from types
import StringType
28 class JpegXmpIO(object):
30 JPEG_XMP_LEADIN
= 'http://ns.adobe.com/xap/1.0/\x00'
31 JPEG_XMP_LEADIN_LENGTH
= len(JPEG_XMP_LEADIN
)
34 0xFFC0: ("SOF0", "Baseline DCT", True),
35 0xFFC1: ("SOF1", "Extended Sequential DCT", True),
36 0xFFC2: ("SOF2", "Progressive DCT", True),
37 0xFFC3: ("SOF3", "Spatial lossless", True),
38 0xFFC4: ("DHT", "Define Huffman table", True),
39 0xFFC5: ("SOF5", "Differential sequential DCT", True),
40 0xFFC6: ("SOF6", "Differential progressive DCT", True),
41 0xFFC7: ("SOF7", "Differential spatial", True),
42 0xFFC8: ("JPG", "Extension", False),
43 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", True),
44 0xFFCA: ("SOF10", "Progressive DCT (AC)", True),
45 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", True),
46 0xFFCC: ("DAC", "Define arithmetic coding conditioning", True),
47 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", True),
48 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", True),
49 0xFFCF: ("SOF15", "Differential spatial (AC)", True),
50 0xFFD0: ("RST0", "Restart 0", False),
51 0xFFD1: ("RST1", "Restart 1", False),
52 0xFFD2: ("RST2", "Restart 2", False),
53 0xFFD3: ("RST3", "Restart 3", False),
54 0xFFD4: ("RST4", "Restart 4", False),
55 0xFFD5: ("RST5", "Restart 5", False),
56 0xFFD6: ("RST6", "Restart 6", False),
57 0xFFD7: ("RST7", "Restart 7", False),
58 0xFFD8: ("SOI", "Start of image", False),
59 0xFFD9: ("EOI", "End of image", False),
60 0xFFDA: ("SOS", "Start of scan", True),
61 0xFFDB: ("DQT", "Define quantization table", True),
62 0xFFDC: ("DNL", "Define number of lines", True),
63 0xFFDD: ("DRI", "Define restart interval", True),
64 0xFFDE: ("DHP", "Define hierarchical progression", True),
65 0xFFDF: ("EXP", "Expand reference component", True),
66 0xFFE0: ("APP0", "Application segment 0", True),
67 0xFFE1: ("APP1", "Application segment 1", True),
68 0xFFE2: ("APP2", "Application segment 2", True),
69 0xFFE3: ("APP3", "Application segment 3", True),
70 0xFFE4: ("APP4", "Application segment 4", True),
71 0xFFE5: ("APP5", "Application segment 5", True),
72 0xFFE6: ("APP6", "Application segment 6", True),
73 0xFFE7: ("APP7", "Application segment 7", True),
74 0xFFE8: ("APP8", "Application segment 8", True),
75 0xFFE9: ("APP9", "Application segment 9", True),
76 0xFFEA: ("APP10", "Application segment 10", True),
77 0xFFEB: ("APP11", "Application segment 11", True),
78 0xFFEC: ("APP12", "Application segment 12", True),
79 0xFFED: ("APP13", "Application segment 13", True),
80 0xFFEE: ("APP14", "Application segment 14", True),
81 0xFFEF: ("APP15", "Application segment 15", True),
82 0xFFF0: ("JPG0", "Extension 0", False),
83 0xFFF1: ("JPG1", "Extension 1", False),
84 0xFFF2: ("JPG2", "Extension 2", False),
85 0xFFF3: ("JPG3", "Extension 3", False),
86 0xFFF4: ("JPG4", "Extension 4", False),
87 0xFFF5: ("JPG5", "Extension 5", False),
88 0xFFF6: ("JPG6", "Extension 6", False),
89 0xFFF7: ("JPG7", "Extension 7", False),
90 0xFFF8: ("JPG8", "Extension 8", False),
91 0xFFF9: ("JPG9", "Extension 9", False),
92 0xFFFA: ("JPG10", "Extension 10", False),
93 0xFFFB: ("JPG11", "Extension 11", False),
94 0xFFFC: ("JPG12", "Extension 12", False),
95 0xFFFD: ("JPG13", "Extension 13", False),
96 0xFFFE: ("COM", "Comment", True)
102 return ord(c
[o
+1]) + (ord(c
[o
])<<8)
105 def getBlockInfo(marker
, f
):
107 length
= JpegXmpIO
.i16(f
.read(2))
109 markerInfo
= JpegXmpIO
.MARKERS
[marker
]
110 blockInfo
= { 'name' : markerInfo
[0]
111 , 'description' : markerInfo
[1]
115 jump
= start
+ length
121 def getBlockInfos(f
) :
131 if JpegXmpIO
.MARKERS
.has_key(i
):
132 name
, desciption
, handle
= JpegXmpIO
.MARKERS
[i
]
135 blockInfo
= JpegXmpIO
.getBlockInfo(i
, f
)
136 blockInfos
.append(blockInfo
)
137 if i
== 0xFFDA: # start of scan
140 elif i
== 0 or i
== 65535:
141 # padded marker or junk; move on
148 def genJpegXmpBlock(uXmpData
, paddingSize
=2) :
151 block
+= JpegXmpIO
.JPEG_XMP_LEADIN
152 block
+= XMP
.genXMPPacket(uXmpData
, paddingSize
)
153 # utf-8 mandatory in jpeg files (xmp specification)
154 block
= block
.encode('utf-8')
156 length
= len(block
) + 2
158 # TODO : reduce padding size if this assertion occurs
159 assert length
<= 0xfffd, "Jpeg block too long: %d (max: 0xfffd)" % hex(length
)
161 chrlength
= chr(length
>> 8 & 0xff) + chr(length
& 0xff)
163 block
= chrlength
+ block
172 blockInfos
= JpegXmpIO
.getBlockInfos(f
)
173 app1BlockInfos
= [b
for b
in blockInfos
if b
['name'] == 'APP1']
177 for info
in app1BlockInfos
:
178 f
.seek(info
['start'])
179 data
= f
.read(info
['length'])[2:]
180 if data
.startswith(JpegXmpIO
.JPEG_XMP_LEADIN
) :
181 xmpBlocks
.append(data
)
183 assert len(xmpBlocks
) <= 1, "Multiple xmp block data is not yet supported."
185 if len(xmpBlocks
) == 1 :
187 packet
= data
[len(JpegXmpIO
.JPEG_XMP_LEADIN
):]
193 def write(original
, new
, uxmp
) :
195 blockInfos
= JpegXmpIO
.getBlockInfos(original
)
196 app1BlockInfos
= [b
for b
in blockInfos
if b
['name'] == 'APP1']
200 for info
in app1BlockInfos
:
201 original
.seek(info
['start'])
202 lead
= original
.read(JpegXmpIO
.JPEG_XMP_LEADIN_LENGTH
+2)[2:]
203 if lead
== JpegXmpIO
.JPEG_XMP_LEADIN
:
204 xmpBlockInfos
.append(info
)
207 assert len(xmpBlockInfos
) <= 1, "Multiple xmp block data is not yet supported."
209 if isinstance(uxmp
, StringType
) :
210 uxmp
= unicode(uxmp
, 'utf-8')
212 if len(xmpBlockInfos
) == 0 :
213 blockInfo
= [b
for b
in blockInfos
if b
['name'] == 'APP13']
216 blockInfo
= [b
for b
in blockInfos
if b
['name'] == 'APP1']
219 blockInfo
= [b
for b
in blockInfos
if b
['name'] == 'APP0']
221 if not blockInfo
: raise ValueError, "No suitable place to write xmp segment"
224 print 'create xmp after: %s' % info
['name']
227 before
= original
.read(info
['start'] + info
['length'])
228 after
= original
.read()
230 jpegBlock
= '\xFF\xE1' + JpegXmpIO
.genJpegXmpBlock(uxmp
)
233 info
= xmpBlockInfos
[0]
236 before
= original
.read(info
['start'])
238 original
.seek(info
['start'] + info
['length'])
239 after
= original
.read()
241 jpegBlock
= JpegXmpIO
.genJpegXmpBlock(uxmp
)
248 # if original == new :
255 XMP
.registerReader('image/jpeg', JpegXmpIO
.read
)
256 XMP
.registerWriter('image/jpeg', JpegXmpIO
.write
)