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

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

143 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 collections.abc import Sequence 

20from io import BytesIO 

21from typing import cast 

22 

23from . import Image, ImageFile 

24from ._binary import i16be as i16 

25from ._binary import i32be as i32 

26from ._deprecate import deprecate 

27 

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

29 

30 

31def __getattr__(name: str) -> bytes: 

32 if name == "PAD": 

33 deprecate("IptcImagePlugin.PAD", 12) 

34 return b"\0\0\0\0" 

35 msg = f"module '{__name__}' has no attribute '{name}'" 

36 raise AttributeError(msg) 

37 

38 

39# 

40# Helpers 

41 

42 

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

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

45 

46 

47def _i8(c: int | bytes) -> int: 

48 return c if isinstance(c, int) else c[0] 

49 

50 

51def i(c: bytes) -> int: 

52 """.. deprecated:: 10.2.0""" 

53 deprecate("IptcImagePlugin.i", 12) 

54 return _i(c) 

55 

56 

57def dump(c: Sequence[int | bytes]) -> None: 

58 """.. deprecated:: 10.2.0""" 

59 deprecate("IptcImagePlugin.dump", 12) 

60 for i in c: 

61 print(f"{_i8(i):02x}", end=" ") 

62 print() 

63 

64 

65## 

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

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

68 

69 

70class IptcImageFile(ImageFile.ImageFile): 

71 format = "IPTC" 

72 format_description = "IPTC/NAA" 

73 

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

75 return _i(self.info[key]) 

76 

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

78 # 

79 # get a IPTC field header 

80 s = self.fp.read(5) 

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

82 return None, 0 

83 

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

85 

86 # syntax 

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

88 msg = "invalid IPTC/NAA file" 

89 raise SyntaxError(msg) 

90 

91 # field size 

92 size = s[3] 

93 if size > 132: 

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

95 raise OSError(msg) 

96 elif size == 128: 

97 size = 0 

98 elif size > 128: 

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

100 else: 

101 size = i16(s, 3) 

102 

103 return tag, size 

104 

105 def _open(self) -> None: 

106 # load descriptive fields 

107 while True: 

108 offset = self.fp.tell() 

109 tag, size = self.field() 

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

111 break 

112 if size: 

113 tagdata = self.fp.read(size) 

114 else: 

115 tagdata = None 

116 if tag in self.info: 

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

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

119 else: 

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

121 else: 

122 self.info[tag] = tagdata 

123 

124 # mode 

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

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

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

128 id = self.info[(3, 65)][0] - 1 

129 else: 

130 id = 0 

131 if layers == 1 and not component: 

132 self._mode = "L" 

133 elif layers == 3 and component: 

134 self._mode = "RGB"[id] 

135 elif layers == 4 and component: 

136 self._mode = "CMYK"[id] 

137 

138 # size 

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

140 

141 # compression 

142 try: 

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

144 except KeyError as e: 

145 msg = "Unknown IPTC image compression" 

146 raise OSError(msg) from e 

147 

148 # tile 

149 if tag == (8, 10): 

150 self.tile = [ 

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

152 ] 

153 

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

155 if len(self.tile) != 1 or self.tile[0][0] != "iptc": 

156 return ImageFile.ImageFile.load(self) 

157 

158 offset, compression = self.tile[0][2:] 

159 

160 self.fp.seek(offset) 

161 

162 # Copy image data to temporary file 

163 o = BytesIO() 

164 if compression == "raw": 

165 # To simplify access to the extracted file, 

166 # prepend a PPM header 

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

168 while True: 

169 type, size = self.field() 

170 if type != (8, 10): 

171 break 

172 while size > 0: 

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

174 if not s: 

175 break 

176 o.write(s) 

177 size -= len(s) 

178 

179 with Image.open(o) as _im: 

180 _im.load() 

181 self.im = _im.im 

182 return None 

183 

184 

185Image.register_open(IptcImageFile.format, IptcImageFile) 

186 

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

188 

189 

190def getiptcinfo( 

191 im: ImageFile.ImageFile, 

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

193 """ 

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

195 

196 :param im: An image containing IPTC data. 

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

198 no IPTC information block was found. 

199 """ 

200 from . import JpegImagePlugin, TiffImagePlugin 

201 

202 data = None 

203 

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

205 if isinstance(im, IptcImageFile): 

206 # return info dictionary right away 

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

208 if isinstance(k, tuple): 

209 info[k] = v 

210 return info 

211 

212 elif isinstance(im, JpegImagePlugin.JpegImageFile): 

213 # extract the IPTC/NAA resource 

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

215 if photoshop: 

216 data = photoshop.get(0x0404) 

217 

218 elif isinstance(im, TiffImagePlugin.TiffImageFile): 

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

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

221 try: 

222 data = im.tag_v2[TiffImagePlugin.IPTC_NAA_CHUNK] 

223 except KeyError: 

224 pass 

225 

226 if data is None: 

227 return None # no properties 

228 

229 # create an IptcImagePlugin object without initializing it 

230 class FakeImage: 

231 pass 

232 

233 fake_im = FakeImage() 

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

235 iptc_im = cast(IptcImageFile, fake_im) 

236 

237 # parse the IPTC information chunk 

238 iptc_im.info = {} 

239 iptc_im.fp = BytesIO(data) 

240 

241 try: 

242 iptc_im._open() 

243 except (IndexError, KeyError): 

244 pass # expected failure 

245 

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

247 if isinstance(k, tuple): 

248 info[k] = v 

249 return info