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.
digikam/digikam/libs/dmetadata/dmetadata.cpp

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