# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # pyexiv2 is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** from pyexiv2.metadata import ImageMetadata from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag from pyexiv2.xmp import XmpTag from pyexiv2.utils import FixedOffset, Rational import datetime import os import tempfile import time import unittest from testutils import EMPTY_JPG_DATA class TestImageMetadata(unittest.TestCase): def setUp(self): # Create an empty image file fd, self.pathname = tempfile.mkstemp(suffix='.jpg') os.write(fd, EMPTY_JPG_DATA) os.close(fd) # Write some metadata m = ImageMetadata(self.pathname) m.read() m['Exif.Image.Make'] = 'EASTMAN KODAK COMPANY' m['Exif.Image.DateTime'] = datetime.datetime(2009, 2, 9, 13, 33, 20) m['Iptc.Application2.Caption'] = ['blabla'] m['Iptc.Application2.DateCreated'] = [datetime.date(2004, 7, 13)] m['Xmp.dc.format'] = ('image', 'jpeg') m['Xmp.dc.subject'] = ['image', 'test', 'pyexiv2'] m.comment = 'Hello World!' m.write() self.metadata = ImageMetadata(self.pathname) def tearDown(self): os.remove(self.pathname) ###################### # Test general methods ###################### def test_not_read_raises(self): # http://bugs.launchpad.net/pyexiv2/+bug/687373 self.assertRaises(IOError, self.metadata.write) self.assertRaises(IOError, self.metadata.__getattribute__, 'dimensions') self.assertRaises(IOError, self.metadata.__getattribute__, 'mime_type') self.assertRaises(IOError, self.metadata.__getattribute__, 'exif_keys') self.assertRaises(IOError, self.metadata.__getattribute__, 'iptc_keys') self.assertRaises(IOError, self.metadata.__getattribute__, 'xmp_keys') self.assertRaises(IOError, self.metadata._get_exif_tag, 'Exif.Image.Make') self.assertRaises(IOError, self.metadata._get_iptc_tag, 'Iptc.Application2.Caption') self.assertRaises(IOError, self.metadata._get_xmp_tag, 'Xmp.dc.format') self.assertRaises(IOError, self.metadata._set_exif_tag, 'Exif.Image.Make', 'foobar') self.assertRaises(IOError, self.metadata._set_iptc_tag, 'Iptc.Application2.Caption', ['foobar']) self.assertRaises(IOError, self.metadata._set_xmp_tag, 'Xmp.dc.format', ('foo', 'bar')) self.assertRaises(IOError, self.metadata._delete_exif_tag, 'Exif.Image.Make') self.assertRaises(IOError, self.metadata._delete_iptc_tag, 'Iptc.Application2.Caption') self.assertRaises(IOError, self.metadata._delete_xmp_tag, 'Xmp.dc.format') self.assertRaises(IOError, self.metadata._get_comment) self.assertRaises(IOError, self.metadata._set_comment, 'foobar') self.assertRaises(IOError, self.metadata._del_comment) self.assertRaises(IOError, self.metadata.__getattribute__, 'previews') other = ImageMetadata(self.pathname) self.assertRaises(IOError, self.metadata.copy, other) self.assertRaises(IOError, self.metadata.__getattribute__, 'buffer') thumb = self.metadata.exif_thumbnail self.assertRaises(IOError, thumb.__getattribute__, 'mime_type') self.assertRaises(IOError, thumb.__getattribute__, 'extension') self.assertRaises(IOError, thumb.write_to_file, '/tmp/foobar.jpg') self.assertRaises(IOError, thumb.erase) self.assertRaises(IOError, thumb.set_from_file, '/tmp/foobar.jpg') self.assertRaises(IOError, thumb.__getattribute__, 'data') self.assertRaises(IOError, thumb.__setattr__, 'data', EMPTY_JPG_DATA) self.assertRaises(IOError, self.metadata.__getattribute__, 'iptc_charset') def test_read(self): self.assertRaises(IOError, self.metadata.__getattribute__, '_image') self.metadata.read() self.failIfEqual(self.metadata._image, None) def test_read_nonexistent_file(self): metadata = ImageMetadata('idontexist') self.failUnlessRaises(IOError, metadata.read) def test_write_preserve_timestamps(self): stat = os.stat(self.pathname) atime = round(stat.st_atime) mtime = round(stat.st_mtime) metadata = ImageMetadata(self.pathname) metadata.read() metadata.comment = 'Yellow Submarine' time.sleep(1.1) metadata.write(preserve_timestamps=True) stat2 = os.stat(self.pathname) atime2 = round(stat2.st_atime) mtime2 = round(stat2.st_mtime) self.failUnlessEqual(atime2, atime) self.failUnlessEqual(mtime2, mtime) def test_write_dont_preserve_timestamps(self): stat = os.stat(self.pathname) atime = round(stat.st_atime) mtime = round(stat.st_mtime) metadata = ImageMetadata(self.pathname) metadata.read() metadata.comment = 'Yellow Submarine' time.sleep(1.1) metadata.write() stat2 = os.stat(self.pathname) atime2 = round(stat2.st_atime) mtime2 = round(stat2.st_mtime) # It is not safe to assume that atime will have been modified when the # file has been read, as it may depend on mount options (e.g. noatime, # relatime). # See discussion at http://bugs.launchpad.net/pyexiv2/+bug/624999. #self.failIfEqual(atime2, atime) self.failIfEqual(mtime2, mtime) metadata.comment = 'Yesterday' time.sleep(1.1) metadata.write(preserve_timestamps=True) stat3 = os.stat(self.pathname) atime3 = round(stat3.st_atime) mtime3 = round(stat3.st_mtime) self.failUnlessEqual(atime3, atime2) self.failUnlessEqual(mtime3, mtime2) ########################### # Test EXIF-related methods ########################### def test_exif_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['exif'], None) keys = self.metadata.exif_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['exif'], keys) def test_get_exif_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Get an existing tag key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assert_(isinstance(tag, ExifTag)) self.assertEqual(self.metadata._tags['exif'][key], tag) # Try to get an nonexistent tag key = 'Exif.Photo.Sharpness' self.failUnlessRaises(KeyError, self.metadata._get_exif_tag, key) def test_set_exif_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Try to set a tag with wrong type tag = 'Not an exif tag' self.failUnlessRaises(TypeError, self.metadata._set_exif_tag, tag) self.assertEqual(self.metadata._tags['exif'], {}) def test_set_exif_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Create a new tag tag = ExifTag('Exif.Thumbnail.Orientation', 1) self.assert_(tag.key not in self.metadata.exif_keys) self.metadata._set_exif_tag(tag.key, tag) self.assert_(tag.key in self.metadata.exif_keys) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(tag.key)._getRawValue(), tag.raw_value) def test_set_exif_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag tag = ExifTag('Exif.Image.DateTime', datetime.datetime(2009, 3, 20, 20, 32, 0)) self.metadata._set_exif_tag(tag.key, tag) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(tag.key)._getRawValue(), tag.raw_value) def test_set_exif_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag already cached key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) new_tag = ExifTag(key, 'World Company') self.metadata._set_exif_tag(key, new_tag) self.assertEqual(self.metadata._tags['exif'], {key: new_tag}) self.assert_(key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), new_tag.raw_value) def test_set_exif_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Exif.Thumbnail.Orientation' value = 1 self.metadata._set_exif_tag(key, value) self.assert_(key in self.metadata.exif_keys) self.assert_(key in self.metadata._image._exifKeys()) tag = self.metadata._get_exif_tag(key) self.assertEqual(tag.value, value) self.assertEqual(self.metadata._tags['exif'], {key: tag}) self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), tag.raw_value) def test_delete_exif_tag_inexistent(self): self.metadata.read() key = 'Exif.Image.Artist' self.failUnlessRaises(KeyError, self.metadata._delete_exif_tag, key) def test_delete_exif_tag_not_cached(self): self.metadata.read() key = 'Exif.Image.DateTime' self.assertEqual(self.metadata._tags['exif'], {}) self.assert_(key in self.metadata.exif_keys) self.metadata._delete_exif_tag(key) self.assertEqual(self.metadata._tags['exif'], {}) self.failIf(key in self.metadata.exif_keys) def test_delete_exif_tag_cached(self): self.metadata.read() key = 'Exif.Image.DateTime' self.assert_(key in self.metadata.exif_keys) tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) self.metadata._delete_exif_tag(key) self.assertEqual(self.metadata._tags['exif'], {}) self.failIf(key in self.metadata.exif_keys) ########################### # Test IPTC-related methods ########################### def test_iptc_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['iptc'], None) keys = self.metadata.iptc_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['iptc'], keys) def test_get_iptc_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Get an existing tag key = 'Iptc.Application2.DateCreated' tag = self.metadata._get_iptc_tag(key) self.assert_(isinstance(tag, IptcTag)) self.assertEqual(self.metadata._tags['iptc'][key], tag) # Try to get an nonexistent tag key = 'Iptc.Application2.Copyright' self.failUnlessRaises(KeyError, self.metadata._get_iptc_tag, key) def test_set_iptc_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Try to set a tag with wrong type tag = 'Not an iptc tag' self.failUnlessRaises(TypeError, self.metadata._set_iptc_tag, tag) self.assertEqual(self.metadata._tags['iptc'], {}) def test_set_iptc_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Create a new tag tag = IptcTag('Iptc.Application2.Writer', ['Nobody']) self.assert_(tag.key not in self.metadata.iptc_keys) self.metadata._set_iptc_tag(tag.key, tag) self.assert_(tag.key in self.metadata.iptc_keys) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_value) def test_set_iptc_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag tag = IptcTag('Iptc.Application2.Caption', ['A picture.']) self.metadata._set_iptc_tag(tag.key, tag) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_value) def test_set_iptc_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag already cached key = 'Iptc.Application2.Caption' tag = self.metadata._get_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'][key], tag) new_tag = IptcTag(key, ['A picture.']) self.metadata._set_iptc_tag(key, new_tag) self.assertEqual(self.metadata._tags['iptc'], {key: new_tag}) self.assert_(key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), new_tag.raw_value) def test_set_iptc_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Iptc.Application2.Writer' values = ['Nobody'] self.metadata._set_iptc_tag(key, values) self.assert_(key in self.metadata.iptc_keys) self.assert_(key in self.metadata._image._iptcKeys()) tag = self.metadata._get_iptc_tag(key) self.assertEqual(tag.value, values) self.assertEqual(self.metadata._tags['iptc'], {key: tag}) self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), tag.raw_value) def test_delete_iptc_tag_inexistent(self): self.metadata.read() key = 'Iptc.Application2.LocationCode' self.failUnlessRaises(KeyError, self.metadata._delete_iptc_tag, key) def test_delete_iptc_tag_not_cached(self): self.metadata.read() key = 'Iptc.Application2.Caption' self.assertEqual(self.metadata._tags['iptc'], {}) self.assert_(key in self.metadata.iptc_keys) self.metadata._delete_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'], {}) self.failIf(key in self.metadata.iptc_keys) def test_delete_iptc_tag_cached(self): self.metadata.read() key = 'Iptc.Application2.Caption' self.assert_(key in self.metadata.iptc_keys) tag = self.metadata._get_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'][key], tag) self.metadata._delete_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'], {}) self.failIf(key in self.metadata.iptc_keys) ########################## # Test XMP-related methods ########################## def test_xmp_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['xmp'], None) keys = self.metadata.xmp_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['xmp'], keys) def test_get_xmp_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Get an existing tag key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assert_(isinstance(tag, XmpTag)) self.assertEqual(self.metadata._tags['xmp'][key], tag) # Try to get an nonexistent tag key = 'Xmp.xmp.Label' self.failUnlessRaises(KeyError, self.metadata._get_xmp_tag, key) def test_set_xmp_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Try to set a tag with wrong type tag = 'Not an xmp tag' self.failUnlessRaises(TypeError, self.metadata._set_xmp_tag, tag) self.assertEqual(self.metadata._tags['xmp'], {}) def test_set_xmp_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Create a new tag tag = XmpTag('Xmp.dc.title', {'x-default': 'This is not a title', 'fr-FR': "Ceci n'est pas un titre"}) self.assert_(tag.key not in self.metadata.xmp_keys) self.metadata._set_xmp_tag(tag.key, tag) self.assert_(tag.key in self.metadata.xmp_keys) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(tag.key)._getLangAltValue(), tag.raw_value) def test_set_xmp_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag tag = XmpTag('Xmp.dc.format', ('image', 'png')) self.metadata._set_xmp_tag(tag.key, tag) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(tag.key)._getTextValue(), tag.raw_value) def test_set_xmp_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag already cached key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) new_tag = XmpTag(key, ['hello', 'world']) self.metadata._set_xmp_tag(key, new_tag) self.assertEqual(self.metadata._tags['xmp'], {key: new_tag}) self.assert_(key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), new_tag.raw_value) def test_set_xmp_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Xmp.dc.title' value = {'x-default': 'This is not a title', 'fr-FR': "Ceci n'est pas un titre"} self.metadata._set_xmp_tag(key, value) self.assert_(key in self.metadata.xmp_keys) self.assert_(key in self.metadata._image._xmpKeys()) tag = self.metadata._get_xmp_tag(key) self.assertEqual(tag.value, value) self.assertEqual(self.metadata._tags['xmp'], {key: tag}) self.assertEqual(self.metadata._image._getXmpTag(key)._getLangAltValue(), tag.raw_value) def test_delete_xmp_tag_inexistent(self): self.metadata.read() key = 'Xmp.xmp.CreatorTool' self.failUnlessRaises(KeyError, self.metadata._delete_xmp_tag, key) def test_delete_xmp_tag_not_cached(self): self.metadata.read() key = 'Xmp.dc.subject' self.assertEqual(self.metadata._tags['xmp'], {}) self.assert_(key in self.metadata.xmp_keys) self.metadata._delete_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'], {}) self.failIf(key in self.metadata.xmp_keys) def test_delete_xmp_tag_cached(self): self.metadata.read() key = 'Xmp.dc.subject' self.assert_(key in self.metadata.xmp_keys) tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) self.metadata._delete_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'], {}) self.failIf(key in self.metadata.xmp_keys) ########################### # Test dictionary interface ########################### def test_getitem(self): self.metadata.read() # Get existing tags key = 'Exif.Image.DateTime' tag = self.metadata[key] self.assert_(isinstance(tag, ExifTag)) key = 'Iptc.Application2.Caption' tag = self.metadata[key] self.assert_(isinstance(tag, IptcTag)) key = 'Xmp.dc.format' tag = self.metadata[key] self.assert_(isinstance(tag, XmpTag)) # Try to get nonexistent tags keys = ('Exif.Image.SamplesPerPixel', 'Iptc.Application2.FixtureId', 'Xmp.xmp.Rating', 'Wrong.Noluck.Raise') for key in keys: self.failUnlessRaises(KeyError, self.metadata.__getitem__, key) def test_setitem(self): self.metadata.read() # Set new tags key = 'Exif.Photo.ExposureBiasValue' tag = ExifTag(key, Rational(0, 3)) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.City' tag = IptcTag(key, ['Barcelona']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.description' tag = XmpTag(key, {'x-default': 'Sunset picture.'}) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag) # Replace existing tags key = 'Exif.Photo.ExifVersion' tag = ExifTag(key, '0220') self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.Caption' tag = IptcTag(key, ['Sunset on Barcelona.']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.subject' tag = XmpTag(key, ['sunset', 'Barcelona', 'beautiful', 'beach']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag) def test_delitem(self): self.metadata.read() # Delete existing tags key = 'Exif.Image.Make' del self.metadata[key] self.failIf(key in self.metadata._keys['exif']) self.failIf(key in self.metadata._tags['exif']) key = 'Iptc.Application2.Caption' del self.metadata[key] self.failIf(key in self.metadata._keys['iptc']) self.failIf(key in self.metadata._tags['iptc']) key = 'Xmp.dc.subject' del self.metadata[key] self.failIf(key in self.metadata._keys['xmp']) self.failIf(key in self.metadata._tags['xmp']) # Try to delete nonexistent tags keys = ('Exif.Image.SamplesPerPixel', 'Iptc.Application2.FixtureId', 'Xmp.xmp.Rating', 'Wrong.Noluck.Raise') for key in keys: self.failUnlessRaises(KeyError, self.metadata.__delitem__, key) def test_replace_tag_by_itself(self): # Test that replacing an existing tag by itself # doesn’t result in an ugly segmentation fault # (see https://bugs.launchpad.net/pyexiv2/+bug/622739). self.metadata.read() keys = self.metadata.exif_keys + \ self.metadata.iptc_keys + \ self.metadata.xmp_keys for key in keys: self.metadata[key] = self.metadata[key] ########################## # Test the image comment # ########################## def test_get_comment(self): self.metadata.read() self.failUnlessEqual(self.metadata.comment, 'Hello World!') def test_set_comment(self): self.metadata.read() comment = 'Welcome to the real world.' self.metadata.comment = comment self.failUnlessEqual(self.metadata.comment, comment) self.metadata.comment = None self.failUnlessEqual(self.metadata.comment, '') def test_delete_comment(self): self.metadata.read() del self.metadata.comment self.failUnlessEqual(self.metadata.comment, '') #################### # Test metadata copy #################### def _set_up_other(self): self.other = ImageMetadata.from_buffer(EMPTY_JPG_DATA) def test_copy_metadata(self): self.metadata.read() self._set_up_other() self.other.read() families = ('exif', 'iptc', 'xmp') for family in families: self.failUnlessEqual(getattr(self.other, '%s_keys' % family), []) self.metadata.copy(self.other) for family in ('exif', 'iptc', 'xmp'): self.failUnlessEqual(self.other._keys[family], None) self.failUnlessEqual(self.other._tags[family], {}) keys = getattr(self.metadata, '%s_keys' % family) self.failUnlessEqual(getattr(self.other._image, '_%sKeys' % family)(), keys) self.failUnlessEqual(getattr(self.other, '%s_keys' % family), keys) for key in self.metadata.exif_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) for key in self.metadata.iptc_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) for key in self.metadata.xmp_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) self.failUnlessEqual(self.metadata.comment, self.other.comment) ############################# # Test MutableMapping methods ############################# def _set_up_clean(self): self.clean = ImageMetadata.from_buffer(EMPTY_JPG_DATA) def test_mutablemapping(self): self._set_up_clean() self.clean.read() self.assertEqual(len(self.clean), 0) self.assertTrue('Exif.Image.DateTimeOriginal' not in self.clean) key = 'Exif.Image.DateTimeOriginal' correctDate = datetime.datetime(2007,03,11) incorrectDate = datetime.datetime(2009,03,25) tag_date = ExifTag(key,correctDate) false_tag_date = ExifTag(key,incorrectDate) self.clean[key] = tag_date self.assertEqual(len(self.clean), 1) self.assertTrue('Exif.Image.DateTimeOriginal' in self.clean) self.assertEqual(self.clean.get('Exif.Image.DateTimeOriginal', false_tag_date), tag_date) self.assertEqual(self.clean.get('Exif.Image.DateTime', tag_date), tag_date) key = 'Exif.Photo.UserComment' tag = ExifTag(key,'UserComment') self.clean[key] = tag key = 'Iptc.Application2.Caption' tag = IptcTag(key,['Caption']) self.clean[key] = tag key = 'Xmp.dc.subject' tag = XmpTag(key, ['subject', 'values']) self.clean[key] = tag self.assertTrue('Exif.Photo.UserComment' in self.clean) self.assertTrue('Iptc.Application2.Caption' in self.clean) self.assertTrue('Xmp.dc.subject' in self.clean) self.clean.clear() self.assertEqual(len(self.clean), 0) self.assertTrue('Exif.Photo.UserComment' not in self.clean) self.assertTrue('Iptc.Application2.Caption' not in self.clean) self.assertTrue('Xmp.dc.subject' not in self.clean) ########################### # Test the EXIF thumbnail # ########################### def _test_thumbnail_tags(self, there): keys = ('Exif.Thumbnail.Compression', 'Exif.Thumbnail.JPEGInterchangeFormat', 'Exif.Thumbnail.JPEGInterchangeFormatLength') for key in keys: self.assertEqual(key in self.metadata.exif_keys, there) def test_no_exif_thumbnail(self): self.metadata.read() thumb = self.metadata.exif_thumbnail self.assertEqual(thumb.mime_type, '') self.assertEqual(thumb.extension, '') self.assertEqual(thumb.data, '') self._test_thumbnail_tags(False) def test_set_exif_thumbnail_from_data(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) def test_set_exif_thumbnail_from_file(self): fd, pathname = tempfile.mkstemp(suffix='.jpg') os.write(fd, EMPTY_JPG_DATA) os.close(fd) self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.set_from_file(pathname) os.remove(pathname) self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) def test_write_exif_thumbnail_to_file(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA fd, pathname = tempfile.mkstemp() os.close(fd) os.remove(pathname) thumb.write_to_file(pathname) pathname = pathname + thumb.extension fd = open(pathname, 'rb') self.assertEqual(fd.read(), EMPTY_JPG_DATA) fd.close() os.remove(pathname) def test_erase_exif_thumbnail(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) thumb.erase() self.assertEqual(thumb.mime_type, '') self.assertEqual(thumb.extension, '') self.assertEqual(thumb.data, '') self._test_thumbnail_tags(False) def test_set_exif_thumbnail_from_invalid_data(self): # No check on the format of the buffer is performed, therefore it will # always work. self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = 'invalid' self.assertEqual(thumb.mime_type, 'image/jpeg') self._test_thumbnail_tags(True) def test_set_exif_thumbnail_from_inexistent_file(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail fd, pathname = tempfile.mkstemp() os.close(fd) os.remove(pathname) self.failUnlessRaises(IOError, thumb.set_from_file, pathname) self._test_thumbnail_tags(False) def test_exif_thumbnail_is_preview(self): self.metadata.read() self._test_thumbnail_tags(False) self.assertEqual(len(self.metadata.previews), 0) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self._test_thumbnail_tags(True) self.assertEqual(len(self.metadata.previews), 1) preview = self.metadata.previews[0] self.assertEqual(thumb.mime_type, preview.mime_type) self.assertEqual(thumb.extension, preview.extension) self.assertEqual(thumb.data, preview.data) ######################### # Test the IPTC charset # ######################### def test_guess_iptc_charset(self): # If no charset is defined, exiv2 guesses it from the encoding of the # metadata. self.metadata.read() self.assertEqual(self.metadata.iptc_charset, 'ascii') self.metadata['Iptc.Application2.City'] = [u'Córdoba'] self.assertEqual(self.metadata.iptc_charset, 'utf-8') def test_set_iptc_charset_utf8(self): self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) self.assertEqual(self.metadata.iptc_charset, 'ascii') values = ('utf-8', 'utf8', 'u8', 'utf', 'utf_8') for value in values: self.metadata.iptc_charset = value self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.metadata.iptc_charset = value.upper() self.assertEqual(self.metadata.iptc_charset, 'utf-8') def test_set_invalid_iptc_charset(self): self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) values = ('invalid', 'utf-9', '3.14') for value in values: self.assertRaises(ValueError, self.metadata.__setattr__, 'iptc_charset', value) def test_set_unhandled_iptc_charset(self): # At the moment, the only charset handled is UTF-8. self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) values = ('ascii', 'iso8859_15', 'shift_jis') for value in values: self.assertRaises(ValueError, self.metadata.__setattr__, 'iptc_charset', value) def test_delete_iptc_charset(self): self.metadata.read() key = 'Iptc.Envelope.CharacterSet' self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) del self.metadata.iptc_charset self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) self.metadata.iptc_charset = 'utf-8' self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.assert_(key in self.metadata.iptc_keys) del self.metadata.iptc_charset self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) self.metadata.iptc_charset = 'utf-8' self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.assert_(key in self.metadata.iptc_keys) self.metadata.iptc_charset = None self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys)