// *****************************************************************************
/*
* Copyright (C) 2006-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>
*/
// *****************************************************************************
#include "exiv2wrapper.hpp"
#include "boost/python/stl_iterator.hpp"
// Custom error codes for Exiv2 exceptions
#define METADATA_NOT_READ 101
#define NON_REPEATABLE 102
#define KEY_NOT_FOUND 103
#define THUMB_ACCESS 104
#define NO_THUMBNAIL 105
// Custom macros
#define CHECK_METADATA_READ \
if (!_dataRead) throw Exiv2::Error(METADATA_NOT_READ);
namespace exiv2wrapper
{
// Base constructor
Image::Image(const std::string& filename)
{
// Release the GIL to allow other python threads to run
// while opening the file.
Py_BEGIN_ALLOW_THREADS
_filename = filename;
_image = Exiv2::ImageFactory::open(filename);
assert(_image.get() != 0);
_dataRead = false;
// Re-acquire the GIL
Py_END_ALLOW_THREADS
}
// Copy constructor
Image::Image(const Image& image)
{
// Release the GIL to allow other python threads to run
// while opening the file.
Py_BEGIN_ALLOW_THREADS
_filename = image._filename;
_image = Exiv2::ImageFactory::open(_filename);
assert(_image.get() != 0);
_dataRead = false;
// Re-acquire the GIL
Py_END_ALLOW_THREADS
}
void Image::readMetadata()
{
// Release the GIL to allow other python threads to run
// while reading metadata.
Py_BEGIN_ALLOW_THREADS
_image->readMetadata();
_exifData = _image->exifData();
_iptcData = _image->iptcData();
_xmpData = _image->xmpData();
_dataRead = true;
// Re-acquire the GIL
Py_END_ALLOW_THREADS
}
void Image::writeMetadata()
{
CHECK_METADATA_READ
// Release the GIL to allow other python threads to run
// while writing metadata.
Py_BEGIN_ALLOW_THREADS
_image->setExifData(_exifData);
_image->setIptcData(_iptcData);
_image->setXmpData(_xmpData);
_image->writeMetadata();
// Re-acquire the GIL
Py_END_ALLOW_THREADS
}
boost::python::list Image::exifKeys()
{
CHECK_METADATA_READ
boost::python::list keys;
for(Exiv2::ExifMetadata::iterator i = _exifData.begin();
i != _exifData.end();
++i)
{
keys.append(i->key());
}
return keys;
}
const ExifTag Image::getExifTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::ExifKey exifKey = Exiv2::ExifKey(key);
if(_exifData.findKey(exifKey) == _exifData.end())
{
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
return ExifTag(key, &_exifData[key]);
}
void Image::setExifTagValue(std::string key, std::string value)
{
CHECK_METADATA_READ
_exifData[key] = value;
}
void Image::deleteExifTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::ExifKey exifKey = Exiv2::ExifKey(key);
Exiv2::ExifMetadata::iterator datum = _exifData.findKey(exifKey);
if(datum == _exifData.end())
{
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
_exifData.erase(datum);
}
boost::python::list Image::iptcKeys()
{
CHECK_METADATA_READ
boost::python::list keys;
for(Exiv2::IptcMetadata::iterator i = _iptcData.begin();
i != _iptcData.end();
++i)
{
// The key is appended to the list if and only if it is not already
// present.
if (keys.count(i->key()) == 0)
{
keys.append(i->key());
}
}
return keys;
}
const IptcTag Image::getIptcTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key);
if(_iptcData.findKey(iptcKey) == _iptcData.end())
{
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
Exiv2::IptcMetadata* data = new Exiv2::IptcMetadata();
for (Exiv2::IptcMetadata::iterator iterator = _iptcData.begin();
iterator != _iptcData.end(); ++iterator)
{
if (iterator->key() == key)
{
data->push_back(*iterator);
}
}
return IptcTag(key, data);
}
/*void Image::setIptcTag(std::string key, std::string value, unsigned int index=0)
{
CHECK_METADATA_READ
unsigned int indexCounter = index;
Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key);
Exiv2::IptcMetadata::iterator dataIterator = _iptcData.findKey(iptcKey);
while ((indexCounter > 0) && (dataIterator != _iptcData.end()))
{
dataIterator = std::find_if(++dataIterator, _iptcData.end(),
Exiv2::FindMetadatumById::FindMetadatumById(iptcKey.tag(), iptcKey.record()));
--indexCounter;
}
if (dataIterator != _iptcData.end())
{
// The tag at given index already exists, override it
dataIterator->setValue(value);
}
else
{
// Either index is greater than the index of the last repetition
// of the tag, or the tag does not exist yet.
// In both cases, it is created.
Exiv2::Iptcdatum iptcDatum(iptcKey);
iptcDatum.setValue(value);
int state = _iptcData.add(iptcDatum);
if (state == 6)
throw Exiv2::Error(NON_REPEATABLE);
}
}*/
void Image::setIptcTagValues(std::string key, boost::python::list values)
{
CHECK_METADATA_READ
Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key);
unsigned int index = 0;
unsigned int max = len(values);
Exiv2::IptcMetadata::iterator dataIterator = _iptcData.findKey(iptcKey);
while (index < max)
{
std::string value = boost::python::extract<std::string>(values[index++]);
if (dataIterator != _iptcData.end())
{
// Override an existing value
dataIterator->setValue(value);
dataIterator = std::find_if(++dataIterator, _iptcData.end(),
Exiv2::FindMetadatumById::FindMetadatumById(iptcKey.tag(),
iptcKey.record()));
}
else
{
// Append a new value
Exiv2::Iptcdatum iptcDatum(iptcKey);
iptcDatum.setValue(value);
int state = _iptcData.add(iptcDatum);
if (state == 6)
{
throw Exiv2::Error(NON_REPEATABLE);
}
}
}
// Erase the remaining values if any
while (dataIterator != _iptcData.end())
{
_iptcData.erase(dataIterator);
dataIterator = std::find_if(dataIterator, _iptcData.end(),
Exiv2::FindMetadatumById::FindMetadatumById(iptcKey.tag(),
iptcKey.record()));
}
}
void Image::deleteIptcTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key);
Exiv2::IptcMetadata::iterator dataIterator = _iptcData.findKey(iptcKey);
if (dataIterator == _iptcData.end())
{
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
while (dataIterator != _iptcData.end())
{
_iptcData.erase(dataIterator);
dataIterator = std::find_if(++dataIterator, _iptcData.end(),
Exiv2::FindMetadatumById::FindMetadatumById(iptcKey.tag(),
iptcKey.record()));
}
}
boost::python::list Image::xmpKeys()
{
CHECK_METADATA_READ
boost::python::list keys;
for(Exiv2::XmpMetadata::iterator i = _xmpData.begin();
i != _xmpData.end();
++i)
{
keys.append(i->key());
}
return keys;
}
const XmpTag Image::getXmpTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::XmpKey xmpKey = Exiv2::XmpKey(key);
if(_xmpData.findKey(xmpKey) == _xmpData.end())
{
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
return XmpTag(key, &_xmpData[key]);
}
void Image::setXmpTagTextValue(const std::string& key, const std::string& value)
{
CHECK_METADATA_READ
_xmpData[key].setValue(value);
}
void Image::setXmpTagArrayValue(const std::string& key, const boost::python::list& values)
{
CHECK_METADATA_READ
Exiv2::Xmpdatum& datum = _xmpData[key];
// Reset the value
datum.setValue(0);
for(boost::python::stl_input_iterator<std::string> iterator(values);
iterator != boost::python::stl_input_iterator<std::string>();
++iterator)
{
datum.setValue(*iterator);
}
}
void Image::setXmpTagLangAltValue(const std::string& key, const boost::python::dict& values)
{
CHECK_METADATA_READ
Exiv2::Xmpdatum& datum = _xmpData[key];
// Reset the value
datum.setValue(0);
for(boost::python::stl_input_iterator<std::string> iterator(values);
iterator != boost::python::stl_input_iterator<std::string>();
++iterator)
{
std::string key = *iterator;
std::string value = boost::python::extract<std::string>(values.get(key));
datum.setValue("lang=\"" + key + "\" " + value);
}
}
void Image::deleteXmpTag(std::string key)
{
CHECK_METADATA_READ
Exiv2::XmpKey xmpKey = Exiv2::XmpKey(key);
Exiv2::XmpMetadata::iterator i = _xmpData.findKey(xmpKey);
if(i != _xmpData.end())
{
_xmpData.erase(i);
}
else
throw Exiv2::Error(KEY_NOT_FOUND, key);
}
/*
boost::python::tuple Image::getThumbnailData()
{
CHECK_METADATA_READ
Exiv2::Thumbnail::AutoPtr thumbnail = _exifData.getThumbnail();
if (thumbnail.get() != 0)
{
std::string format(_exifData.thumbnailFormat());
// Copy the data buffer in a string. Since the data buffer can
// contain null char ('\x00'), the string cannot be simply
// constructed like that:
// std::string data((char*) dataBuffer.pData_);
// because it would be truncated after the first occurence of a
// null char. Therefore, it has to be copied char by char.
Exiv2::DataBuf dataBuffer = _exifData.copyThumbnail();
char* charData = (char*) dataBuffer.pData_;
long dataLen = dataBuffer.size_;
// First allocate the memory for the whole string...
std::string data(dataLen, ' ');
// ... then fill it with the raw jpeg data.
for(long i = 0; i < dataLen; ++i)
{
data[i] = charData[i];
}
return boost::python::make_tuple(format, data);
}
else
throw Exiv2::Error(THUMB_ACCESS);
}
void Image::setThumbnailData(std::string data)
{
CHECK_METADATA_READ
const Exiv2::byte* dataBuf = (const Exiv2::byte*) data.c_str();
_exifData.setJpegThumbnail(dataBuf, data.size());
}
void Image::deleteThumbnail()
{
CHECK_METADATA_READ
_exifData.eraseThumbnail();
}
void Image::dumpThumbnailToFile(const std::string path)
{
CHECK_METADATA_READ
int result = _exifData.writeThumbnail(path);
if (result == 8)
throw Exiv2::Error(NO_THUMBNAIL);
}
void Image::setThumbnailFromJpegFile(const std::string path)
{
CHECK_METADATA_READ
_exifData.setJpegThumbnail(path);
}
*/
ExifTag::ExifTag(const std::string& key, Exiv2::Exifdatum* datum): _key(key)
{
if (datum != 0)
{
_datum = datum;
}
else
{
_datum = new Exiv2::Exifdatum(_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);
_label = Exiv2::ExifTags::tagLabel(tag, ifd);
_description = Exiv2::ExifTags::tagDesc(tag, ifd);
_sectionName = Exiv2::ExifTags::sectionName(tag, ifd);
_sectionDescription = Exiv2::ExifTags::sectionDesc(tag, ifd);
}
void ExifTag::setRawValue(const std::string& value)
{
_datum->setValue(value);
}
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::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::getRawValue()
{
return _datum->toString();
}
const std::string ExifTag::getHumanValue()
{
std::ostringstream buffer;
buffer << *_datum;
return buffer.str();
}
IptcTag::IptcTag(const std::string& key, Exiv2::IptcMetadata* data): _key(key)
{
if (data != 0)
{
_data = data;
}
else
{
_data = new Exiv2::IptcMetadata();
_data->push_back(Exiv2::Iptcdatum(_key));
}
Exiv2::IptcMetadata::iterator iterator = _data->begin();
const uint16_t tag = iterator->tag();
const uint16_t record = iterator->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);
if (!_repeatable && (_data->size() > 1))
{
// The tag is not repeatable but we are trying to assign it more than
// one value.
throw Exiv2::Error(NON_REPEATABLE);
}
}
void IptcTag::setRawValues(const boost::python::list& values)
{
if (!_repeatable && (boost::python::len(values) > 1))
{
// The tag is not repeatable but we are trying to assign it more than
// one value.
throw Exiv2::Error(NON_REPEATABLE);
}
_data->clear();
for(boost::python::stl_input_iterator<std::string> iterator(values);
iterator != boost::python::stl_input_iterator<std::string>();
++iterator)
{
Exiv2::Iptcdatum datum(_key);
datum.setValue(*iterator);
_data->push_back(datum);
}
}
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 boost::python::list IptcTag::getRawValues()
{
boost::python::list values;
for(Exiv2::IptcMetadata::iterator iterator = _data->begin();
iterator != _data->end(); ++iterator)
{
values.append(iterator->toString());
}
return values;
}
XmpTag::XmpTag(const std::string& key, Exiv2::Xmpdatum* datum): _key(key)
{
if (datum != 0)
{
_datum = datum;
}
else
{
_datum = new Exiv2::Xmpdatum(_key);
}
_exiv2_type = Exiv2::TypeInfo::typeName(Exiv2::XmpProperties::propertyType(_key));
const char* title = Exiv2::XmpProperties::propertyTitle(_key);
if (title != 0)
{
_title = title;
}
const char* description = Exiv2::XmpProperties::propertyDesc(_key);
if (description != 0)
{
_description = description;
}
const Exiv2::XmpPropertyInfo* info = Exiv2::XmpProperties::propertyInfo(_key);
if (info != 0)
{
_name = info->name_;
_type = info->xmpValueType_;
}
}
void XmpTag::setTextValue(const std::string& value)
{
_datum->setValue(value);
}
void XmpTag::setArrayValue(const boost::python::list& values)
{
// Reset the value
_datum->setValue(0);
for(boost::python::stl_input_iterator<std::string> iterator(values);
iterator != boost::python::stl_input_iterator<std::string>();
++iterator)
{
_datum->setValue(*iterator);
}
}
void XmpTag::setLangAltValue(const boost::python::dict& values)
{
// Reset the value
_datum->setValue(0);
for(boost::python::stl_input_iterator<std::string> iterator(values);
iterator != boost::python::stl_input_iterator<std::string>();
++iterator)
{
std::string key = *iterator;
std::string value = boost::python::extract<std::string>(values.get(key));
_datum->setValue("lang=\"" + key + "\" " + value);
}
}
const std::string XmpTag::getKey()
{
return _key.key();
}
const std::string XmpTag::getExiv2Type()
{
return _exiv2_type;
}
const std::string XmpTag::getType()
{
return _type;
}
const std::string XmpTag::getName()
{
return _name;
}
const std::string XmpTag::getTitle()
{
return _title;
}
const std::string XmpTag::getDescription()
{
return _description;
}
const std::string XmpTag::getTextValue()
{
return dynamic_cast<const Exiv2::XmpTextValue*>(&_datum->value())->value_;
}
const boost::python::list XmpTag::getArrayValue()
{
std::vector<std::string> value =
dynamic_cast<const Exiv2::XmpArrayValue*>(&_datum->value())->value_;
boost::python::list rvalue;
for(std::vector<std::string>::const_iterator i = value.begin();
i != value.end(); ++i)
{
rvalue.append(*i);
}
return rvalue;
}
const boost::python::dict XmpTag::getLangAltValue()
{
Exiv2::LangAltValue::ValueType value =
dynamic_cast<const Exiv2::LangAltValue*>(&_datum->value())->value_;
boost::python::dict rvalue;
for (Exiv2::LangAltValue::ValueType::const_iterator i = value.begin();
i != value.end(); ++i)
{
rvalue[i->first] = i->second;
}
return rvalue;
}
// TODO: update the errors code to reflect changes from src/error.cpp in libexiv2
void translateExiv2Error(Exiv2::Error const& error)
{
// Use the Python 'C' API to set up an exception object
// Building a C++ string first allows this code to compile with all
// versions of libexiv2 (< 0.13 and >= 0.13), because the way exceptions
// are handled in libexiv2 was changed in 0.13.
const std::string sMessage(error.what());
const char* message = sMessage.c_str();
// The type of the Python exception depends on the error code
// Warning: this piece of code should be updated in case the error codes
// defined by Exiv2 (file 'src/error.cpp') are changed
switch (error.code())
{
case -2:
case -1:
case 1:
case 2:
PyErr_SetString(PyExc_RuntimeError, message);
break;
case 3:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 17:
case 18:
case 20:
case 21:
case 23:
case 31:
case 32:
case 33:
case 36:
case 37:
PyErr_SetString(PyExc_IOError, message);
break;
case 4:
case 5:
case 6:
case 7:
PyErr_SetString(PyExc_IndexError, message);
break;
case 8:
case 22:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 34:
PyErr_SetString(PyExc_ValueError, message);
break;
case 16:
case 19:
case 35:
PyErr_SetString(PyExc_MemoryError, message);
break;
// custom error codes
case METADATA_NOT_READ:
PyErr_SetString(PyExc_IOError, "Image metadata has not been read yet");
break;
case NON_REPEATABLE:
PyErr_SetString(PyExc_KeyError, "Tag is not repeatable");
break;
case KEY_NOT_FOUND:
PyErr_SetString(PyExc_KeyError, "Tag not set");
break;
case THUMB_ACCESS:
PyErr_SetString(PyExc_IOError, "Cannot access image thumbnail");
break;
case NO_THUMBNAIL:
PyErr_SetString(PyExc_IOError, "The EXIF data does not contain a thumbnail");
break;
default:
PyErr_SetString(PyExc_RuntimeError, message);
}
}
} // End of namespace exiv2wrapper