# 
 | 
# 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. 
 | 
# 
 | 
  
 | 
  
 | 
__version__ = "0.3" 
 | 
  
 | 
  
 | 
import Image, ImageFile 
 | 
import os, tempfile 
 | 
  
 | 
  
 | 
COMPRESSION = { 
 | 
    1: "raw", 
 | 
    5: "jpeg" 
 | 
} 
 | 
  
 | 
PAD = chr(0) * 4 
 | 
  
 | 
# 
 | 
# Helpers 
 | 
  
 | 
def i16(c): 
 | 
    return ord(c[1]) + (ord(c[0])<<8) 
 | 
  
 | 
def i32(c): 
 | 
    return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24) 
 | 
  
 | 
def i(c): 
 | 
    return i32((PAD + c)[-4:]) 
 | 
  
 | 
def dump(c): 
 | 
    for i in c: 
 | 
        print "%02x" % ord(i), 
 | 
    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 = ord(s[1]), ord(s[2]) 
 | 
  
 | 
        # syntax 
 | 
        if ord(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9: 
 | 
            raise SyntaxError, "invalid IPTC/NAA file" 
 | 
  
 | 
        # field size 
 | 
        size = ord(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 _is_raw(self, offset, size): 
 | 
        # 
 | 
        # check if the file can be mapped 
 | 
  
 | 
        # DISABLED: the following only slows things down... 
 | 
        return 0 
 | 
  
 | 
        self.fp.seek(offset) 
 | 
        t, sz = self.field() 
 | 
        if sz != size[0]: 
 | 
            return 0 
 | 
        y = 1 
 | 
        while 1: 
 | 
            self.fp.seek(sz, 1) 
 | 
            t, s = self.field() 
 | 
            if t != (8, 10): 
 | 
                break 
 | 
            if s != sz: 
 | 
                return 0 
 | 
            y = y + 1 
 | 
        return y == size[1] 
 | 
  
 | 
    def _open(self): 
 | 
  
 | 
        # load descriptive fields 
 | 
        while 1: 
 | 
            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.keys(): 
 | 
                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 = ord(self.info[(3,60)][0]) 
 | 
        component = ord(self.info[(3,60)][1]) 
 | 
        if self.info.has_key((3,65)): 
 | 
            id = ord(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): 
 | 
            if compression == "raw" and self._is_raw(offset, self.size): 
 | 
                self.tile = [(compression, (offset, size + 5, -1), 
 | 
                             (0, 0, self.size[0], self.size[1]))] 
 | 
            else: 
 | 
                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 
 | 
        outfile = tempfile.mktemp() 
 | 
        o = open(outfile, "wb") 
 | 
        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 1: 
 | 
            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 = size - len(s) 
 | 
        o.close() 
 | 
  
 | 
        try: 
 | 
            try: 
 | 
                # fast 
 | 
                self.im = Image.core.open_ppm(outfile) 
 | 
            except: 
 | 
                # slightly slower 
 | 
                im = Image.open(outfile) 
 | 
                im.load() 
 | 
                self.im = im.im 
 | 
        finally: 
 | 
            try: os.unlink(outfile) 
 | 
            except: pass 
 | 
  
 | 
  
 | 
Image.register_open("IPTC", IptcImageFile) 
 | 
  
 | 
Image.register_extension("IPTC", ".iim") 
 | 
  
 | 
## 
 | 
# Get IPTC information from TIFF, JPEG, or IPTC file. 
 | 
# 
 | 
# @param im An image containing IPTC data. 
 | 
# @return A dictionary containing IPTC information, or None if 
 | 
#     no IPTC information block was found. 
 | 
  
 | 
def getiptcinfo(im): 
 | 
  
 | 
    import TiffImagePlugin, JpegImagePlugin 
 | 
    import StringIO 
 | 
  
 | 
    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] == "Photoshop 3.0\x00": 
 | 
                app = app[14:] 
 | 
                # parse the image resource block 
 | 
                offset = 0 
 | 
                while app[offset:offset+4] == "8BIM": 
 | 
                    offset = offset + 4 
 | 
                    # resource code 
 | 
                    code = JpegImagePlugin.i16(app, offset) 
 | 
                    offset = offset + 2 
 | 
                    # resource name (usually empty) 
 | 
                    name_len = ord(app[offset]) 
 | 
                    name = app[offset+1:offset+1+name_len] 
 | 
                    offset = 1 + offset + name_len 
 | 
                    if offset & 1: 
 | 
                        offset = offset + 1 
 | 
                    # resource data block 
 | 
                    size = JpegImagePlugin.i32(app, offset) 
 | 
                    offset = offset + 4 
 | 
                    if code == 0x0404: 
 | 
                        # 0x0404 contains IPTC/NAA data 
 | 
                        data = app[offset:offset+size] 
 | 
                        break 
 | 
                    offset = offset + size 
 | 
                    if offset & 1: 
 | 
                        offset = 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: 
 | 
            type, 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: 
 | 
        pass 
 | 
    im = FakeImage() 
 | 
    im.__class__ = IptcImageFile 
 | 
  
 | 
    # parse the IPTC information chunk 
 | 
    im.info = {} 
 | 
    im.fp = StringIO.StringIO(data) 
 | 
  
 | 
    try: 
 | 
        im._open() 
 | 
    except (IndexError, KeyError): 
 | 
        pass # expected failure 
 | 
  
 | 
    return im.info 
 |