Contrary to my original post, the
Python PIL library does have support for reading
and writing PNG metadata. This is based on the 1.6 and devel snapshot, as of 28-Aug-2007.
The Short Story
The short story is that Image.load reads most PNG metadata into the Image.info dict. But, Image.save ignores Image.info and will erase all metadata!. Use this wrapper function instead:
#
# wrapper around PIL 1.1.6 Image.save to preserve PNG metadata
#
# public domain, Nick Galbreath
# http://blog.modp.com/2007/08/python-pil-and-png-metadata-take-2.html
#
def pngsave(im, file):
# these can be automatically added to Image.info dict
# they are not user-added metadata
reserved = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect')
# undocumented class
from PIL import PngImagePlugin
meta = PngImagePlugin.PngInfo()
# copy metadata into new object
for k,v in im.info.iteritems():
if k in reserved: continue
meta.add_text(k, v, 0)
# and save
im.save(file, "PNG", pnginfo=meta)
Just edit the Image.info as you like and it will get written out.
from PIL import Image
im = Image.new("RGB", (128,128), "Black")
im.info["foo"] = "bar"
pngsave(im, "foo.png")
You can see that it worked by either doing strings foo.png or if you use ImageMagick, identify -verbose foo.png
The Long Story
The next section is mostly for PNG nerds and developers of PIL.
Reading
It dumps the metadata key/value pairs into the standard Image.info field. So far so good.
What's not so good is that is also puts transparency, gamma, aspect, and dpi into the same array. While I guess this is metadata, it is rendering metadata which is treated differently in the PNG file than user-added metadata. I'm not sure what PIL does for other image types -- there may be other keywords. This is really only a problem when it comes to writing metadata, in the next section.
Another issue is that PIL only reads only one of the three different type of metadata chunks that PNG supports. (tEXt: yes, zTXt: no, iTXt: no). This post provides a patch for zTXt.
Writing
By default PIL will erase any user-metadata with Image.save. I would think this is a bug. Editing the Image.info dictionary does not result in changes either. It is completely ignored on write.
Oddly PIL has support for writing metadata, as either uncompressed tEXt or compressed zTXt data (which it isn't able to read!). Here's what you do:
>>> from PIL import Image
>>> from PIL import PngImagePlugin
>>> # let's make an image
>>> im = Image.new("RGB", (128,128), "Black")
>>>
>>> # HERE'S THE SECRET
>>> meta = PngImagePlugin.PngInfo()
>>> meta.add_text("foo", "bar")
>>> im.save("foo.png", "png", pnginfo=meta)
>>>
>>>
>>> # But im.info is not modified
>>> im.info
{}
>>> # but if we re-open the image, we get out
>>> # metadata back
>>> im2 = Image.open("foo2.png")
>>> im2.info
{'foo': 'bar'}
>>> #
>>> # but remember if we save it without
>>> # explicitly adding the metadata, we lose it
>>> im2.save("foo3.png")
>>> im3 = Image.open("foo3.png")
>>> im3.info
{}
>>> # whoops
The secret is making a PngImagePlugin.PngInfo() object, and then adding key/value pairs using the add_text method. It has an optional third argument whether to compress the value text or not (true/false).
Technically, the PNG spec says that tEXt and zTXt should only contain latin-1 characters. I don't see the writer code enforcing this rule, but it's doubtful it matters at all. There is also no support for the iTXt block, which is for UTF-8 data. This doesn't seem to be a big deal since few (if any) image programs support it.
Ideas
Adding support for zTXt seems like a no-brainer. Especially since the writer exists.
Adding support for iTXt would be nice, but it appears nobody really uses it.
Adding support for the tIME (last modified time) seems like another no-brainer. It is currently not read or written.
Lumping together rendering metadata and user metadata in the same dict is not great. In a ideal world it would be nice to store the metadata in a special dict, that said what type of chunk it was in. Loading and saving a file would result in a near identical file. You then could also specify if a metadatum needed compressing or not. This is bonus. I'd be happy with any interface that allowed one to write plain 'ol tEXt chunks.
The hard part is making a uniform system of metadata that can work between different image types.