diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/exif.py | 5 | ||||
-rw-r--r-- | test/iptc.py | 5 | ||||
-rw-r--r-- | test/metadata.py | 692 | ||||
-rw-r--r-- | test/notifying_list.py | 338 | ||||
-rw-r--r-- | test/rational.py | 80 | ||||
-rw-r--r-- | test/xmp.py | 370 |
6 files changed, 1488 insertions, 2 deletions
diff --git a/test/exif.py b/test/exif.py index d32e30d..44ae394 100644 --- a/test/exif.py +++ b/test/exif.py @@ -25,7 +25,10 @@ # ****************************************************************************** import unittest -from pyexiv2 import ExifTag, ExifValueError, Rational + +from pyexiv2.exif import ExifTag, ExifValueError +from pyexiv2.utils import Rational + import datetime diff --git a/test/iptc.py b/test/iptc.py index ba02599..5473090 100644 --- a/test/iptc.py +++ b/test/iptc.py @@ -25,7 +25,10 @@ # ****************************************************************************** import unittest -from pyexiv2 import IptcTag, IptcValueError, FixedOffset + +from pyexiv2.iptc import IptcTag, IptcValueError +from pyexiv2.utils import FixedOffset + import datetime diff --git a/test/metadata.py b/test/metadata.py new file mode 100644 index 0000000..af8646f --- /dev/null +++ b/test/metadata.py @@ -0,0 +1,692 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2009 Olivier Tilloy <olivier@tilloy.net> +# +# 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 <olivier@tilloy.net> +# +# ****************************************************************************** + +import unittest + +from pyexiv2.metadata import ImageMetadata +from pyexiv2.exif import ExifTag +from pyexiv2.iptc import IptcTag +from pyexiv2.xmp import XmpTag + +import datetime + + +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 setXmpTagValue(self, key, value): + self.tags['xmp'][key] = value + + def deleteXmpTag(self, key): + try: + del self.tags['xmp'][key] + except KeyError: + pass + + +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'] = \ + ('Exif.Image.Make', 'Make', 'Manufacturer', 'blabla', 'Ascii', + 'EASTMAN KODAK COMPANY', 'EASTMAN KODAK COMPANY') + tags['Exif.Image.DateTime'] = \ + ('Exif.Image.DateTime', 'DateTime', 'Date and Time', 'blabla', + 'Ascii', '2009:02:09 13:33:20', '2009:02:09 13:33:20') + tags['Exif.Photo.ExifVersion'] = \ + ('Exif.Photo.ExifVersion', 'ExifVersion', 'Exif Version', 'blabla', + 'Undefined', '48 50 50 49 ', '2.21') + self.metadata._image.tags['exif'] = tags + + def _set_iptc_tags(self): + tags = {} + tags['Iptc.Application2.Caption'] = \ + ('Iptc.Application2.Caption', 'Caption', 'Caption', + 'A textual description of the object data.', 'String', + ['blabla']) + tags['Iptc.Application2.DateCreated'] = \ + ('Iptc.Application2.DateCreated', 'DateCreated', 'Date Created', + 'Represented in the form CCYYMMDD to designate the date the ' \ + 'intellectual content of the object data was created rather ' \ + 'than the date of the creation of the physical representation. ' \ + 'Follows ISO 8601 standard.', 'Date', ['2004-07-13']) + self.metadata._image.tags['iptc'] = tags + + def _set_xmp_tags(self): + tags = {} + tags['Xmp.dc.format'] = \ + ('Xmp.dc.format', 'format', 'Format', 'The file format used when ' \ + 'saving the resource. Tools and applications should set this ' \ + 'property to the save format of the data. It may include ' \ + 'appropriate qualifiers.', 'MIMEType', 'image/jpeg') + tags['Xmp.dc.subject'] = \ + ('Xmp.dc.subject', 'subject', 'Subject', 'An unordered array of ' \ + 'descriptive phrases or keywords that specify the topic of the ' \ + 'content of the resource.', 'bag Text', 'image, test, pyexiv2') + tags['Xmp.xmp.CreateDate'] = \ + ('Xmp.xmp.CreateDate', 'CreateDate', 'Create Date', + 'The date and time the resource was originally created.', 'Date', + '2005-09-07T15:07:40-07:00') + tags['Xmp.xmpMM.DocumentID'] = \ + ('Xmp.xmpMM.DocumentID', 'DocumentID', 'Document ID', + 'The common identifier for all versions and renditions of a ' \ + 'document. It should be based on a UUID; see Document and ' \ + 'Instance IDs below.', 'URI', + 'uuid:9A3B7F52214211DAB6308A7391270C13') + self.metadata._image.tags['xmp'] = tags + + ###################### + # Test general methods + ###################### + + def test_read(self): + self.assertEqual(self.metadata._image, None) + self.metadata.read() + self.failIfEqual(self.metadata._image, None) + self.failUnless(self.metadata._image.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(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' + 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' + self.failUnlessRaises(KeyError, self.metadata._get_exif_tag, key) + + 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' + 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._set_exif_tags() + self.assertEqual(self.metadata._tags['exif'], {}) + # Create a new tag + tag = ExifTag('Exif.Thumbnail.Orientation', 'Orientation', + 'Orientation', 'The image orientation viewed in terms ' \ + 'of rows and columns.', 'Short', '1', 'top, left') + self.assertEqual(tag.metadata, None) + self.metadata._set_exif_tag(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], + 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', 'Date and Time', + 'blabla', 'Ascii', '2009:03:20 20:32:00', + '2009:03:20 20:32:00') + self.assertEqual(tag.metadata, None) + self.metadata._set_exif_tag(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], + 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' + tag = self.metadata._get_exif_tag(key) + self.assertEqual(self.metadata._tags['exif'][key], tag) + new_tag = ExifTag(key, 'ExifVersion', 'Exif Version', 'blabla', + 'Undefined', '48 50 50 48 ', '2.20') + self.assertEqual(new_tag.metadata, None) + self.metadata._set_exif_tag(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.fvalue) + + 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, + key, value) + + 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, + key, value) + + 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.metadata._set_exif_tag_value(key, value) + self.assertEqual(self.metadata._image.tags['exif'][key], 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.metadata._delete_exif_tag(key) + self.assertEqual(self.metadata._tags['exif'], {}) + self.failIf(self.metadata._image.tags['exif'].has_key(key)) + + 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)) + 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)) + + ########################### + # Test IPTC-related methods + ########################### + + 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) + self.assertEqual(self.metadata._keys['iptc'], keys) + + 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' + 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' + self.failUnlessRaises(KeyError, self.metadata._get_iptc_tag, key) + + 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' + 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._set_iptc_tags() + self.assertEqual(self.metadata._tags['iptc'], {}) + # Create a new tag + tag = IptcTag('Iptc.Application2.Writer', 'Writer', 'Writer', + 'Identification of the name of the person involved in ' \ + 'the writing, editing or correcting the object data or ' \ + 'caption/abstract.', 'String', ['Nobody']) + self.assertEqual(tag.metadata, None) + self.metadata._set_iptc_tag(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], + tag.raw_value) + + 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', 'Caption', 'Caption', + 'A textual description of the object data.', 'String', + ['A picture.']) + self.assertEqual(tag.metadata, None) + self.metadata._set_iptc_tag(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], + tag.raw_value) + + 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' + tag = self.metadata._get_iptc_tag(key) + self.assertEqual(self.metadata._tags['iptc'][key], tag) + new_tag = IptcTag(key, 'Caption', 'Caption', + 'A textual description of the object data.', 'String', + ['A picture.']) + self.assertEqual(new_tag.metadata, None) + self.metadata._set_iptc_tag(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], + new_tag.raw_value) + + 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, + key, values) + + 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, + 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() + 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.metadata._set_iptc_tag_values(key, values) + self.assertEqual(self.metadata._image.tags['iptc'][key], 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.metadata._delete_iptc_tag(key) + self.assertEqual(self.metadata._tags['iptc'], {}) + self.failIf(self.metadata._image.tags['iptc'].has_key(key)) + + 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)) + 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)) + + ########################## + # Test XMP-related methods + ########################## + + 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(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' + 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' + self.failUnlessRaises(KeyError, self.metadata._get_xmp_tag, key) + + 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' + 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._set_xmp_tags() + self.assertEqual(self.metadata._tags['xmp'], {}) + # Create a new tag + tag = XmpTag('Xmp.dc.title', 'title', 'Title', 'The title of the ' \ + 'document, or the name given to the resource. Typically,' \ + ' it will be a name by which the resource is formally ' \ + 'known.', 'Lang Alt', + 'lang="x-default" This is not a title, ' \ + 'lang="fr-FR" Ceci n\'est pas un titre') + self.assertEqual(tag.metadata, None) + self.metadata._set_xmp_tag(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], + 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', 'format', 'Format', 'The file format ' \ + 'used when saving the resource. Tools and applications ' \ + 'should set this property to the save format of the ' \ + 'data. It may include appropriate qualifiers.', + 'MIMEType', 'image/png') + self.assertEqual(tag.metadata, None) + self.metadata._set_xmp_tag(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], + 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' + tag = self.metadata._get_xmp_tag(key) + self.assertEqual(self.metadata._tags['xmp'][key], tag) + new_tag = XmpTag(key, 'CreateDate', 'Create Date', + 'The date and time the resource was originally ' \ + 'created.', 'Date', '2009-04-21T20:07+01:00') + self.assertEqual(new_tag.metadata, None) + self.metadata._set_xmp_tag(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], + new_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, + key, value) + + def test_set_xmp_tag_value_wrong_type(self): + self.metadata.read() + self._set_xmp_tags() + key = 'Xmp.xmp.CreateDate' + 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() + self._set_xmp_tags() + key = 'Xmp.xmp.CreateDate' + tag = self.metadata._get_xmp_tag(key) + value = '2009-04-21T20:12:47+01:00' + self.failIfEqual(self.metadata._image.tags['xmp'][key], value) + self.metadata._set_xmp_tag_value(key, value) + self.assertEqual(self.metadata._image.tags['xmp'][key], 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.metadata._delete_xmp_tag(key) + self.assertEqual(self.metadata._tags['xmp'], {}) + self.failIf(self.metadata._image.tags['xmp'].has_key(key)) + + 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)) + 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)) + + ########################### + # Test dictionary interface + ########################### + + 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' + 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' + tag = self.metadata[key] + self.assertEqual(type(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() + self._set_exif_tags() + self._set_iptc_tags() + self._set_xmp_tags() + # Set new tags + key = 'Exif.Photo.ExposureBiasValue' + tag = ExifTag(key, 'ExposureBiasValue', 'Exposure Bias', + 'The exposure bias. The units is the APEX value. ' \ + 'Ordinarily it is given in the range of -99.99 to 99.99.', + 'SRational', '0/3', '0') + 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, 'City', 'City', 'Identifies city of object data ' \ + 'origin according to guidelines established by the ' \ + 'provider.', 'String', ['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, 'description', 'Description', 'A textual ' \ + 'description of the content of the resource. Multiple ' \ + 'values may be present for different languages.', + 'Lang Alt', 'lang="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, 'ExifVersion', 'Exif Version', 'The version of ' \ + 'this standard supported. Nonexistence of this field is' \ + ' taken to mean nonconformance to the standard.', + 'Undefined', '48 50 50 48 ', '2.20') + 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, 'Caption', 'Caption', 'A textual description of ' \ + 'the object data.', 'String', ['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, 'subject', 'Subject', 'An unordered array of ' \ + 'descriptive phrases or keywords that specify the topic ' \ + 'of the content of the resource.', 'bag Text', + '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() + self._set_exif_tags() + self._set_iptc_tags() + self._set_xmp_tags() + # Delete existing tags + key = 'Exif.Photo.ExifVersion' + 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' + del self.metadata[key] + 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) diff --git a/test/notifying_list.py b/test/notifying_list.py new file mode 100644 index 0000000..e80ec9c --- /dev/null +++ b/test/notifying_list.py @@ -0,0 +1,338 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2009 Olivier Tilloy <olivier@tilloy.net> +# +# 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 <olivier@tilloy.net> +# +# ****************************************************************************** + +import unittest +from pyexiv2.utils import ListenerInterface, NotifyingList +import random + + +class SimpleListener(ListenerInterface): + + def __init__(self): + self.changes = 0 + + def contents_changed(self): + self.changes += 1 + + +class TestNotifyingList(unittest.TestCase): + + def setUp(self): + self.values = NotifyingList([5, 7, 9, 14, 57, 3, 2]) + + def test_no_listener(self): + # No listener is registered, nothing should happen. + self.values[3] = 13 + del self.values[5] + self.values.append(17) + self.values.extend([11, 22]) + self.values.insert(4, 24) + self.values.pop() + self.values.remove(9) + self.values.reverse() + self.values.sort() + self.values += [8, 4] + self.values *= 3 + self.values[3:4] = [8, 4] + del self.values[3:5] + + def test_listener_interface(self): + self.values.register_listener(ListenerInterface()) + self.failUnlessRaises(NotImplementedError, + self.values.__setitem__, 3, 13) + self.failUnlessRaises(NotImplementedError, self.values.__delitem__, 5) + self.failUnlessRaises(NotImplementedError, self.values.append, 17) + self.failUnlessRaises(NotImplementedError, self.values.extend, [11, 22]) + self.failUnlessRaises(NotImplementedError, self.values.insert, 4, 24) + self.failUnlessRaises(NotImplementedError, self.values.pop) + self.failUnlessRaises(NotImplementedError, self.values.remove, 9) + self.failUnlessRaises(NotImplementedError, self.values.reverse) + self.failUnlessRaises(NotImplementedError, self.values.sort) + self.failUnlessRaises(NotImplementedError, self.values.__iadd__, [8, 4]) + self.failUnlessRaises(NotImplementedError, self.values.__imul__, 3) + self.failUnlessRaises(NotImplementedError, self.values.__setslice__, + 3, 4, [8, 4]) + self.failUnlessRaises(NotImplementedError, self.values.__delslice__, + 3, 5) + + def _register_listeners(self): + # Register a random number of listeners + listeners = [SimpleListener() for i in xrange(random.randint(3, 20))] + for listener in listeners: + self.values.register_listener(listener) + return listeners + + def test_setitem(self): + listeners = self._register_listeners() + + self.values[3] = 13 + self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.failUnlessRaises(IndexError, self.values.__setitem__, 9, 27) + self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_delitem(self): + listeners = self._register_listeners() + + del self.values[5] + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.failUnlessRaises(IndexError, self.values.__delitem__, 9) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_append(self): + listeners = self._register_listeners() + + self.values.append(17) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 17]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_extend(self): + listeners = self._register_listeners() + + self.values.extend([11, 22]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.failUnlessRaises(TypeError, self.values.extend, 26) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_insert(self): + listeners = self._register_listeners() + + self.values.insert(4, 24) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 24, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_pop(self): + listeners = self._register_listeners() + + self.values.pop() + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.values.pop(4) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 3]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 2) + + self.values.pop(-2) + self.failUnlessEqual(self.values, [5, 7, 9, 3]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 3) + + self.failUnlessRaises(IndexError, self.values.pop, 33) + self.failUnlessEqual(self.values, [5, 7, 9, 3]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 3) + + def test_remove(self): + listeners = self._register_listeners() + + self.values.remove(9) + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.failUnlessRaises(ValueError, self.values.remove, 33) + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_reverse(self): + listeners = self._register_listeners() + + self.values.reverse() + self.failUnlessEqual(self.values, [2, 3, 57, 14, 9, 7, 5]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_sort(self): + listeners = self._register_listeners() + + self.values.sort() + self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.values.sort(cmp=lambda x, y: y - x) + self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 2) + + self.values.sort(key=lambda x: x * x) + self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 3) + + self.values.sort(reverse=True) + self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 4) + + def test_iadd(self): + listeners = self._register_listeners() + + self.values += [44, 31, 19] + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 44, 31, 19]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_imul(self): + listeners = self._register_listeners() + + self.values *= 3 + self.failUnlessEqual(self.values, + [5, 7, 9, 14, 57, 3, 2, + 5, 7, 9, 14, 57, 3, 2, + 5, 7, 9, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + def test_setslice(self): + listeners = self._register_listeners() + + # Basic slicing (of the form [i:j]): implemented as __setslice__. + + self.values[2:4] = [3, 4] + self.failUnlessEqual(self.values, [5, 7, 3, 4, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + self.values[3:5] = [77, 8, 12] + self.failUnlessEqual(self.values, [5, 7, 3, 77, 8, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 2) + + self.values[2:5] = [1, 0] + self.failUnlessEqual(self.values, [5, 7, 1, 0, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 3) + + self.values[0:2] = [] + self.failUnlessEqual(self.values, [1, 0, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 4) + + self.values[2:2] = [7, 5] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 5) + + # With negatives indexes + + self.values[4:-2] = [9] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 6) + + self.values[-2:1] = [6, 4] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 6, 4, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 7) + + self.values[-5:-2] = [8] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 8, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 8) + + # With missing (implicit) indexes + + self.values[:2] = [4] + self.failUnlessEqual(self.values, [4, 7, 5, 8, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 9) + + self.values[4:] = [1] + self.failUnlessEqual(self.values, [4, 7, 5, 8, 1]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 10) + + self.values[:] = [5, 7, 9, 14, 57, 3, 2] + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 11) + + def test_delslice(self): + listeners = self._register_listeners() + + del self.values[2:3] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + del self.values[2:2] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 1) + + # With negatives indexes + + del self.values[4:-1] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 2]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 2) + + del self.values[-1:5] + self.failUnlessEqual(self.values, [5, 7, 14, 57]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 3) + + del self.values[-2:-1] + self.failUnlessEqual(self.values, [5, 7, 57]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 4) + + # With missing (implicit) indexes + + del self.values[:1] + self.failUnlessEqual(self.values, [7, 57]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 5) + + del self.values[1:] + self.failUnlessEqual(self.values, [7]) + for listener in listeners: + self.failUnlessEqual(listener.changes, 6) + + del self.values[:] + self.failUnlessEqual(self.values, []) + for listener in listeners: + self.failUnlessEqual(listener.changes, 7) diff --git a/test/rational.py b/test/rational.py new file mode 100644 index 0000000..070bf6c --- /dev/null +++ b/test/rational.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2008-2009 Olivier Tilloy <olivier@tilloy.net> +# +# 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 <olivier@tilloy.net> +# +# ****************************************************************************** + +import unittest + +from pyexiv2.utils import Rational + + +class TestRational(unittest.TestCase): + + def test_constructor(self): + r = Rational(2, 1) + self.assertEqual(r.numerator, 2) + self.assertEqual(r.denominator, 1) + self.assertRaises(ZeroDivisionError, Rational, 1, 0) + + def test_read_only(self): + r = Rational(3, 4) + try: + r.numerator = 5 + except AttributeError: + pass + else: + self.fail('Numerator is not read-only.') + try: + r.denominator = 5 + except AttributeError: + pass + else: + self.fail('Denominator is not read-only.') + + def test_from_string(self): + self.assertEqual(Rational.from_string('4/3'), Rational(4, 3)) + self.assertEqual(Rational.from_string('-4/3'), Rational(-4, 3)) + self.assertRaises(ValueError, Rational.from_string, '+3/5') + self.assertRaises(ValueError, Rational.from_string, '3 / 5') + self.assertRaises(ValueError, Rational.from_string, '3/-5') + self.assertRaises(ValueError, Rational.from_string, 'invalid') + + def test_to_string(self): + self.assertEqual(str(Rational(3, 5)), '3/5') + self.assertEqual(str(Rational(-3, 5)), '-3/5') + + def test_to_float(self): + self.assertEqual(Rational(3, 6).to_float(), 0.5) + self.assertEqual(Rational(11, 11).to_float(), 1.0) + self.assertEqual(Rational(-2, 8).to_float(), -0.25) + self.assertEqual(Rational(0, 3).to_float(), 0.0) + + def test_equality(self): + r1 = Rational(2, 1) + r2 = Rational(2, 1) + r3 = Rational(8, 4) + r4 = Rational(3, 2) + self.assertEqual(r1, r2) + self.assertEqual(r1, r3) + self.assertNotEqual(r1, r4) diff --git a/test/xmp.py b/test/xmp.py new file mode 100644 index 0000000..a61e375 --- /dev/null +++ b/test/xmp.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2009 Olivier Tilloy <olivier@tilloy.net> +# +# 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 <olivier@tilloy.net> +# +# ****************************************************************************** + +import unittest + +from pyexiv2.xmp import XmpTag, XmpValueError +from pyexiv2.utils import FixedOffset + +import datetime + + +class ImageMetadataMock(object): + + tags = {} + + def _set_xmp_tag_value(self, key, value): + self.tags[key] = value + + def _delete_xmp_tag(self, key): + try: + del self.tags[key] + except KeyError: + pass + + +class TestXmpTag(unittest.TestCase): + + def test_convert_to_python_bag(self): + xtype = 'bag Text' + # Valid values + self.assertEqual(XmpTag._convert_to_python('', xtype), []) + self.assertEqual(XmpTag._convert_to_python('One value only', xtype), [u'One value only']) + self.assertEqual(XmpTag._convert_to_python('Some, text, keyword, this is a test', xtype), + [u'Some', u'text', u'keyword', u'this is a test']) + + def test_convert_to_string_bag(self): + xtype = 'bag Text' + # Valid values + self.assertEqual(XmpTag._convert_to_string([], xtype), '') + self.assertEqual(XmpTag._convert_to_string(['One value only'], xtype), 'One value only') + self.assertEqual(XmpTag._convert_to_string([u'One value only'], xtype), 'One value only') + self.assertEqual(XmpTag._convert_to_string([u'Some', u'text', u'keyword', u'this is a test'], xtype), + 'Some, text, keyword, this is a test') + self.assertEqual(XmpTag._convert_to_string(['Some ', ' text '], xtype), + 'Some , text ') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, [1, 2, 3], xtype) + + def test_convert_to_python_boolean(self): + xtype = 'Boolean' + # Valid values + self.assertEqual(XmpTag._convert_to_python('True', xtype), True) + self.assertEqual(XmpTag._convert_to_python('False', xtype), False) + # Invalid values: not converted + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, None, xtype) + + def test_convert_to_string_boolean(self): + xtype = 'Boolean' + # Valid values + self.assertEqual(XmpTag._convert_to_string(True, xtype), 'True') + self.assertEqual(XmpTag._convert_to_string(False, xtype), 'False') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + def test_convert_to_python_date(self): + xtype = 'Date' + # Valid values + self.assertEqual(XmpTag._convert_to_python('1999', xtype), + datetime.date(1999, 1, 1)) + self.assertEqual(XmpTag._convert_to_python('1999-10', xtype), + datetime.date(1999, 10, 1)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13', xtype), + datetime.date(1999, 10, 13)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03Z', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset()), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03+06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('+', 6, 0)), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03-06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('-', 6, 0)), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54Z', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset()), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54+06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset('+', 6, 0)), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54-06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset('-', 6, 0)), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54.721Z', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset()), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54.721+06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset('+', 6, 0)), + datetime.timedelta(0)) + self.assertEqual(XmpTag._convert_to_python('1999-10-13T05:03:54.721-06:00', xtype) - \ + datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset('-', 6, 0)), + datetime.timedelta(0)) + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '11/10/1983', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '-1000', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '2009-13', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '2009-10-32', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '2009-10-30T25:12Z', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '2009-10-30T23:67Z', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '2009-01-22T21', xtype) + + def test_convert_to_string_date(self): + xtype = 'Date' + # Valid values + self.assertEqual(XmpTag._convert_to_string(datetime.date(2009, 2, 4), xtype), + '2009-02-04') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13), xtype), + '1999-10-13') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset()), xtype), + '1999-10-13T05:03Z') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('+', 5, 30)), xtype), + '1999-10-13T05:03+05:30') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('-', 11, 30)), xtype), + '1999-10-13T05:03-11:30') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset()), xtype), + '1999-10-13T05:03:27Z') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset('+', 5, 30)), xtype), + '1999-10-13T05:03:27+05:30') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset('-', 11, 30)), xtype), + '1999-10-13T05:03:27-11:30') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset()), xtype), + '1999-10-13T05:03:27.1243Z') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset('+', 5, 30)), xtype), + '1999-10-13T05:03:27.1243+05:30') + self.assertEqual(XmpTag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset('-', 11, 30)), xtype), + '1999-10-13T05:03:27.1243-11:30') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + def test_convert_to_python_integer(self): + xtype = 'Integer' + # Valid values + self.assertEqual(XmpTag._convert_to_python('23', xtype), 23) + self.assertEqual(XmpTag._convert_to_python('+5628', xtype), 5628) + self.assertEqual(XmpTag._convert_to_python('-4', xtype), -4) + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'abc', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '5,64', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '47.0001', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, '1E3', xtype) + + def test_convert_to_string_integer(self): + xtype = 'Integer' + # Valid values + self.assertEqual(XmpTag._convert_to_string(123, xtype), '123') + self.assertEqual(XmpTag._convert_to_string(-57, xtype), '-57') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 3.14, xtype) + + def test_convert_to_python_langalt(self): + xtype = 'Lang Alt' + # Valid values + self.assertEqual(XmpTag._convert_to_python('lang="x-default" some text', xtype), + {'x-default': u'some text'}) + self.assertEqual(XmpTag._convert_to_python('lang="x-default" some text, lang="fr-FR" du texte', xtype), + {'x-default': u'some text', 'fr-FR': u'du texte'}) + self.assertEqual(XmpTag._convert_to_python('lang="x-default" some text , lang="fr-FR" du texte ', xtype), + {'x-default': u'some text ', 'fr-FR': u' du texte '}) + self.assertEqual(XmpTag._convert_to_python('lang="x-default" some text, lang="fr-FR" du texte, lang="es-ES" un texto', xtype), + {'x-default': u'some text', 'fr-FR': u'du texte', 'es-ES': u'un texto'}) + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'lang="malformed', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'xlang="x-default" some text', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'lang="x-default" some text, xlang="fr-FR" du texte', xtype) + + def test_convert_to_string_langalt(self): + xtype = 'Lang Alt' + # Valid values + self.assertEqual(XmpTag._convert_to_string({'x-default': 'some text'}, xtype), + 'lang="x-default" some text') + self.assertEqual(XmpTag._convert_to_string({'x-default': u'some text'}, xtype), + 'lang="x-default" some text') + self.assertEqual(XmpTag._convert_to_string({'x-default': 'some text', 'fr-FR': 'du texte'}, xtype), + 'lang="x-default" some text, lang="fr-FR" du texte') + self.assertEqual(XmpTag._convert_to_string({'x-default': u'some text', 'fr-FR': 'du texte'}, xtype), + 'lang="x-default" some text, lang="fr-FR" du texte') + self.assertEqual(XmpTag._convert_to_string({'x-default': u'some text', 'fr-FR': 'du texte', 'es-ES': 'un texto'}, xtype), + 'lang="x-default" some text, lang="es-ES" un texto, lang="fr-FR" du texte') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, {}, xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, {'x-default': 25}, xtype) + + def test_convert_to_python_mimetype(self): + xtype = 'MIMEType' + # Valid values + self.assertEqual(XmpTag._convert_to_python('image/jpeg', xtype), + {'type': 'image', 'subtype': 'jpeg'}) + self.assertEqual(XmpTag._convert_to_python('video/ogg', xtype), + {'type': 'video', 'subtype': 'ogg'}) + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, 'image-jpeg', xtype) + + def test_convert_to_string_mimetype(self): + xtype = 'MIMEType' + # Valid values + self.assertEqual(XmpTag._convert_to_string({'type': 'image', 'subtype': 'jpeg'}, xtype), 'image/jpeg') + self.assertEqual(XmpTag._convert_to_string({'type': 'video', 'subtype': 'ogg'}, xtype), 'video/ogg') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, 'invalid', xtype) + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, {'type': 'image'}, xtype) + + def test_convert_to_python_propername(self): + xtype = 'ProperName' + # Valid values + self.assertEqual(XmpTag._convert_to_python('Gérard', xtype), u'Gérard') + self.assertEqual(XmpTag._convert_to_python('Python Software Foundation', xtype), u'Python Software Foundation') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, None, xtype) + + def test_convert_to_string_propername(self): + xtype = 'ProperName' + # Valid values + self.assertEqual(XmpTag._convert_to_string('Gérard', xtype), 'Gérard') + self.assertEqual(XmpTag._convert_to_string(u'Gérard', xtype), 'Gérard') + self.assertEqual(XmpTag._convert_to_string(u'Python Software Foundation', xtype), 'Python Software Foundation') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + def test_convert_to_python_text(self): + xtype = 'Text' + # Valid values + self.assertEqual(XmpTag._convert_to_python('Some text.', xtype), u'Some text.') + self.assertEqual(XmpTag._convert_to_python('Some text with exotic chàräctérʐ.', xtype), + u'Some text with exotic chàräctérʐ.') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_python, None, xtype) + + def test_convert_to_string_text(self): + xtype = 'Text' + # Valid values + self.assertEqual(XmpTag._convert_to_string(u'Some text', xtype), 'Some text') + self.assertEqual(XmpTag._convert_to_string(u'Some text with exotic chàräctérʐ.', xtype), + 'Some text with exotic chàräctérʐ.') + self.assertEqual(XmpTag._convert_to_string('Some text with exotic chàräctérʐ.', xtype), + 'Some text with exotic chàräctérʐ.') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + def test_convert_to_python_uri(self): + xtype = 'URI' + # Valid values + self.assertEqual(XmpTag._convert_to_python('http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_python('https://example.com', xtype), 'https://example.com') + self.assertEqual(XmpTag._convert_to_python('http://localhost:8000/resource', xtype), + 'http://localhost:8000/resource') + self.assertEqual(XmpTag._convert_to_python('uuid:9A3B7F52214211DAB6308A7391270C13', xtype), + 'uuid:9A3B7F52214211DAB6308A7391270C13') + + def test_convert_to_string_uri(self): + xtype = 'URI' + # Valid values + self.assertEqual(XmpTag._convert_to_string('http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_string(u'http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_string('https://example.com', xtype), 'https://example.com') + self.assertEqual(XmpTag._convert_to_string('http://localhost:8000/resource', xtype), + 'http://localhost:8000/resource') + self.assertEqual(XmpTag._convert_to_string('uuid:9A3B7F52214211DAB6308A7391270C13', xtype), + 'uuid:9A3B7F52214211DAB6308A7391270C13') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + def test_convert_to_python_url(self): + xtype = 'URL' + # Valid values + self.assertEqual(XmpTag._convert_to_python('http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_python('https://example.com', xtype), 'https://example.com') + self.assertEqual(XmpTag._convert_to_python('http://localhost:8000/resource', xtype), + 'http://localhost:8000/resource') + + def test_convert_to_string_url(self): + xtype = 'URL' + # Valid values + self.assertEqual(XmpTag._convert_to_string('http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_string(u'http://example.com', xtype), 'http://example.com') + self.assertEqual(XmpTag._convert_to_string('https://example.com', xtype), 'https://example.com') + self.assertEqual(XmpTag._convert_to_string('http://localhost:8000/resource', xtype), + 'http://localhost:8000/resource') + # Invalid values + self.failUnlessRaises(XmpValueError, XmpTag._convert_to_string, None, xtype) + + # TODO: other types + + + def test_set_value_no_metadata(self): + tag = XmpTag('Xmp.xmp.ModifyDate', 'ModifyDate', 'Modify Date', + 'The date and time the resource was last modified. Note:' \ + ' The value of this property is not necessarily the same' \ + "as the file's system modification date because it is " \ + 'set before the file is saved.', 'Date', + '2005-09-07T15:09:51-07:00') + 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): + tag = XmpTag('Xmp.xmp.ModifyDate', 'ModifyDate', 'Modify Date', + 'The date and time the resource was last modified. Note:' \ + ' The value of this property is not necessarily the same' \ + "as the file's system modification date because it is " \ + 'set before the file is saved.', 'Date', + '2005-09-07T15:09:51-07:00') + 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_del_value_no_metadata(self): + tag = XmpTag('Xmp.xmp.ModifyDate', 'ModifyDate', 'Modify Date', + 'The date and time the resource was last modified. Note:' \ + ' The value of this property is not necessarily the same' \ + "as the file's system modification date because it is " \ + 'set before the file is saved.', 'Date', + '2005-09-07T15:09:51-07:00') + del tag.value + self.failIf(hasattr(tag, 'value')) + + def test_del_value_with_metadata(self): + tag = XmpTag('Xmp.xmp.ModifyDate', 'ModifyDate', 'Modify Date', + 'The date and time the resource was last modified. Note:' \ + ' The value of this property is not necessarily the same' \ + "as the file's system modification date because it is " \ + 'set before the file is saved.', 'Date', + '2005-09-07T15:09:51-07:00') + tag.metadata = ImageMetadataMock() + tag.metadata._set_xmp_tag_value(tag.key, tag.to_string()) + self.assertEqual(tag.metadata.tags, {tag.key: '2005-09-07T15:09:51-07:00'}) + del tag.value + self.failIf(hasattr(tag, 'value')) + self.failIf(tag.metadata.tags.has_key(tag.key)) |