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.
545 lines
16 KiB
545 lines
16 KiB
/* ============================================================
|
|
*
|
|
* This file is a part of digiKam project
|
|
* http://www.digikam.org
|
|
*
|
|
* Date : 2006-02-23
|
|
* Description : image metadata interface
|
|
*
|
|
* Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
* Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
|
|
*
|
|
* 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.
|
|
*
|
|
* ============================================================ */
|
|
|
|
// TQt includes.
|
|
|
|
#include <tqdom.h>
|
|
#include <tqfile.h>
|
|
|
|
// LibKDcraw includes.
|
|
|
|
#include <libkdcraw/dcrawinfocontainer.h>
|
|
#include <libkdcraw/kdcraw.h>
|
|
|
|
// Local includes.
|
|
|
|
#include "daboutdata.h"
|
|
#include "constants.h"
|
|
#include "ddebug.h"
|
|
#include "dmetadata.h"
|
|
|
|
namespace Digikam
|
|
{
|
|
|
|
DMetadata::DMetadata()
|
|
: KExiv2Iface::KExiv2()
|
|
{
|
|
}
|
|
|
|
DMetadata::DMetadata(const TQString& filePath)
|
|
: KExiv2Iface::KExiv2()
|
|
{
|
|
load(filePath);
|
|
}
|
|
|
|
DMetadata::~DMetadata()
|
|
{
|
|
}
|
|
|
|
bool DMetadata::load(const TQString& filePath)
|
|
{
|
|
// In first, we trying to get metadata using Exiv2,
|
|
// else we will use dcraw to extract minimal information.
|
|
|
|
if (!KExiv2::load(filePath))
|
|
{
|
|
if (!loadUsingDcraw(filePath))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DMetadata::loadUsingDcraw(const TQString& filePath)
|
|
{
|
|
KDcrawIface::DcrawInfoContainer identify;
|
|
if (KDcrawIface::KDcraw::rawFileIdentify(identify, filePath))
|
|
{
|
|
long int num=1, den=1;
|
|
|
|
if (!identify.model.isNull())
|
|
setExifTagString("Exif.Image.Model", identify.model.latin1(), false);
|
|
|
|
if (!identify.make.isNull())
|
|
setExifTagString("Exif.Image.Make", identify.make.latin1(), false);
|
|
|
|
if (!identify.owner.isNull())
|
|
setExifTagString("Exif.Image.Artist", identify.owner.latin1(), false);
|
|
|
|
if (identify.sensitivity != -1)
|
|
setExifTagLong("Exif.Photo.ISOSpeedRatings", identify.sensitivity, false);
|
|
|
|
if (identify.dateTime.isValid())
|
|
setImageDateTime(identify.dateTime, false, false);
|
|
|
|
if (identify.exposureTime != -1.0)
|
|
{
|
|
convertToRational(1/identify.exposureTime, &num, &den, 8);
|
|
setExifTagRational("Exif.Photo.ExposureTime", num, den, false);
|
|
}
|
|
|
|
if (identify.aperture != -1.0)
|
|
{
|
|
convertToRational(identify.aperture, &num, &den, 8);
|
|
setExifTagRational("Exif.Photo.ApertureValue", num, den, false);
|
|
}
|
|
|
|
if (identify.focalLength != -1.0)
|
|
{
|
|
convertToRational(identify.focalLength, &num, &den, 8);
|
|
setExifTagRational("Exif.Photo.FocalLength", num, den, false);
|
|
}
|
|
|
|
if (identify.imageSize.isValid())
|
|
setImageDimensions(identify.imageSize, false);
|
|
|
|
// A RAW image is always uncalibrated. */
|
|
setImageColorWorkSpace(WORKSPACE_UNCALIBRATED, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TQString DMetadata::getImageComment() const
|
|
{
|
|
if (getFilePath().isEmpty())
|
|
return TQString();
|
|
|
|
// In first we trying to get image comments, outside of Exif and IPTC.
|
|
|
|
TQString comment = getCommentsDecoded();
|
|
if (!comment.isEmpty())
|
|
return comment;
|
|
|
|
// In second, we trying to get Exif comments
|
|
|
|
if (!getExif().isEmpty())
|
|
{
|
|
TQString exifComment = getExifComment();
|
|
if (!exifComment.isEmpty())
|
|
return exifComment;
|
|
}
|
|
|
|
// In third, we trying to get IPTC comments
|
|
|
|
if (!getIptc().isEmpty())
|
|
{
|
|
TQString iptcComment = getIptcTagString("Iptc.Application2.Caption", false);
|
|
if (!iptcComment.isEmpty() && !iptcComment.stripWhiteSpace().isEmpty())
|
|
return iptcComment;
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
bool DMetadata::setImageComment(const TQString& comment)
|
|
{
|
|
//See bug #139313: An empty string is also a valid value
|
|
//if (comment.isEmpty())
|
|
// return false;
|
|
|
|
DDebug() << getFilePath() << " ==> Comment: " << comment << endl;
|
|
|
|
if (!setProgramId())
|
|
return false;
|
|
|
|
// In first we trying to set image comments, outside of Exif and IPTC.
|
|
|
|
if (!setComments(comment.utf8()))
|
|
return false;
|
|
|
|
// In Second we write comments into Exif.
|
|
|
|
if (!setExifComment(comment))
|
|
return false;
|
|
|
|
// In Third we write comments into Iptc.
|
|
// Note that Caption IPTC tag is limited to 2000 char and ASCII charset.
|
|
|
|
TQString commentIptc = comment;
|
|
commentIptc.truncate(2000);
|
|
|
|
if (!setIptcTagString("Iptc.Application2.Caption", commentIptc))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Iptc.Application2.Urgency <==> digiKam Rating links:
|
|
|
|
digiKam IPTC
|
|
Rating Urgency
|
|
|
|
0 star <=> 8 // Least important
|
|
1 star <=> 7
|
|
1 star <== 6
|
|
2 star <=> 5
|
|
3 star <=> 4
|
|
4 star <== 3
|
|
4 star <=> 2
|
|
5 star <=> 1 // Most important
|
|
*/
|
|
|
|
int DMetadata::getImageRating() const
|
|
{
|
|
if (getFilePath().isEmpty())
|
|
return -1;
|
|
|
|
// Check Exif rating tag set by Windows Vista
|
|
// Note : no need to check rating in percent tags (Exif.image.0x4747) here because
|
|
// its appear always with rating tag value (Exif.image.0x4749).
|
|
|
|
if (!getExif().isEmpty())
|
|
{
|
|
long rating = -1;
|
|
if (getExifTagLong("Exif.Image.0x4746", rating))
|
|
{
|
|
if (rating >= RatingMin && rating <= RatingMax)
|
|
return rating;
|
|
}
|
|
}
|
|
|
|
// Check Iptc Urgency tag content
|
|
|
|
if (!getIptc().isEmpty())
|
|
{
|
|
TQString IptcUrgency(getIptcTagData("Iptc.Application2.Urgency"));
|
|
|
|
if (!IptcUrgency.isEmpty())
|
|
{
|
|
if (IptcUrgency == TQString("1"))
|
|
return 5;
|
|
else if (IptcUrgency == TQString("2"))
|
|
return 4;
|
|
else if (IptcUrgency == TQString("3"))
|
|
return 4;
|
|
else if (IptcUrgency == TQString("4"))
|
|
return 3;
|
|
else if (IptcUrgency == TQString("5"))
|
|
return 2;
|
|
else if (IptcUrgency == TQString("6"))
|
|
return 1;
|
|
else if (IptcUrgency == TQString("7"))
|
|
return 1;
|
|
else if (IptcUrgency == TQString("8"))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool DMetadata::setImageRating(int rating)
|
|
{
|
|
if (rating < RatingMin || rating > RatingMax)
|
|
{
|
|
DDebug() << k_funcinfo << "Rating value to write is out of range!" << endl;
|
|
return false;
|
|
}
|
|
|
|
DDebug() << getFilePath() << " ==> Rating: " << rating << endl;
|
|
|
|
if (!setProgramId())
|
|
return false;
|
|
|
|
// Set Exif rating tag used by Windows Vista.
|
|
|
|
if (!setExifTagLong("Exif.Image.0x4746", rating))
|
|
return false;
|
|
|
|
// Wrapper around rating percents managed by Windows Vista.
|
|
int ratePercents = 0;
|
|
switch(rating)
|
|
{
|
|
case 0:
|
|
ratePercents = 0;
|
|
break;
|
|
case 1:
|
|
ratePercents = 1;
|
|
break;
|
|
case 2:
|
|
ratePercents = 25;
|
|
break;
|
|
case 3:
|
|
ratePercents = 50;
|
|
break;
|
|
case 4:
|
|
ratePercents = 75;
|
|
break;
|
|
case 5:
|
|
ratePercents = 99;
|
|
break;
|
|
}
|
|
|
|
if (!setExifTagLong("Exif.Image.0x4749", ratePercents))
|
|
return false;
|
|
|
|
// Set Iptc Urgency tag value.
|
|
|
|
TQString urgencyTag;
|
|
|
|
switch(rating)
|
|
{
|
|
case 0:
|
|
urgencyTag = TQString("8");
|
|
break;
|
|
case 1:
|
|
urgencyTag = TQString("7");
|
|
break;
|
|
case 2:
|
|
urgencyTag = TQString("5");
|
|
break;
|
|
case 3:
|
|
urgencyTag = TQString("4");
|
|
break;
|
|
case 4:
|
|
urgencyTag = TQString("3");
|
|
break;
|
|
case 5:
|
|
urgencyTag = TQString("1");
|
|
break;
|
|
}
|
|
|
|
if (!setIptcTagString("Iptc.Application2.Urgency", urgencyTag))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DMetadata::setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey)
|
|
{
|
|
TQString truncatedText = text;
|
|
truncatedText.truncate(maxLength);
|
|
DDebug() << getFilePath() << " ==> " << debugLabel << ": " << truncatedText << endl;
|
|
return setIptcTagString(tagKey, truncatedText); // returns false if failed
|
|
}
|
|
|
|
bool DMetadata::setImagePhotographerId(const TQString& author, const TQString& authorTitle)
|
|
{
|
|
if (!setProgramId())
|
|
return false;
|
|
|
|
//TODO Exernalize the hard-coded values
|
|
if (!setIptcTag(author, 32, "Author", "Iptc.Application2.Byline")) return false;
|
|
if (!setIptcTag(authorTitle, 32, "Author Title", "Iptc.Application2.BylineTitle")) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DMetadata::setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright)
|
|
{
|
|
if (!setProgramId())
|
|
return false;
|
|
|
|
//TODO Exernalize the hard-coded values
|
|
if (!setIptcTag(credit, 32, "Credit", "Iptc.Application2.Credit")) return false;
|
|
if (!setIptcTag(source, 32, "Source", "Iptc.Application2.Source")) return false;
|
|
if (!setIptcTag(copyright, 128, "Copyright", "Iptc.Application2.Copyright")) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DMetadata::setProgramId(bool on)
|
|
{
|
|
if (on)
|
|
{
|
|
TQString version(digikam_version);
|
|
TQString software("digiKam");
|
|
return setImageProgramId(software, version);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
PhotoInfoContainer DMetadata::getPhotographInformations() const
|
|
{
|
|
PhotoInfoContainer photoInfo;
|
|
|
|
if (!getExif().isEmpty())
|
|
{
|
|
photoInfo.dateTime = getImageDateTime();
|
|
photoInfo.make = getExifTagString("Exif.Image.Make");
|
|
photoInfo.model = getExifTagString("Exif.Image.Model");
|
|
|
|
photoInfo.aperture = getExifTagString("Exif.Photo.FNumber");
|
|
if (photoInfo.aperture.isEmpty())
|
|
photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue");
|
|
|
|
photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime");
|
|
if (photoInfo.exposureTime.isEmpty())
|
|
photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue");
|
|
|
|
photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode");
|
|
photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram");
|
|
|
|
photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength");
|
|
photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm");
|
|
|
|
photoInfo.sensitivity = getExifTagString("Exif.Photo.ISOSpeedRatings");
|
|
if (photoInfo.sensitivity.isEmpty())
|
|
photoInfo.sensitivity = getExifTagString("Exif.Photo.ExposureIndex");
|
|
|
|
photoInfo.flash = getExifTagString("Exif.Photo.Flash");
|
|
photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance");
|
|
}
|
|
|
|
return photoInfo;
|
|
}
|
|
|
|
/**
|
|
The following methods set and get an XML dataset into a private IPTC.Application2 tags
|
|
to backup digiKam image properties. The XML text data are compressed using zlib and stored
|
|
like a byte array. The XML text data format are like below:
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<digikamproperties>
|
|
<comments value="A cool photo from Adrien..." />
|
|
<date value="2006-11-23T13:36:26" />
|
|
<rating value="4" />
|
|
<tagslist>
|
|
<tag path="Gilles/Adrien/testphoto" />
|
|
<tag path="monuments/Trocadero/Tour Eiffel" />
|
|
<tag path="City/Paris" />
|
|
</tagslist>
|
|
</digikamproperties>
|
|
|
|
*/
|
|
|
|
bool DMetadata::getXMLImageProperties(TQString& comments, TQDateTime& date,
|
|
int& rating, TQStringList& tagsPath)
|
|
{
|
|
rating = 0;
|
|
|
|
TQByteArray data = getIptcTagData("Iptc.Application2.0x00ff");
|
|
if (data.isEmpty())
|
|
return false;
|
|
TQByteArray decompressedData = tqUncompress(data);
|
|
TQString doc;
|
|
TQDataStream ds(decompressedData, IO_ReadOnly);
|
|
ds >> doc;
|
|
|
|
TQDomDocument xmlDoc;
|
|
TQString error;
|
|
int row, col;
|
|
if (!xmlDoc.setContent(doc, true, &error, &row, &col))
|
|
{
|
|
DDebug() << doc << endl;
|
|
DDebug() << error << " :: row=" << row << " , col=" << col << endl;
|
|
return false;
|
|
}
|
|
|
|
TQDomElement rootElem = xmlDoc.documentElement();
|
|
if (rootElem.tagName() != TQString::fromLatin1("digikamproperties"))
|
|
return false;
|
|
|
|
for (TQDomNode node = rootElem.firstChild();
|
|
!node.isNull(); node = node.nextSibling())
|
|
{
|
|
TQDomElement e = node.toElement();
|
|
TQString name = e.tagName();
|
|
TQString val = e.attribute(TQString::fromLatin1("value"));
|
|
|
|
if (name == TQString::fromLatin1("comments"))
|
|
{
|
|
comments = val;
|
|
}
|
|
else if (name == TQString::fromLatin1("date"))
|
|
{
|
|
if (val.isEmpty()) continue;
|
|
date = TQDateTime::fromString(val, TQt::ISODate);
|
|
}
|
|
else if (name == TQString::fromLatin1("rating"))
|
|
{
|
|
if (val.isEmpty()) continue;
|
|
bool ok=false;
|
|
rating = val.toInt(&ok);
|
|
if (!ok) rating = 0;
|
|
}
|
|
else if (name == TQString::fromLatin1("tagslist"))
|
|
{
|
|
for (TQDomNode node2 = e.firstChild();
|
|
!node2.isNull(); node2 = node2.nextSibling())
|
|
{
|
|
TQDomElement e2 = node2.toElement();
|
|
TQString name2 = e2.tagName();
|
|
TQString val2 = e2.attribute(TQString::fromLatin1("path"));
|
|
|
|
if (name2 == TQString::fromLatin1("tag"))
|
|
{
|
|
if (val2.isEmpty()) continue;
|
|
tagsPath.append(val2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DMetadata::setXMLImageProperties(const TQString& comments, const TQDateTime& date,
|
|
int rating, const TQStringList& tagsPath)
|
|
{
|
|
TQDomDocument xmlDoc;
|
|
|
|
xmlDoc.appendChild(xmlDoc.createProcessingInstruction( TQString::fromLatin1("xml"),
|
|
TQString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) );
|
|
|
|
TQDomElement propertiesElem = xmlDoc.createElement(TQString::fromLatin1("digikamproperties"));
|
|
xmlDoc.appendChild( propertiesElem );
|
|
|
|
TQDomElement c = xmlDoc.createElement(TQString::fromLatin1("comments"));
|
|
c.setAttribute(TQString::fromLatin1("value"), comments);
|
|
propertiesElem.appendChild(c);
|
|
|
|
TQDomElement d = xmlDoc.createElement(TQString::fromLatin1("date"));
|
|
d.setAttribute(TQString::fromLatin1("value"), date.toString(TQt::ISODate));
|
|
propertiesElem.appendChild(d);
|
|
|
|
TQDomElement r = xmlDoc.createElement(TQString::fromLatin1("rating"));
|
|
r.setAttribute(TQString::fromLatin1("value"), rating);
|
|
propertiesElem.appendChild(r);
|
|
|
|
TQDomElement tagsElem = xmlDoc.createElement(TQString::fromLatin1("tagslist"));
|
|
propertiesElem.appendChild(tagsElem);
|
|
|
|
TQStringList path = tagsPath;
|
|
for ( TQStringList::Iterator it = path.begin(); it != path.end(); ++it )
|
|
{
|
|
TQDomElement e = xmlDoc.createElement(TQString::fromLatin1("tag"));
|
|
e.setAttribute(TQString::fromLatin1("path"), *it);
|
|
tagsElem.appendChild(e);
|
|
}
|
|
|
|
TQByteArray data, compressedData;
|
|
TQDataStream ds(data, IO_WriteOnly);
|
|
ds << xmlDoc.toString();
|
|
compressedData = tqCompress(data);
|
|
return (setIptcTagData("Iptc.Application2.0x00ff", compressedData));
|
|
}
|
|
|
|
} // NameSpace Digikam
|