mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-30 20:06:17 +03:00
d244fbb299
iter(dict) is equivalent to iter(dict.keys()), so simply act on the dict instead of adding the extra call. Inspired by Lennart Regebro's PyCon 2017 presentation "Prehistoric Patterns in Python". Available at: https://www.youtube.com/watch?v=V5-JH23Vk0I
258 lines
6.6 KiB
Python
258 lines
6.6 KiB
Python
#
|
|
# 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.
|
|
#
|
|
|
|
from __future__ import print_function
|
|
|
|
from . import Image, ImageFile
|
|
from ._binary import i8, i16be as i16, i32be as i32, o8
|
|
import os
|
|
import tempfile
|
|
|
|
__version__ = "0.3"
|
|
|
|
COMPRESSION = {
|
|
1: "raw",
|
|
5: "jpeg"
|
|
}
|
|
|
|
PAD = o8(0) * 4
|
|
|
|
|
|
#
|
|
# Helpers
|
|
|
|
def i(c):
|
|
return i32((PAD + c)[-4:])
|
|
|
|
|
|
def dump(c):
|
|
for i in c:
|
|
print("%02x" % i8(i), end=' ')
|
|
print()
|
|
|
|
|
|
##
|
|
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
|
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
|
|
|
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])
|
|
|
|
# syntax
|
|
if i8(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9:
|
|
raise SyntaxError("invalid IPTC/NAA file")
|
|
|
|
# field size
|
|
size = i8(s[3])
|
|
if size > 132:
|
|
raise IOError("illegal field length in IPTC/NAA file")
|
|
elif size == 128:
|
|
size = 0
|
|
elif size > 128:
|
|
size = i(self.fp.read(size-128))
|
|
else:
|
|
size = i16(s[3:])
|
|
|
|
return tag, size
|
|
|
|
def _open(self):
|
|
|
|
# load descriptive fields
|
|
while True:
|
|
offset = self.fp.tell()
|
|
tag, size = self.field()
|
|
if not tag or tag == (8, 10):
|
|
break
|
|
if size:
|
|
tagdata = self.fp.read(size)
|
|
else:
|
|
tagdata = None
|
|
if tag in self.info:
|
|
if isinstance(self.info[tag], list):
|
|
self.info[tag].append(tagdata)
|
|
else:
|
|
self.info[tag] = [self.info[tag], tagdata]
|
|
else:
|
|
self.info[tag] = tagdata
|
|
|
|
# print(tag, self.info[tag])
|
|
|
|
# mode
|
|
layers = i8(self.info[(3, 60)][0])
|
|
component = i8(self.info[(3, 60)][1])
|
|
if (3, 65) in self.info:
|
|
id = i8(self.info[(3, 65)][0])-1
|
|
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))
|
|
|
|
# compression
|
|
try:
|
|
compression = COMPRESSION[self.getint((3, 120))]
|
|
except KeyError:
|
|
raise IOError("Unknown IPTC image compression")
|
|
|
|
# tile
|
|
if tag == (8, 10):
|
|
self.tile = [("iptc", (compression, offset),
|
|
(0, 0, self.size[0], self.size[1]))]
|
|
|
|
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)
|
|
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:
|
|
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)
|
|
o.close()
|
|
|
|
try:
|
|
_im = Image.open(outfile)
|
|
_im.load()
|
|
self.im = _im.im
|
|
finally:
|
|
try:
|
|
os.unlink(outfile)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
Image.register_open(IptcImageFile.format, IptcImageFile)
|
|
|
|
Image.register_extension(IptcImageFile.format, ".iim")
|
|
|
|
|
|
def getiptcinfo(im):
|
|
"""
|
|
Get IPTC information from TIFF, JPEG, or IPTC file.
|
|
|
|
:param im: An image containing IPTC data.
|
|
:returns: A dictionary containing IPTC information, or None if
|
|
no IPTC information block was found.
|
|
"""
|
|
from . import TiffImagePlugin, JpegImagePlugin
|
|
import io
|
|
|
|
data = None
|
|
|
|
if isinstance(im, IptcImageFile):
|
|
# return info dictionary right away
|
|
return im.info
|
|
|
|
elif isinstance(im, JpegImagePlugin.JpegImageFile):
|
|
# extract the IPTC/NAA resource
|
|
try:
|
|
app = im.app["APP13"]
|
|
if app[:14] == b"Photoshop 3.0\x00":
|
|
app = app[14:]
|
|
# parse the image resource block
|
|
offset = 0
|
|
while app[offset:offset+4] == b"8BIM":
|
|
offset += 4
|
|
# resource code
|
|
code = i16(app, offset)
|
|
offset += 2
|
|
# resource name (usually empty)
|
|
name_len = i8(app[offset])
|
|
# name = app[offset+1:offset+1+name_len]
|
|
offset = 1 + offset + name_len
|
|
if offset & 1:
|
|
offset += 1
|
|
# resource data block
|
|
size = i32(app, offset)
|
|
offset += 4
|
|
if code == 0x0404:
|
|
# 0x0404 contains IPTC/NAA data
|
|
data = app[offset:offset+size]
|
|
break
|
|
offset = offset + size
|
|
if offset & 1:
|
|
offset += 1
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
|
|
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]
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
|
|
if data is None:
|
|
return None # no properties
|
|
|
|
# create an IptcImagePlugin object without initializing it
|
|
class FakeImage(object):
|
|
pass
|
|
im = FakeImage()
|
|
im.__class__ = IptcImageFile
|
|
|
|
# parse the IPTC information chunk
|
|
im.info = {}
|
|
im.fp = io.BytesIO(data)
|
|
|
|
try:
|
|
im._open()
|
|
except (IndexError, KeyError):
|
|
pass # expected failure
|
|
|
|
return im.info
|