File indexing completed on 2025-01-05 03:56:27
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-09-15 0007 * Description : Exiv2 library interface. 0008 * Exif manipulation methods 0009 * 0010 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "metaengine_p.h" 0018 0019 // C++ includes 0020 0021 #include <cctype> 0022 0023 // Qt includes 0024 0025 #include <QBuffer> 0026 0027 // KDE includes 0028 0029 #include <klocalizedstring.h> 0030 0031 // Local includes 0032 0033 #include "metaengine_rotation.h" 0034 #include "digikam_config.h" 0035 #include "digikam_debug.h" 0036 0037 #if defined(Q_CC_CLANG) 0038 # pragma clang diagnostic push 0039 # pragma clang diagnostic ignored "-Wdeprecated-declarations" 0040 #endif 0041 0042 namespace Digikam 0043 { 0044 0045 bool MetaEngine::canWriteExif(const QString& filePath) 0046 { 0047 QMutexLocker lock(&s_metaEngineMutex); 0048 0049 try 0050 { 0051 0052 #if defined Q_OS_WIN && defined EXV_UNICODE_PATH 0053 0054 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const wchar_t*)filePath.utf16()); 0055 0056 #elif defined Q_OS_WIN 0057 0058 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(QFile::encodeName(filePath).constData()); 0059 0060 #else 0061 0062 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(filePath.toUtf8().constData()); 0063 0064 #endif 0065 0066 Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif); 0067 0068 return ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)); 0069 } 0070 catch (Exiv2::AnyError& e) 0071 { 0072 qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot check Exif access mode with Exiv2:(Error #" 0073 << (int)e.code() << ": " << QString::fromStdString(e.what()) << ")"; 0074 } 0075 catch (...) 0076 { 0077 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0078 } 0079 0080 return false; 0081 } 0082 0083 bool MetaEngine::hasExif() const 0084 { 0085 return !d->exifMetadata().empty(); 0086 } 0087 0088 bool MetaEngine::clearExif() const 0089 { 0090 QMutexLocker lock(&s_metaEngineMutex); 0091 0092 try 0093 { 0094 d->exifMetadata().clear(); 0095 0096 return true; 0097 } 0098 catch (Exiv2::AnyError& e) 0099 { 0100 d->printExiv2ExceptionError(QLatin1String("Cannot clear Exif data with Exiv2:"), e); 0101 } 0102 catch (...) 0103 { 0104 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0105 } 0106 0107 return false; 0108 } 0109 0110 QByteArray MetaEngine::getExifEncoded(bool addExifHeader) const 0111 { 0112 QMutexLocker lock(&s_metaEngineMutex); 0113 0114 try 0115 { 0116 if (!d->exifMetadata().empty()) 0117 { 0118 QByteArray data; 0119 Exiv2::ExifData& exif = d->exifMetadata(); 0120 Exiv2::Blob blob; 0121 Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif); 0122 QByteArray ba((const char*)&blob[0], (int)blob.size()); 0123 0124 if (addExifHeader) 0125 { 0126 const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; 0127 data.resize(ba.size() + sizeof(ExifHeader)); 0128 memcpy(data.data(), ExifHeader, sizeof(ExifHeader)); 0129 memcpy(data.data() + sizeof(ExifHeader), ba.data(), ba.size()); 0130 } 0131 else 0132 { 0133 data = ba; 0134 } 0135 0136 return data; 0137 } 0138 } 0139 catch (Exiv2::AnyError& e) 0140 { 0141 if (!d->filePath.isEmpty()) 0142 { 0143 qCDebug(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData(); 0144 } 0145 0146 d->printExiv2ExceptionError(QLatin1String("Cannot get Exif data with Exiv2:"), e); 0147 } 0148 catch (...) 0149 { 0150 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0151 } 0152 0153 return QByteArray(); 0154 } 0155 0156 bool MetaEngine::setExif(const QByteArray& data) const 0157 { 0158 QMutexLocker lock(&s_metaEngineMutex); 0159 0160 try 0161 { 0162 if (!data.isEmpty()) 0163 { 0164 Exiv2::ExifParser::decode(d->exifMetadata(), (const Exiv2::byte*)data.data(), data.size()); 0165 0166 return (!d->exifMetadata().empty()); 0167 } 0168 } 0169 catch (Exiv2::AnyError& e) 0170 { 0171 if (!d->filePath.isEmpty()) 0172 { 0173 qCCritical(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData(); 0174 } 0175 0176 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif data with Exiv2:"), e); 0177 } 0178 catch (...) 0179 { 0180 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0181 } 0182 0183 return false; 0184 } 0185 0186 QString MetaEngine::getExifComment(bool readDescription) const 0187 { 0188 QString comment = getExifTagComment("Exif.Photo.UserComment"); 0189 0190 if (!comment.isEmpty()) 0191 { 0192 return comment; 0193 } 0194 0195 if (readDescription) 0196 { 0197 comment = getExifTagComment("Exif.Image.ImageDescription"); 0198 0199 return comment; 0200 } 0201 0202 return QString(); 0203 } 0204 0205 QString MetaEngine::getExifTagComment(const char* exifTagName) const 0206 { 0207 QMutexLocker lock(&s_metaEngineMutex); 0208 0209 try 0210 { 0211 // Since Exiv2-0.27.3 empty comment fields are output 0212 // as "binary comment". As workaround we filter it out. 0213 0214 QStringList blackList; 0215 blackList << QLatin1String("binary comment"); 0216 0217 if (!d->exifMetadata().empty()) 0218 { 0219 std::string exifkey(exifTagName); 0220 Exiv2::ExifData exifData(d->exifMetadata()); 0221 Exiv2::ExifKey key(exifkey); 0222 Exiv2::ExifData::const_iterator it = exifData.findKey(key); 0223 0224 if (it != exifData.end()) 0225 { 0226 QString exifComment; 0227 0228 if (QLatin1String(exifTagName) == QLatin1String("Exif.Image.XPComment")) 0229 { 0230 exifComment = QString::fromStdString(it->print(&exifData)); 0231 } 0232 else 0233 { 0234 exifComment = d->convertCommentValue(*it); 0235 } 0236 0237 QString trimmedComment = exifComment.trimmed(); 0238 0239 // Some cameras fill in nonsense default values 0240 0241 if (QLatin1String(exifTagName) == QLatin1String("Exif.Image.ImageDescription")) 0242 { 0243 blackList << QLatin1String("SONY DSC"); // + whitespace 0244 blackList << QLatin1String("OLYMPUS DIGITAL CAMERA"); 0245 blackList << QLatin1String("MINOLTA DIGITAL CAMERA"); 0246 } 0247 0248 // some cameras fill the UserComment with whitespace 0249 0250 if (!exifComment.isEmpty() && !trimmedComment.isEmpty() && !blackList.contains(trimmedComment)) 0251 { 0252 return exifComment; 0253 } 0254 } 0255 } 0256 } 0257 catch (Exiv2::AnyError& e) 0258 { 0259 d->printExiv2ExceptionError(QLatin1String("Cannot find Exif User Comment with Exiv2:"), e); 0260 } 0261 catch (...) 0262 { 0263 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0264 } 0265 0266 return QString(); 0267 } 0268 0269 static bool is7BitAscii(const QString& s) 0270 { 0271 for (int i = 0 ; i < s.size() ; ++i) 0272 { 0273 if (s.at(i).unicode() > 0x7f) 0274 { 0275 return false; 0276 } 0277 } 0278 0279 return true; 0280 } 0281 0282 bool MetaEngine::setExifComment(const QString& comment, bool writeDescription) const 0283 { 0284 QMutexLocker lock(&s_metaEngineMutex); 0285 0286 try 0287 { 0288 if (writeDescription) 0289 { 0290 removeExifTag("Exif.Image.ImageDescription"); 0291 } 0292 0293 removeExifTag("Exif.Photo.UserComment"); 0294 0295 if (!comment.isNull()) 0296 { 0297 if (writeDescription) 0298 { 0299 setExifTagString("Exif.Image.ImageDescription", comment); 0300 } 0301 0302 // Write as Unicode only when necessary. 0303 // Check if it's in the ASCII 7bit range 0304 0305 if (is7BitAscii(comment)) 0306 { 0307 // write as ASCII 0308 0309 QString exifComment(QString::fromLatin1("charset=Ascii %1").arg(comment)); 0310 d->exifMetadata()["Exif.Photo.UserComment"] = exifComment.toStdString(); 0311 0312 return true; 0313 } 0314 0315 // write as Unicode (UCS-2) 0316 0317 QString exifComment(QString::fromUtf8("charset=Unicode %1").arg(comment)); 0318 d->exifMetadata()["Exif.Photo.UserComment"] = exifComment.toStdString(); 0319 } 0320 0321 return true; 0322 } 0323 catch (Exiv2::AnyError& e) 0324 { 0325 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Comment with Exiv2:"), e); 0326 } 0327 catch (...) 0328 { 0329 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0330 } 0331 0332 return false; 0333 } 0334 0335 QString MetaEngine::getExifTagTitle(const char* exifTagName) 0336 { 0337 QMutexLocker lock(&s_metaEngineMutex); 0338 0339 try 0340 { 0341 std::string exifkey(exifTagName); 0342 Exiv2::ExifKey ek(exifkey); 0343 0344 return QString::fromStdString(ek.tagLabel()); 0345 } 0346 catch (Exiv2::AnyError& e) 0347 { 0348 d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag title with Exiv2:"), e); 0349 } 0350 catch (...) 0351 { 0352 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0353 } 0354 0355 return QString(); 0356 } 0357 0358 QString MetaEngine::getExifTagDescription(const char* exifTagName) 0359 { 0360 QMutexLocker lock(&s_metaEngineMutex); 0361 0362 try 0363 { 0364 std::string exifkey(exifTagName); 0365 Exiv2::ExifKey ek(exifkey); 0366 0367 return QString::fromStdString(ek.tagDesc()); 0368 } 0369 catch (Exiv2::AnyError& e) 0370 { 0371 d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag description with Exiv2:"), e); 0372 } 0373 catch (...) 0374 { 0375 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0376 } 0377 0378 return QString(); 0379 } 0380 0381 bool MetaEngine::removeExifTag(const char* exifTagName) const 0382 { 0383 QMutexLocker lock(&s_metaEngineMutex); 0384 0385 try 0386 { 0387 Exiv2::ExifKey exifKey(exifTagName); 0388 Exiv2::ExifData::iterator it = d->exifMetadata().findKey(exifKey); 0389 0390 if (it != d->exifMetadata().end()) 0391 { 0392 d->exifMetadata().erase(it); 0393 0394 return true; 0395 } 0396 } 0397 catch (Exiv2::AnyError& e) 0398 { 0399 d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif tag with Exiv2:"), e); 0400 } 0401 catch (...) 0402 { 0403 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0404 } 0405 0406 return false; 0407 } 0408 0409 bool MetaEngine::getExifTagRational(const char* exifTagName, long int& num, long int& den, int component) const 0410 { 0411 QMutexLocker lock(&s_metaEngineMutex); 0412 0413 try 0414 { 0415 Exiv2::ExifKey exifKey(exifTagName); 0416 Exiv2::ExifData exifData(d->exifMetadata()); 0417 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); 0418 0419 if (it != exifData.end()) 0420 { 0421 if ((*it).count()) 0422 { 0423 num = (*it).toRational(component).first; 0424 den = (*it).toRational(component).second; 0425 0426 return true; 0427 } 0428 } 0429 } 0430 catch (Exiv2::AnyError& e) 0431 { 0432 d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif Rational value from key '%1' into image with Exiv2:") 0433 .arg(QLatin1String(exifTagName)), e); 0434 } 0435 catch (...) 0436 { 0437 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0438 } 0439 0440 return false; 0441 } 0442 0443 bool MetaEngine::setExifTagLong(const char* exifTagName, long val) const 0444 { 0445 QMutexLocker lock(&s_metaEngineMutex); 0446 0447 try 0448 { 0449 d->exifMetadata()[exifTagName] = static_cast<int32_t>(val); // krazy:exclude=typedefs 0450 0451 return true; 0452 } 0453 catch (Exiv2::AnyError& e) 0454 { 0455 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag long value into image with Exiv2:"), e); 0456 } 0457 catch (...) 0458 { 0459 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0460 } 0461 0462 return false; 0463 } 0464 0465 bool MetaEngine::setExifTagRational(const char* exifTagName, long int num, long int den) const 0466 { 0467 QMutexLocker lock(&s_metaEngineMutex); 0468 0469 try 0470 { 0471 d->exifMetadata()[exifTagName] = Exiv2::Rational(num, den); 0472 0473 return true; 0474 } 0475 catch (Exiv2::AnyError& e) 0476 { 0477 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag rational value into image with Exiv2:"), e); 0478 } 0479 catch (...) 0480 { 0481 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0482 } 0483 0484 return false; 0485 } 0486 0487 bool MetaEngine::setExifTagData(const char* exifTagName, const QByteArray& data) const 0488 { 0489 if (data.isEmpty()) 0490 { 0491 return false; 0492 } 0493 0494 QMutexLocker lock(&s_metaEngineMutex); 0495 0496 try 0497 { 0498 Exiv2::DataValue val((Exiv2::byte*)data.data(), data.size()); 0499 d->exifMetadata()[exifTagName] = val; 0500 0501 return true; 0502 } 0503 catch (Exiv2::AnyError& e) 0504 { 0505 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag data into image with Exiv2:"), e); 0506 } 0507 catch (...) 0508 { 0509 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0510 } 0511 0512 return false; 0513 } 0514 0515 bool MetaEngine::setExifTagVariant(const char* exifTagName, const QVariant& val, 0516 bool rationalWantSmallDenominator) const 0517 { 0518 0519 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0520 0521 switch (val.typeId()) 0522 0523 #else 0524 0525 switch (val.type()) 0526 0527 #endif 0528 0529 { 0530 case QVariant::Int: 0531 case QVariant::UInt: 0532 case QVariant::Bool: 0533 case QVariant::LongLong: 0534 case QVariant::ULongLong: 0535 { 0536 return setExifTagLong(exifTagName, val.toInt()); 0537 } 0538 0539 case QVariant::Double: 0540 { 0541 long num, den; 0542 0543 if (rationalWantSmallDenominator) 0544 { 0545 convertToRationalSmallDenominator(val.toDouble(), &num, &den); 0546 } 0547 else 0548 { 0549 convertToRational(val.toDouble(), &num, &den, 4); 0550 } 0551 0552 return setExifTagRational(exifTagName, num, den); 0553 } 0554 0555 case QVariant::List: 0556 { 0557 long num = 0, den = 1; 0558 QList<QVariant> list = val.toList(); 0559 0560 if (list.size() >= 1) 0561 { 0562 num = list[0].toInt(); 0563 } 0564 0565 if (list.size() >= 2) 0566 { 0567 den = list[1].toInt(); 0568 } 0569 0570 return setExifTagRational(exifTagName, num, den); 0571 } 0572 0573 case QVariant::Date: 0574 case QVariant::DateTime: 0575 { 0576 QDateTime dateTime = val.toDateTime(); 0577 dateTime.setTimeSpec(Qt::UTC); 0578 0579 if (!dateTime.isValid()) 0580 { 0581 return false; 0582 } 0583 0584 try 0585 { 0586 d->exifMetadata()[exifTagName] = dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss")).toStdString(); 0587 } 0588 catch (Exiv2::AnyError& e) 0589 { 0590 d->printExiv2ExceptionError(QLatin1String("Cannot set Date & Time in image with Exiv2:"), e); 0591 } 0592 catch (...) 0593 { 0594 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0595 } 0596 0597 return false; 0598 } 0599 0600 case QVariant::String: 0601 case QVariant::Char: 0602 { 0603 return setExifTagString(exifTagName, val.toString()); 0604 } 0605 0606 case QVariant::ByteArray: 0607 { 0608 return setExifTagData(exifTagName, val.toByteArray()); 0609 } 0610 0611 default: 0612 { 0613 break; 0614 } 0615 } 0616 0617 return false; 0618 } 0619 0620 QString MetaEngine::createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR) 0621 { 0622 QMutexLocker lock(&s_metaEngineMutex); 0623 0624 try 0625 { 0626 Exiv2::ExifKey key(exifTagName); 0627 Exiv2::Exifdatum datum(key); 0628 0629 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0630 0631 switch (val.typeId()) 0632 0633 #else 0634 0635 switch (val.type()) 0636 0637 #endif 0638 0639 { 0640 case QVariant::Int: 0641 case QVariant::Bool: 0642 case QVariant::LongLong: 0643 case QVariant::ULongLong: 0644 { 0645 datum = (int32_t)val.toInt(); 0646 break; 0647 } 0648 0649 case QVariant::UInt: 0650 { 0651 datum = (uint32_t)val.toUInt(); 0652 break; 0653 } 0654 0655 case QVariant::Double: 0656 { 0657 long num, den; 0658 convertToRationalSmallDenominator(val.toDouble(), &num, &den); 0659 Exiv2::Rational rational; 0660 rational.first = num; 0661 rational.second = den; 0662 datum = rational; 0663 break; 0664 } 0665 0666 case QVariant::List: 0667 { 0668 long num = 0; 0669 long den = 1; 0670 QList<QVariant> list = val.toList(); 0671 0672 if (list.size() >= 1) 0673 { 0674 num = list[0].toInt(); 0675 } 0676 0677 if (list.size() >= 2) 0678 { 0679 den = list[1].toInt(); 0680 } 0681 0682 Exiv2::Rational rational; 0683 rational.first = num; 0684 rational.second = den; 0685 datum = rational; 0686 break; 0687 } 0688 0689 case QVariant::Date: 0690 case QVariant::DateTime: 0691 { 0692 QDateTime dateTime = val.toDateTime(); 0693 dateTime.setTimeSpec(Qt::UTC); 0694 0695 if (!dateTime.isValid()) 0696 { 0697 break; 0698 } 0699 0700 datum = dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss")).toStdString(); 0701 break; 0702 } 0703 0704 case QVariant::ByteArray: 0705 case QVariant::String: 0706 case QVariant::Char: 0707 { 0708 datum = val.toString().toStdString(); 0709 break; 0710 } 0711 0712 default: 0713 { 0714 break; 0715 } 0716 } 0717 0718 std::ostringstream os; 0719 os << datum; 0720 QString tagValue = QString::fromStdString(os.str()); 0721 0722 if (escapeCR) 0723 { 0724 tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); 0725 } 0726 0727 return tagValue; 0728 } 0729 catch (Exiv2::AnyError& e) 0730 { 0731 d->printExiv2ExceptionError(QLatin1String("Cannot get Exif tag user string with Exiv2:"), e); 0732 } 0733 catch (...) 0734 { 0735 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0736 } 0737 0738 return QString(); 0739 } 0740 0741 bool MetaEngine::getExifTagLong(const char* exifTagName, long& val) const 0742 { 0743 return getExifTagLong(exifTagName, val, 0); 0744 } 0745 0746 bool MetaEngine::getExifTagLong(const char* exifTagName, long& val, int component) const 0747 { 0748 QMutexLocker lock(&s_metaEngineMutex); 0749 0750 try 0751 { 0752 Exiv2::ExifKey exifKey(exifTagName); 0753 Exiv2::ExifData exifData(d->exifMetadata()); 0754 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); 0755 0756 if ((it != exifData.end()) && (it->count() > 0)) 0757 { 0758 #if EXIV2_TEST_VERSION(0,27,99) 0759 val = it->toInt64(component); 0760 #else 0761 val = it->toLong(component); 0762 #endif 0763 return true; 0764 } 0765 } 0766 catch (Exiv2::AnyError& e) 0767 { 0768 d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image with Exiv2:") 0769 .arg(QLatin1String(exifTagName)), e); 0770 } 0771 catch (...) 0772 { 0773 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0774 } 0775 0776 return false; 0777 } 0778 0779 QByteArray MetaEngine::getExifTagData(const char* exifTagName) const 0780 { 0781 QMutexLocker lock(&s_metaEngineMutex); 0782 0783 try 0784 { 0785 Exiv2::ExifKey exifKey(exifTagName); 0786 Exiv2::ExifData exifData(d->exifMetadata()); 0787 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); 0788 0789 if (it != exifData.end()) 0790 { 0791 QByteArray data((*it).size(), '\0'); 0792 (*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian); 0793 0794 return data; 0795 } 0796 } 0797 catch (Exiv2::AnyError& e) 0798 { 0799 d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image with Exiv2:") 0800 .arg(QLatin1String(exifTagName)), e); 0801 } 0802 catch (...) 0803 { 0804 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0805 } 0806 0807 return QByteArray(); 0808 } 0809 0810 QVariant MetaEngine::getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts, bool stringEscapeCR, int component) const 0811 { 0812 QMutexLocker lock(&s_metaEngineMutex); 0813 0814 try 0815 { 0816 Exiv2::ExifKey exifKey(exifTagName); 0817 Exiv2::ExifData exifData(d->exifMetadata()); 0818 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); 0819 0820 if (it != exifData.end()) 0821 { 0822 switch (it->typeId()) 0823 { 0824 case Exiv2::unsignedByte: 0825 case Exiv2::unsignedShort: 0826 case Exiv2::unsignedLong: 0827 case Exiv2::signedShort: 0828 case Exiv2::signedLong: 0829 { 0830 if ((int)it->count() > component) 0831 { 0832 #if EXIV2_TEST_VERSION(0,27,99) 0833 return QVariant((int)it->toInt64(component)); 0834 #else 0835 return QVariant((int)it->toLong(component)); 0836 #endif 0837 } 0838 else 0839 { 0840 0841 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0842 0843 return QVariant(QMetaType(QMetaType::Int)); 0844 0845 #else 0846 0847 return QVariant(QVariant::Int); 0848 0849 #endif 0850 0851 } 0852 } 0853 0854 case Exiv2::unsignedRational: 0855 case Exiv2::signedRational: 0856 { 0857 if (rationalAsListOfInts) 0858 { 0859 if ((int)it->count() <= component) 0860 { 0861 0862 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0863 0864 return QVariant(QMetaType(QMetaType::QVariantList)); 0865 0866 #else 0867 0868 return QVariant(QVariant::List); 0869 0870 #endif 0871 0872 } 0873 0874 QList<QVariant> list; 0875 list << (*it).toRational(component).first; 0876 list << (*it).toRational(component).second; 0877 0878 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0879 0880 return QVariant(QMetaType(QMetaType::QVariantList)); 0881 0882 #else 0883 0884 return QVariant(list); 0885 0886 #endif 0887 0888 } 0889 else 0890 { 0891 if ((int)it->count() <= component) 0892 { 0893 0894 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0895 0896 return QVariant(QMetaType(QMetaType::Double)); 0897 0898 #else 0899 0900 return QVariant(QVariant::Double); 0901 0902 #endif 0903 0904 } 0905 0906 // prefer double precision 0907 0908 double num = (*it).toRational(component).first; 0909 double den = (*it).toRational(component).second; 0910 0911 if (den == 0.0) 0912 { 0913 0914 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0915 0916 return QVariant(QMetaType(QMetaType::Double)); 0917 0918 #else 0919 0920 return QVariant(QVariant::Double); 0921 0922 #endif 0923 0924 } 0925 0926 return QVariant(num / den); 0927 } 0928 } 0929 0930 case Exiv2::date: 0931 case Exiv2::time: 0932 { 0933 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate); 0934 dateTime.setTimeSpec(Qt::UTC); 0935 0936 return QVariant(dateTime); 0937 } 0938 0939 case Exiv2::asciiString: 0940 case Exiv2::comment: 0941 case Exiv2::string: 0942 { 0943 std::ostringstream os; 0944 it->write(os, &exifData); 0945 QString tagValue = QString::fromStdString(os.str()); 0946 0947 if (stringEscapeCR) 0948 { 0949 tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); 0950 } 0951 0952 return QVariant(tagValue); 0953 } 0954 0955 default: 0956 { 0957 break; 0958 } 0959 } 0960 } 0961 } 0962 catch (Exiv2::AnyError& e) 0963 { 0964 d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' in the image with Exiv2:") 0965 .arg(QLatin1String(exifTagName)), e); 0966 } 0967 catch (...) 0968 { 0969 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0970 } 0971 0972 return QVariant(); 0973 } 0974 0975 QString MetaEngine::getExifTagString(const char* exifTagName, bool escapeCR) const 0976 { 0977 QMutexLocker lock(&s_metaEngineMutex); 0978 0979 try 0980 { 0981 Exiv2::ExifKey exifKey(exifTagName); 0982 Exiv2::ExifData exifData(d->exifMetadata()); 0983 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); 0984 0985 if (it != exifData.end()) 0986 { 0987 QString tagValue; 0988 QString key = QLatin1String(it->key().c_str()); 0989 0990 if ( 0991 (key == QLatin1String("Exif.CanonCs.LensType")) && 0992 #if EXIV2_TEST_VERSION(0,27,99) 0993 (it->toInt64() == 65535) 0994 #else 0995 (it->toLong() == 65535) 0996 #endif 0997 ) 0998 { 0999 // FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType. 1000 1001 tagValue = QString::fromStdString(it->toString()); 1002 } 1003 else 1004 { 1005 // See BUG #184156 comment #13 1006 1007 tagValue = QString::fromStdString(it->print(&exifData)); 1008 } 1009 1010 if (escapeCR) 1011 { 1012 tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); 1013 } 1014 1015 return tagValue; 1016 } 1017 } 1018 catch (Exiv2::AnyError& e) 1019 { 1020 d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image with Exiv2:") 1021 .arg(QLatin1String(exifTagName)), e); 1022 } 1023 catch (...) 1024 { 1025 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1026 } 1027 1028 return QString(); 1029 } 1030 1031 bool MetaEngine::setExifTagString(const char* exifTagName, const QString& value) const 1032 { 1033 QMutexLocker lock(&s_metaEngineMutex); 1034 1035 try 1036 { 1037 d->exifMetadata()[exifTagName] = value.toStdString(); 1038 1039 return true; 1040 } 1041 catch (Exiv2::AnyError& e) 1042 { 1043 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag string into image with Exiv2:"), e); 1044 } 1045 catch (...) 1046 { 1047 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1048 } 1049 1050 return false; 1051 } 1052 1053 QImage MetaEngine::getExifThumbnail(bool fixOrientation) const 1054 { 1055 QImage thumbnail; 1056 1057 if (d->exifMetadata().empty()) 1058 { 1059 return thumbnail; 1060 } 1061 1062 QMutexLocker lock(&s_metaEngineMutex); 1063 1064 try 1065 { 1066 Exiv2::ExifThumbC thumb(d->exifMetadata()); 1067 Exiv2::DataBuf const c1 = thumb.copy(); 1068 1069 #if EXIV2_TEST_VERSION(0,27,99) 1070 if (c1.size() == 0) 1071 { 1072 return thumbnail; 1073 } 1074 1075 thumbnail.loadFromData(c1.c_data(), c1.size()); 1076 #else 1077 if (c1.size_ == 0) 1078 { 1079 return thumbnail; 1080 } 1081 1082 thumbnail.loadFromData(c1.pData_, c1.size_); 1083 #endif 1084 1085 if (!thumbnail.isNull()) 1086 { 1087 if (fixOrientation) 1088 { 1089 Exiv2::ExifKey key1("Exif.Thumbnail.Orientation"); 1090 Exiv2::ExifKey key2("Exif.Image.Orientation"); 1091 Exiv2::ExifData exifData(d->exifMetadata()); 1092 Exiv2::ExifData::const_iterator it = exifData.findKey(key1); 1093 1094 if (it == exifData.end()) 1095 { 1096 it = exifData.findKey(key2); 1097 } 1098 1099 if (it != exifData.end() && it->count()) 1100 { 1101 #if EXIV2_TEST_VERSION(0,27,99) 1102 long orientation = it->toInt64(); 1103 #else 1104 long orientation = it->toLong(); 1105 #endif 1106 1107 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif Thumbnail Orientation: " << (int)orientation; 1108 1109 rotateExifQImage(thumbnail, (ImageOrientation)orientation); 1110 } 1111 1112 return thumbnail; 1113 } 1114 } 1115 } 1116 catch (Exiv2::AnyError& e) 1117 { 1118 d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Thumbnail with Exiv2:"), e); 1119 } 1120 catch (...) 1121 { 1122 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1123 } 1124 1125 return thumbnail; 1126 } 1127 1128 bool MetaEngine::rotateExifQImage(QImage& image, ImageOrientation orientation) const 1129 { 1130 QTransform matrix = MetaEngineRotation::toTransform(orientation); 1131 1132 if ((orientation != ORIENTATION_NORMAL) && (orientation != ORIENTATION_UNSPECIFIED)) 1133 { 1134 image = image.transformed(matrix); 1135 1136 return true; 1137 } 1138 1139 return false; 1140 } 1141 1142 bool MetaEngine::setExifThumbnail(const QImage& thumbImage) const 1143 { 1144 if (thumbImage.isNull()) 1145 { 1146 return removeExifThumbnail(); 1147 } 1148 1149 QMutexLocker lock(&s_metaEngineMutex); 1150 1151 try 1152 { 1153 QByteArray data; 1154 QBuffer buffer(&data); 1155 buffer.open(QIODevice::WriteOnly); 1156 thumbImage.save(&buffer, "JPEG"); 1157 buffer.close(); 1158 Exiv2::ExifThumb thumb(d->exifMetadata()); 1159 thumb.setJpegThumbnail((Exiv2::byte*)data.data(), data.size()); 1160 1161 return true; 1162 } 1163 catch (Exiv2::AnyError& e) 1164 { 1165 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Thumbnail with Exiv2:"), e); 1166 } 1167 catch (...) 1168 { 1169 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1170 } 1171 1172 return false; 1173 } 1174 1175 bool MetaEngine::setTiffThumbnail(const QImage& thumbImage) const 1176 { 1177 removeExifThumbnail(); 1178 1179 QMutexLocker lock(&s_metaEngineMutex); 1180 1181 try 1182 { 1183 // Make sure IFD0 is explicitly marked as a main image 1184 1185 Exiv2::ExifData::const_iterator pos = d->exifMetadata().findKey(Exiv2::ExifKey("Exif.Image.NewSubfileType")); 1186 1187 if ( 1188 (pos == d->exifMetadata().end()) || 1189 (pos->count() != 1) || 1190 #if EXIV2_TEST_VERSION(0,27,99) 1191 (pos->toInt64() != 0) 1192 #else 1193 (pos->toLong() != 0) 1194 #endif 1195 ) 1196 { 1197 1198 #if EXIV2_TEST_VERSION(0,27,0) 1199 1200 throw Exiv2::Error(Exiv2::kerErrorMessage, "Exif.Image.NewSubfileType missing or not set as main image"); 1201 1202 #else 1203 1204 throw Exiv2::Error(1, "Exif.Image.NewSubfileType missing or not set as main image"); 1205 1206 #endif 1207 1208 } 1209 1210 // Remove sub-IFD tags 1211 1212 std::string subImage1("SubImage1"); 1213 1214 for (Exiv2::ExifData::iterator md = d->exifMetadata().begin() ; md != d->exifMetadata().end() ; ) 1215 { 1216 if (md->groupName() == subImage1) 1217 { 1218 md = d->exifMetadata().erase(md); 1219 } 1220 else 1221 { 1222 ++md; 1223 } 1224 } 1225 1226 if (!thumbImage.isNull()) 1227 { 1228 // Set thumbnail tags 1229 1230 QByteArray data; 1231 QBuffer buffer(&data); 1232 buffer.open(QIODevice::WriteOnly); 1233 thumbImage.save(&buffer, "JPEG"); 1234 buffer.close(); 1235 1236 Exiv2::DataBuf buf((Exiv2::byte*)data.data(), data.size()); 1237 Exiv2::ULongValue val; 1238 val.read("0"); 1239 #if EXIV2_TEST_VERSION(0,27,99) 1240 val.setDataArea(buf.data(), buf.size()); 1241 #else 1242 val.setDataArea(buf.pData_, buf.size_); 1243 #endif 1244 d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormat"] = val; 1245 #if EXIV2_TEST_VERSION(0,27,99) 1246 d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormatLength"] = uint32_t(buf.size()); 1247 #else 1248 d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormatLength"] = uint32_t(buf.size_); 1249 #endif 1250 d->exifMetadata()["Exif.SubImage1.Compression"] = uint16_t(6); // JPEG (old-style) 1251 d->exifMetadata()["Exif.SubImage1.NewSubfileType"] = uint32_t(1); // Thumbnail image 1252 1253 return true; 1254 } 1255 } 1256 catch (Exiv2::AnyError& e) 1257 { 1258 d->printExiv2ExceptionError(QLatin1String("Cannot set TIFF Thumbnail with Exiv2:"), e); 1259 } 1260 catch (...) 1261 { 1262 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1263 } 1264 1265 return false; 1266 } 1267 1268 bool MetaEngine::removeExifThumbnail() const 1269 { 1270 QMutexLocker lock(&s_metaEngineMutex); 1271 1272 try 1273 { 1274 // Remove all IFD0 subimages. 1275 1276 Exiv2::ExifThumb thumb(d->exifMetadata()); 1277 thumb.erase(); 1278 1279 return true; 1280 } 1281 catch (Exiv2::AnyError& e) 1282 { 1283 d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif Thumbnail with Exiv2:"), e); 1284 } 1285 catch (...) 1286 { 1287 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1288 } 1289 1290 return false; 1291 } 1292 1293 MetaEngine::MetaDataMap MetaEngine::getExifTagsDataList(const QStringList& exifKeysFilter, 1294 bool invertSelection, 1295 bool extractBinary) const 1296 { 1297 if (d->exifMetadata().empty()) 1298 { 1299 return MetaDataMap(); 1300 } 1301 1302 QMutexLocker lock(&s_metaEngineMutex); 1303 1304 try 1305 { 1306 Exiv2::ExifData exifData = d->exifMetadata(); 1307 exifData.sortByKey(); 1308 1309 QString ifDItemName; 1310 MetaDataMap metaDataMap; 1311 std::ostringstream os; 1312 QString key; 1313 QString tagValue; 1314 double num = 0.0; 1315 double den = 0.0; 1316 1317 for (Exiv2::ExifData::const_iterator md = exifData.begin() ; md != exifData.end() ; ++md) 1318 { 1319 key = QLatin1String(md->key().c_str()); 1320 1321 // Decode the tag value with a user friendly output. 1322 1323 if (key == QLatin1String("Exif.Photo.UserComment")) 1324 { 1325 tagValue = d->convertCommentValue(*md); 1326 } 1327 else if (key == QLatin1String("Exif.GPSInfo.GPSAreaInformation")) 1328 { 1329 // NOTE: special cases to render contents of these GPS info tags. See bug #425884. 1330 1331 tagValue = d->convertCommentValue(*md); 1332 } 1333 else if (key == QLatin1String("Exif.GPSInfo.GPSProcessingMethod")) 1334 { 1335 // NOTE: special cases to render contents of these GPS info tags. See bug #425884. 1336 1337 tagValue = d->convertCommentValue(*md); 1338 } 1339 else if ((key == QLatin1String("Exif.GPSInfo.GPSTrack")) || 1340 (key == QLatin1String("Exif.GPSInfo.GPSImgDirection"))) 1341 { 1342 // NOTE: special cases to render contents of these GPS info tags. See bug #435317. 1343 // Check value byte size, otherwise Exiv2 crashes in the toRational() function. 1344 1345 if ((*md).count()) 1346 { 1347 num = (*md).toRational().first; 1348 den = (*md).toRational().second; 1349 } 1350 1351 tagValue = (den == 0.0) ? QString::fromStdString(md->print()) 1352 : QString::fromLatin1("%1 deg").arg(num / den); 1353 } 1354 else if (key == QLatin1String("Exif.GPSInfo.GPSSpeed")) 1355 { 1356 // NOTE: special cases to render contents of these GPS info tags. See bug #435317. 1357 // Check value byte size, otherwise Exiv2 crashes in the toRational() function. 1358 1359 if ((*md).count()) 1360 { 1361 num = (*md).toRational().first; 1362 den = (*md).toRational().second; 1363 } 1364 1365 tagValue = (den == 0.0) ? QString::fromStdString(md->print()) 1366 : tagValue = QString::number(num / den); 1367 } 1368 else if (key == QLatin1String("Exif.Image.0x935c")) 1369 { 1370 tagValue = QString::number(md->value().size()); 1371 } 1372 else if ( 1373 (key == QLatin1String("Exif.CanonCs.LensType")) && 1374 #if EXIV2_TEST_VERSION(0,27,99) 1375 (md->toInt64() == 65535) 1376 #else 1377 (md->toLong() == 65535) 1378 #endif 1379 ) 1380 { 1381 // FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType. 1382 1383 tagValue = QString::fromStdString(md->toString()); 1384 } 1385 else 1386 { 1387 if (!extractBinary && (md->typeId() == Exiv2::undefined)) 1388 { 1389 tagValue = i18nc("info", "Binary data %1 bytes", md->size()); 1390 } 1391 else 1392 { 1393 os.str(""); 1394 os.clear(); 1395 md->write(os, &exifData); 1396 1397 // Exif tag contents can be a translated strings, not only simple ascii. 1398 1399 tagValue = QString::fromStdString(os.str()); 1400 } 1401 } 1402 1403 tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); 1404 1405 // We apply a filter to get only the Exif tags that we need. 1406 1407 if (!exifKeysFilter.isEmpty()) 1408 { 1409 if (!invertSelection) 1410 { 1411 if (exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) 1412 { 1413 metaDataMap.insert(key, tagValue); 1414 } 1415 } 1416 else 1417 { 1418 if (!exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) 1419 { 1420 metaDataMap.insert(key, tagValue); 1421 } 1422 } 1423 } 1424 else // else no filter at all. 1425 { 1426 metaDataMap.insert(key, tagValue); 1427 } 1428 } 1429 1430 return metaDataMap; 1431 } 1432 catch (Exiv2::AnyError& e) 1433 { 1434 d->printExiv2ExceptionError(QLatin1String("Cannot parse EXIF metadata with Exiv2:"), e); 1435 } 1436 catch (...) 1437 { 1438 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1439 } 1440 1441 return MetaDataMap(); 1442 } 1443 1444 MetaEngine::TagsMap MetaEngine::getStdExifTagsList() const 1445 { 1446 QMutexLocker lock(&s_metaEngineMutex); 1447 1448 try 1449 { 1450 QList<const Exiv2::TagInfo*> tags; 1451 TagsMap tagsMap; 1452 1453 const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); 1454 1455 while (gi->tagList_ != nullptr) 1456 { 1457 // NOTE: See BUG #375809 : MPF tags = exception Exiv2 0.26 1458 1459 if (QLatin1String(gi->ifdName_) != QLatin1String("Makernote")) 1460 { 1461 Exiv2::TagListFct tl = gi->tagList_; 1462 const Exiv2::TagInfo* ti = tl(); 1463 1464 while (ti->tag_ != 0xFFFF) 1465 { 1466 tags << ti; 1467 ++ti; 1468 } 1469 } 1470 1471 ++gi; 1472 } 1473 1474 for (QList<const Exiv2::TagInfo*>::iterator it = tags.begin() ; it != tags.end() ; ++it) 1475 { 1476 do 1477 { 1478 const Exiv2::TagInfo* const ti = *it; 1479 1480 if (ti) 1481 { 1482 QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); 1483 QStringList values; 1484 values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_); 1485 tagsMap.insert(key, values); 1486 } 1487 1488 ++(*it); 1489 } 1490 while ((*it)->tag_ != 0xffff); 1491 } 1492 1493 return tagsMap; 1494 } 1495 catch (Exiv2::AnyError& e) 1496 { 1497 d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Tags list with Exiv2:"), e); 1498 } 1499 catch (...) 1500 { 1501 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1502 } 1503 1504 return TagsMap(); 1505 } 1506 1507 MetaEngine::TagsMap MetaEngine::getMakernoteTagsList() const 1508 { 1509 QMutexLocker lock(&s_metaEngineMutex); 1510 1511 try 1512 { 1513 QList<const Exiv2::TagInfo*> tags; 1514 TagsMap tagsMap; 1515 1516 const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); 1517 1518 while (gi->tagList_ != nullptr) 1519 { 1520 if (QLatin1String(gi->ifdName_) == QLatin1String("Makernote")) 1521 { 1522 Exiv2::TagListFct tl = gi->tagList_; 1523 const Exiv2::TagInfo* ti = tl(); 1524 1525 while (ti->tag_ != 0xFFFF) 1526 { 1527 tags << ti; 1528 ++ti; 1529 } 1530 } 1531 1532 ++gi; 1533 } 1534 1535 for (QList<const Exiv2::TagInfo*>::iterator it = tags.begin() ; it != tags.end() ; ++it) 1536 { 1537 do 1538 { 1539 const Exiv2::TagInfo* const ti = *it; 1540 1541 if (ti) 1542 { 1543 QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); 1544 QStringList values; 1545 values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_); 1546 tagsMap.insert(key, values); 1547 } 1548 1549 ++(*it); 1550 } 1551 while ((*it)->tag_ != 0xffff); 1552 } 1553 1554 return tagsMap; 1555 } 1556 catch (Exiv2::AnyError& e) 1557 { 1558 d->printExiv2ExceptionError(QLatin1String("Cannot get Makernote Tags list with Exiv2:"), e); 1559 } 1560 catch (...) 1561 { 1562 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 1563 } 1564 1565 return TagsMap(); 1566 } 1567 1568 } // namespace Digikam 1569 1570 #if defined(Q_CC_CLANG) 1571 # pragma clang diagnostic pop 1572 #endif