# -*- 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, make_fraction 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, make_fraction(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)