Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
IptcImagePlugin.py227 linesDownload Raw Back to PIL
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    if isinstance(im, IptcImageFile):
189        # return info dictionary right away
190        return {k: v for k, v in im.info.items() if isinstance(k, tuple)}
191
192    elif isinstance(im, JpegImagePlugin.JpegImageFile):
193        # extract the IPTC/NAA resource
194        photoshop = im.info.get("photoshop")
195        if photoshop:
196            data = photoshop.get(0x0404)
197
198    elif isinstance(im, TiffImagePlugin.TiffImageFile):
199        # get raw data from the IPTC/NAA tag (PhotoShop tags the data
200        # as 4-byte integers, so we cannot use the get method...)
201        try:
202            data = im.tag_v2._tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
203        except KeyError:
204            pass
205
206    if data is None:
207        return None  # no properties
208
209    # create an IptcImagePlugin object without initializing it
210    class FakeImage:
211        pass
212
213    fake_im = FakeImage()
214    fake_im.__class__ = IptcImageFile  # type: ignore[assignment]
215    iptc_im = cast(IptcImageFile, fake_im)
216
217    # parse the IPTC information chunk
218    iptc_im.info = {}
219    iptc_im.fp = BytesIO(data)
220
221    try:
222        iptc_im._open()
223    except (IndexError, KeyError):
224        pass  # expected failure
225
226    return {k: v for k, v in iptc_im.info.items() if isinstance(k, tuple)}
227 
codekingpro/portable-devtools · Team Ai