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
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
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
19from io import BytesIO
20from typing import cast
22from . import Image, ImageFile
23from ._binary import i16be as i16
24from ._binary import i32be as i32
26COMPRESSION = {1: "raw", 5: "jpeg"}
29#
30# Helpers
33def _i(c: bytes) -> int:
34 return i32((b"\0\0\0\0" + c)[-4:])
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.
42class IptcImageFile(ImageFile.ImageFile):
43 format = "IPTC"
44 format_description = "IPTC/NAA"
46 def getint(self, key: tuple[int, int]) -> int:
47 return _i(self.info[key])
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
57 tag = s[1], s[2]
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)
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)
76 return tag, size
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
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
114 # size
115 self._size = self.getint((3, 20)), self.getint((3, 30))
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
124 # tile
125 if tag == (8, 10):
126 self.tile = [
127 ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band))
128 ]
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
136 assert self.fp is not None
137 self.fp.seek(self.tile[0].offset)
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)
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)
169Image.register_open(IptcImageFile.format, IptcImageFile)
171Image.register_extension(IptcImageFile.format, ".iim")
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.
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
186 data = None
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
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)
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
210 if data is None:
211 return None # no properties
213 # create an IptcImagePlugin object without initializing it
214 class FakeImage:
215 pass
217 fake_im = FakeImage()
218 fake_im.__class__ = IptcImageFile # type: ignore[assignment]
219 iptc_im = cast(IptcImageFile, fake_im)
221 # parse the IPTC information chunk
222 iptc_im.info = {}
223 iptc_im.fp = BytesIO(data)
225 try:
226 iptc_im._open()
227 except (IndexError, KeyError):
228 pass # expected failure
230 for k, v in iptc_im.info.items():
231 if isinstance(k, tuple):
232 info[k] = v
233 return info