Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/IptcImagePlugin.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

136 statements  

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# IPTC/NAA file handling 

6# 

7# history: 

8# 1995-10-01 fl Created 

9# 1998-03-09 fl Cleaned up and added to PIL 

10# 2002-06-18 fl Added getiptcinfo helper 

11# 

12# Copyright (c) Secret Labs AB 1997-2002. 

13# Copyright (c) Fredrik Lundh 1995. 

14# 

15# See the README file for information on usage and redistribution. 

16# 

17from __future__ import annotations 

18 

19from io import BytesIO 

20from typing import cast 

21 

22from . import Image, ImageFile 

23from ._binary import i16be as i16 

24from ._binary import i32be as i32 

25 

26COMPRESSION = {1: "raw", 5: "jpeg"} 

27 

28 

29# 

30# Helpers 

31 

32 

33def _i(c: bytes) -> int: 

34 return i32((b"\0\0\0\0" + c)[-4:]) 

35 

36 

37## 

38# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields 

39# from TIFF and JPEG files, use the <b>getiptcinfo</b> function. 

40 

41 

42class IptcImageFile(ImageFile.ImageFile): 

43 format = "IPTC" 

44 format_description = "IPTC/NAA" 

45 

46 def getint(self, key: tuple[int, int]) -> int: 

47 return _i(self.info[key]) 

48 

49 def field(self) -> tuple[tuple[int, int] | None, int]: 

50 # 

51 # get a IPTC field header 

52 assert self.fp is not None 

53 s = self.fp.read(5) 

54 if not s.strip(b"\x00"): 

55 return None, 0 

56 

57 tag = s[1], s[2] 

58 

59 # syntax 

60 if s[0] != 0x1C or tag[0] not in [1, 2, 3, 4, 5, 6, 7, 8, 9, 240]: 

61 msg = "invalid IPTC/NAA file" 

62 raise SyntaxError(msg) 

63 

64 # field size 

65 size = s[3] 

66 if size > 132: 

67 msg = "illegal field length in IPTC/NAA file" 

68 raise OSError(msg) 

69 elif size == 128: 

70 size = 0 

71 elif size > 128: 

72 size = _i(self.fp.read(size - 128)) 

73 else: 

74 size = i16(s, 3) 

75 

76 return tag, size 

77 

78 def _open(self) -> None: 

79 # load descriptive fields 

80 assert self.fp is not None 

81 while True: 

82 offset = self.fp.tell() 

83 tag, size = self.field() 

84 if not tag or tag == (8, 10): 

85 break 

86 if size: 

87 tagdata = self.fp.read(size) 

88 else: 

89 tagdata = None 

90 if tag in self.info: 

91 if isinstance(self.info[tag], list): 

92 self.info[tag].append(tagdata) 

93 else: 

94 self.info[tag] = [self.info[tag], tagdata] 

95 else: 

96 self.info[tag] = tagdata 

97 

98 # mode 

99 layers = self.info[(3, 60)][0] 

100 component = self.info[(3, 60)][1] 

101 if layers == 1 and not component: 

102 self._mode = "L" 

103 band = None 

104 else: 

105 if layers == 3 and component: 

106 self._mode = "RGB" 

107 elif layers == 4 and component: 

108 self._mode = "CMYK" 

109 if (3, 65) in self.info: 

110 band = self.info[(3, 65)][0] - 1 

111 else: 

112 band = 0 

113 

114 # size 

115 self._size = self.getint((3, 20)), self.getint((3, 30)) 

116 

117 # compression 

118 try: 

119 compression = COMPRESSION[self.getint((3, 120))] 

120 except KeyError as e: 

121 msg = "Unknown IPTC image compression" 

122 raise OSError(msg) from e 

123 

124 # tile 

125 if tag == (8, 10): 

126 self.tile = [ 

127 ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band)) 

128 ] 

129 

130 def load(self) -> Image.core.PixelAccess | None: 

131 if self.tile: 

132 args = self.tile[0].args 

133 assert isinstance(args, tuple) 

134 compression, band = args 

135 

136 assert self.fp is not None 

137 self.fp.seek(self.tile[0].offset) 

138 

139 # Copy image data to temporary file 

140 o = BytesIO() 

141 if compression == "raw": 

142 # To simplify access to the extracted file, 

143 # prepend a PPM header 

144 o.write(b"P5\n%d %d\n255\n" % self.size) 

145 while True: 

146 type, size = self.field() 

147 if type != (8, 10): 

148 break 

149 while size > 0: 

150 s = self.fp.read(min(size, 8192)) 

151 if not s: 

152 break 

153 o.write(s) 

154 size -= len(s) 

155 

156 with Image.open(o) as _im: 

157 if band is not None: 

158 bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode) 

159 bands[band] = _im 

160 im = Image.merge(self.mode, bands) 

161 else: 

162 im = _im 

163 im.load() 

164 self.im = im.im 

165 self.tile = [] 

166 return ImageFile.ImageFile.load(self) 

167 

168 

169Image.register_open(IptcImageFile.format, IptcImageFile) 

170 

171Image.register_extension(IptcImageFile.format, ".iim") 

172 

173 

174def getiptcinfo( 

175 im: ImageFile.ImageFile, 

176) -> dict[tuple[int, int], bytes | list[bytes]] | None: 

177 """ 

178 Get IPTC information from TIFF, JPEG, or IPTC file. 

179 

180 :param im: An image containing IPTC data. 

181 :returns: A dictionary containing IPTC information, or None if 

182 no IPTC information block was found. 

183 """ 

184 from . import JpegImagePlugin, TiffImagePlugin 

185 

186 data = None 

187 

188 info: dict[tuple[int, int], bytes | list[bytes]] = {} 

189 if isinstance(im, IptcImageFile): 

190 # return info dictionary right away 

191 for k, v in im.info.items(): 

192 if isinstance(k, tuple): 

193 info[k] = v 

194 return info 

195 

196 elif isinstance(im, JpegImagePlugin.JpegImageFile): 

197 # extract the IPTC/NAA resource 

198 photoshop = im.info.get("photoshop") 

199 if photoshop: 

200 data = photoshop.get(0x0404) 

201 

202 elif isinstance(im, TiffImagePlugin.TiffImageFile): 

203 # get raw data from the IPTC/NAA tag (PhotoShop tags the data 

204 # as 4-byte integers, so we cannot use the get method...) 

205 try: 

206 data = im.tag_v2._tagdata[TiffImagePlugin.IPTC_NAA_CHUNK] 

207 except KeyError: 

208 pass 

209 

210 if data is None: 

211 return None # no properties 

212 

213 # create an IptcImagePlugin object without initializing it 

214 class FakeImage: 

215 pass 

216 

217 fake_im = FakeImage() 

218 fake_im.__class__ = IptcImageFile # type: ignore[assignment] 

219 iptc_im = cast(IptcImageFile, fake_im) 

220 

221 # parse the IPTC information chunk 

222 iptc_im.info = {} 

223 iptc_im.fp = BytesIO(data) 

224 

225 try: 

226 iptc_im._open() 

227 except (IndexError, KeyError): 

228 pass # expected failure 

229 

230 for k, v in iptc_im.info.items(): 

231 if isinstance(k, tuple): 

232 info[k] = v 

233 return info