From 42e685da4e1811455017914e6bfb01b0ceea3f76 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 19 May 2010 19:21:57 +0200 Subject: Don't use mocks to unit-test the ImageMetadata class. --- test/metadata.py | 382 +++++++++++++++---------------------------------------- 1 file changed, 100 insertions(+), 282 deletions(-) diff --git a/test/metadata.py b/test/metadata.py index 237a510..f7ce119 100644 --- a/test/metadata.py +++ b/test/metadata.py @@ -24,8 +24,6 @@ # # ****************************************************************************** -import unittest - from pyexiv2.metadata import ImageMetadata from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag @@ -33,169 +31,38 @@ from pyexiv2.xmp import XmpTag from pyexiv2.utils import FixedOffset, Rational import datetime +import os +import tempfile +import unittest -class _TagMock(object): - - def __init__(self, key, type, value): - self.key = key - self.type = type - self.value = value - - def _getKey(self): - return self.key - - def _getType(self): - return self.type - - def _getRawValue(self): - return self.value - - -class _ExifTagMock(_TagMock): - - def __init__(self, key, type, value, human_value=None): - super(_ExifTagMock, self).__init__(key, type, value) - self.human_value = human_value - - def _getHumanValue(self): - return self.human_value - - def _setRawValue(self, value): - pass - - -class _IptcTagMock(_TagMock): - - def _getRawValues(self): - return self.value - - def _setRawValues(self, values): - pass - - -class _XmpTagMock(_TagMock): - - def __init__(self, key, type, exiv2_type, value): - super(_XmpTagMock, self).__init__(key, type, value) - self.exiv2_type = exiv2_type - - def _getExiv2Type(self): - return self.exiv2_type - - def _getTextValue(self): - return self.value - - def _getArrayValue(self): - return self.value - - def _setTextValue(self, value): - pass - - def _setArrayValue(self, values): - pass - - -class ImageMock(object): - - def __init__(self, filename): - self.filename = filename - self.read = False - self.written = False - self.tags = {'exif': {}, 'iptc': {}, 'xmp': {}} - - def _readMetadata(self): - self.read = True - - def _writeMetadata(self): - self.written = True - - def _exifKeys(self): - return self.tags['exif'].keys() - - def _getExifTag(self, key): - return self.tags['exif'][key] - - def _setExifTagValue(self, key, value): - self.tags['exif'][key] = value - - def _deleteExifTag(self, key): - try: - del self.tags['exif'][key] - except KeyError: - pass - - def _iptcKeys(self): - return self.tags['iptc'].keys() - - def _getIptcTag(self, key): - return self.tags['iptc'][key] - - def _setIptcTagValues(self, key, values): - self.tags['iptc'][key] = values - - def _deleteIptcTag(self, key): - try: - del self.tags['iptc'][key] - except KeyError: - pass - - def _xmpKeys(self): - return self.tags['xmp'].keys() - - def _getXmpTag(self, key): - return self.tags['xmp'][key] - - def _setXmpTagTextValue(self, key, value): - self.tags['xmp'][key] = value - - def _setXmpTagArrayValue(self, key, value): - self.tags['xmp'][key] = value - - def _setXmpTagLangAltValue(self, key, value): - self.tags['xmp'][key] = value - - def _deleteXmpTag(self, key): - try: - del self.tags['xmp'][key] - except KeyError: - pass - - def _copyMetadata(self, other, exif=True, iptc=True, xmp=True): - if exif: - other.tags['exif'] = self.tags['exif'] - if iptc: - other.tags['iptc'] = self.tags['iptc'] - if xmp: - other.tags['xmp'] = self.tags['xmp'] +EMPTY_PNG_DATA = \ + '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08' \ + '\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc```\x00\x00\x00\x04' \ + '\x00\x01\xf6\x178U\x00\x00\x00\x00IEND\xaeB`\x82' class TestImageMetadata(unittest.TestCase): def setUp(self): - self.metadata = ImageMetadata('nofile') - self.metadata._instantiate_image = lambda filename: ImageMock(filename) - - def _set_exif_tags(self): - tags = {} - tags['Exif.Image.Make'] = _ExifTagMock('Exif.Image.Make', 'Ascii', 'EASTMAN KODAK COMPANY') - tags['Exif.Image.DateTime'] = _ExifTagMock('Exif.Image.DateTime', 'Ascii', '2009:02:09 13:33:20') - tags['Exif.Photo.ExifVersion'] = _ExifTagMock('Exif.Photo.ExifVersion', 'Undefined', '48 50 50 49 ') - self.metadata._image.tags['exif'] = tags - - def _set_iptc_tags(self): - tags = {} - tags['Iptc.Application2.Caption'] = _IptcTagMock('Iptc.Application2.Caption', 'String', ['blabla']) - tags['Iptc.Application2.DateCreated'] = _IptcTagMock('Iptc.Application2.DateCreated', 'Date', ['2004-07-13']) - self.metadata._image.tags['iptc'] = tags - - def _set_xmp_tags(self): - tags = {} - tags['Xmp.dc.format'] = _XmpTagMock('Xmp.dc.format', 'MIMEType', 'XmpText', 'image/jpeg') - tags['Xmp.dc.subject'] = _XmpTagMock('Xmp.dc.subject', 'bag Text', 'XmpBag', 'image, test, pyexiv2') - tags['Xmp.xmp.CreateDate'] = _XmpTagMock('Xmp.xmp.CreateDate', 'Date', 'XmpText', '2005-09-07T15:07:40-07:00') - tags['Xmp.xmpMM.DocumentID'] = _XmpTagMock('Xmp.xmpMM.DocumentID', 'URI', 'XmpText', 'uuid:9A3B7F52214211DAB6308A7391270C13') - self.metadata._image.tags['xmp'] = tags + # Create an empty image file + fd, self.pathname = tempfile.mkstemp(suffix='.png') + os.write(fd, EMPTY_PNG_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.write() + self.metadata = ImageMetadata(self.pathname) + + def tearDown(self): + os.remove(self.pathname) ###################### # Test general methods @@ -205,33 +72,24 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._image, None) self.metadata.read() self.failIfEqual(self.metadata._image, None) - self.failUnless(self.metadata._image.read) def test_read_nonexistent_file(self): metadata = ImageMetadata('idontexist') self.failUnlessRaises(IOError, metadata.read) - def test_write(self): - self.metadata.read() - self.failIf(self.metadata._image.written) - self.metadata.write() - self.failUnless(self.metadata._image.written) - ########################### # Test EXIF-related methods ########################### def test_exif_keys(self): self.metadata.read() - self._set_exif_tags() self.assertEqual(self.metadata._keys['exif'], None) keys = self.metadata.exif_keys - self.assertEqual(len(keys), 3) + self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['exif'], keys) def test_get_exif_tag(self): self.metadata.read() - self._set_exif_tags() self.assertEqual(self.metadata._tags['exif'], {}) # Get an existing tag key = 'Exif.Image.Make' @@ -245,7 +103,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_exif_tag_wrong(self): self.metadata.read() - self._set_exif_tags() self.assertEqual(self.metadata._tags['exif'], {}) # Try to set a tag with wrong type tag = 'Not an exif tag' @@ -254,7 +111,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_exif_tag_create(self): self.metadata.read() - self._set_exif_tags() self.assertEqual(self.metadata._tags['exif'], {}) # Create a new tag tag = ExifTag('Exif.Thumbnail.Orientation', 1) @@ -264,13 +120,12 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(tag.metadata, self.metadata) self.assert_(tag.key in self.metadata.exif_keys) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['exif'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['exif'][tag.key], + 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._set_exif_tags() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag tag = ExifTag('Exif.Image.DateTime', datetime.datetime(2009, 3, 20, 20, 32, 0)) @@ -278,47 +133,44 @@ class TestImageMetadata(unittest.TestCase): self.metadata._set_exif_tag(tag.key, tag) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['exif'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['exif'][tag.key], + 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._set_exif_tags() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag already cached - key = 'Exif.Photo.ExifVersion' + key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) - new_tag = ExifTag(key, '48 50 50 48 ', _tag=_ExifTagMock(key, 'Undefined', '48 50 50 48 ', '2.20')) + new_tag = ExifTag(key, 'World Company') self.assertEqual(new_tag.metadata, None) self.metadata._set_exif_tag(key, new_tag) self.assertEqual(new_tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['exif'], {key: new_tag}) - self.assert_(self.metadata._image.tags['exif'].has_key(key)) - # Special case where the formatted value is used instead of the raw - # value. - self.assertEqual(self.metadata._image.tags['exif'][key], new_tag.raw_value) + 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._set_exif_tags() 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_(self.metadata._image.tags['exif'].has_key(key)) + self.assert_(key in self.metadata._image._exifKeys()) tag = self.metadata._get_exif_tag(key) self.assertEqual(tag.value, value) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['exif'], {key: tag}) - self.assertEqual(self.metadata._image.tags['exif'][key], tag.raw_value) + self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), + tag.raw_value) def test_set_exif_tag_value_inexistent(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Photo.ExposureTime' value = '1/500' self.failUnlessRaises(KeyError, self.metadata._set_exif_tag_value, @@ -326,7 +178,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_exif_tag_value_wrong_type(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Image.DateTime' value = datetime.datetime(2009, 3, 24, 9, 37, 36) self.failUnlessRaises(TypeError, self.metadata._set_exif_tag_value, @@ -334,40 +185,36 @@ class TestImageMetadata(unittest.TestCase): def test_set_exif_tag_value(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Image.DateTime' tag = self.metadata._get_exif_tag(key) value = '2009:03:24 09:37:36' - self.failIfEqual(self.metadata._image.tags['exif'][key], value) + self.failIfEqual(self.metadata._image._getExifTag(key)._getRawValue(), value) self.metadata._set_exif_tag_value(key, value) - self.assertEqual(self.metadata._image.tags['exif'][key], value) + self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), value) def test_delete_exif_tag_inexistent(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Image.Artist' self.failUnlessRaises(KeyError, self.metadata._delete_exif_tag, key) def test_delete_exif_tag_not_cached(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Image.DateTime' self.assertEqual(self.metadata._tags['exif'], {}) - self.assert_(self.metadata._image.tags['exif'].has_key(key)) + self.assert_(key in self.metadata._image._exifKeys()) self.metadata._delete_exif_tag(key) self.assertEqual(self.metadata._tags['exif'], {}) - self.failIf(self.metadata._image.tags['exif'].has_key(key)) + self.failIf(key in self.metadata._image._exifKeys()) def test_delete_exif_tag_cached(self): self.metadata.read() - self._set_exif_tags() key = 'Exif.Image.DateTime' - self.assert_(self.metadata._image.tags['exif'].has_key(key)) + self.assert_(key in self.metadata._image._exifKeys()) 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(self.metadata._image.tags['exif'].has_key(key)) + self.failIf(key in self.metadata._image._exifKeys()) ########################### # Test IPTC-related methods @@ -375,7 +222,6 @@ class TestImageMetadata(unittest.TestCase): def test_iptc_keys(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._keys['iptc'], None) keys = self.metadata.iptc_keys self.assertEqual(len(keys), 2) @@ -383,7 +229,6 @@ class TestImageMetadata(unittest.TestCase): def test_get_iptc_tag(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._tags['iptc'], {}) # Get an existing tag key = 'Iptc.Application2.DateCreated' @@ -397,7 +242,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_iptc_tag_wrong(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._tags['iptc'], {}) # Try to set a tag with wrong type tag = 'Not an iptc tag' @@ -406,7 +250,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_iptc_tag_create(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._tags['iptc'], {}) # Create a new tag tag = IptcTag('Iptc.Application2.Writer', ['Nobody']) @@ -416,13 +259,12 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(tag.metadata, self.metadata) self.assert_(tag.key in self.metadata.iptc_keys) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['iptc'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['iptc'][tag.key], + self.assert_(tag.key in self.metadata._image._iptcKeys()) + self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_values) def test_set_iptc_tag_overwrite(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag tag = IptcTag('Iptc.Application2.Caption', ['A picture.']) @@ -430,13 +272,12 @@ class TestImageMetadata(unittest.TestCase): self.metadata._set_iptc_tag(tag.key, tag) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['iptc'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['iptc'][tag.key], + self.assert_(tag.key in self.metadata._image._iptcKeys()) + self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_values) def test_set_iptc_tag_overwrite_already_cached(self): self.metadata.read() - self._set_iptc_tags() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag already cached key = 'Iptc.Application2.Caption' @@ -447,29 +288,28 @@ class TestImageMetadata(unittest.TestCase): self.metadata._set_iptc_tag(key, new_tag) self.assertEqual(new_tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['iptc'], {key: new_tag}) - self.assert_(self.metadata._image.tags['iptc'].has_key(key)) - self.assertEqual(self.metadata._image.tags['iptc'][key], + self.assert_(key in self.metadata._image._iptcKeys()) + self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), new_tag.raw_values) def test_set_iptc_tag_direct_value_assignment(self): self.metadata.read() - self._set_iptc_tags() 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_(self.metadata._image.tags['iptc'].has_key(key)) + self.assert_(key in self.metadata._image._iptcKeys()) tag = self.metadata._get_iptc_tag(key) self.assertEqual(tag.values, values) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['iptc'], {key: tag}) - self.assertEqual(self.metadata._image.tags['iptc'][key], tag.raw_values) + self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), + tag.raw_values) def test_set_iptc_tag_values_inexistent(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.Urgency' values = ['1'] self.failUnlessRaises(KeyError, self.metadata._set_iptc_tag_values, @@ -477,7 +317,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_iptc_tag_values_wrong_type(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.DateCreated' value = '20090324' self.failUnlessRaises(TypeError, self.metadata._set_iptc_tag_values, @@ -488,40 +327,36 @@ class TestImageMetadata(unittest.TestCase): def test_set_iptc_tag_values(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.DateCreated' tag = self.metadata._get_iptc_tag(key) values = ['2009-04-07'] - self.failIfEqual(self.metadata._image.tags['iptc'][key], values) + self.failIfEqual(self.metadata._image._getIptcTag(key)._getRawValues(), values) self.metadata._set_iptc_tag_values(key, values) - self.assertEqual(self.metadata._image.tags['iptc'][key], values) + self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), values) def test_delete_iptc_tag_inexistent(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.LocationCode' self.failUnlessRaises(KeyError, self.metadata._delete_iptc_tag, key) def test_delete_iptc_tag_not_cached(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.Caption' self.assertEqual(self.metadata._tags['iptc'], {}) - self.assert_(self.metadata._image.tags['iptc'].has_key(key)) + self.assert_(key in self.metadata._image._iptcKeys()) self.metadata._delete_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'], {}) - self.failIf(self.metadata._image.tags['iptc'].has_key(key)) + self.failIf(key in self.metadata._image._iptcKeys()) def test_delete_iptc_tag_cached(self): self.metadata.read() - self._set_iptc_tags() key = 'Iptc.Application2.Caption' - self.assert_(self.metadata._image.tags['iptc'].has_key(key)) + self.assert_(key in self.metadata._image._iptcKeys()) 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(self.metadata._image.tags['iptc'].has_key(key)) + self.failIf(key in self.metadata._image._iptcKeys()) ########################## # Test XMP-related methods @@ -529,15 +364,13 @@ class TestImageMetadata(unittest.TestCase): def test_xmp_keys(self): self.metadata.read() - self._set_xmp_tags() self.assertEqual(self.metadata._keys['xmp'], None) keys = self.metadata.xmp_keys - self.assertEqual(len(keys), 4) + self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['xmp'], keys) def test_get_xmp_tag(self): self.metadata.read() - self._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Get an existing tag key = 'Xmp.dc.subject' @@ -551,7 +384,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_xmp_tag_wrong(self): self.metadata.read() - self._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Try to set a tag with wrong type tag = 'Not an xmp tag' @@ -560,7 +392,6 @@ class TestImageMetadata(unittest.TestCase): def test_set_xmp_tag_create(self): self.metadata.read() - self._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Create a new tag tag = XmpTag('Xmp.dc.title', {'x-default': 'This is not a title', @@ -571,13 +402,12 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(tag.metadata, self.metadata) self.assert_(tag.key in self.metadata.xmp_keys) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['xmp'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['xmp'][tag.key], + 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._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag tag = XmpTag('Xmp.dc.format', ('image', 'png')) @@ -585,30 +415,28 @@ class TestImageMetadata(unittest.TestCase): self.metadata._set_xmp_tag(tag.key, tag) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) - self.assert_(self.metadata._image.tags['xmp'].has_key(tag.key)) - self.assertEqual(self.metadata._image.tags['xmp'][tag.key], + 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._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag already cached - key = 'Xmp.xmp.CreateDate' + key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) - new_tag = XmpTag(key, datetime.datetime(2009, 4, 21, 20, 7, 0, tzinfo=FixedOffset('+', 1, 0))) + new_tag = XmpTag(key, ['hello', 'world']) self.assertEqual(new_tag.metadata, None) self.metadata._set_xmp_tag(key, new_tag) self.assertEqual(new_tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['xmp'], {key: new_tag}) - self.assert_(self.metadata._image.tags['xmp'].has_key(key)) - self.assertEqual(self.metadata._image.tags['xmp'][key], + 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._set_xmp_tags() self.assertEqual(self.metadata._tags['xmp'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Xmp.dc.title' @@ -616,16 +444,15 @@ class TestImageMetadata(unittest.TestCase): '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_(self.metadata._image.tags['xmp'].has_key(key)) + self.assert_(key in self.metadata._image._xmpKeys()) tag = self.metadata._get_xmp_tag(key) self.assertEqual(tag.value, value) self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['xmp'], {key: tag}) - self.assertEqual(self.metadata._image.tags['xmp'][key], tag.raw_value) + self.assertEqual(self.metadata._image._getXmpTag(key)._getLangAltValue(), tag.raw_value) def test_set_xmp_tag_value_inexistent(self): self.metadata.read() - self._set_xmp_tags() key = 'Xmp.xmp.Nickname' value = 'oSoMoN' self.failUnlessRaises(KeyError, self.metadata._set_xmp_tag_value, @@ -633,8 +460,7 @@ class TestImageMetadata(unittest.TestCase): def test_set_xmp_tag_value_wrong_type(self): self.metadata.read() - self._set_xmp_tags() - key = 'Xmp.xmp.CreateDate' + key = 'Xmp.dc.subject' tag = self.metadata[key] value = datetime.datetime(2009, 4, 21, 20, 11, 0) self.failUnlessRaises(TypeError, self.metadata._set_xmp_tag_value, @@ -642,40 +468,36 @@ class TestImageMetadata(unittest.TestCase): def test_set_xmp_tag_value(self): self.metadata.read() - self._set_xmp_tags() - key = 'Xmp.xmp.CreateDate' + key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) - value = '2009-04-21T20:12:47+01:00' - self.failIfEqual(self.metadata._image.tags['xmp'][key], value) + value = ['Hello', 'World'] + self.failIfEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), value) self.metadata._set_xmp_tag_value(key, value) - self.assertEqual(self.metadata._image.tags['xmp'][key], value) + self.assertEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), value) def test_delete_xmp_tag_inexistent(self): self.metadata.read() - self._set_xmp_tags() key = 'Xmp.xmp.CreatorTool' self.failUnlessRaises(KeyError, self.metadata._delete_xmp_tag, key) def test_delete_xmp_tag_not_cached(self): self.metadata.read() - self._set_xmp_tags() key = 'Xmp.dc.subject' self.assertEqual(self.metadata._tags['xmp'], {}) - self.assert_(self.metadata._image.tags['xmp'].has_key(key)) + self.assert_(key in self.metadata._image._xmpKeys()) self.metadata._delete_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'], {}) - self.failIf(self.metadata._image.tags['xmp'].has_key(key)) + self.failIf(key in self.metadata._image._xmpKeys()) def test_delete_xmp_tag_cached(self): self.metadata.read() - self._set_xmp_tags() key = 'Xmp.dc.subject' - self.assert_(self.metadata._image.tags['xmp'].has_key(key)) + self.assert_(key in self.metadata._image._xmpKeys()) 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(self.metadata._image.tags['xmp'].has_key(key)) + self.failIf(key in self.metadata._image._xmpKeys()) ########################### # Test dictionary interface @@ -683,17 +505,14 @@ class TestImageMetadata(unittest.TestCase): def test_getitem(self): self.metadata.read() - self._set_exif_tags() - self._set_iptc_tags() - self._set_xmp_tags() # Get existing tags - key = 'Exif.Photo.ExifVersion' + key = 'Exif.Image.DateTime' tag = self.metadata[key] self.assertEqual(type(tag), ExifTag) key = 'Iptc.Application2.Caption' tag = self.metadata[key] self.assertEqual(type(tag), IptcTag) - key = 'Xmp.xmp.CreateDate' + key = 'Xmp.dc.format' tag = self.metadata[key] self.assertEqual(type(tag), XmpTag) # Try to get nonexistent tags @@ -704,9 +523,6 @@ class TestImageMetadata(unittest.TestCase): def test_setitem(self): self.metadata.read() - self._set_exif_tags() - self._set_iptc_tags() - self._set_xmp_tags() # Set new tags key = 'Exif.Photo.ExposureBiasValue' tag = ExifTag(key, Rational(0, 3)) @@ -742,17 +558,14 @@ class TestImageMetadata(unittest.TestCase): def test_delitem(self): self.metadata.read() - self._set_exif_tags() - self._set_iptc_tags() - self._set_xmp_tags() # Delete existing tags - key = 'Exif.Photo.ExifVersion' + key = 'Exif.Image.Make' del self.metadata[key] self.failIf(key in self.metadata._tags['exif']) key = 'Iptc.Application2.Caption' del self.metadata[key] self.failIf(key in self.metadata._tags['iptc']) - key = 'Xmp.xmp.CreateDate' + key = 'Xmp.dc.subject' del self.metadata[key] self.failIf(key in self.metadata._tags['xmp']) # Try to delete nonexistent tags @@ -766,27 +579,32 @@ class TestImageMetadata(unittest.TestCase): #################### def _set_up_other(self): - self.other = ImageMetadata('nofile') - self.other._instantiate_image = lambda filename: ImageMock(filename) + self.other = ImageMetadata.from_buffer(EMPTY_PNG_DATA) def test_copy_metadata(self): self.metadata.read() - self._set_exif_tags() - self._set_iptc_tags() - self._set_xmp_tags() self._set_up_other() self.other.read() - self.failUnlessEqual(self.other.exif_keys, []) - self.failUnlessEqual(self.other.iptc_keys, []) - self.failUnlessEqual(self.other.xmp_keys, []) + families = ('exif', 'iptc', 'xmp') + + for family in families: + self.failUnlessEqual(getattr(self.other, '%s_keys' % family), []) + self.metadata.copy(self.other) - # The actual test is here, the rest of the functionality being mocked... 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].values, self.other[key].values) - self.failUnlessEqual(self.other.exif_keys, self.metadata.exif_keys) - self.failUnlessEqual(self.other.iptc_keys, self.metadata.iptc_keys) - self.failUnlessEqual(self.other.xmp_keys, self.metadata.xmp_keys) + for key in self.metadata.xmp_keys: + self.failUnlessEqual(self.metadata[key].value, self.other[key].value) -- cgit From 0c2a0abd2ee15ac0d239daa8656347b550bd1d9c Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 19 May 2010 19:54:53 +0200 Subject: Attach the image's ExifData to a tag when it is assigned to an image. Remove redundant code that would set the value of a tag twice (in the tag itself, and in the image). Remove the now useless metadata attribute. --- src/exiv2wrapper.cpp | 16 +++++++++------- src/exiv2wrapper.hpp | 11 +++++++---- src/exiv2wrapper_python.cpp | 2 +- src/pyexiv2/exif.py | 9 +++------ src/pyexiv2/metadata.py | 16 +--------------- test/exif.py | 18 +----------------- test/metadata.py | 31 ------------------------------- 7 files changed, 22 insertions(+), 81 deletions(-) diff --git a/src/exiv2wrapper.cpp b/src/exiv2wrapper.cpp index cc2d455..c579b87 100644 --- a/src/exiv2wrapper.cpp +++ b/src/exiv2wrapper.cpp @@ -281,13 +281,6 @@ const ExifTag Image::getExifTag(std::string key) return ExifTag(key, &_exifData[key], &_exifData); } -void Image::setExifTagValue(std::string key, std::string value) -{ - CHECK_METADATA_READ - - _exifData[key] = value; -} - void Image::deleteExifTag(std::string key) { CHECK_METADATA_READ @@ -571,6 +564,15 @@ void ExifTag::setRawValue(const std::string& value) _datum->setValue(value); } +void ExifTag::setParentImage(Image& image) +{ + _data = image.getExifData(); + std::string value = _datum->toString(); + delete _datum; + _datum = &(*_data)[_key.key()]; + _datum->setValue(value); +} + const std::string ExifTag::getKey() { return _key.key(); diff --git a/src/exiv2wrapper.hpp b/src/exiv2wrapper.hpp index 0408e1a..6ba62f9 100644 --- a/src/exiv2wrapper.hpp +++ b/src/exiv2wrapper.hpp @@ -36,6 +36,8 @@ namespace exiv2wrapper { +class Image; + class ExifTag { public: @@ -45,6 +47,7 @@ public: ~ExifTag(); void setRawValue(const std::string& value); + void setParentImage(Image& image); const std::string getKey(); const std::string getType(); @@ -186,10 +189,6 @@ public: // Throw an exception if the tag is not set. const ExifTag getExifTag(std::string key); - // Set the EXIF tag's value. - // If the tag was not previously set, it is created. - void setExifTagValue(std::string key, std::string value); - // Delete the required EXIF tag. // Throw an exception if the tag was not set. void deleteExifTag(std::string key); @@ -238,6 +237,10 @@ public: // Return the image data buffer. std::string getDataBuffer() const; + // Accessors + Exiv2::ExifData* getExifData() { return &_exifData; }; + // ... + private: std::string _filename; Exiv2::byte* _data; diff --git a/src/exiv2wrapper_python.cpp b/src/exiv2wrapper_python.cpp index 5a03132..e970c69 100644 --- a/src/exiv2wrapper_python.cpp +++ b/src/exiv2wrapper_python.cpp @@ -48,6 +48,7 @@ BOOST_PYTHON_MODULE(libexiv2python) class_("_ExifTag", init()) .def("_setRawValue", &ExifTag::setRawValue) + .def("_setParentImage", &ExifTag::setParentImage) .def("_getKey", &ExifTag::getKey) .def("_getType", &ExifTag::getType) @@ -130,7 +131,6 @@ BOOST_PYTHON_MODULE(libexiv2python) .def("_exifKeys", &Image::exifKeys) .def("_getExifTag", &Image::getExifTag) - .def("_setExifTagValue", &Image::setExifTagValue) .def("_deleteExifTag", &Image::deleteExifTag) .def("_iptcKeys", &Image::iptcKeys) diff --git a/src/pyexiv2/exif.py b/src/pyexiv2/exif.py index 6964b4b..2c485e2 100644 --- a/src/pyexiv2/exif.py +++ b/src/pyexiv2/exif.py @@ -72,9 +72,6 @@ class ExifTag(ListenerInterface): - Short, SShort: [list of] int - Rational, SRational: [list of] :class:`pyexiv2.utils.Rational` - Undefined: string - - :attribute metadata: the parent metadata if any, or None - :type metadata: :class:`pyexiv2.metadata.ImageMetadata` """ # According to the EXIF specification, the only accepted format for an Ascii @@ -100,13 +97,15 @@ class ExifTag(ListenerInterface): self._tag = _tag else: self._tag = libexiv2python._ExifTag(key) - self.metadata = None self._raw_value = None self._value = None self._value_cookie = False if value is not None: self._set_value(value) + def _set_owner(self, metadata): + self._tag._setParentImage(metadata._image) + @staticmethod def _from_existing_tag(_tag): # Build a tag from an already existing libexiv2python._ExifTag. @@ -159,8 +158,6 @@ class ExifTag(ListenerInterface): def _set_raw_value(self, value): self._tag._setRawValue(value) - if self.metadata is not None: - self.metadata._set_exif_tag_value(self.key, value) self._raw_value = value self._value_cookie = True diff --git a/src/pyexiv2/metadata.py b/src/pyexiv2/metadata.py index 730e99f..93ab4bb 100644 --- a/src/pyexiv2/metadata.py +++ b/src/pyexiv2/metadata.py @@ -138,7 +138,6 @@ class ImageMetadata(object): except KeyError: _tag = self._image._getExifTag(key) tag = ExifTag._from_existing_tag(_tag) - tag.metadata = self self._tags['exif'][key] = tag return tag @@ -190,23 +189,10 @@ class ImageMetadata(object): else: # As a handy shortcut, accept direct value assignment. tag = ExifTag(key, tag_or_value) - self._image._setExifTagValue(tag.key, tag.raw_value) + tag._set_owner(self) self._tags['exif'][tag.key] = tag if tag.key not in self.exif_keys: self._keys['exif'].append(tag.key) - tag.metadata = self - - def _set_exif_tag_value(self, key, value): - # Overwrite the tag value for an already existing tag. - # The tag is already in cache. - # Warning: this is not meant to be called directly as it doesn't update - # the internal cache (which would leave the object in an inconsistent - # state). - if key not in self.exif_keys: - raise KeyError('Cannot set the value of an inexistent tag') - if type(value) is not str: - raise TypeError('Expecting a string') - self._image._setExifTagValue(key, value) def _set_iptc_tag(self, key, tag_or_values): # Set an IPTC tag. If the tag already exists, its values are diff --git a/test/exif.py b/test/exif.py index 1352bac..e1cf8b9 100644 --- a/test/exif.py +++ b/test/exif.py @@ -32,14 +32,6 @@ from pyexiv2.utils import Rational import datetime -class ImageMetadataMock(object): - - tags = {} - - def _set_exif_tag_value(self, key, value): - self.tags[key] = value - - class TestExifTag(unittest.TestCase): def test_convert_to_python_ascii(self): @@ -315,17 +307,9 @@ class TestExifTag(unittest.TestCase): # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3) - def test_set_value_no_metadata(self): - tag = ExifTag('Exif.Thumbnail.Orientation', 1) # top, left - old_value = tag.value - tag.value = 2 - self.failIfEqual(tag.value, old_value) - - def test_set_value_with_metadata(self): + def test_set_value(self): tag = ExifTag('Exif.Thumbnail.Orientation', 1) # top, left - tag.metadata = ImageMetadataMock() old_value = tag.value tag.value = 2 self.failIfEqual(tag.value, old_value) - self.assertEqual(tag.metadata.tags[tag.key], '2') diff --git a/test/metadata.py b/test/metadata.py index f7ce119..3756d7d 100644 --- a/test/metadata.py +++ b/test/metadata.py @@ -95,7 +95,6 @@ class TestImageMetadata(unittest.TestCase): key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assertEqual(type(tag), ExifTag) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['exif'][key], tag) # Try to get an nonexistent tag key = 'Exif.Photo.Sharpness' @@ -114,10 +113,8 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._tags['exif'], {}) # Create a new tag tag = ExifTag('Exif.Thumbnail.Orientation', 1) - self.assertEqual(tag.metadata, None) self.assert_(tag.key not in self.metadata.exif_keys) self.metadata._set_exif_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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()) @@ -129,9 +126,7 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag tag = ExifTag('Exif.Image.DateTime', datetime.datetime(2009, 3, 20, 20, 32, 0)) - self.assertEqual(tag.metadata, None) self.metadata._set_exif_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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(), @@ -145,9 +140,7 @@ class TestImageMetadata(unittest.TestCase): tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) new_tag = ExifTag(key, 'World Company') - self.assertEqual(new_tag.metadata, None) self.metadata._set_exif_tag(key, new_tag) - self.assertEqual(new_tag.metadata, self.metadata) 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(), @@ -164,34 +157,10 @@ class TestImageMetadata(unittest.TestCase): self.assert_(key in self.metadata._image._exifKeys()) tag = self.metadata._get_exif_tag(key) self.assertEqual(tag.value, value) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['exif'], {key: tag}) self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), tag.raw_value) - def test_set_exif_tag_value_inexistent(self): - self.metadata.read() - key = 'Exif.Photo.ExposureTime' - value = '1/500' - self.failUnlessRaises(KeyError, self.metadata._set_exif_tag_value, - key, value) - - def test_set_exif_tag_value_wrong_type(self): - self.metadata.read() - key = 'Exif.Image.DateTime' - value = datetime.datetime(2009, 3, 24, 9, 37, 36) - self.failUnlessRaises(TypeError, self.metadata._set_exif_tag_value, - key, value) - - def test_set_exif_tag_value(self): - self.metadata.read() - key = 'Exif.Image.DateTime' - tag = self.metadata._get_exif_tag(key) - value = '2009:03:24 09:37:36' - self.failIfEqual(self.metadata._image._getExifTag(key)._getRawValue(), value) - self.metadata._set_exif_tag_value(key, value) - self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), value) - def test_delete_exif_tag_inexistent(self): self.metadata.read() key = 'Exif.Image.Artist' -- cgit From e8573ba3172cb5c21bda75436156632a2c7c104d Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 19 May 2010 22:39:17 +0200 Subject: Attach the image's IptcData to a tag when it is assigned to an image. Remove redundant code that would set the values of a tag twice (in the tag itself, and in the image). Remove the now useless metadata attribute. --- src/exiv2wrapper.cpp | 16 +++++++++------- src/exiv2wrapper.hpp | 6 ++---- src/exiv2wrapper_python.cpp | 2 +- src/pyexiv2/iptc.py | 9 +++------ src/pyexiv2/metadata.py | 19 +------------------ test/iptc.py | 18 +----------------- test/metadata.py | 34 ---------------------------------- 7 files changed, 17 insertions(+), 87 deletions(-) diff --git a/src/exiv2wrapper.cpp b/src/exiv2wrapper.cpp index c579b87..4ecbf48 100644 --- a/src/exiv2wrapper.cpp +++ b/src/exiv2wrapper.cpp @@ -328,13 +328,6 @@ const IptcTag Image::getIptcTag(std::string key) return IptcTag(key, &_iptcData); } -void Image::setIptcTagValues(std::string key, boost::python::list values) -{ - CHECK_METADATA_READ - - set_iptc_tag_values(key, &_iptcData, values); -} - void Image::deleteIptcTag(std::string key) { CHECK_METADATA_READ @@ -686,6 +679,15 @@ void IptcTag::setRawValues(const boost::python::list& values) set_iptc_tag_values(_key.key(), _data, values); } +void IptcTag::setParentImage(Image& image) +{ + const boost::python::list values = getRawValues(); + delete _data; + _from_data = true; + _data = image.getIptcData(); + setRawValues(values); +} + const std::string IptcTag::getKey() { return _key.key(); diff --git a/src/exiv2wrapper.hpp b/src/exiv2wrapper.hpp index 6ba62f9..43c1431 100644 --- a/src/exiv2wrapper.hpp +++ b/src/exiv2wrapper.hpp @@ -81,6 +81,7 @@ public: ~IptcTag(); void setRawValues(const boost::python::list& values); + void setParentImage(Image& image); const std::string getKey(); const std::string getType(); @@ -206,10 +207,6 @@ public: // Throw an exception if the tag is not set. const IptcTag getIptcTag(std::string key); - // Set the IPTC tag's values. If the tag was not previously set, it is - // created. - void setIptcTagValues(std::string key, boost::python::list values); - // Delete (all the repetitions of) the required IPTC tag. // Throw an exception if the tag was not set. void deleteIptcTag(std::string key); @@ -239,6 +236,7 @@ public: // Accessors Exiv2::ExifData* getExifData() { return &_exifData; }; + Exiv2::IptcData* getIptcData() { return &_iptcData; }; // ... private: diff --git a/src/exiv2wrapper_python.cpp b/src/exiv2wrapper_python.cpp index e970c69..77a7df7 100644 --- a/src/exiv2wrapper_python.cpp +++ b/src/exiv2wrapper_python.cpp @@ -64,6 +64,7 @@ BOOST_PYTHON_MODULE(libexiv2python) class_("_IptcTag", init()) .def("_setRawValues", &IptcTag::setRawValues) + .def("_setParentImage", &IptcTag::setParentImage) .def("_getKey", &IptcTag::getKey) .def("_getType", &IptcTag::getType) @@ -135,7 +136,6 @@ BOOST_PYTHON_MODULE(libexiv2python) .def("_iptcKeys", &Image::iptcKeys) .def("_getIptcTag", &Image::getIptcTag) - .def("_setIptcTagValues", &Image::setIptcTagValues) .def("_deleteIptcTag", &Image::deleteIptcTag) .def("_xmpKeys", &Image::xmpKeys) diff --git a/src/pyexiv2/iptc.py b/src/pyexiv2/iptc.py index f92e883..ac1954d 100644 --- a/src/pyexiv2/iptc.py +++ b/src/pyexiv2/iptc.py @@ -72,9 +72,6 @@ class IptcTag(ListenerInterface): - Date: :class:`datetime.date` - Time: :class:`datetime.time` - Undefined: string - - :attribute metadata: the parent metadata if any, or None - :type metadata: :class:`pyexiv2.metadata.ImageMetadata` """ # strptime is not flexible enough to handle all valid Time formats, we use a @@ -96,13 +93,15 @@ class IptcTag(ListenerInterface): self._tag = _tag else: self._tag = libexiv2python._IptcTag(key) - self.metadata = None self._raw_values = None self._values = None self._values_cookie = False if values is not None: self._set_values(values) + def _set_owner(self, metadata): + self._tag._setParentImage(metadata._image) + @staticmethod def _from_existing_tag(_tag): # Build a tag from an already existing libexiv2python._IptcTag @@ -168,8 +167,6 @@ class IptcTag(ListenerInterface): if not isinstance(values, (list, tuple)): raise TypeError('Expecting a list of values') self._tag._setRawValues(values) - if self.metadata is not None: - self.metadata._set_iptc_tag_values(self.key, values) self._raw_values = values self._values_cookie = True diff --git a/src/pyexiv2/metadata.py b/src/pyexiv2/metadata.py index 93ab4bb..3d8892b 100644 --- a/src/pyexiv2/metadata.py +++ b/src/pyexiv2/metadata.py @@ -149,7 +149,6 @@ class ImageMetadata(object): except KeyError: _tag = self._image._getIptcTag(key) tag = IptcTag._from_existing_tag(_tag) - tag.metadata = self self._tags['iptc'][key] = tag return tag @@ -202,28 +201,12 @@ class ImageMetadata(object): else: # As a handy shortcut, accept direct value assignment. tag = IptcTag(key, tag_or_values) - self._image._setIptcTagValues(tag.key, tag.raw_values) + tag._set_owner(self) self._tags['iptc'][tag.key] = tag if tag.key not in self.iptc_keys: self._keys['iptc'].append(tag.key) tag.metadata = self - def _set_iptc_tag_values(self, key, values): - # Overwrite the tag values for an already existing tag. - # The tag is already in cache. - # Warning: this is not meant to be called directly as it doesn't update - # the internal cache (which would leave the object in an inconsistent - # state). - # FIXME: this is sub-optimal as it sets all the values regardless of how - # many of them really changed. Need to implement the same method with an - # index/range parameter (here and in the C++ wrapper). - if key not in self.iptc_keys: - raise KeyError('Cannot set the value of an inexistent tag') - if type(values) is not list or not \ - reduce(lambda x, y: x and type(y) is str, values, True): - raise TypeError('Expecting a list of strings') - self._image._setIptcTagValues(key, values) - def _set_xmp_tag(self, key, tag_or_value): # Set an XMP tag. If the tag already exists, its value is overwritten. if isinstance(tag_or_value, XmpTag): diff --git a/test/iptc.py b/test/iptc.py index 160c599..3b151a5 100644 --- a/test/iptc.py +++ b/test/iptc.py @@ -32,14 +32,6 @@ from pyexiv2.utils import FixedOffset import datetime -class ImageMetadataMock(object): - - tags = {} - - def _set_iptc_tag_values(self, key, values): - self.tags[key] = values - - class TestIptcTag(unittest.TestCase): def test_convert_to_python_short(self): @@ -192,17 +184,9 @@ class TestIptcTag(unittest.TestCase): tag = IptcTag('Iptc.Application2.City', ['Seattle']) self.failUnlessRaises(TypeError, tag._set_values, 'Barcelona') - def test_set_values_no_metadata(self): - tag = IptcTag('Iptc.Application2.City', ['Seattle']) - old_values = tag.values - tag.values = ['Barcelona'] - self.failIfEqual(tag.values, old_values) - - def test_set_values_with_metadata(self): + def test_set_values(self): tag = IptcTag('Iptc.Application2.City', ['Seattle']) - tag.metadata = ImageMetadataMock() old_values = tag.values tag.values = ['Barcelona'] self.failIfEqual(tag.values, old_values) - self.assertEqual(tag.metadata.tags[tag.key], ['Barcelona']) diff --git a/test/metadata.py b/test/metadata.py index 3756d7d..3ba162d 100644 --- a/test/metadata.py +++ b/test/metadata.py @@ -203,7 +203,6 @@ class TestImageMetadata(unittest.TestCase): key = 'Iptc.Application2.DateCreated' tag = self.metadata._get_iptc_tag(key) self.assertEqual(type(tag), IptcTag) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['iptc'][key], tag) # Try to get an nonexistent tag key = 'Iptc.Application2.Copyright' @@ -222,10 +221,8 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._tags['iptc'], {}) # Create a new tag tag = IptcTag('Iptc.Application2.Writer', ['Nobody']) - self.assertEqual(tag.metadata, None) self.assert_(tag.key not in self.metadata.iptc_keys) self.metadata._set_iptc_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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()) @@ -237,9 +234,7 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag tag = IptcTag('Iptc.Application2.Caption', ['A picture.']) - self.assertEqual(tag.metadata, None) self.metadata._set_iptc_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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(), @@ -253,9 +248,7 @@ class TestImageMetadata(unittest.TestCase): tag = self.metadata._get_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'][key], tag) new_tag = IptcTag(key, ['A picture.']) - self.assertEqual(new_tag.metadata, None) self.metadata._set_iptc_tag(key, new_tag) - self.assertEqual(new_tag.metadata, self.metadata) 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(), @@ -272,37 +265,10 @@ class TestImageMetadata(unittest.TestCase): self.assert_(key in self.metadata._image._iptcKeys()) tag = self.metadata._get_iptc_tag(key) self.assertEqual(tag.values, values) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['iptc'], {key: tag}) self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), tag.raw_values) - def test_set_iptc_tag_values_inexistent(self): - self.metadata.read() - key = 'Iptc.Application2.Urgency' - values = ['1'] - self.failUnlessRaises(KeyError, self.metadata._set_iptc_tag_values, - key, values) - - def test_set_iptc_tag_values_wrong_type(self): - self.metadata.read() - key = 'Iptc.Application2.DateCreated' - value = '20090324' - self.failUnlessRaises(TypeError, self.metadata._set_iptc_tag_values, - key, value) - values = [datetime.date(2009, 3, 24)] - self.failUnlessRaises(TypeError, self.metadata._set_iptc_tag_values, - key, values) - - def test_set_iptc_tag_values(self): - self.metadata.read() - key = 'Iptc.Application2.DateCreated' - tag = self.metadata._get_iptc_tag(key) - values = ['2009-04-07'] - self.failIfEqual(self.metadata._image._getIptcTag(key)._getRawValues(), values) - self.metadata._set_iptc_tag_values(key, values) - self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), values) - def test_delete_iptc_tag_inexistent(self): self.metadata.read() key = 'Iptc.Application2.LocationCode' -- cgit From 345251460bfd25e68eed67435eb035a46efa8006 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 20 May 2010 11:04:41 +0200 Subject: Moving around the code that writes IPTC tag values, there is no need for it to be in its own static function now. --- src/exiv2wrapper.cpp | 96 +++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/src/exiv2wrapper.cpp b/src/exiv2wrapper.cpp index 4ecbf48..83a5712 100644 --- a/src/exiv2wrapper.cpp +++ b/src/exiv2wrapper.cpp @@ -41,58 +41,6 @@ namespace exiv2wrapper { -// Static helper function to set the values of an IptcData for a given key -static void set_iptc_tag_values(const std::string& key, - Exiv2::IptcData* metadata, - const boost::python::list& values) -{ - Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key); - unsigned int index = 0; - unsigned int max = boost::python::len(values); - Exiv2::IptcMetadata::iterator iterator = metadata->findKey(iptcKey); - while (index < max) - { - std::string value = boost::python::extract(values[index++]); - if (iterator != metadata->end()) - { - // Override an existing value - iterator->setValue(value); - // Jump to the next datum matching the key - ++iterator; - while ((iterator != metadata->end()) && (iterator->key() != key)) - { - ++iterator; - } - } - else - { - // Append a new value - Exiv2::Iptcdatum datum(iptcKey); - datum.setValue(value); - int state = metadata->add(datum); - if (state == 6) - { - throw Exiv2::Error(NON_REPEATABLE); - } - // Reset iterator that has been invalidated by appending a datum - iterator = metadata->end(); - } - } - // Erase the remaining values if any - while (iterator != metadata->end()) - { - if (iterator->key() == key) - { - iterator = metadata->erase(iterator); - } - else - { - ++iterator; - } - } -} - - void Image::_instantiate_image() { // If an exception is thrown, it has to be done outside of the @@ -676,7 +624,49 @@ void IptcTag::setRawValues(const boost::python::list& values) throw Exiv2::Error(NON_REPEATABLE); } - set_iptc_tag_values(_key.key(), _data, values); + unsigned int index = 0; + unsigned int max = boost::python::len(values); + Exiv2::IptcMetadata::iterator iterator = _data->findKey(_key); + while (index < max) + { + std::string value = boost::python::extract(values[index++]); + if (iterator != _data->end()) + { + // Override an existing value + iterator->setValue(value); + // Jump to the next datum matching the key + ++iterator; + while ((iterator != _data->end()) && (iterator->key() != _key.key())) + { + ++iterator; + } + } + else + { + // Append a new value + Exiv2::Iptcdatum datum(_key); + datum.setValue(value); + int state = _data->add(datum); + if (state == 6) + { + throw Exiv2::Error(NON_REPEATABLE); + } + // Reset iterator that has been invalidated by appending a datum + iterator = _data->end(); + } + } + // Erase the remaining values if any + while (iterator != _data->end()) + { + if (iterator->key() == _key.key()) + { + iterator = _data->erase(iterator); + } + else + { + ++iterator; + } + } } void IptcTag::setParentImage(Image& image) -- cgit From bb33f2b87aea2038f18b8484d3895e2757f0d72f Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 20 May 2010 13:40:00 +0200 Subject: Attach the image's XmpData to a tag when it is assigned to an image. Remove redundant code that would set the value of a tag twice (in the tag itself, and in the image). Remove the now useless metadata attribute. --- src/exiv2wrapper.cpp | 77 +++++++++++++++++++++------------------------ src/exiv2wrapper.hpp | 7 ++--- src/exiv2wrapper_python.cpp | 4 +-- src/pyexiv2/metadata.py | 29 +---------------- src/pyexiv2/xmp.py | 9 ++---- test/metadata.py | 32 ------------------- test/xmp.py | 18 +---------- 7 files changed, 44 insertions(+), 132 deletions(-) diff --git a/src/exiv2wrapper.cpp b/src/exiv2wrapper.cpp index 83a5712..d8ee8e5 100644 --- a/src/exiv2wrapper.cpp +++ b/src/exiv2wrapper.cpp @@ -329,47 +329,6 @@ const XmpTag Image::getXmpTag(std::string key) return XmpTag(key, &_xmpData[key]); } -void Image::setXmpTagTextValue(const std::string& key, const std::string& value) -{ - CHECK_METADATA_READ - - _xmpData[key].setValue(value); -} - -void Image::setXmpTagArrayValue(const std::string& key, const boost::python::list& values) -{ - CHECK_METADATA_READ - - Exiv2::Xmpdatum& datum = _xmpData[key]; - // Reset the value - datum.setValue(0); - - for(boost::python::stl_input_iterator iterator(values); - iterator != boost::python::stl_input_iterator(); - ++iterator) - { - datum.setValue(*iterator); - } -} - -void Image::setXmpTagLangAltValue(const std::string& key, const boost::python::dict& values) -{ - CHECK_METADATA_READ - - Exiv2::Xmpdatum& datum = _xmpData[key]; - // Reset the value - datum.setValue(0); - - for(boost::python::stl_input_iterator iterator(values); - iterator != boost::python::stl_input_iterator(); - ++iterator) - { - std::string key = *iterator; - std::string value = boost::python::extract(values.get(key)); - datum.setValue("lang=\"" + key + "\" " + value); - } -} - void Image::deleteXmpTag(std::string key) { CHECK_METADATA_READ @@ -814,6 +773,42 @@ void XmpTag::setLangAltValue(const boost::python::dict& values) } } +void XmpTag::setParentImage(Image& image) +{ + switch (Exiv2::XmpProperties::propertyType(_key)) + { + case Exiv2::xmpText: + { + const std::string value = getTextValue(); + delete _datum; + _from_datum = true; + _datum = &(*image.getXmpData())[_key.key()]; + setTextValue(value); + break; + } + case Exiv2::xmpAlt: + case Exiv2::xmpBag: + case Exiv2::xmpSeq: + { + const boost::python::list value = getArrayValue(); + delete _datum; + _from_datum = true; + _datum = &(*image.getXmpData())[_key.key()]; + setArrayValue(value); + break; + } + case Exiv2::langAlt: + { + const boost::python::dict value = getLangAltValue(); + delete _datum; + _from_datum = true; + _datum = &(*image.getXmpData())[_key.key()]; + setLangAltValue(value); + break; + } + } +} + const std::string XmpTag::getKey() { return _key.key(); diff --git a/src/exiv2wrapper.hpp b/src/exiv2wrapper.hpp index 43c1431..d929f8b 100644 --- a/src/exiv2wrapper.hpp +++ b/src/exiv2wrapper.hpp @@ -120,6 +120,7 @@ public: void setTextValue(const std::string& value); void setArrayValue(const boost::python::list& values); void setLangAltValue(const boost::python::dict& values); + void setParentImage(Image& image); const std::string getKey(); const std::string getExiv2Type(); @@ -217,10 +218,6 @@ public: // Throw an exception if the tag is not set. const XmpTag getXmpTag(std::string key); - void setXmpTagTextValue(const std::string& key, const std::string& value); - void setXmpTagArrayValue(const std::string& key, const boost::python::list& values); - void setXmpTagLangAltValue(const std::string& key, const boost::python::dict& values); - // Delete the required XMP tag. // Throw an exception if the tag was not set. void deleteXmpTag(std::string key); @@ -237,7 +234,7 @@ public: // Accessors Exiv2::ExifData* getExifData() { return &_exifData; }; Exiv2::IptcData* getIptcData() { return &_iptcData; }; - // ... + Exiv2::XmpData* getXmpData() { return &_xmpData; }; private: std::string _filename; diff --git a/src/exiv2wrapper_python.cpp b/src/exiv2wrapper_python.cpp index 77a7df7..faa9b81 100644 --- a/src/exiv2wrapper_python.cpp +++ b/src/exiv2wrapper_python.cpp @@ -83,6 +83,7 @@ BOOST_PYTHON_MODULE(libexiv2python) .def("_setTextValue", &XmpTag::setTextValue) .def("_setArrayValue", &XmpTag::setArrayValue) .def("_setLangAltValue", &XmpTag::setLangAltValue) + .def("_setParentImage", &XmpTag::setParentImage) .def("_getKey", &XmpTag::getKey) .def("_getExiv2Type", &XmpTag::getExiv2Type) @@ -140,9 +141,6 @@ BOOST_PYTHON_MODULE(libexiv2python) .def("_xmpKeys", &Image::xmpKeys) .def("_getXmpTag", &Image::getXmpTag) - .def("_setXmpTagTextValue", &Image::setXmpTagTextValue) - .def("_setXmpTagArrayValue", &Image::setXmpTagArrayValue) - .def("_setXmpTagLangAltValue", &Image::setXmpTagLangAltValue) .def("_deleteXmpTag", &Image::deleteXmpTag) .def("_previews", &Image::previews) diff --git a/src/pyexiv2/metadata.py b/src/pyexiv2/metadata.py index 3d8892b..5cbfeb5 100644 --- a/src/pyexiv2/metadata.py +++ b/src/pyexiv2/metadata.py @@ -160,7 +160,6 @@ class ImageMetadata(object): except KeyError: _tag = self._image._getXmpTag(key) tag = XmpTag._from_existing_tag(_tag) - tag.metadata = self self._tags['xmp'][key] = tag return tag @@ -205,7 +204,6 @@ class ImageMetadata(object): self._tags['iptc'][tag.key] = tag if tag.key not in self.iptc_keys: self._keys['iptc'].append(tag.key) - tag.metadata = self def _set_xmp_tag(self, key, tag_or_value): # Set an XMP tag. If the tag already exists, its value is overwritten. @@ -214,35 +212,10 @@ class ImageMetadata(object): else: # As a handy shortcut, accept direct value assignment. tag = XmpTag(key, tag_or_value) - type = tag._tag._getExiv2Type() - if type == 'XmpText': - self._image._setXmpTagTextValue(tag.key, tag.raw_value) - elif type in ('XmpAlt', 'XmpBag', 'XmpSeq'): - self._image._setXmpTagArrayValue(tag.key, tag.raw_value) - elif type == 'LangAlt': - self._image._setXmpTagLangAltValue(tag.key, tag.raw_value) + tag._set_owner(self) self._tags['xmp'][tag.key] = tag if tag.key not in self.xmp_keys: self._keys['xmp'].append(tag.key) - tag.metadata = self - - def _set_xmp_tag_value(self, key, value): - # Overwrite the tag value for an already existing tag. - # The tag is already in cache. - # Warning: this is not meant to be called directly as it doesn't update - # the internal cache (which would leave the object in an inconsistent - # state). - if key not in self.xmp_keys: - raise KeyError('Cannot set the value of an inexistent tag') - type = self._tags['xmp'][key]._tag._getExiv2Type() - if type == 'XmpText' and isinstance(value, str): - self._image._setXmpTagTextValue(key, value) - elif type in ('XmpAlt', 'XmpBag', 'XmpSeq') and isinstance(value, (list, tuple)): - self._image._setXmpTagArrayValue(key, value) - elif type == 'LangAlt' and isinstance(value, dict): - self._image._setXmpTagLangAltValue(key, value) - else: - raise TypeError('Expecting either a string, a list, a tuple or a dict') def __setitem__(self, key, tag_or_value): """ diff --git a/src/pyexiv2/xmp.py b/src/pyexiv2/xmp.py index 63fe0a4..2142c41 100644 --- a/src/pyexiv2/xmp.py +++ b/src/pyexiv2/xmp.py @@ -80,9 +80,6 @@ class XmpTag(object): - Thumbnail: *[not implemented yet]* - URI, URL: string - XPath: *[not implemented yet]* - - :attribute metadata: the parent metadata if any, or None - :type metadata: :class:`pyexiv2.metadata.ImageMetadata` """ # FIXME: should inherit from ListenerInterface and implement observation of @@ -108,13 +105,15 @@ class XmpTag(object): self._tag = _tag else: self._tag = libexiv2python._XmpTag(key) - self.metadata = None self._raw_value = None self._value = None self._value_cookie = False if value is not None: self._set_value(value) + def _set_owner(self, metadata): + self._tag._setParentImage(metadata._image) + @staticmethod def _from_existing_tag(_tag): # Build a tag from an already existing libexiv2python._XmpTag @@ -174,8 +173,6 @@ class XmpTag(object): raise ValueError('Empty LangAlt') self._tag._setLangAltValue(value) - if self.metadata is not None: - self.metadata._set_xmp_tag_value(self.key, value) self._raw_value = value self._value_cookie = True diff --git a/test/metadata.py b/test/metadata.py index 3ba162d..86935ce 100644 --- a/test/metadata.py +++ b/test/metadata.py @@ -311,7 +311,6 @@ class TestImageMetadata(unittest.TestCase): key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assertEqual(type(tag), XmpTag) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['xmp'][key], tag) # Try to get an nonexistent tag key = 'Xmp.xmp.Label' @@ -331,10 +330,8 @@ class TestImageMetadata(unittest.TestCase): # 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.assertEqual(tag.metadata, None) self.assert_(tag.key not in self.metadata.xmp_keys) self.metadata._set_xmp_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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()) @@ -346,9 +343,7 @@ class TestImageMetadata(unittest.TestCase): self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag tag = XmpTag('Xmp.dc.format', ('image', 'png')) - self.assertEqual(tag.metadata, None) self.metadata._set_xmp_tag(tag.key, tag) - self.assertEqual(tag.metadata, self.metadata) 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(), @@ -362,9 +357,7 @@ class TestImageMetadata(unittest.TestCase): tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) new_tag = XmpTag(key, ['hello', 'world']) - self.assertEqual(new_tag.metadata, None) self.metadata._set_xmp_tag(key, new_tag) - self.assertEqual(new_tag.metadata, self.metadata) 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(), @@ -382,34 +375,9 @@ class TestImageMetadata(unittest.TestCase): self.assert_(key in self.metadata._image._xmpKeys()) tag = self.metadata._get_xmp_tag(key) self.assertEqual(tag.value, value) - self.assertEqual(tag.metadata, self.metadata) self.assertEqual(self.metadata._tags['xmp'], {key: tag}) self.assertEqual(self.metadata._image._getXmpTag(key)._getLangAltValue(), tag.raw_value) - def test_set_xmp_tag_value_inexistent(self): - self.metadata.read() - key = 'Xmp.xmp.Nickname' - value = 'oSoMoN' - self.failUnlessRaises(KeyError, self.metadata._set_xmp_tag_value, - key, value) - - def test_set_xmp_tag_value_wrong_type(self): - self.metadata.read() - key = 'Xmp.dc.subject' - tag = self.metadata[key] - value = datetime.datetime(2009, 4, 21, 20, 11, 0) - self.failUnlessRaises(TypeError, self.metadata._set_xmp_tag_value, - key, value) - - def test_set_xmp_tag_value(self): - self.metadata.read() - key = 'Xmp.dc.subject' - tag = self.metadata._get_xmp_tag(key) - value = ['Hello', 'World'] - self.failIfEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), value) - self.metadata._set_xmp_tag_value(key, value) - self.assertEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), value) - def test_delete_xmp_tag_inexistent(self): self.metadata.read() key = 'Xmp.xmp.CreatorTool' diff --git a/test/xmp.py b/test/xmp.py index a4a5832..10e03a8 100644 --- a/test/xmp.py +++ b/test/xmp.py @@ -32,14 +32,6 @@ from pyexiv2.utils import FixedOffset import datetime -class ImageMetadataMock(object): - - tags = {} - - def _set_xmp_tag_value(self, key, value): - self.tags[key] = value - - class TestXmpTag(unittest.TestCase): def test_convert_to_python_bag(self): @@ -292,19 +284,11 @@ class TestXmpTag(unittest.TestCase): # TODO: other types - def test_set_value_no_metadata(self): - tag = XmpTag('Xmp.xmp.ModifyDate', datetime.datetime(2005, 9, 7, 15, 9, 51, tzinfo=FixedOffset('-', 7, 0))) - old_value = tag.value - tag.value = datetime.datetime(2009, 4, 22, 8, 30, 27, tzinfo=FixedOffset()) - self.failIfEqual(tag.value, old_value) - - def test_set_value_with_metadata(self): + def test_set_value(self): tag = XmpTag('Xmp.xmp.ModifyDate', datetime.datetime(2005, 9, 7, 15, 9, 51, tzinfo=FixedOffset('-', 7, 0))) - tag.metadata = ImageMetadataMock() old_value = tag.value tag.value = datetime.datetime(2009, 4, 22, 8, 30, 27, tzinfo=FixedOffset()) self.failIfEqual(tag.value, old_value) - self.assertEqual(tag.metadata.tags[tag.key], '2009-04-22T08:30:27Z') def test_set_value_empty(self): tag = XmpTag('Xmp.dc.creator') -- cgit