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.
2468 lines
70 KiB
2468 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-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
* Copyright (C) 2006-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
|
|
*
|
|
* NOTE: Do not use kdDebug() in this implementation because
|
|
* it will be multithreaded. Use tqDebug() 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 ANSI includes.
|
|
|
|
extern "C"
|
|
{
|
|
#include <sys/stat.h>
|
|
#include <utime.h>
|
|
}
|
|
|
|
// TQt includes.
|
|
|
|
#include <tqfile.h>
|
|
#include <tqimage.h>
|
|
#include <tqsize.h>
|
|
#include <tqtextcodec.h>
|
|
#include <tqwmatrix.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqbuffer.h>
|
|
|
|
// Local includes.
|
|
|
|
#include "version.h"
|
|
#include "kexiv2private.h"
|
|
#include "kexiv2.h"
|
|
|
|
namespace KExiv2Iface
|
|
{
|
|
|
|
KExiv2::KExiv2()
|
|
{
|
|
d = new KExiv2Priv;
|
|
}
|
|
|
|
KExiv2::KExiv2(const TQString& filePath)
|
|
{
|
|
d = new KExiv2Priv;
|
|
load(filePath);
|
|
}
|
|
|
|
KExiv2::~KExiv2()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool KExiv2::supportMetadataWritting(const TQString& typeMime)
|
|
{
|
|
if (typeMime == TQString("image/jpeg"))
|
|
{
|
|
return true;
|
|
}
|
|
else if (typeMime == TQString("image/tiff"))
|
|
{
|
|
return true;
|
|
}
|
|
else if (typeMime == TQString("image/png"))
|
|
{
|
|
return true;
|
|
}
|
|
else if (typeMime == TQString("image/jp2"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString KExiv2::version()
|
|
{
|
|
return TQString( kexiv2_version );
|
|
}
|
|
|
|
TQString KExiv2::Exiv2Version()
|
|
{
|
|
// Since 0.14.0 release, we can extract run-time version of Exiv2.
|
|
// else we return make version.
|
|
|
|
return TQString(Exiv2::version());
|
|
}
|
|
|
|
bool KExiv2::clearComments()
|
|
{
|
|
return setComments(TQByteArray());
|
|
}
|
|
|
|
bool KExiv2::clearExif()
|
|
{
|
|
try
|
|
{
|
|
d->exifMetadata.clear();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot clear Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::clearIptc()
|
|
{
|
|
try
|
|
{
|
|
d->iptcMetadata.clear();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot clear Iptc data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString KExiv2::getFilePath() const
|
|
{
|
|
return d->filePath;
|
|
}
|
|
|
|
TQByteArray KExiv2::getComments() const
|
|
{
|
|
return TQByteArray().duplicate(d->imageComments.data(), d->imageComments.size());
|
|
}
|
|
|
|
TQString KExiv2::getCommentsDecoded() const
|
|
{
|
|
return d->detectEncodingAndDecode(d->imageComments);
|
|
}
|
|
|
|
TQByteArray KExiv2::getExif() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->exifMetadata.empty())
|
|
{
|
|
Exiv2::ExifData& exif = d->exifMetadata;
|
|
Exiv2::Blob blob;
|
|
Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif);
|
|
TQByteArray ba(blob.size());
|
|
if (ba.size())
|
|
memcpy(ba.data(), (const char*)&blob[0], blob.size());
|
|
|
|
return ba;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
tqDebug ("From file %s", d->filePath.ascii());
|
|
|
|
d->printExiv2ExceptionError("Cannot get Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return TQByteArray();
|
|
}
|
|
|
|
TQByteArray KExiv2::getIptc(bool addIrbHeader) const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
Exiv2::IptcData& iptc = d->iptcMetadata;
|
|
Exiv2::DataBuf c2;
|
|
|
|
if (addIrbHeader)
|
|
{
|
|
c2 = Exiv2::Photoshop::setIptcIrb(0, 0, iptc);
|
|
}
|
|
else
|
|
{
|
|
c2 = Exiv2::IptcParser::encode(d->iptcMetadata);
|
|
}
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
TQByteArray data(c2.size());
|
|
if (data.size())
|
|
memcpy(data.data(), c2.c_data(), c2.size());
|
|
#else
|
|
TQByteArray data(c2.size_);
|
|
if (data.size())
|
|
memcpy(data.data(), c2.pData_, c2.size_);
|
|
#endif
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
tqDebug ("From file %s", d->filePath.ascii());
|
|
|
|
d->printExiv2ExceptionError("Cannot get Iptc data using Exiv2 ",e);
|
|
}
|
|
|
|
return TQByteArray();
|
|
}
|
|
|
|
bool KExiv2::setComments(const TQByteArray& data)
|
|
{
|
|
d->imageComments = std::string(data.data(), data.size());
|
|
return true;
|
|
}
|
|
|
|
bool KExiv2::setExif(const TQByteArray& data)
|
|
{
|
|
try
|
|
{
|
|
if (!data.isEmpty())
|
|
{
|
|
Exiv2::ExifParser::decode(d->exifMetadata, (const Exiv2::byte*)data.data(), data.size());
|
|
return (!d->exifMetadata.empty());
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
tqDebug ("From file %s", d->filePath.ascii());
|
|
|
|
d->printExiv2ExceptionError("Cannot set Exif data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setIptc(const TQByteArray& data)
|
|
{
|
|
try
|
|
{
|
|
if (!data.isEmpty())
|
|
{
|
|
Exiv2::IptcParser::decode(d->iptcMetadata, (const Exiv2::byte*)data.data(), data.size());
|
|
return (!d->iptcMetadata.empty());
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
if (!d->filePath.isEmpty())
|
|
tqDebug ("From file %s", d->filePath.ascii());
|
|
|
|
d->printExiv2ExceptionError("Cannot set Iptc data using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::load(const TQByteArray& imgData)
|
|
{
|
|
if (imgData.isEmpty())
|
|
return false;
|
|
|
|
try
|
|
{
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
|
|
#endif
|
|
d->filePath = TQString();
|
|
image->readMetadata();
|
|
|
|
// Image comments ---------------------------------
|
|
|
|
d->imageComments = image->comment();
|
|
|
|
// Exif metadata ----------------------------------
|
|
|
|
d->exifMetadata = image->exifData();
|
|
|
|
// Iptc metadata ----------------------------------
|
|
|
|
d->iptcMetadata = image->iptcData();
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::load(const TQString& filePath)
|
|
{
|
|
TQFileInfo finfo(filePath);
|
|
if (filePath.isEmpty() || !finfo.isReadable())
|
|
{
|
|
tqDebug("File '%s' is not readable.", finfo.fileName().ascii());
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#endif
|
|
d->filePath = filePath;
|
|
image->readMetadata();
|
|
|
|
// Image comments ---------------------------------
|
|
|
|
d->imageComments = image->comment();
|
|
|
|
// Exif metadata ----------------------------------
|
|
|
|
d->exifMetadata = image->exifData();
|
|
|
|
// Iptc metadata ----------------------------------
|
|
|
|
d->iptcMetadata = image->iptcData();
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::save(const TQString& filePath)
|
|
{
|
|
if (filePath.isEmpty())
|
|
return false;
|
|
|
|
// NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only.
|
|
TQFileInfo finfo(filePath);
|
|
TQFileInfo dinfo(finfo.dirPath());
|
|
if (!finfo.isWritable())
|
|
{
|
|
tqDebug("File '%s' is read-only. Metadata not saved.", finfo.fileName().ascii());
|
|
return false;
|
|
}
|
|
if (!dinfo.isWritable())
|
|
{
|
|
tqDebug("Dir '%s' is read-only. Metadata not saved.", dinfo.filePath().ascii());
|
|
return false;
|
|
}
|
|
|
|
// TIFF/EP Raw files based are supported by Exiv2 0.18 as experimental. We will do touch it for the moment.
|
|
// Metadata writing is supported in implementation from svn trunk.
|
|
TQString rawTiffBased("dng nef pef 3fr arw cr2 dcr erf k25 kdc mos orf raw sr2 srf");
|
|
if (rawTiffBased.contains(finfo.extension(false).lower()))
|
|
{
|
|
tqDebug("'%s' is TIFF based RAW file and writing mode is disable with this libkexiv2 version. Metadata not saved.",
|
|
finfo.fileName().ascii());
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Exiv2::AccessMode mode;
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#endif
|
|
|
|
// We need to load target file metadata to merge with new one. It's mandatory with TIFF format:
|
|
// like all tiff file structure is based on Exif.
|
|
image->readMetadata();
|
|
|
|
// Image Comments ---------------------------------
|
|
|
|
mode = image->checkMode(Exiv2::mdComment);
|
|
if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
|
|
{
|
|
image->setComment(d->imageComments);
|
|
}
|
|
|
|
// Exif metadata ----------------------------------
|
|
|
|
mode = image->checkMode(Exiv2::mdExif);
|
|
if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
|
|
{
|
|
if (image->mimeType() == "image/tiff")
|
|
{
|
|
// With tiff image we cannot overwrite whole Exif data as well, because
|
|
// image data are stored in Exif container. We need to take a care about
|
|
// to not lost image data.
|
|
Exiv2::ExifData exif = image->exifData();
|
|
TQStringList untouchedTags;
|
|
untouchedTags << "Exif.Image.ImageWidth";
|
|
untouchedTags << "Exif.Image.ImageLength";
|
|
untouchedTags << "Exif.Image.BitsPerSample";
|
|
untouchedTags << "Exif.Image.Compression";
|
|
untouchedTags << "Exif.Image.PhotometricInterpretation";
|
|
untouchedTags << "Exif.Image.FillOrder";
|
|
untouchedTags << "Exif.Image.SamplesPerPixel";
|
|
untouchedTags << "Exif.Image.StripOffsets";
|
|
untouchedTags << "Exif.Image.RowsPerStrip";
|
|
untouchedTags << "Exif.Image.StripByteCounts";
|
|
untouchedTags << "Exif.Image.XResolution";
|
|
untouchedTags << "Exif.Image.YResolution";
|
|
untouchedTags << "Exif.Image.PlanarConfiguration";
|
|
untouchedTags << "Exif.Image.ResolutionUnit";
|
|
|
|
for (Exiv2::ExifData::iterator it = d->exifMetadata.begin(); it != d->exifMetadata.end(); ++it)
|
|
{
|
|
if (!untouchedTags.contains(it->key().c_str()))
|
|
{
|
|
exif[it->key().c_str()] = d->exifMetadata[it->key().c_str()];
|
|
}
|
|
}
|
|
|
|
image->setExifData(exif);
|
|
}
|
|
else
|
|
{
|
|
image->setExifData(d->exifMetadata);
|
|
}
|
|
}
|
|
|
|
// Iptc metadata ----------------------------------
|
|
|
|
mode = image->checkMode(Exiv2::mdIptc);
|
|
if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
|
|
{
|
|
image->setIptcData(d->iptcMetadata);
|
|
}
|
|
|
|
// NOTE: Don't touch access and modification timestamp of file.
|
|
struct stat st;
|
|
::stat(TQFile::encodeName(filePath), &st);
|
|
|
|
struct utimbuf ut;
|
|
ut.modtime = st.st_mtime;
|
|
ut.actime = st.st_atime;
|
|
|
|
image->writeMetadata();
|
|
|
|
::utime(TQFile::encodeName(filePath), &ut);
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot save metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::applyChanges()
|
|
{
|
|
if (d->filePath.isEmpty())
|
|
return false;
|
|
|
|
return save(d->filePath);
|
|
}
|
|
|
|
bool KExiv2::isReadOnly(const TQString& filePath)
|
|
{
|
|
if (!canWriteComment(filePath))
|
|
return true;
|
|
|
|
if (!canWriteExif(filePath))
|
|
return true;
|
|
|
|
if (!canWriteIptc(filePath))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::canWriteComment(const TQString& filePath)
|
|
{
|
|
try
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#endif
|
|
|
|
Exiv2::AccessMode mode = image->checkMode(Exiv2::mdComment);
|
|
return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
std::string s(e.what());
|
|
tqDebug("%s (Error #%i: %s)", "Cannot check Comment access mode using Exiv2 ", (int)e.code(), s.c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::canWriteExif(const TQString& filePath)
|
|
{
|
|
try
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#endif
|
|
|
|
Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif);
|
|
return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
std::string s(e.what());
|
|
tqDebug("%s (Error #%i: %s)", "Cannot check Exif access mode using Exiv2 ", (int)e.code(), s.c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::canWriteIptc(const TQString& filePath)
|
|
{
|
|
try
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#else
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
|
|
(TQFile::encodeName(filePath)));
|
|
#endif
|
|
|
|
Exiv2::AccessMode mode = image->checkMode(Exiv2::mdIptc);
|
|
return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
std::string s(e.what());
|
|
tqDebug("%s (Error #%i: %s)", "Cannot check Iptc access mode using Exiv2 ", (int)e.code(), s.c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setImageProgramId(const TQString& program, const TQString& version)
|
|
{
|
|
try
|
|
{
|
|
// Record program info in Exif.Image.ProcessingSoftware tag (only available with Exiv2 >= 0.14.0).
|
|
|
|
TQString software(program);
|
|
software.append("-");
|
|
software.append(version);
|
|
d->exifMetadata["Exif.Image.ProcessingSoftware"] = software.ascii();
|
|
|
|
// 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())
|
|
{
|
|
TQString 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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Program identity into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQSize KExiv2::getImageDimensions() const
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return TQSize();
|
|
|
|
try
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
int64_t width=-1, height=-1;
|
|
#else
|
|
long width=-1, height=-1;
|
|
#endif
|
|
|
|
// 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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
width = it->toInt64();
|
|
#else
|
|
width = it->toLong();
|
|
#endif
|
|
}
|
|
|
|
Exiv2::ExifKey key2("Exif.Photo.PixelYDimension");
|
|
Exiv2::ExifData::iterator it2 = exifData.findKey(key2);
|
|
|
|
if (it2 != exifData.end())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
height = it2->toInt64();
|
|
#else
|
|
height = it2->toLong();
|
|
#endif
|
|
}
|
|
|
|
if (width != -1 && height != -1)
|
|
return TQSize(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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
width = it3->toInt64();
|
|
#else
|
|
width = it3->toLong();
|
|
#endif
|
|
}
|
|
|
|
Exiv2::ExifKey key4("Exif.Image.ImageLength");
|
|
Exiv2::ExifData::iterator it4 = exifData.findKey(key4);
|
|
|
|
if (it4 != exifData.end())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
height = it4->toInt64();
|
|
#else
|
|
height = it4->toLong();
|
|
#endif
|
|
}
|
|
|
|
if (width != -1 && height != -1)
|
|
return TQSize(width, height);
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot parse image dimensions tag using Exiv2 ", e);
|
|
}
|
|
|
|
return TQSize();
|
|
}
|
|
|
|
bool KExiv2::setImageDimensions(const TQSize& 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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set image dimensions using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQImage KExiv2::getExifThumbnail(bool fixOrientation) const
|
|
{
|
|
TQImage thumbnail;
|
|
|
|
if (d->exifMetadata.empty())
|
|
return thumbnail;
|
|
|
|
try
|
|
{
|
|
Exiv2::ExifThumbC thumb(d->exifMetadata);
|
|
Exiv2::DataBuf const c1 = thumb.copy();
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
thumbnail.loadFromData(c1.c_data(), c1.size());
|
|
#else
|
|
thumbnail.loadFromData(c1.pData_, c1.size_);
|
|
#endif
|
|
|
|
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())
|
|
{
|
|
TQWMatrix matrix;
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
int64_t orientation = it->toInt64();
|
|
#else
|
|
long orientation = it->toLong();
|
|
#endif
|
|
tqDebug("Exif Thumbnail Qt::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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get Exif Thumbnail using Exiv2 ", e);
|
|
}
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
bool KExiv2::setExifThumbnail(const TQImage& thumb, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQByteArray data;
|
|
TQBuffer buffer(data);
|
|
buffer.open(IO_WriteOnly);
|
|
thumb.save(&buffer, "JPEG");
|
|
Exiv2::ExifThumb thumb(d->exifMetadata);
|
|
thumb.setJpegThumbnail((Exiv2::byte *)data.data(), data.size());
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->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;
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
int64_t orientation;
|
|
#else
|
|
long orientation;
|
|
#endif
|
|
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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
orientation = it->toInt64();
|
|
#else
|
|
orientation = it->toLong();
|
|
#endif
|
|
tqDebug("Minolta Makernote Qt::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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
orientation = it->toInt64();
|
|
#else
|
|
orientation = it->toLong();
|
|
#endif
|
|
tqDebug("Minolta Makernote Qt::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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
orientation = it->toInt64();
|
|
#else
|
|
orientation = it->toLong();
|
|
#endif
|
|
tqDebug("Exif Qt::Orientation: %i", (int)orientation);
|
|
return (ImageOrientation)orientation;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot parse Exif Qt::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)
|
|
{
|
|
tqDebug("Exif orientation tag value is not correct!");
|
|
return false;
|
|
}
|
|
|
|
d->exifMetadata["Exif.Image.Orientation"] = static_cast<uint16_t>(orientation);
|
|
tqDebug("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);
|
|
tqDebug("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);
|
|
tqDebug("Removing Exif.MinoltaCs5D.Rotation tag");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif Qt::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);
|
|
tqDebug("Exif color workspace tag set to: %i", (int)workspace);
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif color workspace tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQDateTime 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())
|
|
{
|
|
TQDateTime dateTime = TQDateTime::fromString(it2->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// tqDebug("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())
|
|
{
|
|
TQDateTime dateTime = TQDateTime::fromString(it3->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// tqDebug("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())
|
|
{
|
|
TQDateTime dateTime = TQDateTime::fromString(it->toString().c_str(), Qt::ISODate);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// tqDebug("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())
|
|
{
|
|
TQString IptcDateCreated(it->toString().c_str());
|
|
|
|
Exiv2::IptcKey keyTimeCreated("Iptc.Application2.TimeCreated");
|
|
Exiv2::IptcData::iterator it2 = iptcData.findKey(keyTimeCreated);
|
|
|
|
if (it2 != iptcData.end())
|
|
{
|
|
TQString IptcTimeCreated(it2->toString().c_str());
|
|
|
|
TQDate date = TQDate::fromString(IptcDateCreated, Qt::ISODate);
|
|
TQTime time = TQTime::fromString(IptcTimeCreated, Qt::ISODate);
|
|
TQDateTime dateTime = TQDateTime(date, time);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
// tqDebug("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())
|
|
{
|
|
TQString IptcDateDigitization(it3->toString().c_str());
|
|
|
|
Exiv2::IptcKey keyDigitizationTime("Iptc.Application2.DigitizationTime");
|
|
Exiv2::IptcData::iterator it4 = iptcData.findKey(keyDigitizationTime);
|
|
|
|
if (it4 != iptcData.end())
|
|
{
|
|
TQString IptcTimeDigitization(it4->toString().c_str());
|
|
|
|
TQDate date = TQDate::fromString(IptcDateDigitization, Qt::ISODate);
|
|
TQTime time = TQTime::fromString(IptcTimeDigitization, Qt::ISODate);
|
|
TQDateTime dateTime = TQDateTime(date, time);
|
|
|
|
if (dateTime.isValid())
|
|
{
|
|
//tqDebug("Date (IPTC digitalized): %s", dateTime.toString().ascii());
|
|
return dateTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot parse Exif date & time tag using Exiv2 ", e);
|
|
}
|
|
|
|
return TQDateTime();
|
|
}
|
|
|
|
bool KExiv2::setImageDateTime(const TQDateTime& 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(TQString("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(TQString(dateTime.date().toString(Qt::ISODate)).ascii());
|
|
const std::string &iptctime(TQString(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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Date & Time into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::getImagePreview(TQImage& 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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setImagePreview(const TQImage& preview, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQByteArray data;
|
|
TQBuffer buffer(data);
|
|
buffer.open(IO_WriteOnly);
|
|
|
|
// A little bit compressed preview jpeg image to limit IPTC size.
|
|
preview.save(&buffer, "JPEG");
|
|
tqDebug("JPEG image preview size: (%i x %i) pixels - %i bytes",
|
|
preview.width(), preview.height(), (int)data.size());
|
|
|
|
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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString 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())
|
|
{
|
|
// See B.K.O #184156 comment #13
|
|
std::string val = it->print(&exifData);
|
|
TQString tagValue = TQString::fromLocal8Bit(val.c_str());
|
|
if (escapeCR)
|
|
tagValue.replace("\n", " ");
|
|
|
|
return tagValue;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
bool KExiv2::setExifTagString(const char *exifTagName, const TQString& value, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->exifMetadata[exifTagName] = value.ascii();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif tag string into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString 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;
|
|
TQString tagValue = TQString::fromLocal8Bit(os.str().c_str());
|
|
|
|
if (escapeCR)
|
|
tagValue.replace("\n", " ");
|
|
|
|
return tagValue;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("Cannot find Iptc key '%1' into image using Exiv2 ")
|
|
.arg(iptcTagName), e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
bool KExiv2::setIptcTagString(const char *iptcTagName, const TQString& value, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
d->iptcMetadata[iptcTagName] = value.ascii();
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->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())
|
|
{
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
val = (long)it->toInt64();
|
|
#else
|
|
val = it->toLong();
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQByteArray 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())
|
|
{
|
|
TQByteArray data((*it).size());
|
|
if (data.size())
|
|
{
|
|
(*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian);
|
|
}
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
|
|
.arg(exifTagName), e);
|
|
}
|
|
|
|
return TQByteArray();
|
|
}
|
|
|
|
TQByteArray 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())
|
|
{
|
|
TQByteArray data((*it).size());
|
|
if (data.size())
|
|
(*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian);
|
|
return data;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("Cannot find Iptc key '%1' into image using Exiv2 ")
|
|
.arg(iptcTagName), e);
|
|
}
|
|
|
|
return TQByteArray();
|
|
}
|
|
|
|
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 )
|
|
{
|
|
d->printExiv2ExceptionError(TQString("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 )
|
|
{
|
|
d->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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif tag rational value into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setExifTagData(const char *exifTagName, const TQByteArray& 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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif tag data into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setIptcTagData(const char *iptcTagName, const TQByteArray& 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 )
|
|
{
|
|
d->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 )
|
|
{
|
|
d->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::IptcData::iterator it = d->iptcMetadata.begin();
|
|
while(it != d->iptcMetadata.end())
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == TQString(iptcTagName))
|
|
it = d->iptcMetadata.erase(it);
|
|
else
|
|
++it;
|
|
};
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot remove Iptc tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString KExiv2::getExifTagTitle(const char *exifTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string exifkey(exifTagName);
|
|
Exiv2::ExifKey ek(exifkey);
|
|
return TQString::fromLocal8Bit( ek.tagLabel().c_str() );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
TQString KExiv2::getExifTagDescription(const char *exifTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string exifkey(exifTagName);
|
|
Exiv2::ExifKey ek(exifkey);
|
|
return TQString::fromLocal8Bit( ek.tagDesc().c_str() );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
TQString KExiv2::getIptcTagTitle(const char *iptcTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string iptckey(iptcTagName);
|
|
Exiv2::IptcKey ik(iptckey);
|
|
return TQString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetTitle(ik.tag(), ik.record()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
TQString KExiv2::getIptcTagDescription(const char *iptcTagName)
|
|
{
|
|
try
|
|
{
|
|
std::string iptckey(iptcTagName);
|
|
Exiv2::IptcKey ik(iptckey);
|
|
return TQString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetDesc(ik.tag(), ik.record()) );
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
KExiv2::MetaDataMap KExiv2::getExifTagsDataList(const TQStringList &exifKeysFilter, bool invertSelection)
|
|
{
|
|
if (d->exifMetadata.empty())
|
|
return MetaDataMap();
|
|
|
|
try
|
|
{
|
|
Exiv2::ExifData exifData = d->exifMetadata;
|
|
exifData.sortByKey();
|
|
|
|
TQString ifDItemName;
|
|
MetaDataMap metaDataMap;
|
|
|
|
for (Exiv2::ExifData::iterator md = exifData.begin(); md != exifData.end(); ++md)
|
|
{
|
|
TQString key = TQString::fromAscii(md->key().c_str());
|
|
|
|
// Decode the tag value with a user friendly output.
|
|
TQString tagValue;
|
|
if (key == "Exif.Photo.UserComment")
|
|
{
|
|
tagValue = d->convertCommentValue(*md);
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream os;
|
|
os << *md;
|
|
|
|
// Exif tag contents can be an i18n strings, no only simple ascii.
|
|
tagValue = TQString::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)
|
|
{
|
|
d->printExiv2ExceptionError("Cannot parse EXIF metadata using Exiv2 ", e);
|
|
}
|
|
|
|
return MetaDataMap();
|
|
}
|
|
|
|
KExiv2::MetaDataMap KExiv2::getIptcTagsDataList(const TQStringList &iptcKeysFilter, bool invertSelection)
|
|
{
|
|
if (d->iptcMetadata.empty())
|
|
return MetaDataMap();
|
|
|
|
try
|
|
{
|
|
Exiv2::IptcData iptcData = d->iptcMetadata;
|
|
iptcData.sortByKey();
|
|
|
|
TQString ifDItemName;
|
|
MetaDataMap metaDataMap;
|
|
|
|
for (Exiv2::IptcData::iterator md = iptcData.begin(); md != iptcData.end(); ++md)
|
|
{
|
|
TQString key = TQString::fromAscii(md->key().c_str());
|
|
|
|
// Decode the tag value with a user friendly output.
|
|
std::ostringstream os;
|
|
os << *md;
|
|
TQString value = TQString::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
|
|
{
|
|
TQString 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
|
|
{
|
|
TQString v = *it;
|
|
v.append(", ");
|
|
v.append(value);
|
|
metaDataMap.replace(key, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return metaDataMap;
|
|
}
|
|
catch (Exiv2::Error& e)
|
|
{
|
|
d->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.
|
|
|
|
TQByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
|
|
if (latRef.isEmpty()) return false;
|
|
|
|
TQByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
|
|
if (lngRef.isEmpty()) return false;
|
|
|
|
TQByteArray 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 )
|
|
{
|
|
d->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).
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::unsignedByte);
|
|
#else
|
|
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
|
|
#endif
|
|
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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif GPS tag using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::removeGPSInfo(bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQStringList gpsTagsKeys;
|
|
|
|
for (Exiv2::ExifData::iterator it = d->exifMetadata.begin();
|
|
it != d->exifMetadata.end(); ++it)
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key.section(".", 1, 1) == TQString("GPSInfo"))
|
|
gpsTagsKeys.append(key);
|
|
}
|
|
|
|
for(TQStringList::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 )
|
|
{
|
|
d->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.0, 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 = round(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;
|
|
}
|
|
|
|
TQStringList KExiv2::getImageKeywords() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
TQStringList keywords;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == TQString("Iptc.Application2.Keywords"))
|
|
{
|
|
TQString val(it->toString().c_str());
|
|
keywords.append(val);
|
|
}
|
|
}
|
|
|
|
return keywords;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get IPTC Keywords from image using Exiv2 ", e);
|
|
}
|
|
|
|
return TQStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageKeywords(const TQStringList& oldKeywords, const TQStringList& newKeywords,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQStringList oldkeys = oldKeywords;
|
|
TQStringList newkeys = newKeywords;
|
|
|
|
tqDebug("%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())
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
TQString val(it->toString().c_str());
|
|
|
|
// Also remove new keywords to avoid duplicates. They will be added again below.
|
|
if ( key == TQString("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 (TQStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
|
|
{
|
|
TQString key = *it;
|
|
key.truncate(64);
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Value::UniquePtr val = Exiv2::Value::create(Exiv2::string);
|
|
#else
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
#endif
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set IPTC Keywords into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQStringList KExiv2::getImageSubjects() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
TQStringList subjects;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == TQString("Iptc.Application2.Subject"))
|
|
{
|
|
TQString val(it->toString().c_str());
|
|
subjects.append(val);
|
|
}
|
|
}
|
|
|
|
return subjects;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get IPTC Subjects from image using Exiv2 ", e);
|
|
}
|
|
|
|
return TQStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageSubjects(const TQStringList& oldSubjects, const TQStringList& newSubjects,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQStringList oldDef = oldSubjects;
|
|
TQStringList newDef = newSubjects;
|
|
|
|
// Remove all old subjects.
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.begin();
|
|
|
|
while(it != iptcData.end())
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
TQString val(it->toString().c_str());
|
|
|
|
if (key == TQString("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 (TQStringList::iterator it = newDef.begin(); it != newDef.end(); ++it)
|
|
{
|
|
TQString key = *it;
|
|
key.truncate(236);
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Value::UniquePtr val = Exiv2::Value::create(Exiv2::string);
|
|
#else
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
#endif
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set IPTC Subjects into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQStringList KExiv2::getImageSubCategories() const
|
|
{
|
|
try
|
|
{
|
|
if (!d->iptcMetadata.empty())
|
|
{
|
|
TQStringList subCategories;
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
|
|
for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
|
|
if (key == TQString("Iptc.Application2.SuppCategory"))
|
|
{
|
|
TQString val(it->toString().c_str());
|
|
subCategories.append(val);
|
|
}
|
|
}
|
|
|
|
return subCategories;
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot get IPTC Sub Categories from image using Exiv2 ", e);
|
|
}
|
|
|
|
return TQStringList();
|
|
}
|
|
|
|
bool KExiv2::setImageSubCategories(const TQStringList& oldSubCategories, const TQStringList& newSubCategories,
|
|
bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
TQStringList oldkeys = oldSubCategories;
|
|
TQStringList newkeys = newSubCategories;
|
|
|
|
// Remove all old Sub Categories.
|
|
Exiv2::IptcData iptcData(d->iptcMetadata);
|
|
Exiv2::IptcData::iterator it = iptcData.begin();
|
|
|
|
while(it != iptcData.end())
|
|
{
|
|
TQString key = TQString::fromLocal8Bit(it->key().c_str());
|
|
TQString val(it->toString().c_str());
|
|
|
|
if (key == TQString("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 (TQStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
|
|
{
|
|
TQString key = *it;
|
|
key.truncate(32);
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
Exiv2::Value::UniquePtr val = Exiv2::Value::create(Exiv2::string);
|
|
#else
|
|
Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
|
|
#endif
|
|
val->read(key.latin1());
|
|
iptcData.add(iptcTag, val.get());
|
|
}
|
|
|
|
d->iptcMetadata = iptcData;
|
|
|
|
return true;
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set IPTC Sub Categories into image using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString 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())
|
|
{
|
|
TQString exifComment = d->convertCommentValue(*it);
|
|
|
|
// some cameras fill the UserComment with whitespace
|
|
if (!exifComment.isEmpty() && !exifComment.stripWhiteSpace().isEmpty())
|
|
return exifComment;
|
|
}
|
|
}
|
|
}
|
|
catch( Exiv2::Error &e )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot find Exif User Comment using Exiv2 ", e);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
bool KExiv2::setExifComment(const TQString& comment, bool setProgramName)
|
|
{
|
|
if (!setProgramId(setProgramName))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
if (comment.isEmpty())
|
|
return false;
|
|
|
|
// Write as Unicode only when necessary.
|
|
TQTextCodec *latin1Codec = TQTextCodec::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 )
|
|
{
|
|
d->printExiv2ExceptionError("Cannot set Exif Comment using Exiv2 ", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KExiv2::setProgramId(bool /*on*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // NameSpace KExiv2Iface
|