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 Sequence
21
22from . import Image, ImageFile
23from ._binary import i16be as i16
24from ._binary import i32be as i32
25from ._deprecate import deprecate
26
27COMPRESSION = {1: "raw", 5: "jpeg"}
28
29
30def __getattr__(name: str) -> bytes:
31 if name == "PAD":
32 deprecate("IptcImagePlugin.PAD", 12)
33 return b"\0\0\0\0"
34 msg = f"module '{__name__}' has no attribute '{name}'"
35 raise AttributeError(msg)
36
37
38#
39# Helpers
40
41
42def _i(c: bytes) -> int:
43 return i32((b"\0\0\0\0" + c)[-4:])
44
45
46def _i8(c: int | bytes) -> int:
47 return c if isinstance(c, int) else c[0]
48
49
50def i(c: bytes) -> int:
51 """.. deprecated:: 10.2.0"""
52 deprecate("IptcImagePlugin.i", 12)
53 return _i(c)
54
55
56def dump(c: Sequence[int | bytes]) -> None:
57 """.. deprecated:: 10.2.0"""
58 deprecate("IptcImagePlugin.dump", 12)
59 for i in c:
60 print(f"{_i8(i):02x}", end=" ")
61 print()
62
63
64##
65# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
66# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
67
68
69class IptcImageFile(ImageFile.ImageFile):
70 format = "IPTC"
71 format_description = "IPTC/NAA"
72
73 def getint(self, key: tuple[int, int]) -> int:
74 return _i(self.info[key])
75
76 def field(self) -> tuple[tuple[int, int] | None, int]:
77 #
78 # get a IPTC field header
79 s = self.fp.read(5)
80 if not s.strip(b"\x00"):
81 return None, 0
82
83 tag = s[1], s[2]
84
85 # syntax
86 if s[0] != 0x1C or tag[0] not in [1, 2, 3, 4, 5, 6, 7, 8, 9, 240]:
87 msg = "invalid IPTC/NAA file"
88 raise SyntaxError(msg)
89
90 # field size
91 size = s[3]
92 if size > 132:
93 msg = "illegal field length in IPTC/NAA file"
94 raise OSError(msg)
95 elif size == 128:
96 size = 0
97 elif size > 128:
98 size = _i(self.fp.read(size - 128))
99 else:
100 size = i16(s, 3)
101
102 return tag, size
103
104 def _open(self) -> None:
105 # load descriptive fields
106 while True:
107 offset = self.fp.tell()
108 tag, size = self.field()
109 if not tag or tag == (8, 10):
110 break
111 if size:
112 tagdata = self.fp.read(size)
113 else:
114 tagdata = None
115 if tag in self.info:
116 if isinstance(self.info[tag], list):
117 self.info[tag].append(tagdata)
118 else:
119 self.info[tag] = [self.info[tag], tagdata]
120 else:
121 self.info[tag] = tagdata
122
123 # mode
124 layers = self.info[(3, 60)][0]
125 component = self.info[(3, 60)][1]
126 if (3, 65) in self.info:
127 id = self.info[(3, 65)][0] - 1
128 else:
129 id = 0
130 if layers == 1 and not component:
131 self._mode = "L"
132 elif layers == 3 and component:
133 self._mode = "RGB"[id]
134 elif layers == 4 and component:
135 self._mode = "CMYK"[id]
136
137 # size
138 self._size = self.getint((3, 20)), self.getint((3, 30))
139
140 # compression
141 try:
142 compression = COMPRESSION[self.getint((3, 120))]
143 except KeyError as e:
144 msg = "Unknown IPTC image compression"
145 raise OSError(msg) from e
146
147 # tile
148 if tag == (8, 10):
149 self.tile = [("iptc", (0, 0) + self.size, offset, compression)]
150
151 def load(self):
152 if len(self.tile) != 1 or self.tile[0][0] != "iptc":
153 return ImageFile.ImageFile.load(self)
154
155 offset, compression = self.tile[0][2:]
156
157 self.fp.seek(offset)
158
159 # Copy image data to temporary file
160 o = BytesIO()
161 if compression == "raw":
162 # To simplify access to the extracted file,
163 # prepend a PPM header
164 o.write(b"P5\n%d %d\n255\n" % self.size)
165 while True:
166 type, size = self.field()
167 if type != (8, 10):
168 break
169 while size > 0:
170 s = self.fp.read(min(size, 8192))
171 if not s:
172 break
173 o.write(s)
174 size -= len(s)
175
176 with Image.open(o) as _im:
177 _im.load()
178 self.im = _im.im
179
180
181Image.register_open(IptcImageFile.format, IptcImageFile)
182
183Image.register_extension(IptcImageFile.format, ".iim")
184
185
186def getiptcinfo(im):
187 """
188 Get IPTC information from TIFF, JPEG, or IPTC file.
189
190 :param im: An image containing IPTC data.
191 :returns: A dictionary containing IPTC information, or None if
192 no IPTC information block was found.
193 """
194 from . import JpegImagePlugin, TiffImagePlugin
195
196 data = None
197
198 if isinstance(im, IptcImageFile):
199 # return info dictionary right away
200 return im.info
201
202 elif isinstance(im, JpegImagePlugin.JpegImageFile):
203 # extract the IPTC/NAA resource
204 photoshop = im.info.get("photoshop")
205 if photoshop:
206 data = photoshop.get(0x0404)
207
208 elif isinstance(im, TiffImagePlugin.TiffImageFile):
209 # get raw data from the IPTC/NAA tag (PhotoShop tags the data
210 # as 4-byte integers, so we cannot use the get method...)
211 try:
212 data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
213 except (AttributeError, KeyError):
214 pass
215
216 if data is None:
217 return None # no properties
218
219 # create an IptcImagePlugin object without initializing it
220 class FakeImage:
221 pass
222
223 im = FakeImage()
224 im.__class__ = IptcImageFile
225
226 # parse the IPTC information chunk
227 im.info = {}
228 im.fp = BytesIO(data)
229
230 try:
231 im._open()
232 except (IndexError, KeyError):
233 pass # expected failure
234
235 return im.info