You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2430 lines
70 KiB
2430 lines
70 KiB
/* ============================================================
|
|
*
|
|
* This file is a part of kipi-plugins project
|
|
* http://www.kipi-plugins.org
|
|
*
|
|
* Date : 2006-09-15
|
|
* Description : Exiv2 library interface for KDE
|
|
*
|
|
* Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
* Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
|
|
*
|
|
* NOTE: Do not use kdDebug() in this implementation because
|
|
* it will be multithreaded. Use qDebug() instead.
|
|
* See B.K.O #133026 for details.
|
|
*
|
|
* This program 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, or (at your option) any later version.
|
|
*
|
|
* This program 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.
|
|
*
|
|
* ============================================================ */
|
|
|
|
// C++ includes.
|
|
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
// Qt includes.
|
|
|
|
#include <qfile.h>
|
|
#include <qimage.h>
|
|
#include <qsize.h>
|
|
#include <qtextcodec.h>
|
|
#include <qwmatrix.h>
|
|
#include <qfileinfo.h>
|
|
|
|
// KDE includes.
|
|
|
|
#include <ktempfile.h>
|
|
#include <kstringhandler.h>
|
|
#include <kdeversion.h>
|
|
|
|
// Exiv2 includes.
|
|
|
|
// The pragmas are required to be able to catch exceptions thrown by libexiv2:
|
|
// See http://gcc.gnu.org/wiki/Visibility, the section about c++ exceptions.
|
|
// They are needed for all libexiv2 versions that do not care about visibility.
|
|
#pragma GCC visibility push(default)
|
|
#include <exiv2/error.hpp>
|
|
#include <exiv2/image.hpp>
|
|
#include <exiv2/jpgimage.hpp>
|
|
#include <exiv2/datasets.hpp>
|
|
#include <exiv2/tags.hpp>
|
|
#include <exiv2/types.hpp>
|
|
#include <exiv2/exif.hpp>
|
|
#pragma GCC visibility pop
|
|
|
|
// Make sure an EXIV2_TEST_VERSION macro exists:
|
|
|
|
#ifdef EXIV2_VERSION
|
|
# ifndef EXIV2_TEST_VERSION
|
|
# define EXIV2_TEST_VERSION(major,minor,patch) \
|
|
( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) )
|
|
# endif
|
|
#else
|
|
# define EXIV2_TEST_VERSION(major,minor,patch) (false)
|
|
#endif
|
|
|
|
// Local includes.
|
|
|
|
#include "version.h"
|
|
#include "kexiv2.h"
|
|
|
|
namespace KExiv2Iface
|
|
{
|
|
|
|
class KExiv2Priv
|
|
{
|
|
public:
|
|
|
|
KExiv2Priv(){}
|
|
|
|
QString filePath;
|
|
|
|
std::string imageComments;
|
|
|
|
Exiv2::ExifData exifMetadata;
|
|
|
|
Exiv2::IptcData iptcMetadata;
|
|
|
|
};
|
|
|
|
KExiv2::KExiv2()
|
|
{
|
|
d = new KExiv2Priv;
|
|
}
|
|
|
|
KExiv2::KExiv2(const QString& filePath)
|
|
{
|
|
d = new KExiv2Priv;
|
|
load(filePath);
|
|
}
|
|
|
|
KExiv2::~KExiv2()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
QString KExiv2::version()
|
|
{
|
|
return QString( kexiv2_version );
|
|
}
|
|
|
|
QString KExiv2::Exiv2Version()
|
|
{
|
|
// Since 0.14.0 release, we can extract run-time version of Exiv2.
|
|
// else we return make version.
|
|
|
|
#if (EXIV2_TEST_VERSION(0,14,0))
|
|
return QString(Exiv2::version());
|
|
#else
|
|
return QString("%1.%2.%3").arg(EXIV2_MAJOR_VERSION)
|
|
.arg(EXIV2_MINOR_VERSION)
|
|
.arg(EXIV2_PATCH_VERSION);
|
|
#endif
|
|
}
|
|
|
|
void KExiv2::printExiv2ExceptionError(const QString& msg, Exiv2::Error& e)
|
|
{
|
|
std::string s(e.what());
|
|
qDebug("%s (Error #%i: %s)", msg.ascii(), e.code(), s.c_str());
|
|
}
|
|
|
|
std::string& KExiv2::commentsMetaData()
|
|
{
|
|
return d->imageComments;
|
|
}
|
|
|
|
Exiv2::ExifData& KExiv2::exifMetaData()
|
|
{
|
|
return d->exifMetadata;
|
|
}
|
|
|
|
Exiv2::IptcData& KExiv2::iptcMetaData()
|
|
{
|
|
return d->iptcMetadata;
|
|
}
|
|
|
|
bool KExiv2::clearComments()
|
|
{
|
|
return setComments(QByteArray());
|
|
}
|
|
|
|
bool KExiv2::clearExif()
|
|
{
|
|
try
|
|
{
|
|
d->exifMetadata.clear();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot clear Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::clearIptc()
|
|
{
|
|
try
|
|
{
|
|
d->iptcMetadata.clear();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot clear Iptc data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::getFilePath() const
|
|
{
|
|
return d->filePath;
|
|
}
|
|
|
|
QByteArray KExiv2::getComments() const
|
|
{
|
|
return QByteArray().duplicate(d->imageComments.data(), d->imageComments.size());
|
|
}
|
|
|
|
QString KExiv2::getCommentsDecoded() const
|
|
{
|
|
return detectEncodingAndDecode(getCommentsString());
|
|
}
|
|
|
|
std::string KExiv2::getCommentsString() const
|
|
{
|
|
return d->imageComments;
|
|
}
|
|
|
|
QByteArray KExiv2::getExif() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
|
|
Exiv2::ExifData& exif = d->exifMetadata;
|
|
Exiv2::DataBuf c2 = exif.copy();
|
|
QByteArray data(c2.size_);
|
|
if (data.size())
|
|
memcpy(data.data(), c2.pData_, c2.size_);
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot get Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return QByteArray();
|
|
}
|
|
|
|
QByteArray KExiv2::getIptc(bool addIrbHeader) const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
Exiv2::IptcData& iptc = d->iptcMetadata;
|
|
Exiv2::DataBuf c2;
|
|
|
|
if (addIrbHeader)
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,10,0))
|
|
c2 = Exiv2::Photoshop::setIptcIrb(0, 0, iptc);
|
|
#else
|
|
qDebug("Exiv2 version is to old. Cannot add Irb header to IPTC metadata");
|
|
return QByteArray();
|
|
#endif
|
|
}
|
|
else
|
|
c2 = iptc.copy();
|
|
|
|
QByteArray data(c2.size_);
|
|
if (data.size())
|
|
memcpy(data.data(), c2.pData_, c2.size_);
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot get Iptc data using Exiv2 ",e);
|
|
}
|
|
|
|
return QByteArray();
|
|
}
|
|
|
|
bool KExiv2::setComments(const QByteArray& data)
|
|
{
|
|
d->imageComments = std::string(data.data(), data.size());
|
|
return true;
|
|
}
|
|
|
|
bool KExiv2::setExif(const QByteArray& data)
|
|
{
|
|
try
|
|
{
|
|
if (!data.isEmpty())
|
|
{
|
|
if (d->exifMetadata.load((const Exiv2::byte*)data.data(), data.size()) != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot set Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setIptc(const QByteArray& data)
|
|
{
|
|
try
|
|
{
|
|
if (!data.isEmpty())
|
|
{
|
|
if (d->iptcMetadata.load((const Exiv2::byte*)data.data(), data.size()) != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot set Iptc data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setExif(Exiv2::DataBuf const data)
|
|
{
|
|
try
|
|
{
|
|
if (data.size_ != 0)
|
|
{
|
|
if (d->exifMetadata.load(data.pData_, data.size_) != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot set Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setIptc(Exiv2::DataBuf const data)
|
|
{
|
|
try
|
|
{
|
|
if (data.size_ != 0)
|
|
{
|
|
if (d->iptcMetadata.load(data.pData_, data.size_) != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
qDebug ("From file %s", d->filePath.ascii());
|
|
|
|
printExiv2ExceptionError("Cannot set Iptc data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::load(const QString& filePath)
|
|
{
|
|
QFileInfo finfo(filePath);
|
|
if (filePath.isEmpty() || !finfo.isReadable())
|
|
{
|
|
qDebug("File '%s' is not readable.", finfo.fileName().ascii());
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(QFile::encodeName(filePath)));
|
|
image->readMetadata();
|
|
|
|
// Image comments ---------------------------------
|
|
|
|
d->imageComments = image->comment();
|
|
|
|
// Exif metadata ----------------------------------
|
|
|
|
d->exifMetadata = image->exifData();
|
|
|
|
// Iptc metadata ----------------------------------
|
|
|
|
d->iptcMetadata = image->iptcData();
|
|
|
|
d->filePath = filePath;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::save(const QString& filePath)
|
|
{
|
|
if (filePath.isEmpty())
|
|
return false;
|
|
|
|
// NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only.
|
|
QFileInfo finfo(filePath);
|
|
QFileInfo dinfo(finfo.dirPath());
|
|
if (!finfo.isWritable())
|
|
{
|
|
qDebug("File '%s' is read-only. Metadata not saved.", finfo.fileName().ascii());
|
|
return false;
|
|
}
|
|
if (!dinfo.isWritable())
|
|
{
|
|
qDebug("Dir '%s' is read-only. Metadata not saved.", dinfo.filePath().ascii());
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(QFile::encodeName(filePath)));
|
|
|
|
// Image Comments ---------------------------------
|
|
|
|
if (!d->imageComments.empty())
|
|
{
|
|
image->setComment(d->imageComments);
|
|
}
|
|
|
|
// Exif metadata ----------------------------------
|
|
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
image->setExifData(d->exifMetadata);
|
|
}
|
|
|
|
// Iptc metadata ----------------------------------
|
|
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
image->setIptcData(d->iptcMetadata);
|
|
}
|
|
|
|
image->writeMetadata();
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot save metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::applyChanges()
|
|
{
|
|
return save(d->filePath);
|
|
}
|
|
|
|
bool KExiv2::isReadOnly(const QString& filePath)
|
|
{
|
|
QFileInfo fi(filePath);
|
|
QString ext = fi.extension(false).upper();
|
|
|
|
if (ext != QString("JPG") && ext != QString("JPEG") && ext != QString("JPE"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setImageProgramId(const QString& program, const QString& version)
|
|
{
|
|
try
|
|
{
|
|
// Record program info in Exif.Image.ProcessingSoftware tag (only available with Exiv2 >= 0.14.0).
|
|
|
|
#if (EXIV2_TEST_VERSION(0,14,0))
|
|
QString software(program);
|
|
software.append("-");
|
|
software.append(version);
|
|
d->exifMetadata["Exif.Image.ProcessingSoftware"] = software.ascii();
|
|
#endif
|
|
|
|
// See B.K.O #142564: Check if Exif.Image.Software already exist. If yes, do not touch this tag.
|
|
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifKey key("Exif.Image.Software");
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
|
|
if (it == exifData.end())
|
|
{
|
|
QString software(program);
|
|
software.append("-");
|
|
software.append(version);
|
|
d->exifMetadata["Exif.Image.Software"] = software.ascii();
|
|
}
|
|
}
|
|
|
|
// Record program info in IPTC tags.
|
|
|
|
d->iptcMetadata["Iptc.Application2.Program"] = program.ascii();
|
|
d->iptcMetadata["Iptc.Application2.ProgramVersion"] = version.ascii();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Program identity into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QSize KExiv2::getImageDimensions() const
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return QSize();
|
|
|
|
try
|
|
{
|
|
long width=-1, height=-1;
|
|
|
|
// Try to get Exif.Photo tags
|
|
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifKey key("Exif.Photo.PixelXDimension");
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
|
|
if (it != exifData.end())
|
|
width = it->toLong();
|
|
|
|
Exiv2::ExifKey key2("Exif.Photo.PixelYDimension");
|
|
Exiv2::ExifData::iterator it2 = exifData.findKey(key2);
|
|
|
|
if (it2 != exifData.end())
|
|
height = it2->toLong();
|
|
|
|
if (width != -1 && height != -1)
|
|
return QSize(width, height);
|
|
|
|
// Try to get Exif.Image tags
|
|
|
|
width=-1;
|
|
height=-1;
|
|
|
|
Exiv2::ExifKey key3("Exif.Image.ImageWidth");
|
|
Exiv2::ExifData::iterator it3 = exifData.findKey(key3);
|
|
|
|
if (it3 != exifData.end())
|
|
width = it3->toLong();
|
|
|
|
Exiv2::ExifKey key4("Exif.Image.ImageLength");
|
|
Exiv2::ExifData::iterator it4 = exifData.findKey(key4);
|
|
|
|
if (it4 != exifData.end())
|
|
height = it4->toLong();
|
|
|
|
if (width != -1 && height != -1)
|
|
return QSize(width, height);
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot parse image dimensions tag using Exiv2 ", e);
|
|
}
|
|
|
|
return QSize();
|
|
}
|
|
|
|
bool KExiv2::setImageDimensions(const QSize& size, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// NOTE: see B.K.O #144604: you a cast to record an unsigned integer value.
|
|
d->exifMetadata["Exif.Image.ImageWidth"] = static_cast<uint32_t>(size.width());
|
|
d->exifMetadata["Exif.Image.ImageLength"] = static_cast<uint32_t>(size.height());
|
|
d->exifMetadata["Exif.Photo.PixelXDimension"] = static_cast<uint32_t>(size.width());
|
|
d->exifMetadata["Exif.Photo.PixelYDimension"] = static_cast<uint32_t>(size.height());
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set image dimensions using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QImage KExiv2::getExifThumbnail(bool fixOrientation) const
|
|
{
|
|
QImage thumbnail;
|
|
|
|
if (d->exifMetadata.empty())
|
|
return thumbnail;
|
|
|
|
try
|
|
{
|
|
Exiv2::DataBuf const c1(d->exifMetadata.copyThumbnail());
|
|
thumbnail.loadFromData(c1.pData_, c1.size_);
|
|
|
|
if (!thumbnail.isNull())
|
|
{
|
|
if (fixOrientation)
|
|
{
|
|
Exiv2::ExifKey key("Exif.Thumbnail.Orientation");
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
if (it != exifData.end())
|
|
{
|
|
QWMatrix matrix;
|
|
long orientation = it->toLong();
|
|
qDebug("Exif Thumbnail Orientation: %i", (int)orientation);
|
|
|
|
switch (orientation)
|
|
{
|
|
case ORIENTATION_HFLIP:
|
|
matrix.scale(-1, 1);
|
|
break;
|
|
|
|
case ORIENTATION_ROT_180:
|
|
matrix.rotate(180);
|
|
break;
|
|
|
|
case ORIENTATION_VFLIP:
|
|
matrix.scale(1, -1);
|
|
break;
|
|
|
|
case ORIENTATION_ROT_90_HFLIP:
|
|
matrix.scale(-1, 1);
|
|
matrix.rotate(90);
|
|
break;
|
|
|
|
case ORIENTATION_ROT_90:
|
|
matrix.rotate(90);
|
|
break;
|
|
|
|
case ORIENTATION_ROT_90_VFLIP:
|
|
matrix.scale(1, -1);
|
|
matrix.rotate(90);
|
|
break;
|
|
|
|
case ORIENTATION_ROT_270:
|
|
matrix.rotate(270);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( orientation != ORIENTATION_NORMAL )
|
|
thumbnail = thumbnail.xForm( matrix );
|
|
}
|
|
|
|
return thumbnail;
|
|
}
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get Exif Thumbnail using Exiv2 ", e);
|
|
}
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
bool KExiv2::setExifThumbnail(const QImage& thumb, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
KTempFile thumbFile(QString(), "KExiv2ExifThumbnail");
|
|
thumbFile.setAutoDelete(true);
|
|
thumb.save(thumbFile.name(), "JPEG");
|
|
|
|
const std::string &fileName( (const char*)(QFile::encodeName(thumbFile.name())) );
|
|
d->exifMetadata.setJpegThumbnail( fileName );
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif Thumbnail using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
KExiv2::ImageOrientation KExiv2::getImageOrientation() const
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return ORIENTATION_UNSPECIFIED;
|
|
|
|
// Workaround for older Exiv2 versions which do not support
|
|
// Minolta Makernotes and throw an error for such keys.
|
|
bool supportMinolta = true;
|
|
try
|
|
{
|
|
Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
|
|
Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
supportMinolta = false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it;
|
|
long orientation;
|
|
ImageOrientation imageOrient = ORIENTATION_NORMAL;
|
|
|
|
// Because some camera set a wrong standard exif orientation tag,
|
|
// We need to check makernote tags in first!
|
|
|
|
// -- Minolta Cameras ----------------------------------
|
|
|
|
if (supportMinolta)
|
|
{
|
|
Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
|
|
it = exifData.findKey(minoltaKey1);
|
|
|
|
if (it != exifData.end())
|
|
{
|
|
orientation = it->toLong();
|
|
qDebug("Minolta Makernote Orientation: %i", (int)orientation);
|
|
switch(orientation)
|
|
{
|
|
case 76:
|
|
imageOrient = ORIENTATION_ROT_90;
|
|
break;
|
|
case 82:
|
|
imageOrient = ORIENTATION_ROT_270;
|
|
break;
|
|
}
|
|
return imageOrient;
|
|
}
|
|
|
|
Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
|
|
it = exifData.findKey(minoltaKey2);
|
|
|
|
if (it != exifData.end())
|
|
{
|
|
orientation = it->toLong();
|
|
qDebug("Minolta Makernote Orientation: %i", (int)orientation);
|
|
switch(orientation)
|
|
{
|
|
case 76:
|
|
imageOrient = ORIENTATION_ROT_90;
|
|
break;
|
|
case 82:
|
|
imageOrient = ORIENTATION_ROT_270;
|
|
break;
|
|
}
|
|
return imageOrient;
|
|
}
|
|
}
|
|
|
|
// -- Standard Exif tag --------------------------------
|
|
|
|
Exiv2::ExifKey keyStd("Exif.Image.Orientation");
|
|
it = exifData.findKey(keyStd);
|
|
|
|
if (it != exifData.end())
|
|
{
|
|
orientation = it->toLong();
|
|
qDebug("Exif Orientation: %i", (int)orientation);
|
|
return (ImageOrientation)orientation;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot parse Exif Orientation tag using Exiv2 ", e);
|
|
}
|
|
|
|
return ORIENTATION_UNSPECIFIED;
|
|
}
|
|
|
|
bool KExiv2::setImageOrientation(ImageOrientation orientation, bool setProgramName)
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return false;
|
|
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
// Workaround for older Exiv2 versions which do not support
|
|
// Minolta Makernotes and throw an error for such keys.
|
|
bool supportMinolta = true;
|
|
try
|
|
{
|
|
Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
|
|
Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
supportMinolta = false;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (orientation < ORIENTATION_UNSPECIFIED || orientation > ORIENTATION_ROT_270)
|
|
{
|
|
qDebug("Exif orientation tag value is not correct!");
|
|
return false;
|
|
}
|
|
|
|
d->exifMetadata["Exif.Image.Orientation"] = static_cast<uint16_t>(orientation);
|
|
qDebug("Exif orientation tag set to: %i", (int)orientation);
|
|
|
|
// -- Minolta Cameras ----------------------------------
|
|
|
|
if (supportMinolta)
|
|
{
|
|
// Minolta camera store image rotation in Makernote.
|
|
// We remove these information to prevent duplicate values.
|
|
|
|
Exiv2::ExifData::iterator it;
|
|
|
|
Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
|
|
it = d->exifMetadata.findKey(minoltaKey1);
|
|
if (it != d->exifMetadata.end())
|
|
{
|
|
d->exifMetadata.erase(it);
|
|
qDebug("Removing Exif.MinoltaCs7D.Rotation tag");
|
|
}
|
|
|
|
Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
|
|
it = d->exifMetadata.findKey(minoltaKey2);
|
|
if (it != d->exifMetadata.end())
|
|
{
|
|
d->exifMetadata.erase(it);
|
|
qDebug("Removing Exif.MinoltaCs5D.Rotation tag");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif Orientation tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
KExiv2::ImageColorWorkSpace KExiv2::getImageColorWorkSpace() const
|
|
{
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
long colorSpace;
|
|
|
|
if (getExifTagLong("Exif.Photo.ColorSpace", colorSpace))
|
|
{
|
|
switch (colorSpace)
|
|
{
|
|
case 1:
|
|
{
|
|
return WORKSPACE_SRGB;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
return WORKSPACE_ADOBERGB;
|
|
break;
|
|
}
|
|
case 65535:
|
|
{
|
|
// Nikon camera set Exif.Photo.ColorSpace to uncalibrated and
|
|
// Exif.Nikon3.ColorMode to "MODE2" when users work in AdobRGB color space.
|
|
if (getExifTagString("Exif.Nikon3.ColorMode").contains("MODE2"))
|
|
return WORKSPACE_ADOBERGB;
|
|
|
|
// TODO : add more Makernote parsing here ...
|
|
|
|
return WORKSPACE_UNCALIBRATED;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
return WORKSPACE_UNSPECIFIED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return WORKSPACE_UNSPECIFIED;
|
|
}
|
|
|
|
bool KExiv2::setImageColorWorkSpace(ImageColorWorkSpace workspace, bool setProgramName)
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return false;
|
|
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->exifMetadata["Exif.Photo.ColorSpace"] = static_cast<uint16_t>(workspace);
|
|
qDebug("Exif color workspace tag set to: %i", (int)workspace);
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif color workspace tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QDateTime KExiv2::getImageDateTime() const
|
|
{
|
|
try
|
|
{
|
|
// In first, trying to get Date & time from Exif tags.
|
|
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
// Try Exif date time original.
|
|
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifKey key2("Exif.Photo.DateTimeOriginal");
|
|
Exiv2::ExifData::iterator it2 = exifData.findKey(key2);
|
|
|
|
if (it2 != exifData.end())
|
|
{
|
|
QDateTime dateTime = QDateTime::fromString(it2->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// qDebug("DateTime (Exif original): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
|
|
// Bogus Exif date time original entry. Try Exif date time digitized.
|
|
|
|
Exiv2::ExifKey key3("Exif.Photo.DateTimeDigitized");
|
|
Exiv2::ExifData::iterator it3 = exifData.findKey(key3);
|
|
|
|
if (it3 != exifData.end())
|
|
{
|
|
QDateTime dateTime = QDateTime::fromString(it3->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// qDebug("DateTime (Exif digitalized): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
|
|
// Bogus Exif date time digitized. Try standard Exif date time entry.
|
|
|
|
Exiv2::ExifKey key("Exif.Image.DateTime");
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
|
|
if (it != exifData.end())
|
|
{
|
|
QDateTime dateTime = QDateTime::fromString(it->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// qDebug("DateTime (Exif standard): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// In second, trying to get Date & time from Iptc tags.
|
|
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
// Try creation Iptc date time entries.
|
|
|
|
Exiv2::IptcKey keyDateCreated("Iptc.Application2.DateCreated");
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.findKey(keyDateCreated);
|
|
|
|
if (it != iptcData.end())
|
|
{
|
|
QString IptcDateCreated(it->toString().c_str());
|
|
|
|
Exiv2::IptcKey keyTimeCreated("Iptc.Application2.TimeCreated");
|
|
Exiv2::IptcData::iterator it2 = iptcData.findKey(keyTimeCreated);
|
|
|
|
if (it2 != iptcData.end())
|
|
{
|
|
QString IptcTimeCreated(it2->toString().c_str());
|
|
|
|
QDate date = QDate::fromString(IptcDateCreated, Qt::ISODate);
|
|
QTime time = QTime::fromString(IptcTimeCreated, Qt::ISODate);
|
|
QDateTime dateTime = QDateTime(date, time);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// qDebug("Date (IPTC created): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try digitization Iptc date time entries.
|
|
|
|
Exiv2::IptcKey keyDigitizationDate("Iptc.Application2.DigitizationDate");
|
|
Exiv2::IptcData::iterator it3 = iptcData.findKey(keyDigitizationDate);
|
|
|
|
if (it3 != iptcData.end())
|
|
{
|
|
QString IptcDateDigitization(it3->toString().c_str());
|
|
|
|
Exiv2::IptcKey keyDigitizationTime("Iptc.Application2.DigitizationTime");
|
|
Exiv2::IptcData::iterator it4 = iptcData.findKey(keyDigitizationTime);
|
|
|
|
if (it4 != iptcData.end())
|
|
{
|
|
QString IptcTimeDigitization(it4->toString().c_str());
|
|
|
|
QDate date = QDate::fromString(IptcDateDigitization, Qt::ISODate);
|
|
QTime time = QTime::fromString(IptcTimeDigitization, Qt::ISODate);
|
|
QDateTime dateTime = QDateTime(date, time);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
//qDebug("Date (IPTC digitalized): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot parse Exif date & time tag using Exiv2 ", e);
|
|
}
|
|
|
|
return QDateTime();
|
|
}
|
|
|
|
bool KExiv2::setImageDateTime(const QDateTime& dateTime, bool setDateTimeDigitized, bool setProgramName)
|
|
{
|
|
if(!dateTime.isValid())
|
|
return false;
|
|
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// In first we write date & time into Exif.
|
|
|
|
// DateTimeDigitized is set by slide scanners etc. when a picture is digitized.
|
|
// DateTimeOriginal specifies the date/time when the picture was taken.
|
|
// For digital cameras, these dates should be both set, and identical.
|
|
// Reference: http://www.exif.org/Exif2-2.PDF, chapter 4.6.5, table 4, section F.
|
|
|
|
const std::string &exifdatetime(dateTime.toString(QString("yyyy:MM:dd hh:mm:ss")).ascii());
|
|
d->exifMetadata["Exif.Image.DateTime"] = exifdatetime;
|
|
d->exifMetadata["Exif.Photo.DateTimeOriginal"] = exifdatetime;
|
|
if(setDateTimeDigitized)
|
|
d->exifMetadata["Exif.Photo.DateTimeDigitized"] = exifdatetime;
|
|
|
|
// In Second we write date & time into Iptc.
|
|
|
|
const std::string &iptcdate(dateTime.date().toString(Qt::ISODate).ascii());
|
|
const std::string &iptctime(dateTime.time().toString(Qt::ISODate).ascii());
|
|
d->iptcMetadata["Iptc.Application2.DateCreated"] = iptcdate;
|
|
d->iptcMetadata["Iptc.Application2.TimeCreated"] = iptctime;
|
|
if(setDateTimeDigitized)
|
|
{
|
|
d->iptcMetadata["Iptc.Application2.DigitizationDate"] = iptcdate;
|
|
d->iptcMetadata["Iptc.Application2.DigitizationTime"] = iptctime;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Date & Time into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::getImagePreview(QImage& preview) const
|
|
{
|
|
try
|
|
{
|
|
// In first we trying to get from Iptc preview tag.
|
|
if (preview.loadFromData(getIptcTagData("Iptc.Application2.Preview")) )
|
|
return true;
|
|
|
|
// TODO : Added here Makernotes preview extraction when Exiv2 will be fixed for that.
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setImagePreview(const QImage& preview, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
KTempFile previewFile(QString(), "KExiv2ImagePreview");
|
|
previewFile.setAutoDelete(true);
|
|
// A little bit compressed preview jpeg image to limit IPTC size.
|
|
preview.save(previewFile.name(), "JPEG");
|
|
|
|
QFile file(previewFile.name());
|
|
if ( !file.open(IO_ReadOnly) )
|
|
return false;
|
|
|
|
qDebug("JPEG image preview size: (%i x %i) pixels - %i bytes",
|
|
preview.width(), preview.height(), (int)file.size());
|
|
|
|
QByteArray data(file.size());
|
|
QDataStream stream( &file );
|
|
stream.readRawBytes(data.data(), data.size());
|
|
file.close();
|
|
|
|
Exiv2::DataValue val;
|
|
val.read((Exiv2::byte *)data.data(), data.size());
|
|
d->iptcMetadata["Iptc.Application2.Preview"] = val;
|
|
|
|
// See http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf Appendix A for details.
|
|
d->iptcMetadata["Iptc.Application2.PreviewFormat"] = 11; // JPEG
|
|
d->iptcMetadata["Iptc.Application2.PreviewVersion"] = 1;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::getExifTagString(const char* exifTagName, bool escapeCR) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::ExifKey exifKey(exifTagName);
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
|
|
if (it != exifData.end())
|
|
{
|
|
std::ostringstream os;
|
|
os << *it;
|
|
QString tagValue = QString::fromLocal8Bit(os.str().c_str());
|
|
|
|
if (escapeCR)
|
|
tagValue.replace("\n", " ");
|
|
|
|
return tagValue;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
bool KExiv2::setExifTagString(const char *exifTagName, const QString& value, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->exifMetadata[exifTagName] = value.ascii();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif tag string into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::getIptcTagString(const char* iptcTagName, bool escapeCR) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::IptcKey iptcKey(iptcTagName);
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
|
|
if (it != iptcData.end())
|
|
{
|
|
std::ostringstream os;
|
|
os << *it;
|
|
QString tagValue = QString::fromLocal8Bit(os.str().c_str());
|
|
|
|
if (escapeCR)
|
|
tagValue.replace("\n", " ");
|
|
|
|
return tagValue;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Iptc key '%1' into image using Exiv2 ")
|
|
.arg(iptcTagName), e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
bool KExiv2::setIptcTagString(const char *iptcTagName, const QString& value, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->iptcMetadata[iptcTagName] = value.ascii();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Iptc tag string into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::getExifTagLong(const char* exifTagName, long &val) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::ExifKey exifKey(exifTagName);
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
|
|
if (it != exifData.end())
|
|
{
|
|
val = it->toLong();
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QByteArray KExiv2::getExifTagData(const char* exifTagName) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::ExifKey exifKey(exifTagName);
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
|
|
if (it != exifData.end())
|
|
{
|
|
QByteArray data((*it).size());
|
|
if (data.size())
|
|
(*it).copy((Exiv2::byte*)data.data(), exifData.byteOrder());
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return QByteArray();
|
|
}
|
|
|
|
QByteArray KExiv2::getIptcTagData(const char *iptcTagName) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::IptcKey iptcKey(iptcTagName);
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
|
|
if (it != iptcData.end())
|
|
{
|
|
QByteArray data((*it).size());
|
|
if (data.size())
|
|
(*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian);
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Iptc key '%1' into image using Exiv2 ")
|
|
.arg(iptcTagName), e);
|
|
}
|
|
|
|
return QByteArray();
|
|
}
|
|
|
|
bool KExiv2::getExifTagRational(const char *exifTagName, long int &num, long int &den, int component) const
|
|
{
|
|
try
|
|
{
|
|
Exiv2::ExifKey exifKey(exifTagName);
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
|
|
if (it != exifData.end())
|
|
{
|
|
num = (*it).toRational(component).first;
|
|
den = (*it).toRational(component).second;
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError(QString("Cannot find Exif Rational value from key '%1' "
|
|
"into image using Exiv2 ").arg(exifTagName), e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setExifTagLong(const char *exifTagName, long val, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->exifMetadata[exifTagName] = static_cast<int32_t>(val);
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif tag long value into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setExifTagRational(const char *exifTagName, long int num, long int den, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->exifMetadata[exifTagName] = Exiv2::Rational(num, den);
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif tag rational value into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setExifTagData(const char *exifTagName, const QByteArray& data, bool setProgramName)
|
|
{
|
|
if (data.isEmpty())
|
|
return false;
|
|
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Exiv2::DataValue val((Exiv2::byte *)data.data(), data.size());
|
|
d->exifMetadata[exifTagName] = val;
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif tag data into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setIptcTagData(const char *iptcTagName, const QByteArray& data, bool setProgramName)
|
|
{
|
|
if (data.isEmpty())
|
|
return false;
|
|
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Exiv2::DataValue val((Exiv2::byte *)data.data(), data.size());
|
|
d->iptcMetadata[iptcTagName] = val;
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Iptc tag data into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::removeExifTag(const char *exifTagName, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Exiv2::ExifKey exifKey(exifTagName);
|
|
Exiv2::ExifData::iterator it = d->exifMetadata.findKey(exifKey);
|
|
if (it != d->exifMetadata.end())
|
|
{
|
|
d->exifMetadata.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot remove Exif tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::removeIptcTag(const char *iptcTagName, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Exiv2::IptcKey iptcKey(iptcTagName);
|
|
Exiv2::IptcData::iterator it = d->iptcMetadata.findKey(iptcKey);
|
|
if (it != d->iptcMetadata.end())
|
|
{
|
|
d->iptcMetadata.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot remove Iptc tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::getExifTagTitle(const char *exifTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string exifkey(exifTagName);
|
|
Exiv2::ExifKey ek(exifkey);
|
|
return QString::fromLocal8Bit( Exiv2::ExifTags::tagTitle(ek.tag(), ek.ifdId()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QString KExiv2::getExifTagDescription(const char *exifTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string exifkey(exifTagName);
|
|
Exiv2::ExifKey ek(exifkey);
|
|
return QString::fromLocal8Bit( Exiv2::ExifTags::tagDesc(ek.tag(), ek.ifdId()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QString KExiv2::getIptcTagTitle(const char *iptcTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string iptckey(iptcTagName);
|
|
Exiv2::IptcKey ik(iptckey);
|
|
return QString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetTitle(ik.tag(), ik.record()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QString KExiv2::getIptcTagDescription(const char *iptcTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string iptckey(iptcTagName);
|
|
Exiv2::IptcKey ik(iptckey);
|
|
return QString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetDesc(ik.tag(), ik.record()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
KExiv2::MetaDataMap KExiv2::getExifTagsDataList(const QStringList &exifKeysFilter, bool invertSelection)
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return MetaDataMap();
|
|
|
|
try
|
|
{
|
|
Exiv2::ExifData exifData = d->exifMetadata;
|
|
exifData.sortByKey();
|
|
|
|
QString ifDItemName;
|
|
MetaDataMap metaDataMap;
|
|
|
|
for (Exiv2::ExifData::iterator md = exifData.begin(); md != exifData.end(); ++md)
|
|
{
|
|
QString key = QString::fromAscii(md->key().c_str());
|
|
|
|
// Decode the tag value with a user friendly output.
|
|
QString tagValue;
|
|
if (key == "Exif.Photo.UserComment")
|
|
{
|
|
tagValue = convertCommentValue(*md);
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream os;
|
|
os << *md;
|
|
|
|
// Exif tag contents can be an i18n strings, no only simple ascii.
|
|
tagValue = QString::fromLocal8Bit(os.str().c_str());
|
|
}
|
|
tagValue.replace("\n", " ");
|
|
|
|
// We apply a filter to get only the Exif tags that we need.
|
|
|
|
if (!invertSelection)
|
|
{
|
|
if (exifKeysFilter.contains(key.section(".", 1, 1)))
|
|
metaDataMap.insert(key, tagValue);
|
|
}
|
|
else
|
|
{
|
|
if (!exifKeysFilter.contains(key.section(".", 1, 1)))
|
|
metaDataMap.insert(key, tagValue);
|
|
}
|
|
}
|
|
|
|
return metaDataMap;
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot parse EXIF metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return MetaDataMap();
|
|
}
|
|
|
|
KExiv2::MetaDataMap KExiv2::getIptcTagsDataList(const QStringList &iptcKeysFilter, bool invertSelection)
|
|
{
|
|
if (d->iptcMetadata.empty())
|
|
return MetaDataMap();
|
|
|
|
try
|
|
{
|
|
Exiv2::IptcData iptcData = d->iptcMetadata;
|
|
iptcData.sortByKey();
|
|
|
|
QString ifDItemName;
|
|
MetaDataMap metaDataMap;
|
|
|
|
for (Exiv2::IptcData::iterator md = iptcData.begin(); md != iptcData.end(); ++md)
|
|
{
|
|
QString key = QString::fromAscii(md->key().c_str());
|
|
|
|
// Decode the tag value with a user friendly output.
|
|
std::ostringstream os;
|
|
os << *md;
|
|
QString value = QString::fromAscii(os.str().c_str());
|
|
// To make a string just on one line.
|
|
value.replace("\n", " ");
|
|
|
|
// Some IPTC key are redondancy. check if already one exist...
|
|
MetaDataMap::iterator it = metaDataMap.find(key);
|
|
|
|
// We apply a filter to get only the Exif tags that we need.
|
|
|
|
if (!invertSelection)
|
|
{
|
|
if (iptcKeysFilter.contains(key.section(".", 1, 1)))
|
|
{
|
|
if (it == metaDataMap.end())
|
|
metaDataMap.insert(key, value);
|
|
else
|
|
{
|
|
QString v = *it;
|
|
v.append(", ");
|
|
v.append(value);
|
|
metaDataMap.replace(key, v);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!iptcKeysFilter.contains(key.section(".", 1, 1)))
|
|
{
|
|
if (it == metaDataMap.end())
|
|
metaDataMap.insert(key, value);
|
|
else
|
|
{
|
|
QString v = *it;
|
|
v.append(", ");
|
|
v.append(value);
|
|
metaDataMap.replace(key, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return metaDataMap;
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
printExiv2ExceptionError("Cannot parse IPTC metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return MetaDataMap();
|
|
}
|
|
|
|
bool KExiv2::getGPSInfo(double& altitude, double& latitude, double& longitude) const
|
|
{
|
|
try
|
|
{
|
|
double num, den, min, sec;
|
|
latitude=0.0, longitude=0.0, altitude=0.0;
|
|
|
|
// Get the reference in first.
|
|
|
|
QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
|
|
if (latRef.isEmpty()) return false;
|
|
|
|
QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
|
|
if (lngRef.isEmpty()) return false;
|
|
|
|
QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");
|
|
|
|
// Latitude decoding.
|
|
|
|
Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
|
|
if (it != exifData.end())
|
|
{
|
|
num = (double)((*it).toRational(0).first);
|
|
den = (double)((*it).toRational(0).second);
|
|
latitude = num/den;
|
|
|
|
num = (double)((*it).toRational(1).first);
|
|
den = (double)((*it).toRational(1).second);
|
|
min = num/den;
|
|
if (min != -1.0)
|
|
latitude = latitude + min/60.0;
|
|
|
|
num = (double)((*it).toRational(2).first);
|
|
den = (double)((*it).toRational(2).second);
|
|
sec = num/den;
|
|
if (sec != -1.0)
|
|
latitude = latitude + sec/3600.0;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (latRef[0] == 'S') latitude *= -1.0;
|
|
|
|
// Longitude decoding.
|
|
|
|
Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
|
|
it = exifData.findKey(exifKey2);
|
|
if (it != exifData.end())
|
|
{
|
|
num = (double)((*it).toRational(0).first);
|
|
den = (double)((*it).toRational(0).second);
|
|
longitude = num/den;
|
|
|
|
num = (double)((*it).toRational(1).first);
|
|
den = (double)((*it).toRational(1).second);
|
|
min = num/den;
|
|
if (min != -1.0)
|
|
longitude = longitude + min/60.0;
|
|
|
|
num = (double)((*it).toRational(2).first);
|
|
den = (double)((*it).toRational(2).second);
|
|
sec = num/den;
|
|
if (sec != -1.0)
|
|
longitude = longitude + sec/3600.0;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (lngRef[0] == 'W') longitude *= -1.0;
|
|
|
|
// Altitude decoding.
|
|
|
|
if (!altRef.isEmpty())
|
|
{
|
|
Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
|
|
it = exifData.findKey(exifKey3);
|
|
if (it != exifData.end())
|
|
{
|
|
num = (double)((*it).toRational(0).first);
|
|
den = (double)((*it).toRational(0).second);
|
|
altitude = num/den;
|
|
}
|
|
|
|
if (altRef[0] == '1') altitude *= -1.0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get Exif GPS tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setGPSInfo(double altitude, double latitude, double longitude, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// In first, we need to clean up all existing GPS info.
|
|
removeGPSInfo();
|
|
|
|
char scratchBuf[100];
|
|
long int nom, denom;
|
|
long int deg, min;
|
|
|
|
// Do all the easy constant ones first.
|
|
// GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
|
|
// (and, must be present).
|
|
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
|
|
value->read("2 0 0 0");
|
|
d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());
|
|
|
|
// Datum: the datum of the measured data. If not given, we insert WGS-84.
|
|
d->exifMetadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
|
|
|
|
// Now start adding data.
|
|
|
|
// ALTITUDE.
|
|
// Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
|
|
value = Exiv2::Value::create(Exiv2::unsignedByte);
|
|
if (altitude >= 0) value->read("0");
|
|
else value->read("1");
|
|
d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
|
|
|
|
// And the actual altitude, as absolute value.
|
|
convertToRational(fabs(altitude), &nom, &denom, 4);
|
|
snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
|
|
d->exifMetadata["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
|
|
|
|
// LATTITUDE
|
|
// Latitude reference: "N" or "S".
|
|
if (latitude < 0)
|
|
{
|
|
// Less than Zero: ie, minus: means
|
|
// Southern hemisphere. Where I live.
|
|
d->exifMetadata["Exif.GPSInfo.GPSLatitudeRef"] = "S";
|
|
}
|
|
else
|
|
{
|
|
// More than Zero: ie, plus: means
|
|
// Northern hemisphere.
|
|
d->exifMetadata["Exif.GPSInfo.GPSLatitudeRef"] = "N";
|
|
}
|
|
|
|
// Now the actual lattitude itself.
|
|
// This is done as three rationals.
|
|
// I choose to do it as:
|
|
// dd/1 - degrees.
|
|
// mmmm/100 - minutes
|
|
// 0/1 - seconds
|
|
// Exif standard says you can do it with minutes
|
|
// as mm/1 and then seconds as ss/1, but its
|
|
// (slightly) more accurate to do it as
|
|
// mmmm/100 than to split it.
|
|
// We also absolute the value (with fabs())
|
|
// as the sign is encoded in LatRef.
|
|
// Further note: original code did not translate between
|
|
// dd.dddddd to dd mm.mm - that's why we now multiply
|
|
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
|
|
deg = (int)floor(fabs(latitude)); // Slice off after decimal.
|
|
min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
|
|
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
|
|
d->exifMetadata["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
|
|
|
|
// LONGITUDE
|
|
// Longitude reference: "E" or "W".
|
|
if (longitude < 0)
|
|
{
|
|
// Less than Zero: ie, minus: means
|
|
// Western hemisphere.
|
|
d->exifMetadata["Exif.GPSInfo.GPSLongitudeRef"] = "W";
|
|
}
|
|
else
|
|
{
|
|
// More than Zero: ie, plus: means
|
|
// Eastern hemisphere. Where I live.
|
|
d->exifMetadata["Exif.GPSInfo.GPSLongitudeRef"] = "E";
|
|
}
|
|
|
|
// Now the actual longitude itself.
|
|
// This is done as three rationals.
|
|
// I choose to do it as:
|
|
// dd/1 - degrees.
|
|
// mmmm/100 - minutes
|
|
// 0/1 - seconds
|
|
// Exif standard says you can do it with minutes
|
|
// as mm/1 and then seconds as ss/1, but its
|
|
// (slightly) more accurate to do it as
|
|
// mmmm/100 than to split it.
|
|
// We also absolute the value (with fabs())
|
|
// as the sign is encoded in LongRef.
|
|
// Further note: original code did not translate between
|
|
// dd.dddddd to dd mm.mm - that's why we now multiply
|
|
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
|
|
deg = (int)floor(fabs(longitude)); // Slice off after decimal.
|
|
min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
|
|
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
|
|
d->exifMetadata["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif GPS tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::removeGPSInfo(bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
QStringList gpsTagsKeys;
|
|
|
|
for (Exiv2::ExifData::iterator it = d->exifMetadata.begin();
|
|
it != d->exifMetadata.end(); ++it)
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key.section(".", 1, 1) == QString("GPSInfo"))
|
|
gpsTagsKeys.append(key);
|
|
}
|
|
|
|
for(QStringList::Iterator it2 = gpsTagsKeys.begin(); it2 != gpsTagsKeys.end(); ++it2)
|
|
{
|
|
Exiv2::ExifKey gpsKey((*it2).ascii());
|
|
Exiv2::ExifData::iterator it3 = d->exifMetadata.findKey(gpsKey);
|
|
if (it3 != d->exifMetadata.end())
|
|
d->exifMetadata.erase(it3);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot remove Exif GPS tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void KExiv2::convertToRational(double number, long int* numerator,
|
|
long int* denominator, int rounding)
|
|
{
|
|
// This function converts the given decimal number
|
|
// to a rational (fractional) number.
|
|
//
|
|
// Examples in comments use Number as 25.12345, Rounding as 4.
|
|
|
|
// Split up the number.
|
|
double whole = trunc(number);
|
|
double fractional = number - whole;
|
|
|
|
// Calculate the "number" used for rounding.
|
|
// This is 10^Digits - ie, 4 places gives us 10000.
|
|
double rounder = pow(10, rounding);
|
|
|
|
// Round the fractional part, and leave the number
|
|
// as greater than 1.
|
|
// To do this we: (for example)
|
|
// 0.12345 * 10000 = 1234.5
|
|
// floor(1234.5) = 1234 - now bigger than 1 - ready...
|
|
fractional = trunc(fractional * rounder);
|
|
|
|
// Convert the whole thing to a fraction.
|
|
// Fraction is:
|
|
// (25 * 10000) + 1234 251234
|
|
// ------------------- = ------ = 25.1234
|
|
// 10000 10000
|
|
double numTemp = (whole * rounder) + fractional;
|
|
double denTemp = rounder;
|
|
|
|
// Now we should reduce until we can reduce no more.
|
|
|
|
// Try simple reduction...
|
|
// if Num
|
|
// ----- = integer out then....
|
|
// Den
|
|
if (trunc(numTemp / denTemp) == (numTemp / denTemp))
|
|
{
|
|
// Divide both by Denominator.
|
|
numTemp /= denTemp;
|
|
denTemp /= denTemp;
|
|
}
|
|
|
|
// And, if that fails, brute force it.
|
|
while (1)
|
|
{
|
|
// Jump out if we can't integer divide one.
|
|
if ((numTemp / 2) != trunc(numTemp / 2)) break;
|
|
if ((denTemp / 2) != trunc(denTemp / 2)) break;
|
|
// Otherwise, divide away.
|
|
numTemp /= 2;
|
|
denTemp /= 2;
|
|
}
|
|
|
|
// Copy out the numbers.
|
|
*numerator = (int)numTemp;
|
|
*denominator = (int)denTemp;
|
|
}
|
|
|
|
QStringList KExiv2::getImageKeywords() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
QStringList keywords;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == QString("Iptc.Application2.Keywords"))
|
|
{
|
|
QString val(it->toString().c_str());
|
|
keywords.append(val);
|
|
}
|
|
}
|
|
|
|
return keywords;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get IPTC Keywords from image using Exiv2 ", e);
|
|
}
|
|
|
|
return QStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageKeywords(const QStringList& oldKeywords, const QStringList& newKeywords,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
QStringList oldkeys = oldKeywords;
|
|
QStringList newkeys = newKeywords;
|
|
|
|
qDebug("%s ==> Keywords: %s", d->filePath.ascii(), newkeys.join(",").ascii());
|
|
|
|
// Remove all old keywords.
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.begin();
|
|
|
|
while(it != iptcData.end())
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
QString val(it->toString().c_str());
|
|
|
|
// Also remove new keywords to avoid duplicates. They will be added again below.
|
|
if ( key == QString("Iptc.Application2.Keywords") &&
|
|
(oldKeywords.contains(val) || newKeywords.contains(val))
|
|
)
|
|
it = iptcData.erase(it);
|
|
else
|
|
++it;
|
|
};
|
|
|
|
// Add new keywords. Note that Keywords IPTC tag is limited to 64 char but can be redondant.
|
|
|
|
Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords");
|
|
|
|
for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
|
|
{
|
|
QString key = *it;
|
|
key.truncate(64);
|
|
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set IPTC Keywords into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QStringList KExiv2::getImageSubjects() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
QStringList subjects;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == QString("Iptc.Application2.Subject"))
|
|
{
|
|
QString val(it->toString().c_str());
|
|
subjects.append(val);
|
|
}
|
|
}
|
|
|
|
return subjects;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get IPTC Subjects from image using Exiv2 ", e);
|
|
}
|
|
|
|
return QStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageSubjects(const QStringList& oldSubjects, const QStringList& newSubjects,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
QStringList oldDef = oldSubjects;
|
|
QStringList newDef = newSubjects;
|
|
|
|
// Remove all old subjects.
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.begin();
|
|
|
|
while(it != iptcData.end())
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
QString val(it->toString().c_str());
|
|
|
|
if (key == QString("Iptc.Application2.Subject") && oldDef.contains(val))
|
|
it = iptcData.erase(it);
|
|
else
|
|
++it;
|
|
};
|
|
|
|
// Add new subjects. Note that Keywords IPTC tag is limited to 236 char but can be redondant.
|
|
|
|
Exiv2::IptcKey iptcTag("Iptc.Application2.Subject");
|
|
|
|
for (QStringList::iterator it = newDef.begin(); it != newDef.end(); ++it)
|
|
{
|
|
QString key = *it;
|
|
key.truncate(236);
|
|
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set IPTC Subjects into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QStringList KExiv2::getImageSubCategories() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
QStringList subCategories;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == QString("Iptc.Application2.SuppCategory"))
|
|
{
|
|
QString val(it->toString().c_str());
|
|
subCategories.append(val);
|
|
}
|
|
}
|
|
|
|
return subCategories;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot get IPTC Sub Categories from image using Exiv2 ", e);
|
|
}
|
|
|
|
return QStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageSubCategories(const QStringList& oldSubCategories, const QStringList& newSubCategories,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
QStringList oldkeys = oldSubCategories;
|
|
QStringList newkeys = newSubCategories;
|
|
|
|
// Remove all old Sub Categories.
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.begin();
|
|
|
|
while(it != iptcData.end())
|
|
{
|
|
QString key = QString::fromLocal8Bit(it->key().c_str());
|
|
QString val(it->toString().c_str());
|
|
|
|
if (key == QString("Iptc.Application2.SuppCategory") && oldSubCategories.contains(val))
|
|
it = iptcData.erase(it);
|
|
else
|
|
++it;
|
|
};
|
|
|
|
// Add new Sub Categories. Note that SubCategories IPTC tag is limited to 32
|
|
// characters but can be redondant.
|
|
|
|
Exiv2::IptcKey iptcTag("Iptc.Application2.SuppCategory");
|
|
|
|
for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
|
|
{
|
|
QString key = *it;
|
|
key.truncate(32);
|
|
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set IPTC Sub Categories into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::getExifComment() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
Exiv2::ExifKey key("Exif.Photo.UserComment");
|
|
Exiv2::ExifData exifData(d->exifMetadata);
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
|
|
if (it != exifData.end())
|
|
{
|
|
QString exifComment = convertCommentValue(*it);
|
|
|
|
// some cameras fill the UserComment with whitespace
|
|
if (!exifComment.isEmpty() && !exifComment.stripWhiteSpace().isEmpty())
|
|
return exifComment;
|
|
}
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot find Exif User Comment using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
bool KExiv2::setExifComment(const QString& comment, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
if (comment.isEmpty())
|
|
return false;
|
|
|
|
// Write as Unicode only when necessary.
|
|
QTextCodec *latin1Codec = QTextCodec::codecForName("iso8859-1");
|
|
if (latin1Codec->canEncode(comment))
|
|
{
|
|
// write as ASCII
|
|
std::string exifComment("charset=\"Ascii\" ");
|
|
exifComment += comment.latin1();
|
|
d->exifMetadata["Exif.Photo.UserComment"] = exifComment;
|
|
}
|
|
else
|
|
{
|
|
// write as Unicode (UCS-2)
|
|
|
|
// Be aware that we are dealing with a UCS-2 string.
|
|
// Null termination means \0\0, strlen does not work,
|
|
// do not use any const-char*-only methods,
|
|
// pass a std::string and not a const char * to ExifDatum::operator=().
|
|
const unsigned short *ucs2 = comment.ucs2();
|
|
std::string exifComment("charset=\"Unicode\" ");
|
|
exifComment.append((const char*)ucs2, sizeof(unsigned short) * comment.length());
|
|
d->exifMetadata["Exif.Photo.UserComment"] = exifComment;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot set Exif Comment using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KExiv2::convertCommentValue(const Exiv2::Exifdatum &exifDatum)
|
|
{
|
|
try
|
|
{
|
|
std::string comment;
|
|
std::string charset;
|
|
|
|
#if (EXIV2_TEST_VERSION(0,11,0))
|
|
comment = exifDatum.toString();
|
|
#else
|
|
// workaround for bug in TIFF parser: CommentValue is loaded as DataValue
|
|
const Exiv2::Value &value = exifDatum.value();
|
|
Exiv2::byte *data = new Exiv2::byte[value.size()];
|
|
value.copy(data, Exiv2::invalidByteOrder);
|
|
Exiv2::CommentValue commentValue;
|
|
// this read method is hidden in CommentValue
|
|
static_cast<Exiv2::Value &>(commentValue).read(data, value.size(), Exiv2::invalidByteOrder);
|
|
comment = commentValue.toString();
|
|
delete [] data;
|
|
#endif
|
|
|
|
// libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified
|
|
// Before conversion to QString, we must know the charset, so we stay with std::string for a while
|
|
if (comment.length() > 8 && comment.substr(0, 8) == "charset=")
|
|
{
|
|
// the prepended charset specification is followed by a blank
|
|
std::string::size_type pos = comment.find_first_of(' ');
|
|
if (pos != std::string::npos)
|
|
{
|
|
// extract string between the = and the blank
|
|
charset = comment.substr(8, pos-8);
|
|
// get the rest of the string after the charset specification
|
|
comment = comment.substr(pos+1);
|
|
}
|
|
}
|
|
|
|
if (charset == "\"Unicode\"")
|
|
{
|
|
// QString expects a null-terminated UCS-2 string.
|
|
// Is it already null terminated? In any case, add termination "\0\0" for safety.
|
|
comment.resize(comment.length() + 2, '\0');
|
|
return QString::fromUcs2((unsigned short *)comment.data());
|
|
}
|
|
else if (charset == "\"Jis\"")
|
|
{
|
|
QTextCodec *codec = QTextCodec::codecForName("JIS7");
|
|
return codec->toUnicode(comment.c_str());
|
|
}
|
|
else if (charset == "\"Ascii\"")
|
|
{
|
|
return QString::fromLatin1(comment.c_str());
|
|
}
|
|
else
|
|
{
|
|
return detectEncodingAndDecode(comment);
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
printExiv2ExceptionError("Cannot convert Comment using Exiv2 ", e);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QString KExiv2::detectEncodingAndDecode(const std::string &value)
|
|
{
|
|
// For charset autodetection, we could use sophisticated code
|
|
// (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent),
|
|
// but that is probably too much.
|
|
// We check for UTF8, Local encoding and ASCII.
|
|
|
|
if (value.empty())
|
|
return QString();
|
|
|
|
#if KDE_IS_VERSION(3,2,0)
|
|
if (KStringHandler::isUtf8(value.c_str()))
|
|
{
|
|
return QString::fromUtf8(value.c_str());
|
|
}
|
|
#else
|
|
// anyone who is still running KDE 3.0 or 3.1 is missing so many features
|
|
// that he will have to accept this missing feature.
|
|
return QString::fromUtf8(value.c_str());
|
|
#endif
|
|
|
|
// Utf8 has a pretty unique byte pattern.
|
|
// Thats not true for ASCII, it is not possible
|
|
// to reliably autodetect different ISO-8859 charsets.
|
|
// We try if QTextCodec can decide here, otherwise we use Latin1.
|
|
// Or use local8Bit as default?
|
|
|
|
// load QTextCodecs
|
|
QTextCodec *latin1Codec = QTextCodec::codecForName("iso8859-1");
|
|
//QTextCodec *utf8Codec = QTextCodec::codecForName("utf8");
|
|
QTextCodec *localCodec = QTextCodec::codecForLocale();
|
|
|
|
// make heuristic match
|
|
int latin1Score = latin1Codec->heuristicContentMatch(value.c_str(), value.length());
|
|
int localScore = localCodec->heuristicContentMatch(value.c_str(), value.length());
|
|
|
|
// convert string:
|
|
// Use whatever has the larger score, local or ASCII
|
|
if (localScore >= 0 && localScore >= latin1Score)
|
|
{
|
|
// workaround for bug #134999:
|
|
// The QLatin15Codec may crash if strlen < value.length()
|
|
int length = value.length();
|
|
if (localCodec->name() == QString::fromLatin1("ISO 8859-15"))
|
|
length = strlen(value.c_str());
|
|
return localCodec->toUnicode(value.c_str(), length);
|
|
}
|
|
else
|
|
return QString::fromLatin1(value.c_str());
|
|
}
|
|
|
|
bool KExiv2::setProgramId(bool /*on*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // NameSpace KExiv2Iface
|