diff options
author | Olivier Tilloy <olivier@tilloy.net> | 2010-12-26 19:30:33 +0100 |
---|---|---|
committer | Olivier Tilloy <olivier@tilloy.net> | 2010-12-26 19:30:33 +0100 |
commit | 66afe34c75790fba2b8395ce781bd9d16868e0e3 (patch) | |
tree | d2109d2a2bd0a627ce06f27e99f7b5d04558073b | |
parent | f178386b9ec255d7456977bb23b205802a0452e7 (diff) | |
download | pyexiv2-66afe34c75790fba2b8395ce781bd9d16868e0e3.tar.gz |
Handle fractions in a transparent manner,
using the convenience functions defined in module pyexiv2.utils.
-rw-r--r-- | src/pyexiv2/exif.py | 30 | ||||
-rw-r--r-- | src/pyexiv2/utils.py | 9 | ||||
-rw-r--r-- | src/pyexiv2/xmp.py | 10 | ||||
-rw-r--r-- | test/ReadMetadataTestCase.py | 61 | ||||
-rw-r--r-- | test/exif.py | 21 | ||||
-rw-r--r-- | test/metadata.py | 4 | ||||
-rw-r--r-- | test/pickling.py | 8 | ||||
-rw-r--r-- | test/xmp.py | 13 |
8 files changed, 74 insertions, 82 deletions
diff --git a/src/pyexiv2/exif.py b/src/pyexiv2/exif.py index 4f3fe03..8c58ede 100644 --- a/src/pyexiv2/exif.py +++ b/src/pyexiv2/exif.py @@ -30,7 +30,7 @@ EXIF specific code. import libexiv2python -from pyexiv2.utils import Rational, Fraction, \ +from pyexiv2.utils import is_fraction, make_fraction, \ NotifyingList, ListenerInterface, \ undefined_to_string, string_to_undefined @@ -72,7 +72,8 @@ class ExifTag(ListenerInterface): - Comment: string - Long, SLong: [list of] long - Short, SShort: [list of] int - - Rational, SRational: [list of] :class:`pyexiv2.utils.Rational` + - Rational, SRational: [list of] :class:`fractions.Fraction` if available + (Python ≥ 2.6) or :class:`pyexiv2.utils.Rational` - Undefined: string """ @@ -316,7 +317,7 @@ class ExifTag(ListenerInterface): elif self.type in ('Rational', 'SRational'): try: - r = Rational.from_string(value) + r = make_fraction(value) except (ValueError, ZeroDivisionError): raise ExifValueError(value, self.type) else: @@ -424,17 +425,26 @@ class ExifTag(ListenerInterface): raise ExifValueError(value, self.type) elif self.type == 'Rational': - if (isinstance(value, Rational) or \ - (Fraction is not None and isinstance(value, Fraction))) \ - and value.numerator >= 0: - return str(value) + if is_fraction(value) and value.numerator >= 0: + r = str(value) + if r == '0': + # fractions.Fraction.__str__ returns '0' for a null + # numerator, but libexiv2 always expects a string in the + # form 'numerator/denominator'. + r = '0/1' + return r else: raise ExifValueError(value, self.type) elif self.type == 'SRational': - if isinstance(value, Rational) or \ - (Fraction is not None and isinstance(value, Fraction)): - return str(value) + if is_fraction(value): + r = str(value) + if r == '0': + # fractions.Fraction.__str__ returns '0' for a null + # numerator, but libexiv2 always expects a string in the + # form 'numerator/denominator'. + r = '0/1' + return r else: raise ExifValueError(value, self.type) diff --git a/src/pyexiv2/utils.py b/src/pyexiv2/utils.py index 3c5aa95..473acdb 100644 --- a/src/pyexiv2/utils.py +++ b/src/pyexiv2/utils.py @@ -31,11 +31,10 @@ Utilitary classes and functions. import datetime import re -# Support for fractions.Fraction as a replacement for the Rational class is not -# implemented yet as we have to support versions of Python < 2.6 -# (see https://launchpad.net/bugs/514415). -# However, it doesn’t hurt to accept Fraction objects as values when the module -# is available (see https://launchpad.net/bugs/683232). +# pyexiv2 uses fractions.Fraction when available (Python ≥ 2.6), or falls back +# on the custom Rational class. This should be transparent to the application +# developer as both classes have a similar API. +# This module contains convenience functions to ease manipulation of fractions. try: from fractions import Fraction except ImportError: diff --git a/src/pyexiv2/xmp.py b/src/pyexiv2/xmp.py index ab2edeb..469af98 100644 --- a/src/pyexiv2/xmp.py +++ b/src/pyexiv2/xmp.py @@ -30,7 +30,7 @@ XMP specific code. import libexiv2python -from pyexiv2.utils import FixedOffset, Rational, Fraction, GPSCoordinate +from pyexiv2.utils import FixedOffset, is_fraction, make_fraction, GPSCoordinate import datetime import re @@ -74,7 +74,8 @@ class XmpTag(object): - Integer: int - Locale: *[not implemented yet]* - MIMEType: 2-tuple of strings - - Rational: :class:`pyexiv2.utils.Rational` + - Rational: :class:`fractions.Fraction` if available (Python ≥ 2.6) or + :class:`pyexiv2.utils.Rational` - Real: *[not implemented yet]* - AgentName, ProperName, Text: unicode string - Thumbnail: *[not implemented yet]* @@ -338,7 +339,7 @@ class XmpTag(object): elif type == 'Rational': try: - return Rational.from_string(value) + return make_fraction(value) except (ValueError, ZeroDivisionError): raise XmpValueError(value, type) @@ -435,8 +436,7 @@ class XmpTag(object): raise XmpValueError(value, type) elif type == 'Rational': - if isinstance(value, Rational) or \ - (Fraction is not None and isinstance(value, Fraction)): + if is_fraction(value): return str(value) else: raise XmpValueError(value, type) diff --git a/test/ReadMetadataTestCase.py b/test/ReadMetadataTestCase.py index 40f0843..3bf8e09 100644 --- a/test/ReadMetadataTestCase.py +++ b/test/ReadMetadataTestCase.py @@ -25,12 +25,19 @@ # # ****************************************************************************** +import pyexiv2 +from pyexiv2.utils import is_fraction, make_fraction + import unittest -import testutils import os.path -import pyexiv2 import datetime +import testutils + + +FRACTION = 'fraction' + + class ReadMetadataTestCase(unittest.TestCase): """ @@ -38,7 +45,10 @@ class ReadMetadataTestCase(unittest.TestCase): """ def check_type_and_value(self, tag, etype, evalue): - self.assert_(isinstance(tag.value, etype)) + if etype == FRACTION: + self.assert_(is_fraction(tag.value)) + else: + self.assert_(isinstance(tag.value, etype)) self.assertEqual(tag.value, evalue) def check_type_and_values(self, tag, etype, evalues): @@ -68,8 +78,8 @@ class ReadMetadataTestCase(unittest.TestCase): # Exhaustive tests on the values of EXIF metadata exifTags = [('Exif.Image.ImageDescription', str, 'Well it is a smiley that happens to be green'), - ('Exif.Image.XResolution', pyexiv2.Rational, pyexiv2.Rational(72, 1)), - ('Exif.Image.YResolution', pyexiv2.Rational, pyexiv2.Rational(72, 1)), + ('Exif.Image.XResolution', FRACTION, make_fraction(72, 1)), + ('Exif.Image.YResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.ResolutionUnit', int, 2), ('Exif.Image.Software', str, 'ImageReady'), ('Exif.Image.DateTime', datetime.datetime, datetime.datetime(2004, 7, 13, 21, 23, 44)), @@ -115,36 +125,22 @@ class ReadMetadataTestCase(unittest.TestCase): ('Xmp.dc.source', unicode, u'FreeFoto.com'), ('Xmp.dc.subject', list, [u'Communications']), ('Xmp.dc.title', dict, {u'x-default': u'Communications'}), - ('Xmp.exif.ApertureValue', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(8, 1)), - ('Xmp.exif.BrightnessValue', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(333, 1280)), + ('Xmp.exif.ApertureValue', FRACTION, make_fraction(8, 1)), + ('Xmp.exif.BrightnessValue', FRACTION, make_fraction(333, 1280)), ('Xmp.exif.ColorSpace', int, 1), ('Xmp.exif.DateTimeOriginal', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.ExifVersion', unicode, u'0200'), - ('Xmp.exif.ExposureBiasValue', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(-13, 20)), + ('Xmp.exif.ExposureBiasValue', FRACTION, make_fraction(-13, 20)), ('Xmp.exif.ExposureProgram', int, 4), - ('Xmp.exif.FNumber', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(3, 5)), + ('Xmp.exif.FNumber', FRACTION, make_fraction(3, 5)), ('Xmp.exif.FileSource', int, 0), ('Xmp.exif.FlashpixVersion', unicode, u'0100'), - ('Xmp.exif.FocalLength', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(0, 1)), + ('Xmp.exif.FocalLength', FRACTION, make_fraction(0, 1)), ('Xmp.exif.FocalPlaneResolutionUnit', int, 2), - ('Xmp.exif.FocalPlaneXResolution', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(3085, 256)), - ('Xmp.exif.FocalPlaneYResolution', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(3085, 256)), + ('Xmp.exif.FocalPlaneXResolution', FRACTION, make_fraction(3085, 256)), + ('Xmp.exif.FocalPlaneYResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.GPSLatitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('54,59.380000N')), @@ -162,9 +158,7 @@ class ReadMetadataTestCase(unittest.TestCase): ('Xmp.exif.PixelYDimension', int, 1600), ('Xmp.exif.SceneType', int, 0), ('Xmp.exif.SensingMethod', int, 2), - ('Xmp.exif.ShutterSpeedValue', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(30827, 3245)), + ('Xmp.exif.ShutterSpeedValue', FRACTION, make_fraction(30827, 3245)), ('Xmp.pdf.Keywords', unicode, u'Communications'), ('Xmp.photoshop.AuthorsPosition', unicode, u'Photographer'), ('Xmp.photoshop.CaptionWriter', unicode, u'Ian Britton'), @@ -191,13 +185,9 @@ class ReadMetadataTestCase(unittest.TestCase): ('Xmp.tiff.Orientation', int, 1), ('Xmp.tiff.ResolutionUnit', int, 2), ('Xmp.tiff.Software', unicode, u'Adobe Photoshop 7.0'), - ('Xmp.tiff.XResolution', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(300, 1)), + ('Xmp.tiff.XResolution', FRACTION, make_fraction(300, 1)), ('Xmp.tiff.YCbCrPositioning', int, 2), - ('Xmp.tiff.YResolution', - pyexiv2.utils.Rational, - pyexiv2.utils.Rational(300, 1)), + ('Xmp.tiff.YResolution', FRACTION, make_fraction(300, 1)), ('Xmp.xmp.CreateDate', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), @@ -215,3 +205,4 @@ class ReadMetadataTestCase(unittest.TestCase): self.assertEqual(image.xmp_keys, [tag[0] for tag in xmpTags]) for key, ktype, value in xmpTags: self.check_type_and_value(image[key], ktype, value) + diff --git a/test/exif.py b/test/exif.py index 1f929b7..38e19b7 100644 --- a/test/exif.py +++ b/test/exif.py @@ -27,7 +27,7 @@ import unittest from pyexiv2.exif import ExifTag, ExifValueError -from pyexiv2.utils import Rational, Fraction +from pyexiv2.utils import make_fraction import datetime @@ -260,7 +260,7 @@ class TestExifTag(unittest.TestCase): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') - self.assertEqual(tag._convert_to_python('5/3'), Rational(5, 3)) + self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') @@ -272,21 +272,19 @@ class TestExifTag(unittest.TestCase): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') - self.assertEqual(tag._convert_to_string(Rational(5, 3)), '5/3') - if Fraction is not None: - self.assertEqual(tag._convert_to_string(Fraction('1.6')), '8/5') + self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, - tag._convert_to_string, Rational(-5, 3)) + tag._convert_to_string, make_fraction(-5, 3)) def test_convert_to_python_srational(self): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') - self.assertEqual(tag._convert_to_python('5/3'), Rational(5, 3)) - self.assertEqual(tag._convert_to_python('-5/3'), Rational(-5, 3)) + self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) + self.assertEqual(tag._convert_to_python('-5/3'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') @@ -297,11 +295,8 @@ class TestExifTag(unittest.TestCase): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') - self.assertEqual(tag._convert_to_string(Rational(5, 3)), '5/3') - self.assertEqual(tag._convert_to_string(Rational(-5, 3)), '-5/3') - if Fraction is not None: - self.assertEqual(tag._convert_to_string(Fraction('1.6')), '8/5') - self.assertEqual(tag._convert_to_string(Fraction('-1.6')), '-8/5') + self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') + self.assertEqual(tag._convert_to_string(make_fraction(-5, 3)), '-5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') diff --git a/test/metadata.py b/test/metadata.py index bc4d1ff..177ec9d 100644 --- a/test/metadata.py +++ b/test/metadata.py @@ -28,7 +28,7 @@ from pyexiv2.metadata import ImageMetadata from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag from pyexiv2.xmp import XmpTag -from pyexiv2.utils import FixedOffset, Rational +from pyexiv2.utils import FixedOffset, make_fraction import datetime import os @@ -501,7 +501,7 @@ class TestImageMetadata(unittest.TestCase): self.metadata.read() # Set new tags key = 'Exif.Photo.ExposureBiasValue' - tag = ExifTag(key, Rational(0, 3)) + tag = ExifTag(key, make_fraction(0, 3)) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) diff --git a/test/pickling.py b/test/pickling.py index d98c146..8d0101b 100644 --- a/test/pickling.py +++ b/test/pickling.py @@ -27,7 +27,7 @@ from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag from pyexiv2.xmp import XmpTag -from pyexiv2.utils import Rational, FixedOffset +from pyexiv2.utils import make_fraction, FixedOffset import unittest import pickle @@ -49,8 +49,8 @@ class TestPicklingTags(unittest.TestCase): tags.append(ExifTag('Exif.Image.TimeZoneOffset', 7)) tags.append(ExifTag('Exif.Image.ImageWidth', 7492)) tags.append(ExifTag('Exif.OlympusCs.ManometerReading', 29)) - tags.append(ExifTag('Exif.Image.XResolution', Rational(7, 3))) - tags.append(ExifTag('Exif.Image.BaselineExposure', Rational(-7, 3))) + tags.append(ExifTag('Exif.Image.XResolution', make_fraction(7, 3))) + tags.append(ExifTag('Exif.Image.BaselineExposure', make_fraction(-7, 3))) tags.append(ExifTag('Exif.Photo.ExifVersion', '0100')) for tag in tags: s = pickle.dumps(tag) @@ -102,7 +102,7 @@ class TestPicklingTags(unittest.TestCase): tags.append(XmpTag('Xmp.dc.source', 'bleh')) tags.append(XmpTag('Xmp.xmpMM.DocumentID', 'http://example.com')) tags.append(XmpTag('Xmp.xmp.BaseURL', 'http://example.com')) - tags.append(XmpTag('Xmp.xmpDM.videoPixelAspectRatio', Rational(5, 3))) + tags.append(XmpTag('Xmp.xmpDM.videoPixelAspectRatio', make_fraction(5, 3))) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) diff --git a/test/xmp.py b/test/xmp.py index 3379851..f3fa4c3 100644 --- a/test/xmp.py +++ b/test/xmp.py @@ -28,7 +28,7 @@ import unittest from pyexiv2.xmp import XmpTag, XmpValueError, register_namespace, \ unregister_namespace, unregister_namespaces -from pyexiv2.utils import FixedOffset, Rational, Fraction +from pyexiv2.utils import FixedOffset, make_fraction from pyexiv2.metadata import ImageMetadata import datetime @@ -288,8 +288,8 @@ class TestXmpTag(unittest.TestCase): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') - self.assertEqual(tag._convert_to_python('5/3', 'Rational'), Rational(5, 3)) - self.assertEqual(tag._convert_to_python('-5/3', 'Rational'), Rational(-5, 3)) + self.assertEqual(tag._convert_to_python('5/3', 'Rational'), make_fraction(5, 3)) + self.assertEqual(tag._convert_to_python('-5/3', 'Rational'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'Rational') @@ -300,11 +300,8 @@ class TestXmpTag(unittest.TestCase): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') - self.assertEqual(tag._convert_to_string(Rational(5, 3), 'Rational'), '5/3') - self.assertEqual(tag._convert_to_string(Rational(-5, 3), 'Rational'), '-5/3') - if Fraction is not None: - self.assertEqual(tag._convert_to_string(Fraction('1.6'), 'Rational'), '8/5') - self.assertEqual(tag._convert_to_string(Fraction('-1.6'), 'Rational'), '-8/5') + self.assertEqual(tag._convert_to_string(make_fraction(5, 3), 'Rational'), '5/3') + self.assertEqual(tag._convert_to_string(make_fraction(-5, 3), 'Rational'), '-5/3') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Rational') |