From a40fa3744742d67b87bb5c04d0ad33a426d3f314 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 4 Nov 2009 10:05:25 +0100 Subject: Preliminary investigation to implement the *Tag classes in C++. --- src/exiv2wrapper.cpp | 141 ++++++++++++++++++++ src/exiv2wrapper.hpp | 67 ++++++++++ src/exiv2wrapper_python.cpp | 31 +++++ src/pyexiv2/exif2.py | 307 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 546 insertions(+) create mode 100644 src/pyexiv2/exif2.py diff --git a/src/exiv2wrapper.cpp b/src/exiv2wrapper.cpp index ba31d99..96c840a 100644 --- a/src/exiv2wrapper.cpp +++ b/src/exiv2wrapper.cpp @@ -468,6 +468,147 @@ void Image::setThumbnailFromJpegFile(const std::string path) } */ + +ExifTag::ExifTag(const std::string& key): _key(key), _datum(_key) +{ + const uint16_t tag = _datum.tag(); + const Exiv2::IfdId ifd = _datum.ifdId(); + _type = Exiv2::TypeInfo::typeName(Exiv2::ExifTags::tagType(tag, ifd)); + _name = Exiv2::ExifTags::tagName(tag, ifd); + _title = Exiv2::ExifTags::tagTitle(tag, ifd); + _label = Exiv2::ExifTags::tagLabel(tag, ifd); + _description = Exiv2::ExifTags::tagDesc(tag, ifd); + _sectionName = Exiv2::ExifTags::sectionName(tag, ifd); + _sectionDescription = Exiv2::ExifTags::sectionDesc(tag, ifd); + _value = _datum.toString(); +} + +void ExifTag::setValue(const std::string& value) +{ + _datum.setValue(value); + _value = _datum.toString(); +} + +const std::string ExifTag::getKey() +{ + return _key.key(); +} + +const std::string ExifTag::getType() +{ + return _type; +} + +const std::string ExifTag::getName() +{ + return _name; +} + +const std::string ExifTag::getTitle() +{ + return _title; +} + +const std::string ExifTag::getLabel() +{ + return _label; +} + +const std::string ExifTag::getDescription() +{ + return _description; +} + +const std::string ExifTag::getSectionName() +{ + return _sectionName; +} + +const std::string ExifTag::getSectionDescription() +{ + return _sectionDescription; +} + +const std::string ExifTag::getValue() +{ + return _value; +} + + +IptcTag::IptcTag(const std::string& key): _key(key), _datum(_key) +{ + const uint16_t tag = _datum.tag(); + const uint16_t record = _datum.record(); + _type = Exiv2::TypeInfo::typeName(Exiv2::IptcDataSets::dataSetType(tag, record)); + _name = Exiv2::IptcDataSets::dataSetName(tag, record); + _title = Exiv2::IptcDataSets::dataSetTitle(tag, record); + _description = Exiv2::IptcDataSets::dataSetDesc(tag, record); + // What is the photoshop name anyway? Where is it used? + _photoshopName = Exiv2::IptcDataSets::dataSetPsName(tag, record); + _repeatable = Exiv2::IptcDataSets::dataSetRepeatable(tag, record); + _recordName = Exiv2::IptcDataSets::recordName(record); + _recordDescription = Exiv2::IptcDataSets::recordDesc(record); + _value = _datum.toString(); +} + +void IptcTag::setValue(const std::string& value) +{ + _datum.setValue(value); + _value = _datum.toString(); +} + +const std::string IptcTag::getKey() +{ + return _key.key(); +} + +const std::string IptcTag::getType() +{ + return _type; +} + +const std::string IptcTag::getName() +{ + return _name; +} + +const std::string IptcTag::getTitle() +{ + return _title; +} + +const std::string IptcTag::getDescription() +{ + return _description; +} + +const std::string IptcTag::getPhotoshopName() +{ + return _photoshopName; +} + +const bool IptcTag::isRepeatable() +{ + return _repeatable; +} + +const std::string IptcTag::getRecordName() +{ + return _recordName; +} + +const std::string IptcTag::getRecordDescription() +{ + return _recordDescription; +} + +const std::string IptcTag::getValue() +{ + return _value; +} + + + // TODO: update the errors code to reflect changes from src/error.cpp in libexiv2 void translateExiv2Error(Exiv2::Error const& error) { diff --git a/src/exiv2wrapper.hpp b/src/exiv2wrapper.hpp index 9a728f8..8f593a8 100644 --- a/src/exiv2wrapper.hpp +++ b/src/exiv2wrapper.hpp @@ -155,6 +155,73 @@ private: bool _dataRead; }; + +class ExifTag +{ +public: + // Constructor + ExifTag(const std::string& key); + + void setValue(const std::string& value); + + const std::string getKey(); + const std::string getType(); + const std::string getName(); + const std::string getTitle(); + const std::string getLabel(); + const std::string getDescription(); + const std::string getSectionName(); + const std::string getSectionDescription(); + const std::string getValue(); + +private: + Exiv2::ExifKey _key; + Exiv2::Exifdatum _datum; + std::string _type; + std::string _name; + std::string _title; + std::string _label; + std::string _description; + std::string _sectionName; + std::string _sectionDescription; + std::string _value; +}; + + +class IptcTag +{ +public: + // Constructor + IptcTag(const std::string& key); + + void setValue(const std::string& value); + + const std::string getKey(); + const std::string getType(); + const std::string getName(); + const std::string getTitle(); + const std::string getDescription(); + const std::string getPhotoshopName(); + const bool isRepeatable(); + const std::string getRecordName(); + const std::string getRecordDescription(); + const std::string getValue(); + +private: + Exiv2::IptcKey _key; + Exiv2::Iptcdatum _datum; + std::string _type; + std::string _name; + std::string _title; + std::string _description; + std::string _photoshopName; + bool _repeatable; + std::string _recordName; + std::string _recordDescription; + std::string _value; +}; + + // Translate an Exiv2 generic exception into a Python exception void translateExiv2Error(Exiv2::Error const& error); diff --git a/src/exiv2wrapper_python.cpp b/src/exiv2wrapper_python.cpp index c4eb9d8..54967a5 100644 --- a/src/exiv2wrapper_python.cpp +++ b/src/exiv2wrapper_python.cpp @@ -75,5 +75,36 @@ BOOST_PYTHON_MODULE(libexiv2python) // .def("setComment", &Image::setComment) // .def("clearComment", &Image::clearComment) ; + + class_("ExifTag", init()) + + .def("_setValue", &ExifTag::setValue) + + .def("_getKey", &ExifTag::getKey) + .def("_getType", &ExifTag::getType) + .def("_getName", &ExifTag::getName) + .def("_getTitle", &ExifTag::getTitle) + .def("_getLabel", &ExifTag::getLabel) + .def("_getDescription", &ExifTag::getDescription) + .def("_getSectionName", &ExifTag::getSectionName) + .def("_getSectionDescription", &ExifTag::getSectionDescription) + .def("_getValue", &ExifTag::getValue) + ; + + class_("IptcTag", init()) + + .def("_setValue", &IptcTag::setValue) + + .def("_getKey", &IptcTag::getKey) + .def("_getType", &IptcTag::getType) + .def("_getName", &IptcTag::getName) + .def("_getTitle", &IptcTag::getTitle) + .def("_getDescription", &IptcTag::getDescription) + .def("_getPhotoshopName", &IptcTag::getPhotoshopName) + .def("_isRepeatable", &IptcTag::isRepeatable) + .def("_getRecordName", &IptcTag::getRecordName) + .def("_getRecordDescription", &IptcTag::getRecordDescription) + .def("_getValue", &IptcTag::getValue) + ; } diff --git a/src/pyexiv2/exif2.py b/src/pyexiv2/exif2.py new file mode 100644 index 0000000..d73c072 --- /dev/null +++ b/src/pyexiv2/exif2.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2006-2009 Olivier Tilloy +# +# This file is part of the pyexiv2 distribution. +# +# pyexiv2 is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# pyexiv2 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyexiv2; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. +# +# Author: Olivier Tilloy +# +# ****************************************************************************** + +import libexiv2python + +from pyexiv2.utils import Rational + +import time +import datetime + + +class ExifValueError(ValueError): + + """ + Exception raised when failing to parse the value of an EXIF tag. + + @ivar value: the value that fails to be parsed + @type value: C{str} + @ivar type: the EXIF type of the tag + @type type: C{str} + """ + + def __init__(self, value, type): + self.value = value + self.type = type + + def __str__(self): + return 'Invalid value for EXIF type [%s]: [%s]' % \ + (self.type, self.value) + + +class ExifTag(libexiv2python.ExifTag): + + """ + DOCME + """ + + # According to the EXIF specification, the only accepted format for an Ascii + # value representing a datetime is '%Y:%m:%d %H:%M:%S', but it seems that + # others formats can be found in the wild. + _datetime_formats = ('%Y:%m:%d %H:%M:%S', + '%Y-%m-%d %H:%M:%S', + '%Y-%m-%dT%H:%M:%SZ') + + _date_formats = ('%Y:%m:%d',) + + def __init__(self, key, value=None): + """ + DOCME + """ + #libexiv2python.ExifTag.__init__(key) + super(ExifTag, self).__init__(key) + if value is not None: + self._set_value(value) + else: + self._raw_value = None + self._value = None + + def _convert_to_string(self, value): + """ + DOCME + """ + # TODO: implement me + return str(value) + + @property + def key(self): + return self._getKey() + + @property + def type(self): + return self._getType() + + @property + def name(self): + return self._getName() + + @property + def title(self): + return self._getTitle() + + @property + def label(self): + return self._getLabel() + + @property + def description(self): + return self._getDescription() + + @property + def section_name(self): + return self._getSectionName() + + @property + def section_description(self): + return self._getSectionDescription() + + def _get_value(self): + return self._value + + def _set_value(self, new_value): + self._value = new_value + self._raw_value = self._convert_to_string(new_value) + self._setValue(self._raw_value) + + value = property(fget=_get_value, fset=_set_value, doc=None) + + def _convert_to_python(self, value): + """ + Convert one raw value to its corresponding python type. + + @param value: the raw value to be converted + @type value: C{str} + + @return: the value converted to its corresponding python type + @rtype: depends on C{self.type} (DOCME) + + @raise ExifValueError: if the conversion fails + """ + if self.type == 'Ascii': + # The value may contain a Datetime + for format in self._datetime_formats: + try: + t = time.strptime(value, format) + except ValueError: + continue + else: + return datetime.datetime(*t[:6]) + # Or a Date (e.g. Exif.GPSInfo.GPSDateStamp) + for format in self._date_formats: + try: + t = time.strptime(value, format) + except ValueError: + continue + else: + return datetime.date(*t[:3]) + # Default to string. + # There is currently no charset conversion. + # TODO: guess the encoding and decode accordingly into unicode + # where relevant. + return value + + elif self.type == 'Byte': + return value + + elif self.type == 'Short': + try: + return int(value) + except ValueError: + raise ExifValueError(value, self.type) + + elif self.type in ('Long', 'SLong'): + try: + return long(value) + except ValueError: + raise ExifValueError(value, self.type) + + elif self.type in ('Rational', 'SRational'): + try: + r = Rational.from_string(value) + except (ValueError, ZeroDivisionError): + raise ExifValueError(value, self.type) + else: + if self.type == 'Rational' and r.numerator < 0: + raise ExifValueError(value, self.type) + return r + + elif self.type == 'Undefined': + # There is currently no charset conversion. + # TODO: guess the encoding and decode accordingly into unicode + # where relevant. + return self.fvalue + + raise ExifValueError(value, self.type) + + def _convert_to_string(self, value): + """ + Convert one value to its corresponding string representation, suitable + to pass to libexiv2. + + @param value: the value to be converted + @type value: depends on C{self.type} (DOCME) + + @return: the value converted to its corresponding string representation + @rtype: C{str} + + @raise ExifValueError: if the conversion fails + """ + if self.type == 'Ascii': + if type(value) is datetime.datetime: + return value.strftime(self._datetime_formats[0]) + elif type(value) is datetime.date: + if self.key == 'Exif.GPSInfo.GPSDateStamp': + # Special case + return value.strftime(self._date_formats[0]) + else: + return value.strftime('%s 00:00:00' % self._date_formats[0]) + elif type(value) is unicode: + try: + return value.encode('utf-8') + except UnicodeEncodeError: + raise ExifValueError(value, self.type) + elif type(value) is str: + return value + else: + raise ExifValueError(value, self.type) + + elif self.type == 'Byte': + if type(value) is unicode: + try: + return value.encode('utf-8') + except UnicodeEncodeError: + raise ExifValueError(value, self.type) + elif type(value) is str: + return value + else: + raise ExifValueError(value, self.type) + + elif self.type == 'Short': + if type(value) is int and value >= 0: + return str(value) + else: + raise ExifValueError(value, self.type) + + elif self.type == 'Long': + if type(value) in (int, long) and value >= 0: + return str(value) + else: + raise ExifValueError(value, self.type) + + elif self.type == 'SLong': + if type(value) in (int, long): + return str(value) + else: + raise ExifValueError(value, self.type) + + elif self.type == 'Rational': + if type(value) is Rational and value.numerator >= 0: + return str(value) + else: + raise ExifValueError(value, self.type) + + elif self.type == 'SRational': + if type(value) is Rational: + return str(value) + else: + raise ExifValueError(value, self.type) + + elif self.type == 'Undefined': + if type(value) is unicode: + try: + return value.encode('utf-8') + except UnicodeEncodeError: + raise ExifValueError(value, self.type) + elif type(value) is str: + return value + else: + raise ExifValueError(value, self.type) + + raise ExifValueError(value, self.type) + + def __str__(self): + """ + Return a string representation of the value of the EXIF tag suitable to + pass to libexiv2 to set it. + + @rtype: C{str} + """ + return self._convert_to_string(self._value) + + def __repr__(self): + """ + Return a string representation of the EXIF tag for debugging purposes. + + @rtype: C{str} + """ + left = '%s [%s]' % (self.key, self.type) + if self.type == 'Undefined' and len(self._value) > 100: + right = '(Binary value suppressed)' + else: + #right = self.fvalue + right = str(self) + return '<%s = %s>' % (left, right) + -- cgit