Pillow/src/PIL/IptcImagePlugin.py

231 lines
5.6 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# IPTC/NAA file handling
#
# history:
# 1995-10-01 fl Created
# 1998-03-09 fl Cleaned up and added to PIL
# 2002-06-18 fl Added getiptcinfo helper
#
# Copyright (c) Secret Labs AB 1997-2002.
# Copyright (c) Fredrik Lundh 1995.
#
# See the README file for information on usage and redistribution.
#
2014-07-30 20:39:11 +04:00
import os
import tempfile
2010-07-31 06:52:47 +04:00
from . import Image, ImageFile
from ._binary import i8
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
2019-03-21 16:28:20 +03:00
COMPRESSION = {1: "raw", 5: "jpeg"}
2010-07-31 06:52:47 +04:00
PAD = o8(0) * 4
2010-07-31 06:52:47 +04:00
2014-07-30 20:39:11 +04:00
2010-07-31 06:52:47 +04:00
#
# Helpers
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
def i(c):
return i32((PAD + c)[-4:])
2014-07-30 20:39:11 +04:00
2010-07-31 06:52:47 +04:00
def dump(c):
for i in c:
2019-03-21 16:28:20 +03:00
print("%02x" % i8(i), end=" ")
print()
2010-07-31 06:52:47 +04:00
2014-07-30 20:39:11 +04:00
2010-07-31 06:52:47 +04:00
##
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
class IptcImageFile(ImageFile.ImageFile):
format = "IPTC"
format_description = "IPTC/NAA"
def getint(self, key):
return i(self.info[key])
def field(self):
#
# get a IPTC field header
s = self.fp.read(5)
if not len(s):
return None, 0
tag = i8(s[1]), i8(s[2])
2010-07-31 06:52:47 +04:00
# syntax
if i8(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9:
raise SyntaxError("invalid IPTC/NAA file")
2010-07-31 06:52:47 +04:00
# field size
size = i8(s[3])
2010-07-31 06:52:47 +04:00
if size > 132:
raise OSError("illegal field length in IPTC/NAA file")
2010-07-31 06:52:47 +04:00
elif size == 128:
size = 0
elif size > 128:
2019-03-21 16:28:20 +03:00
size = i(self.fp.read(size - 128))
2010-07-31 06:52:47 +04:00
else:
size = i16(s[3:])
return tag, size
def _open(self):
# load descriptive fields
while True:
2010-07-31 06:52:47 +04:00
offset = self.fp.tell()
tag, size = self.field()
2014-07-30 20:39:11 +04:00
if not tag or tag == (8, 10):
2010-07-31 06:52:47 +04:00
break
if size:
tagdata = self.fp.read(size)
else:
tagdata = None
if tag in self.info:
2010-07-31 06:52:47 +04:00
if isinstance(self.info[tag], list):
self.info[tag].append(tagdata)
else:
self.info[tag] = [self.info[tag], tagdata]
else:
self.info[tag] = tagdata
# mode
2014-07-30 20:39:11 +04:00
layers = i8(self.info[(3, 60)][0])
component = i8(self.info[(3, 60)][1])
if (3, 65) in self.info:
2019-03-21 16:28:20 +03:00
id = i8(self.info[(3, 65)][0]) - 1
2010-07-31 06:52:47 +04:00
else:
id = 0
if layers == 1 and not component:
self.mode = "L"
elif layers == 3 and component:
self.mode = "RGB"[id]
elif layers == 4 and component:
self.mode = "CMYK"[id]
# size
self._size = self.getint((3, 20)), self.getint((3, 30))
2010-07-31 06:52:47 +04:00
# compression
try:
2014-07-30 20:39:11 +04:00
compression = COMPRESSION[self.getint((3, 120))]
except KeyError as e:
raise OSError("Unknown IPTC image compression") from e
2010-07-31 06:52:47 +04:00
# tile
2014-07-30 20:39:11 +04:00
if tag == (8, 10):
2019-03-21 16:28:20 +03:00
self.tile = [
("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
]
2010-07-31 06:52:47 +04:00
def load(self):
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
return ImageFile.ImageFile.load(self)
type, tile, box = self.tile[0]
encoding, offset = tile
self.fp.seek(offset)
# Copy image data to temporary file
o_fd, outfile = tempfile.mkstemp(text=False)
o = os.fdopen(o_fd)
2010-07-31 06:52:47 +04:00
if encoding == "raw":
# To simplify access to the extracted file,
# prepend a PPM header
o.write("P5\n%d %d\n255\n" % self.size)
while True:
2010-07-31 06:52:47 +04:00
type, size = self.field()
if type != (8, 10):
break
while size > 0:
s = self.fp.read(min(size, 8192))
if not s:
break
o.write(s)
size -= len(s)
2010-07-31 06:52:47 +04:00
o.close()
try:
2020-02-18 12:49:05 +03:00
with Image.open(outfile) as _im:
_im.load()
self.im = _im.im
2010-07-31 06:52:47 +04:00
finally:
2014-07-30 20:39:11 +04:00
try:
os.unlink(outfile)
2015-12-02 08:23:49 +03:00
except OSError:
2014-07-30 20:39:11 +04:00
pass
2010-07-31 06:52:47 +04:00
Image.register_open(IptcImageFile.format, IptcImageFile)
2010-07-31 06:52:47 +04:00
Image.register_extension(IptcImageFile.format, ".iim")
2010-07-31 06:52:47 +04:00
2014-07-30 20:39:11 +04:00
2010-07-31 06:52:47 +04:00
def getiptcinfo(im):
2016-09-24 12:10:46 +03:00
"""
Get IPTC information from TIFF, JPEG, or IPTC file.
2010-07-31 06:52:47 +04:00
2016-09-24 12:10:46 +03:00
:param im: An image containing IPTC data.
:returns: A dictionary containing IPTC information, or None if
no IPTC information block was found.
"""
import io
2010-07-31 06:52:47 +04:00
from . import JpegImagePlugin, TiffImagePlugin
2010-07-31 06:52:47 +04:00
data = None
if isinstance(im, IptcImageFile):
# return info dictionary right away
return im.info
elif isinstance(im, JpegImagePlugin.JpegImageFile):
# extract the IPTC/NAA resource
2019-03-06 02:28:45 +03:00
photoshop = im.info.get("photoshop")
if photoshop:
data = photoshop.get(0x0404)
2010-07-31 06:52:47 +04:00
elif isinstance(im, TiffImagePlugin.TiffImageFile):
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
# as 4-byte integers, so we cannot use the get method...)
try:
data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
2010-07-31 06:52:47 +04:00
except (AttributeError, KeyError):
pass
if data is None:
2014-07-30 20:39:11 +04:00
return None # no properties
2010-07-31 06:52:47 +04:00
# create an IptcImagePlugin object without initializing it
class FakeImage:
2010-07-31 06:52:47 +04:00
pass
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
im = FakeImage()
im.__class__ = IptcImageFile
# parse the IPTC information chunk
im.info = {}
im.fp = io.BytesIO(data)
2010-07-31 06:52:47 +04:00
try:
im._open()
except (IndexError, KeyError):
2014-07-30 20:39:11 +04:00
pass # expected failure
2010-07-31 06:52:47 +04:00
return im.info