26
26
# ******************************************************************************
29
Manipulation of EXIF, IPTC and XMP metadata and thumbnails embedded in images.
31
The L{ImageMetadata} class provides read/write access to all the metadata and
32
the various thumbnails embedded in an image file such as JPEG and TIFF files.
34
Metadata is accessed through subclasses of L{MetadataTag} and the tag values are
35
conveniently wrapped in python objects.
36
A tag containing a date/time information for the image
37
(e.g. C{Exif.Photo.DateTimeOriginal}) will be represented by a python
38
C{datetime.datetime} object.
40
This module is a python layer on top of the low-level python binding of the
41
C++ library Exiv2, libpyexiv2.
43
A typical use of this binding would be:
46
>>> metadata = pyexiv2.ImageMetadata('test/smiley.jpg')
48
>>> print metadata.exif_keys
49
['Exif.Image.ImageDescription', 'Exif.Image.XResolution',
50
'Exif.Image.YResolution', 'Exif.Image.ResolutionUnit', 'Exif.Image.Software',
51
'Exif.Image.DateTime', 'Exif.Image.Artist', 'Exif.Image.Copyright',
52
'Exif.Image.ExifTag', 'Exif.Photo.Flash', 'Exif.Photo.PixelXDimension',
53
'Exif.Photo.PixelYDimension']
54
>>> print metadata['Exif.Image.DateTime'].value
57
>>> metadata['Exif.Image.DateTime'].value = datetime.datetime.today()
69
__version__ = (0, 2, 1)
71
__exiv2_version__ = libexiv2python.__exiv2_version__
74
class FixedOffset(datetime.tzinfo):
77
Fixed positive or negative offset from a local time east from UTC.
79
@ivar sign: the sign of the offset ('+' or '-')
81
@ivar hours: the absolute number of hours of the offset
83
@ivar minutes: the absolute number of minutes of the offset
88
def __init__(self, sign='+', hours=0, minutes=0):
90
Initialize an offset from a sign ('+' or '-') and an absolute value
91
expressed in hours and minutes.
92
No check on the validity of those values is performed, it is the
93
responsibility of the caller to pass valid values.
95
@param sign: the sign of the offset ('+' or '-')
97
@param hours: an absolute number of hours
99
@param minutes: an absolute number of minutes
100
@type minutes: C{int}
104
self.minutes = minutes
106
def utcoffset(self, dt):
108
Return offset of local time from UTC, in minutes east of UTC.
109
If local time is west of UTC, this value will be negative.
111
@param dt: the local time
112
@type dt: C{datetime.time}
114
@return: a whole number of minutes in the range -1439 to 1439 inclusive
115
@rtype: C{datetime.timedelta}
117
total = self.hours * 60 + self.minutes
120
return datetime.timedelta(minutes = total)
124
Return the daylight saving time (DST) adjustment.
125
In this implementation, it is always nil.
127
@param dt: the local time
128
@type dt: C{datetime.time}
130
@return: the DST adjustment (always nil)
131
@rtype: C{datetime.timedelta}
133
return datetime.timedelta(0)
135
def tzname(self, dt):
137
Return a string representation of the offset in the format '±%H:%M'.
138
If the offset is nil, the representation is, by convention, 'Z'.
140
@param dt: the local time
141
@type dt: C{datetime.time}
143
@return: a human-readable representation of the offset
146
if self.hours == 0 and self.minutes == 0:
149
return '%s%02d:%02d' % (self.sign, self.hours, self.minutes)
151
def __equal__(self, other):
153
Test equality between this offset and another offset.
155
@param other: another offset
156
@type other: L{FixedOffset}
158
@return: C{True} if the offset are equal, C{False} otherwise
161
return (self.sign == other.sign) and (self.hours == other.hours) and \
162
(self.minutes == other.minutes)
165
def UndefinedToString(undefined):
167
Convert an undefined string into its corresponding sequence of bytes.
168
The undefined string must contain the ascii codes of a sequence of bytes,
169
each followed by a blank space (e.g. "48 50 50 49 " will be converted into
171
The Undefined type is part of the EXIF specification.
173
@param undefined: an undefined string
174
@type undefined: C{str}
176
@return: the corresponding decoded string
179
return ''.join(map(lambda x: chr(int(x)), undefined.rstrip().split(' ')))
182
def StringToUndefined(sequence):
184
Convert a string into its undefined form.
185
The undefined form contains a sequence of ascii codes, each followed by a
186
blank space (e.g. "0221" will be converted into "48 50 50 49 ").
187
The Undefined type is part of the EXIF specification.
189
@param sequence: a sequence of bytes
190
@type sequence: C{str}
192
@return: the corresponding undefined string
195
return ''.join(map(lambda x: '%d ' % ord(x), sequence))
198
class Rational(object):
201
A class representing a rational number.
203
Its numerator and denominator are read-only properties.
206
_format_re = re.compile(r'(?P<numerator>-?\d+)/(?P<denominator>\d+)')
208
def __init__(self, numerator, denominator):
212
@param numerator: the numerator
213
@type numerator: C{long}
214
@param denominator: the denominator
215
@type denominator: C{long}
217
@raise ZeroDivisionError: if the denominator equals zero
220
msg = 'Denominator of a rational number cannot be zero.'
221
raise ZeroDivisionError(msg)
222
self._numerator = long(numerator)
223
self._denominator = long(denominator)
227
return self._numerator
230
def denominator(self):
231
return self._denominator
234
def from_string(string):
236
Instantiate a Rational from a string formatted as
237
C{[-]numerator/denominator}.
239
@param string: a string representation of a rational number
242
@return: the rational number parsed
245
@raise ValueError: if the format of the string is invalid
247
match = Rational._format_re.match(string)
249
raise ValueError('Invalid format for a rational: %s' % string)
250
gd = match.groupdict()
251
return Rational(long(gd['numerator']), long(gd['denominator']))
255
@return: a floating point number approximation of the value
258
return float(self._numerator) / self._denominator
260
def __eq__(self, other):
262
Compare two rational numbers for equality.
264
Two rational numbers are equal if their reduced forms are equal.
266
@param other: the rational number to compare to self for equality
267
@type other: L{Rational}
269
@return: C{True} if equal, C{False} otherwise
272
return (self._numerator * other._denominator) == \
273
(other._numerator * self._denominator)
277
Return a string representation of the rational number.
279
return '%d/%d' % (self._numerator, self._denominator)
282
class ListenerInterface(object):
285
Interface that an object that wants to listen to changes on another object
289
def contents_changed(self):
291
React on changes on the object observed.
292
Override to implement specific behaviours.
294
raise NotImplementedError()
297
class NotifyingList(list):
300
A simplistic implementation of a notifying list.
301
Any changes to the list are notified in a synchronous way to all previously
302
registered listeners. A listener must implement the L{ListenerInterface}.
305
# Useful documentation:
306
# file:///usr/share/doc/python2.5/html/lib/typesseq-mutable.html
307
# http://docs.python.org/reference/datamodel.html#additional-methods-for-emulation-of-sequence-types
309
def __init__(self, items=[]):
310
super(NotifyingList, self).__init__(items)
311
self._listeners = set()
313
def register_listener(self, listener):
315
Register a new listener to be notified of changes.
317
@param listener: any object that listens for changes
318
@type listener: any class that implements the L{ListenerInterface}
320
self._listeners.add(listener)
322
def unregister_listener(self, listener):
324
Unregister a previously registered listener.
326
@param listener: a previously registered listener
327
@type listener: any class that implements the L{ListenerInterface}
329
@raise KeyError: if the listener was not previously registered
331
self._listeners.remove(listener)
333
def _notify_listeners(self, *args):
334
for listener in self._listeners:
335
listener.contents_changed(*args)
337
def __setitem__(self, index, item):
338
# FIXME: support slice arguments for extended slicing
339
super(NotifyingList, self).__setitem__(index, item)
340
self._notify_listeners()
342
def __delitem__(self, index):
343
# FIXME: support slice arguments for extended slicing
344
super(NotifyingList, self).__delitem__(index)
345
self._notify_listeners()
347
def append(self, item):
348
super(NotifyingList, self).append(item)
349
self._notify_listeners()
351
def extend(self, items):
352
super(NotifyingList, self).extend(items)
353
self._notify_listeners()
355
def insert(self, index, item):
356
super(NotifyingList, self).insert(index, item)
357
self._notify_listeners()
359
def pop(self, index=None):
361
item = super(NotifyingList, self).pop()
363
item = super(NotifyingList, self).pop(index)
364
self._notify_listeners()
367
def remove(self, item):
368
super(NotifyingList, self).remove(item)
369
self._notify_listeners()
372
super(NotifyingList, self).reverse()
373
self._notify_listeners()
375
def sort(self, cmp=None, key=None, reverse=False):
376
super(NotifyingList, self).sort(cmp, key, reverse)
377
self._notify_listeners()
379
def __iadd__(self, other):
380
self = super(NotifyingList, self).__iadd__(other)
381
self._notify_listeners()
384
def __imul__(self, coefficient):
385
self = super(NotifyingList, self).__imul__(coefficient)
386
self._notify_listeners()
389
def __setslice__(self, i, j, items):
390
# __setslice__ is deprecated but needs to be overridden for completeness
391
super(NotifyingList, self).__setslice__(i, j, items)
392
self._notify_listeners()
394
def __delslice__(self, i, j):
395
# __delslice__ is deprecated but needs to be overridden for completeness
397
super(NotifyingList, self).__delslice__(i, j)
399
self._notify_listeners()
402
class MetadataTag(object):
405
A generic metadata tag.
406
It is meant to be subclassed to implement specific tag types behaviours.
408
@ivar key: a unique key that identifies the tag
410
@ivar name: the short internal name that identifies the tag within
413
@ivar label: a human readable label for the tag
415
@ivar description: a description of the function of the tag
416
@type description: C{str}
417
@ivar type: the data type name
419
@ivar raw_value: the raw value of the tag as provided by exiv2
420
@type raw_value: C{str}
421
@ivar metadata: reference to the containing metadata if any
422
@type metadata: L{pyexiv2.ImageMetadata}
425
def __init__(self, key, name, label, description, type, value):
428
# FIXME: all attributes that may contain a localized string should be
431
self.description = description
433
self.raw_value = value
438
Return a string representation of the value of the tag suitable to pass
439
to libexiv2 to set it.
443
return self.raw_value
447
Return a string representation of the tag for debugging purposes.
451
return '<%s [%s] = %s>' % (self.key, self.type, self.raw_value)
454
class ExifValueError(ValueError):
457
Exception raised when failing to parse the value of an EXIF tag.
459
@ivar value: the value that fails to be parsed
461
@ivar type: the EXIF type of the tag
465
def __init__(self, value, type):
470
return 'Invalid value for EXIF type [%s]: [%s]' % \
471
(self.type, self.value)
474
class ExifTag(MetadataTag, ListenerInterface):
477
An EXIF metadata tag.
478
This tag has an additional field that contains the value of the tag
479
formatted as a human readable string.
481
@ivar fvalue: the value of the tag formatted as a human readable string
485
# According to the EXIF specification, the only accepted format for an Ascii
486
# value representing a datetime is '%Y:%m:%d %H:%M:%S', but it seems that
487
# others formats can be found in the wild.
488
_datetime_formats = ('%Y:%m:%d %H:%M:%S',
490
'%Y-%m-%dT%H:%M:%SZ')
492
_date_formats = ('%Y:%m:%d',)
494
def __init__(self, key, name, label, description, type, value, fvalue):
495
super(ExifTag, self).__init__(key, name, label,
496
description, type, value)
500
def _init_values(self):
501
# Initial conversion of the raw values to their corresponding python
503
if self.type in ('Short', 'Long', 'SLong', 'Rational', 'SRational'):
504
# May contain multiple values
505
values = self.raw_value.split()
507
# Make values a notifying list
508
values = map(self._convert_to_python, values)
509
self._value = NotifyingList(values)
510
self._value.register_listener(self)
512
self._value = self._convert_to_python(self.raw_value)
514
def _get_value(self):
517
def _set_value(self, new_value):
518
if self.metadata is not None:
519
if isinstance(new_value, (list, tuple)):
520
raw_values = map(self._convert_to_string, new_value)
521
raw_value = ' '.join(raw_values)
523
raw_value = self._convert_to_string(new_value)
524
self.metadata._set_exif_tag_value(self.key, raw_value)
526
if isinstance(self._value, NotifyingList):
527
self._value.unregister_listener(self)
529
if isinstance(new_value, NotifyingList):
530
# Already a notifying list
531
self._value = new_value
532
self._value.register_listener(self)
533
elif isinstance(new_value, (list, tuple)):
534
# Make the values a notifying list
535
self._value = NotifyingList(new_value)
536
self._value.register_listener(self)
539
self._value = new_value
541
def _del_value(self):
542
if self.metadata is not None:
543
self.metadata._delete_exif_tag(self.key)
545
if isinstance(self._value, NotifyingList):
546
self._value.unregister_listener(self)
550
"""the value of the tag converted to its corresponding python type"""
551
value = property(fget=_get_value, fset=_set_value, fdel=_del_value,
554
def contents_changed(self):
556
Implementation of the L{ListenerInterface}.
557
React on changes to the list of values of the tag.
559
# self._value is a list of value and its contents changed.
560
self._set_value(self._value)
562
def _convert_to_python(self, value):
564
Convert one raw value to its corresponding python type.
566
@param value: the raw value to be converted
569
@return: the value converted to its corresponding python type
570
@rtype: depends on C{self.type} (DOCME)
572
@raise ExifValueError: if the conversion fails
574
if self.type == 'Ascii':
575
# The value may contain a Datetime
576
for format in self._datetime_formats:
578
t = time.strptime(value, format)
582
return datetime.datetime(*t[:6])
583
# Or a Date (e.g. Exif.GPSInfo.GPSDateStamp)
584
for format in self._date_formats:
586
t = time.strptime(value, format)
590
return datetime.date(*t[:3])
592
# There is currently no charset conversion.
593
# TODO: guess the encoding and decode accordingly into unicode
597
elif self.type == 'Byte':
600
elif self.type == 'Short':
604
raise ExifValueError(value, self.type)
606
elif self.type in ('Long', 'SLong'):
610
raise ExifValueError(value, self.type)
612
elif self.type in ('Rational', 'SRational'):
614
r = Rational.from_string(value)
615
except (ValueError, ZeroDivisionError):
616
raise ExifValueError(value, self.type)
618
if self.type == 'Rational' and r.numerator < 0:
619
raise ExifValueError(value, self.type)
622
elif self.type == 'Undefined':
623
# There is currently no charset conversion.
624
# TODO: guess the encoding and decode accordingly into unicode
628
raise ExifValueError(value, self.type)
630
def _convert_to_string(self, value):
632
Convert one value to its corresponding string representation, suitable
635
@param value: the value to be converted
636
@type value: depends on C{self.type} (DOCME)
638
@return: the value converted to its corresponding string representation
641
@raise ExifValueError: if the conversion fails
643
if self.type == 'Ascii':
644
if type(value) is datetime.datetime:
645
return value.strftime(self._datetime_formats[0])
646
elif type(value) is datetime.date:
647
if self.key == 'Exif.GPSInfo.GPSDateStamp':
649
return value.strftime(self._date_formats[0])
651
return value.strftime('%s 00:00:00' % self._date_formats[0])
652
elif type(value) is unicode:
654
return value.encode('utf-8')
655
except UnicodeEncodeError:
656
raise ExifValueError(value, self.type)
657
elif type(value) is str:
660
raise ExifValueError(value, self.type)
662
elif self.type == 'Byte':
663
if type(value) is unicode:
665
return value.encode('utf-8')
666
except UnicodeEncodeError:
667
raise ExifValueError(value, self.type)
668
elif type(value) is str:
671
raise ExifValueError(value, self.type)
673
elif self.type == 'Short':
674
if type(value) is int and value >= 0:
677
raise ExifValueError(value, self.type)
679
elif self.type == 'Long':
680
if type(value) in (int, long) and value >= 0:
683
raise ExifValueError(value, self.type)
685
elif self.type == 'SLong':
686
if type(value) in (int, long):
689
raise ExifValueError(value, self.type)
691
elif self.type == 'Rational':
692
if type(value) is Rational and value.numerator >= 0:
695
raise ExifValueError(value, self.type)
697
elif self.type == 'SRational':
698
if type(value) is Rational:
701
raise ExifValueError(value, self.type)
703
elif self.type == 'Undefined':
704
if type(value) is unicode:
706
return value.encode('utf-8')
707
except UnicodeEncodeError:
708
raise ExifValueError(value, self.type)
709
elif type(value) is str:
712
raise ExifValueError(value, self.type)
714
raise ExifValueError(value, self.type)
718
Return a string representation of the value of the EXIF tag suitable to
719
pass to libexiv2 to set it.
723
return self._convert_to_string(self.value)
727
Return a string representation of the EXIF tag for debugging purposes.
731
left = '%s [%s]' % (self.key, self.type)
732
if self.type == 'Undefined' and len(self._value) > 100:
733
right = '(Binary value suppressed)'
736
return '<%s = %s>' % (left, right)
739
class IptcValueError(ValueError):
742
Exception raised when failing to parse the value of an IPTC tag.
744
@ivar value: the value that fails to be parsed
746
@ivar type: the IPTC type of the tag
750
def __init__(self, value, type):
755
return 'Invalid value for IPTC type [%s]: [%s]' % \
756
(self.type, self.value)
759
class IptcTag(MetadataTag):
762
An IPTC metadata tag.
763
This tag can have several values (tags that have the repeatable property).
766
# strptime is not flexible enough to handle all valid Time formats, we use a
767
# custom regular expression
768
_time_zone_re = r'(?P<sign>\+|-)(?P<ohours>\d{2}):(?P<ominutes>\d{2})'
769
_time_re = re.compile(r'(?P<hours>\d{2}):(?P<minutes>\d{2}):(?P<seconds>\d{2})(?P<tzd>%s)' % _time_zone_re)
771
def __init__(self, key, name, label, description, type, values):
772
super(IptcTag, self).__init__(key, name, label,
773
description, type, values)
776
def _init_values(self):
777
# Initial conversion of the raw values to their corresponding python
779
values = map(self._convert_to_python, self.raw_value)
780
# Make values a notifying list
781
self._values = NotifyingList(values)
782
self._values.register_listener(self)
784
def _get_values(self):
787
def _set_values(self, new_values):
788
if self.metadata is not None:
789
raw_values = map(self._convert_to_string, new_values)
790
self.metadata._set_iptc_tag_values(self.key, raw_values)
791
# Make values a notifying list if needed
792
if isinstance(new_values, NotifyingList):
793
self._values = new_values
795
self._values = NotifyingList(new_values)
797
def _del_values(self):
798
if self.metadata is not None:
799
self.metadata._delete_iptc_tag(self.key)
802
"""the list of values of the tag converted to their corresponding python
804
values = property(fget=_get_values, fset=_set_values, fdel=_del_values,
807
def contents_changed(self):
809
Implementation of the L{ListenerInterface}.
810
React on changes to the list of values of the tag.
812
# The contents of self._values was changed.
813
# The following is a quick, non optimal solution.
814
self._set_values(self._values)
816
def _convert_to_python(self, value):
818
Convert one raw value to its corresponding python type.
820
@param value: the raw value to be converted
823
@return: the value converted to its corresponding python type
824
@rtype: depends on C{self.type} (DOCME)
826
@raise IptcValueError: if the conversion fails
828
if self.type == 'Short':
832
raise IptcValueError(value, self.type)
834
elif self.type == 'String':
835
# There is currently no charset conversion.
836
# TODO: guess the encoding and decode accordingly into unicode
840
elif self.type == 'Date':
841
# According to the IPTC specification, the format for a string field
842
# representing a date is '%Y%m%d'. However, the string returned by
843
# exiv2 using method DateValue::toString() is formatted using
844
# pattern '%Y-%m-%d'.
847
t = time.strptime(value, format)
848
return datetime.date(*t[:3])
850
raise IptcValueError(value, self.type)
852
elif self.type == 'Time':
853
# According to the IPTC specification, the format for a string field
854
# representing a time is '%H%M%S±%H%M'. However, the string returned
855
# by exiv2 using method TimeValue::toString() is formatted using
856
# pattern '%H:%M:%S±%H:%M'.
857
match = IptcTag._time_re.match(value)
859
raise IptcValueError(value, self.type)
860
gd = match.groupdict()
862
tzinfo = FixedOffset(gd['sign'], int(gd['ohours']),
865
raise IptcValueError(value, self.type)
867
return datetime.time(int(gd['hours']), int(gd['minutes']),
868
int(gd['seconds']), tzinfo=tzinfo)
869
except (TypeError, ValueError):
870
raise IptcValueError(value, self.type)
872
elif self.type == 'Undefined':
873
# Binary data, return it unmodified
876
raise IptcValueError(value, self.type)
878
def _convert_to_string(self, value):
880
Convert one value to its corresponding string representation, suitable
883
@param value: the value to be converted
884
@type value: depends on C{self.type} (DOCME)
886
@return: the value converted to its corresponding string representation
889
@raise IptcValueError: if the conversion fails
891
if self.type == 'Short':
892
if type(value) is int:
895
raise IptcValueError(value, self.type)
897
elif self.type == 'String':
898
if type(value) is unicode:
900
return value.encode('utf-8')
901
except UnicodeEncodeError:
902
raise IptcValueError(value, self.type)
903
elif type(value) is str:
906
raise IptcValueError(value, self.type)
908
elif self.type == 'Date':
909
if type(value) in (datetime.date, datetime.datetime):
910
# ISO 8601 date format
911
return value.strftime('%Y%m%d')
913
raise IptcValueError(value, self.type)
915
elif self.type == 'Time':
916
if type(value) in (datetime.time, datetime.datetime):
917
r = value.strftime('%H%M%S')
918
if value.tzinfo is not None:
919
r += value.strftime('%z')
924
raise IptcValueError(value, self.type)
926
elif self.type == 'Undefined':
927
if type(value) is str:
930
raise IptcValueError(value, self.type)
932
raise IptcValueError(value, self.type)
936
Return a list of string representations of the values of the IPTC tag
937
suitable to pass to libexiv2 to set it.
939
@rtype: C{list} of C{str}
941
return map(self._convert_to_string, self.values)
945
Return a string representation of the list of values of the IPTC tag.
949
return ', '.join(self.to_string())
953
Return a string representation of the IPTC tag for debugging purposes.
957
return '<%s [%s] = %s>' % (self.key, self.type, str(self))
960
class XmpValueError(ValueError):
963
Exception raised when failing to parse the value of an XMP tag.
965
@ivar value: the value that fails to be parsed
967
@ivar type: the XMP type of the tag
970
def __init__(self, value, type):
975
return 'Invalid value for XMP type [%s]: [%s]' % \
976
(self.type, self.value)
979
class XmpTag(MetadataTag):
985
# strptime is not flexible enough to handle all valid Date formats, we use a
986
# custom regular expression
987
_time_zone_re = r'Z|((?P<sign>\+|-)(?P<ohours>\d{2}):(?P<ominutes>\d{2}))'
988
_time_re = r'(?P<hours>\d{2})(:(?P<minutes>\d{2})(:(?P<seconds>\d{2})(.(?P<decimal>\d+))?)?(?P<tzd>%s))?' % _time_zone_re
989
_date_re = re.compile(r'(?P<year>\d{4})(-(?P<month>\d{2})(-(?P<day>\d{2})(T(?P<time>%s))?)?)?' % _time_re)
991
def __init__(self, key, name, label, description, type, value):
992
super(XmpTag, self).__init__(key, name, label, description, type, value)
993
self._value = XmpTag._convert_to_python(value, type)
995
def _get_value(self):
998
def _set_value(self, new_value):
999
if self.metadata is not None:
1000
raw_value = XmpTag._convert_to_string(new_value, self.type)
1001
self.metadata._set_xmp_tag_value(self.key, raw_value)
1002
self._value = new_value
1004
def _del_value(self):
1005
if self.metadata is not None:
1006
self.metadata._delete_xmp_tag(self.key)
1009
"""the value of the tag converted to its corresponding python type"""
1010
value = property(fget=_get_value, fset=_set_value, fdel=_del_value,
1014
def _convert_to_python(value, xtype):
1016
Convert a raw value to its corresponding python type.
1018
@param value: the raw value to be converted
1020
@param xtype: the XMP type of the value
1023
@return: the value converted to its corresponding python type
1024
@rtype: depends on xtype (DOCME)
1026
@raise XmpValueError: if the conversion fails
1028
if xtype.startswith('bag '):
1029
# FIXME: make the value a notifying list.
1032
values = value.split(', ')
1033
return map(lambda x: XmpTag._convert_to_python(x, xtype[4:]), values)
1035
elif xtype == 'Boolean':
1038
elif value == 'False':
1041
raise XmpValueError(value, xtype)
1043
elif xtype == 'Choice':
1045
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1047
elif xtype == 'Colorant':
1049
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1051
elif xtype == 'Date':
1052
match = XmpTag._date_re.match(value)
1054
raise XmpValueError(value, xtype)
1055
gd = match.groupdict()
1056
if gd['month'] is not None:
1057
month = int(gd['month'])
1060
if gd['day'] is not None:
1061
day = int(gd['day'])
1064
if gd['time'] is None:
1066
return datetime.date(int(gd['year']), month, day)
1068
raise XmpValueError(value, xtype)
1070
if gd['minutes'] is None:
1072
raise XmpValueError(value, xtype)
1073
if gd['seconds'] is not None:
1074
seconds = int(gd['seconds'])
1077
if gd['decimal'] is not None:
1078
microseconds = int(float('0.%s' % gd['decimal']) * 1E6)
1081
if gd['tzd'] == 'Z':
1082
tzinfo = FixedOffset()
1084
tzinfo = FixedOffset(gd['sign'], int(gd['ohours']),
1085
int(gd['ominutes']))
1087
return datetime.datetime(int(gd['year']), month, day,
1088
int(gd['hours']), int(gd['minutes']),
1089
seconds, microseconds, tzinfo)
1091
raise XmpValueError(value, xtype)
1093
elif xtype == 'Dimensions':
1095
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1097
elif xtype == 'Font':
1099
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1101
elif xtype == 'Integer':
1105
raise XmpValueError(value, xtype)
1107
elif xtype == 'Lang Alt':
1108
matches = value.split('lang="')
1110
if nb < 2 or matches[0] != '':
1111
raise XmpValueError(value, xtype)
1113
for i, match in enumerate(matches[1:]):
1115
qualifier, text = match.split('" ', 1)
1117
raise XmpValueError(value, xtype)
1119
if not text.rstrip().endswith(','):
1121
# If not the last match, it should end with a comma
1122
raise XmpValueError(value, xtype)
1124
result[qualifier] = text
1126
result[qualifier] = unicode(text, 'utf-8')
1128
raise XmpValueError(value, xtype)
1131
result[qualifier] = unicode(text.rstrip()[:-1], 'utf-8')
1133
raise XmpValueError(value, xtype)
1136
elif xtype == 'Locale':
1139
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1141
elif xtype == 'MIMEType':
1143
mtype, msubtype = value.split('/', 1)
1145
raise XmpValueError(value, xtype)
1147
return {'type': mtype, 'subtype': msubtype}
1149
elif xtype == 'Real':
1151
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1153
elif xtype in ('ProperName', 'Text'):
1155
return unicode(value, 'utf-8')
1157
raise XmpValueError(value, xtype)
1159
elif xtype == 'Thumbnail':
1161
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1163
elif xtype in ('URI', 'URL'):
1166
elif xtype == 'XPath':
1168
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1170
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1173
def _convert_to_string(value, xtype):
1175
Convert a value to its corresponding string representation, suitable to
1178
@param value: the value to be converted
1179
@type value: depends on xtype (DOCME)
1180
@param xtype: the XMP type of the value
1183
@return: the value converted to its corresponding string representation
1186
@raise XmpValueError: if the conversion fails
1188
if xtype.startswith('bag '):
1189
if type(value) in (list, tuple):
1190
return ', '.join(map(lambda x: XmpTag._convert_to_string(x, xtype[4:]), value))
1192
raise XmpValueError(value, xtype)
1194
elif xtype == 'Boolean':
1195
if type(value) is bool:
1198
raise XmpValueError(value, xtype)
1200
elif xtype == 'Date':
1201
if type(value) is datetime.date:
1202
return value.isoformat()
1203
elif type(value) is datetime.datetime:
1204
if value.hour == 0 and value.minute == 0 and \
1205
value.second == 0 and value.microsecond == 0 and \
1206
(value.tzinfo is None or value.tzinfo == FixedOffset()):
1207
return value.strftime('%Y-%m-%d')
1208
elif value.second == 0 and value.microsecond == 0:
1209
return value.strftime('%Y-%m-%dT%H:%M%Z')
1210
elif value.microsecond == 0:
1211
return value.strftime('%Y-%m-%dT%H:%M:%S%Z')
1213
r = value.strftime('%Y-%m-%dT%H:%M:%S.')
1214
r += str(int(value.microsecond) / 1E6)[2:]
1215
r += value.strftime('%Z')
1218
raise XmpValueError(value, xtype)
1220
elif xtype == 'Integer':
1221
if type(value) in (int, long):
1224
raise XmpValueError(value, xtype)
1226
elif xtype == 'Lang Alt':
1227
if type(value) is dict and len(value) > 0:
1229
for key, avalue in value.iteritems():
1230
if type(key) is unicode:
1232
rkey = key.encode('utf-8')
1233
except UnicodeEncodeError:
1234
raise XmpValueError(value, xtype)
1235
elif type(key) is str:
1238
raise XmpValueError(value, xtype)
1239
if type(avalue) is unicode:
1241
ravalue = avalue.encode('utf-8')
1242
except UnicodeEncodeError:
1243
raise XmpValueError(value, xtype)
1244
elif type(avalue) is str:
1247
raise XmpValueError(value, xtype)
1248
r += 'lang="%s" %s, ' % (rkey, ravalue)
1251
raise XmpValueError(value, xtype)
1253
elif xtype == 'MIMEType':
1254
if type(value) is dict:
1256
return '%s/%s' % (value['type'], value['subtype'])
1258
raise XmpValueError(value, xtype)
1260
raise XmpValueError(value, xtype)
1262
elif xtype in ('ProperName', 'Text', 'URI', 'URL'):
1263
if type(value) is unicode:
1265
return value.encode('utf-8')
1266
except UnicodeEncodeError:
1267
raise XmpValueError(value, xtype)
1268
elif type(value) is str:
1271
raise XmpValueError(value, xtype)
1273
raise NotImplementedError('XMP conversion for type [%s]' % xtype)
1275
def to_string(self):
1277
Return a string representation of the XMP tag suitable to pass to
1278
libexiv2 to set the value of the tag.
1282
return XmpTag._convert_to_string(self.value, self.type)
1286
Return a string representation of the XMP tag for debugging purposes.
1290
r = 'Key = ' + self.key + os.linesep + \
1291
'Name = ' + self.name + os.linesep + \
1292
'Label = ' + self.label + os.linesep + \
1293
'Description = ' + self.description + os.linesep + \
1294
'Type = ' + self.type + os.linesep + \
1295
'Values = ' + str(self.values)
1299
class ImageMetadata(object):
1302
A container for all the metadata attached to an image.
1304
It provides convenient methods for the manipulation of EXIF, IPTC and XMP
1305
metadata embedded in image files such as JPEG and TIFF files, using Python
1307
It also provides access to the thumbnails embedded in an image.
1310
def __init__(self, filename):
1312
@param filename: absolute path to an image file
1313
@type filename: C{str} or C{unicode}
1315
self.filename = filename
1316
if type(filename) is unicode:
1317
self.filename = filename.encode('utf-8')
1319
self._keys = {'exif': None, 'iptc': None, 'xmp': None}
1320
self._tags = {'exif': {}, 'iptc': {}, 'xmp': {}}
1322
def _instantiate_image(self, filename):
1323
# This method is meant to be overridden in unit tests to easily replace
1324
# the internal image reference by a mock.
1325
return libexiv2python.Image(filename)
1329
Read the metadata embedded in the associated image file.
1330
It is necessary to call this method once before attempting to access
1331
the metadata (an exception will be raised if trying to access metadata
1332
before calling this method).
1334
if self._image is None:
1335
self._image = self._instantiate_image(self.filename)
1336
self._image.readMetadata()
1340
Write the metadata back to the associated image file.
1342
self._image.writeMetadata()
1344
"""List the keys of the available EXIF tags embedded in the image."""
1346
def exif_keys(self):
1347
if self._keys['exif'] is None:
1348
self._keys['exif'] = self._image.exifKeys()
1349
return self._keys['exif']
1351
"""List the keys of the available IPTC tags embedded in the image."""
1353
def iptc_keys(self):
1354
if self._keys['iptc'] is None:
1355
self._keys['iptc'] = self._image.iptcKeys()
1356
return self._keys['iptc']
1358
"""List the keys of the available XMP tags embedded in the image."""
1361
if self._keys['xmp'] is None:
1362
self._keys['xmp'] = self._image.xmpKeys()
1363
return self._keys['xmp']
1365
def _get_exif_tag(self, key):
1366
# Return the EXIF tag for the given key.
1367
# Throw a KeyError if the tag doesn't exist.
1369
return self._tags['exif'][key]
1371
tag = ExifTag(*self._image.getExifTag(key))
1373
self._tags['exif'][key] = tag
1376
def _get_iptc_tag(self, key):
1377
# Return the IPTC tag for the given key.
1378
# Throw a KeyError if the tag doesn't exist.
1380
return self._tags['iptc'][key]
1382
tag = IptcTag(*self._image.getIptcTag(key))
1384
self._tags['iptc'][key] = tag
1387
def _get_xmp_tag(self, key):
1388
# Return the XMP tag for the given key.
1389
# Throw a KeyError if the tag doesn't exist.
1391
return self._tags['xmp'][key]
1393
tag = XmpTag(*self._image.getXmpTag(key))
1395
self._tags['xmp'][key] = tag
1398
def __getitem__(self, key):
1400
Get a metadata tag for a given key.
1402
@param key: a metadata key in the dotted form C{family.group.tag} where
1403
family may be C{exif}, C{iptc} or C{xmp}.
1406
@return: the metadata tag corresponding to the key
1407
@rtype: a subclass of L{pyexiv2.MetadataTag}
1409
@raise KeyError: if the tag doesn't exist
1411
family = key.split('.')[0].lower()
1413
return getattr(self, '_get_%s_tag' % family)(key)
1414
except AttributeError:
1417
def _set_exif_tag(self, tag):
1418
# Set an EXIF tag. If the tag already exists, its value is overwritten.
1419
if type(tag) is not ExifTag:
1420
raise TypeError('Expecting an ExifTag')
1421
self._image.setExifTagValue(tag.key, str(tag))
1422
self._tags['exif'][tag.key] = tag
1425
def _set_exif_tag_value(self, key, value):
1426
# Overwrite the tag value for an already existing tag.
1427
# The tag is already in cache.
1428
# Warning: this is not meant to be called directly as it doesn't update
1429
# the internal cache (which would leave the object in an inconsistent
1431
if key not in self.exif_keys:
1432
raise KeyError('Cannot set the value of an inexistent tag')
1433
if type(value) is not str:
1434
raise TypeError('Expecting a string')
1435
self._image.setExifTagValue(key, value)
1437
def _set_iptc_tag(self, tag):
1438
# Set an IPTC tag. If the tag already exists, its values are
1440
if type(tag) is not IptcTag:
1441
raise TypeError('Expecting an IptcTag')
1442
self._image.setIptcTagValues(tag.key, tag.to_string())
1443
self._tags['iptc'][tag.key] = tag
1446
def _set_iptc_tag_values(self, key, values):
1447
# Overwrite the tag values for an already existing tag.
1448
# The tag is already in cache.
1449
# Warning: this is not meant to be called directly as it doesn't update
1450
# the internal cache (which would leave the object in an inconsistent
1452
# FIXME: this is sub-optimal as it sets all the values regardless of how
1453
# many of them really changed. Need to implement the same method with an
1454
# index/range parameter (here and in the C++ wrapper).
1455
if key not in self.iptc_keys:
1456
raise KeyError('Cannot set the value of an inexistent tag')
1457
if type(values) is not list or not \
1458
reduce(lambda x, y: x and type(y) is str, values, True):
1459
raise TypeError('Expecting a list of strings')
1460
self._image.setIptcTagValues(key, values)
1462
def _set_xmp_tag(self, tag):
1463
# Set an XMP tag. If the tag already exists, its value is overwritten.
1464
if type(tag) is not XmpTag:
1465
raise TypeError('Expecting an XmpTag')
1466
self._image.setXmpTagValue(tag.key, tag.to_string())
1467
self._tags['xmp'][tag.key] = tag
1470
def _set_xmp_tag_value(self, key, value):
1471
# Overwrite the tag value for an already existing tag.
1472
# The tag is already in cache.
1473
# Warning: this is not meant to be called directly as it doesn't update
1474
# the internal cache (which would leave the object in an inconsistent
1476
if key not in self.xmp_keys:
1477
raise KeyError('Cannot set the value of an inexistent tag')
1478
if type(value) is not str:
1479
raise TypeError('Expecting a string')
1480
self._image.setXmpTagValue(key, value)
1482
def __setitem__(self, key, tag):
1484
Set a metadata tag for a given key.
1485
If the tag was previously set, it is overwritten.
1487
@param key: a metadata key in the dotted form C{family.group.tag} where
1488
family may be C{exif}, C{iptc} or C{xmp}.
1490
@param tag: a metadata tag
1491
@type tag: a subclass of L{pyexiv2.MetadataTag}
1493
@raise KeyError: if the key is invalid
1495
family = key.split('.')[0].lower()
1497
return getattr(self, '_set_%s_tag' % family)(tag)
1498
except AttributeError:
1501
def _delete_exif_tag(self, key):
1502
# Delete an EXIF tag.
1503
# Throw a KeyError if the tag doesn't exist.
1504
if key not in self.exif_keys:
1505
raise KeyError('Cannot delete an inexistent tag')
1506
self._image.deleteExifTag(key)
1508
del self._tags['exif'][key]
1510
# The tag was not cached.
1513
def _delete_iptc_tag(self, key):
1514
# Delete an IPTC tag.
1515
# Throw a KeyError if the tag doesn't exist.
1516
if key not in self.iptc_keys:
1517
raise KeyError('Cannot delete an inexistent tag')
1518
self._image.deleteIptcTag(key)
1520
del self._tags['iptc'][key]
1522
# The tag was not cached.
1525
def _delete_xmp_tag(self, key):
1526
# Delete an XMP tag.
1527
# Throw a KeyError if the tag doesn't exist.
1528
if key not in self.xmp_keys:
1529
raise KeyError('Cannot delete an inexistent tag')
1530
self._image.deleteXmpTag(key)
1532
del self._tags['xmp'][key]
1534
# The tag was not cached.
1537
def __delitem__(self, key):
1539
Delete a metadata tag for a given key.
1541
@param key: a metadata key in the dotted form C{family.group.tag} where
1542
family may be C{exif}, C{iptc} or C{xmp}.
1545
@raise KeyError: if the tag with the given key doesn't exist
1547
family = key.split('.')[0].lower()
1549
return getattr(self, '_delete_%s_tag' % family)(key)
1550
except AttributeError:
30
from pyexiv2.metadata import ImageMetadata
1554
33
if __name__ == '__main__':