File indexing completed on 2025-01-19 03:56:02

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  *               Internal private container.
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 // Qt includes
0020 
0021 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
0022 #   include <QTextCodec>
0023 #endif
0024 
0025 #include <qplatformdefs.h>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "digikam_config.h"
0031 #include "metaengine_data_p.h"
0032 #include "drawfiles.h"
0033 #include "exiftoolparser.h"
0034 #include "filereadwritelock.h"
0035 
0036 // Pragma directives to reduce warnings from Exiv2.
0037 
0038 #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG)
0039 #   pragma GCC diagnostic push
0040 #   pragma GCC diagnostic ignored "-Wdeprecated-declarations"
0041 #endif
0042 
0043 #if defined(Q_CC_CLANG)
0044 #   pragma clang diagnostic push
0045 #   pragma clang diagnostic ignored "-Wdeprecated-declarations"
0046 #endif
0047 
0048 namespace Digikam
0049 {
0050 
0051 /**
0052  * This mutex is used to protect all Exiv2 API calls when MetaEngine is used with multi-threads.
0053  */
0054 QRecursiveMutex s_metaEngineMutex;
0055 
0056 /**
0057  * Boolean value about Bmff based file support (CR3, HEIF, HEIC, and AVIF).
0058  * Initialized at run time by initializeExiv2().
0059  */
0060 bool s_metaEngineSupportBmff = false;
0061 
0062 /**
0063  * Boolean value about Exiv2 warning or error in the Exiv2::LogMsg.
0064  * Changed in MetaEngine::Private::printExiv2MessageHandler.
0065  */
0066 bool s_metaEngineWarnOrError = false;
0067 
0068 MetaEngine::Private::Private(MetaEngine* const q)
0069     : writeWithExifTool     (false),
0070       writeRawFiles         (false),
0071       writeDngFiles         (false),
0072       updateFileTimeStamp   (false),
0073       useXMPSidecar4Reading (false),
0074       useCompatibleFileName (false),
0075       metadataWritingMode   (WRITE_TO_FILE_ONLY),
0076       loadedFromSidecar     (false),
0077       parent                (q),
0078       data                  (new MetaEngineData::Private)
0079 {
0080     QMutexLocker lock(&s_metaEngineMutex);
0081     Exiv2::LogMsg::setHandler(MetaEngine::Private::printExiv2MessageHandler);
0082 }
0083 
0084 MetaEngine::Private::~Private()
0085 {
0086 }
0087 
0088 const Exiv2::ExifData& MetaEngine::Private::exifMetadata() const
0089 {
0090     return data.constData()->exifMetadata;
0091 }
0092 
0093 const Exiv2::IptcData& MetaEngine::Private::iptcMetadata() const
0094 {
0095     return data.constData()->iptcMetadata;
0096 }
0097 
0098 const std::string& MetaEngine::Private::itemComments() const
0099 {
0100     return data.constData()->imageComments;
0101 }
0102 
0103 Exiv2::ExifData& MetaEngine::Private::exifMetadata()
0104 {
0105     return data.data()->exifMetadata;
0106 }
0107 
0108 Exiv2::IptcData& MetaEngine::Private::iptcMetadata()
0109 {
0110     return data.data()->iptcMetadata;
0111 }
0112 
0113 std::string& MetaEngine::Private::itemComments()
0114 {
0115     return data.data()->imageComments;
0116 }
0117 
0118 #ifdef _XMP_SUPPORT_
0119 
0120 const Exiv2::XmpData& MetaEngine::Private::xmpMetadata() const
0121 {
0122     return data.constData()->xmpMetadata;
0123 }
0124 
0125 Exiv2::XmpData& MetaEngine::Private::xmpMetadata()
0126 {
0127     return data.data()->xmpMetadata;
0128 }
0129 
0130 #endif // _XMP_SUPPORT_
0131 
0132 void MetaEngine::Private::copyPrivateData(const Private* const other)
0133 {
0134     QMutexLocker lock(&s_metaEngineMutex);
0135 
0136     data                  = other->data;
0137     filePath              = other->filePath;
0138     writeRawFiles         = other->writeRawFiles;
0139     writeDngFiles         = other->writeDngFiles;
0140     updateFileTimeStamp   = other->updateFileTimeStamp;
0141     useXMPSidecar4Reading = other->useXMPSidecar4Reading;
0142     useCompatibleFileName = other->useCompatibleFileName;
0143     metadataWritingMode   = other->metadataWritingMode;
0144 }
0145 
0146 bool MetaEngine::Private::saveToXMPSidecar(const QFileInfo& finfo) const
0147 {
0148     QString xmpFile = MetaEngine::sidecarFilePathForFile(finfo.filePath());
0149 
0150     if (xmpFile.isEmpty())
0151     {
0152         return false;
0153     }
0154 
0155     QFileInfo xmpInfo(xmpFile);
0156     QDateTime modTime = xmpInfo.exists() ? xmpInfo.fileTime(QFileDevice::FileModificationTime)
0157                                          : finfo.fileTime(QFileDevice::FileModificationTime);
0158 
0159     QMutexLocker lock(&s_metaEngineMutex);
0160 
0161     try
0162     {
0163         Exiv2::Image::AutoPtr image;
0164 
0165 #if defined Q_OS_WIN && defined EXV_UNICODE_PATH
0166 
0167         image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp,
0168                                             (const wchar_t*)xmpFile.utf16());
0169 
0170 #elif defined Q_OS_WIN
0171 
0172         image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp,
0173                                             QFile::encodeName(xmpFile).constData());
0174 
0175 #else
0176 
0177         image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp,
0178                                             xmpFile.toUtf8().constData());
0179 
0180 #endif
0181 
0182 #if EXIV2_TEST_VERSION(0,27,99)
0183 
0184         return saveUsingExiv2(xmpInfo, modTime, std::move(image));
0185 
0186 #else
0187 
0188         return saveUsingExiv2(xmpInfo, modTime, image);
0189 
0190 #endif
0191 
0192     }
0193     catch (Exiv2::AnyError& e)
0194     {
0195         printExiv2ExceptionError(QLatin1String("Cannot save metadata to XMP sidecar with Exiv2 backend:"), e);
0196 
0197         return false;
0198     }
0199     catch (...)
0200     {
0201         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0202 
0203         return false;
0204     }
0205 }
0206 
0207 bool MetaEngine::Private::saveToFile(const QFileInfo& finfo) const
0208 {
0209     if (!finfo.isWritable())
0210     {
0211         qCDebug(DIGIKAM_METAENGINE_LOG) << "File" << finfo.fileName()
0212                                         << "is read only. Metadata not written.";
0213         return false;
0214     }
0215 
0216     QDateTime modTime = finfo.fileTime(QFileDevice::FileModificationTime);
0217 
0218     QMutexLocker lock(&s_metaEngineMutex);
0219 
0220     if (writeWithExifTool)
0221     {
0222         return saveUsingExifTool(finfo, modTime);
0223     }
0224     else
0225     {
0226         try
0227         {
0228             Exiv2::Image::AutoPtr image;
0229 
0230 #if defined Q_OS_WIN && defined EXV_UNICODE_PATH
0231 
0232             image = Exiv2::ImageFactory::open((const wchar_t*)finfo.filePath().utf16());
0233 
0234 #elif defined Q_OS_WIN
0235 
0236             image = Exiv2::ImageFactory::open(QFile::encodeName(finfo.filePath()).constData());
0237 
0238 #else
0239 
0240             image = Exiv2::ImageFactory::open(finfo.filePath().toUtf8().constData());
0241 
0242 #endif
0243 
0244 #if EXIV2_TEST_VERSION(0,27,99)
0245 
0246             return saveUsingExiv2(finfo, modTime, std::move(image));
0247 
0248 #else
0249 
0250             return saveUsingExiv2(finfo, modTime, image);
0251 
0252 #endif
0253 
0254         }
0255         catch (Exiv2::AnyError& e)
0256         {
0257             printExiv2ExceptionError(QLatin1String("Cannot save metadata to image with Exiv2 backend:"), e);
0258 
0259             return false;
0260         }
0261         catch (...)
0262         {
0263             qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0264 
0265             return false;
0266         }
0267     }
0268 }
0269 
0270 bool MetaEngine::Private::saveUsingExiv2(const QFileInfo& finfo,
0271                                          const QDateTime& modTime,
0272                                          Exiv2::Image::AutoPtr image) const
0273 {
0274     QMutexLocker lock(&s_metaEngineMutex);
0275 
0276     QString ext = finfo.suffix().toLower();
0277 
0278     if (!ext.isEmpty())
0279     {
0280         if (s_rawFileExtensions().contains(ext))
0281         {
0282             // NOTE: never touch RAW files with Exiv2 as it's not safe. Use ExifTool backend instead.
0283 
0284             return false;
0285         }
0286 
0287         // Check for a PGF image, this would be destroyed by Exiv2.
0288 
0289         if (ext == QLatin1String("pgf"))
0290         {
0291             return false;
0292         }
0293     }
0294 
0295     try
0296     {
0297         Exiv2::AccessMode mode;
0298         bool wroteComment = false;
0299         bool wroteEXIF    = false;
0300         bool wroteIPTC    = false;
0301         bool wroteXMP     = false;
0302 
0303         // We need to load target file metadata to merge with new one. It's mandatory with TIFF format:
0304         // like all tiff file structure is based on Exif.
0305 
0306         image->readMetadata();
0307 
0308         // Image Comments ---------------------------------
0309 
0310         mode = image->checkMode(Exiv2::mdComment);
0311 
0312         if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
0313         {
0314             image->setComment(itemComments());
0315             wroteComment = true;
0316         }
0317 
0318         qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteComment: " << wroteComment;
0319 
0320         // Exif metadata ----------------------------------
0321 
0322         mode = image->checkMode(Exiv2::mdExif);
0323 
0324         if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
0325         {
0326             if (image->mimeType() == "image/tiff")
0327             {
0328                 Exiv2::ExifData orgExif = image->exifData();
0329                 Exiv2::ExifData newExif;
0330                 QStringList     untouchedTags;
0331 
0332                 // With tiff image we cannot overwrite whole Exif data as well, because
0333                 // image data are stored in Exif container. We need to take a care about
0334                 // to not lost image data.
0335 
0336                 untouchedTags << QLatin1String("Exif.Image.ImageWidth");
0337                 untouchedTags << QLatin1String("Exif.Image.ImageLength");
0338                 untouchedTags << QLatin1String("Exif.Image.BitsPerSample");
0339                 untouchedTags << QLatin1String("Exif.Image.Compression");
0340                 untouchedTags << QLatin1String("Exif.Image.PhotometricInterpretation");
0341                 untouchedTags << QLatin1String("Exif.Image.FillOrder");
0342                 untouchedTags << QLatin1String("Exif.Image.SamplesPerPixel");
0343                 untouchedTags << QLatin1String("Exif.Image.StripOffsets");
0344                 untouchedTags << QLatin1String("Exif.Image.RowsPerStrip");
0345                 untouchedTags << QLatin1String("Exif.Image.StripByteCounts");
0346                 untouchedTags << QLatin1String("Exif.Image.XResolution");
0347                 untouchedTags << QLatin1String("Exif.Image.YResolution");
0348                 untouchedTags << QLatin1String("Exif.Image.PlanarConfiguration");
0349                 untouchedTags << QLatin1String("Exif.Image.ResolutionUnit");
0350 
0351                 for (Exiv2::ExifData::const_iterator it = orgExif.begin() ; it != orgExif.end() ; ++it)
0352                 {
0353                     if (untouchedTags.contains(QLatin1String(it->key().c_str())))
0354                     {
0355                         newExif[it->key().c_str()] = orgExif[it->key().c_str()];
0356                     }
0357                 }
0358 
0359                 Exiv2::ExifData readedExif = exifMetadata();
0360 
0361                 for (Exiv2::ExifData::const_iterator it = readedExif.begin() ; it != readedExif.end() ; ++it)
0362                 {
0363                     if (!untouchedTags.contains(QLatin1String(it->key().c_str())))
0364                     {
0365                         newExif[it->key().c_str()] = readedExif[it->key().c_str()];
0366                     }
0367                 }
0368 
0369                 image->setExifData(newExif);
0370             }
0371             else
0372             {
0373                 image->setExifData(exifMetadata());
0374             }
0375 
0376             wroteEXIF = true;
0377         }
0378 
0379         qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteEXIF: " << wroteEXIF;
0380 
0381         // Iptc metadata ----------------------------------
0382 
0383         mode = image->checkMode(Exiv2::mdIptc);
0384 
0385         if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
0386         {
0387             image->setIptcData(iptcMetadata());
0388             wroteIPTC = true;
0389         }
0390 
0391         qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteIPTC: " << wroteIPTC;
0392 
0393         // Xmp metadata -----------------------------------
0394 
0395         mode = image->checkMode(Exiv2::mdXmp);
0396 
0397         if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
0398         {
0399 
0400 #ifdef _XMP_SUPPORT_
0401 
0402             image->setXmpData(xmpMetadata());
0403 
0404             if (image->imageType() == Exiv2::ImageType::xmp)
0405             {
0406                 QList<QPair<QString, QString> > pairedTags;
0407                 typedef QPair<QString, QString> StringPair;
0408 
0409                 // We have XMP tags interacting with IPTC tags when writing to sidecar files.
0410                 // If an XMP tag does not exist remove the corresponding IPTC tag as well.
0411                 // See: https://exiv2.org/conversion.html
0412                 // See: BUG 462071
0413 
0414                 pairedTags << qMakePair(QLatin1String("Xmp.dc.subject"),
0415                                         QLatin1String("Iptc.Application2.Keywords"));
0416 
0417                 Q_FOREACH (const StringPair& pair, pairedTags)
0418                 {
0419                     Exiv2::XmpKey xmpKey(pair.first.toLatin1().constData());
0420                     Exiv2::XmpData::const_iterator it1 = image->xmpData().findKey(xmpKey);
0421 
0422                     if (it1 == image->xmpData().end())
0423                     {
0424                         Exiv2::IptcKey iptcKey(pair.second.toLatin1().constData());
0425                         Exiv2::IptcData::iterator it2 = image->iptcData().findKey(iptcKey);
0426 
0427                         if (it2 != image->iptcData().end())
0428                         {
0429                             image->iptcData().erase(it2);
0430                         }
0431                     }
0432                 }
0433             }
0434 
0435             wroteXMP = true;
0436 
0437 #endif // _XMP_SUPPORT_
0438 
0439         }
0440 
0441         qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteXMP: " << wroteXMP;
0442 
0443         // cppcheck-suppress knownConditionTrueFalse
0444         if      (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP)
0445         {
0446             qCDebug(DIGIKAM_METAENGINE_LOG) << "Writing metadata is not supported for file" << finfo.fileName();
0447 
0448             return false;
0449         }
0450         else if (!wroteEXIF || !wroteIPTC || !wroteXMP)
0451         {
0452             qCDebug(DIGIKAM_METAENGINE_LOG) << "Support for writing metadata is limited for file" << finfo.fileName();
0453         }
0454 
0455         image->writeMetadata();
0456 
0457         if (!updateFileTimeStamp && modTime.isValid())
0458         {
0459             // Don't touch modification timestamp of file.
0460 
0461             QFile modFile(finfo.filePath());
0462 
0463             if (modFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
0464             {
0465                 modFile.setFileTime(modTime, QFileDevice::FileModificationTime);
0466 
0467                 qCDebug(DIGIKAM_METAENGINE_LOG) << "File time stamp restored";
0468 
0469                 modFile.close();
0470             }
0471         }
0472 
0473         return true;
0474     }
0475     catch (Exiv2::AnyError& e)
0476     {
0477         printExiv2ExceptionError(QLatin1String("Cannot save metadata with Exiv2 backend:"), e);
0478     }
0479     catch (...)
0480     {
0481         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0482     }
0483 
0484     return false;
0485 }
0486 
0487 bool MetaEngine::Private::saveUsingExifTool(const QFileInfo& finfo,
0488                                             const QDateTime& modTime) const
0489 {
0490     QString ext = finfo.suffix().toLower();
0491 
0492     if (!writeDngFiles && (ext == QLatin1String("dng")))
0493     {
0494         qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName()
0495                                         << "is a DNG file, "
0496                                         << "writing to such a file is disabled by current settings.";
0497         return false;
0498     }
0499 
0500     if (!writeRawFiles && s_rawFileExtensions().remove(QLatin1String("dng")).contains(ext))
0501     {
0502         qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName()
0503                                         << "is a RAW file, "
0504                                         << "writing to such a file is disabled by current settings.";
0505         return false;
0506     }
0507 
0508     QScopedPointer<ExifToolParser> const parser(new ExifToolParser(nullptr));
0509 
0510     if (!parser->exifToolAvailable())
0511     {
0512         qCWarning(DIGIKAM_METAENGINE_LOG) << "ExifTool is not available to save metadata...";
0513 
0514         return false;
0515     }
0516 
0517     QString dir                   = QDir::temp().path();
0518     SafeTemporaryFile* const temp = new SafeTemporaryFile(dir + QLatin1String("/MetaEngine-XXXXXX.exv"));
0519     temp->setAutoRemove(false);
0520     temp->open();
0521     QString exvPath = temp->safeFilePath();
0522 
0523     // Crash fix: a QTemporaryFile is not properly closed until its destructor is called.
0524 
0525     delete temp;
0526 
0527     // ---
0528 
0529     if (!parent->exportChanges(exvPath))
0530     {
0531         qCWarning(DIGIKAM_METAENGINE_LOG) << "Exiv2 error exception for" << finfo.fileName()
0532                                           << "Writing of metadata aborted!";
0533 
0534         return false;
0535     }
0536 
0537     bool hasCSet = (parent->getIptcTagData("Iptc.Envelope.CharacterSet") == "\33%G");
0538 
0539     if (!parser->applyChanges(finfo.filePath(), exvPath,
0540                               parent->hasExif(), parent->hasXmp(), hasCSet))
0541     {
0542         qCWarning(DIGIKAM_METAENGINE_LOG) << "Cannot apply changes with ExifTool on" << finfo.filePath();
0543         QFile::remove(exvPath);
0544 
0545         return false;
0546     }
0547 
0548     if (!updateFileTimeStamp && modTime.isValid())
0549     {
0550         // Don't touch modification timestamp of file.
0551 
0552         QFile modFile(finfo.filePath());
0553 
0554         if (modFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
0555         {
0556             modFile.setFileTime(modTime, QFileDevice::FileModificationTime);
0557 
0558             qCDebug(DIGIKAM_METAENGINE_LOG) << "File time stamp restored";
0559 
0560             modFile.close();
0561         }
0562     }
0563 
0564     QFile::remove(exvPath);
0565 
0566     return true;
0567 }
0568 
0569 void MetaEngine::Private::printExiv2ExceptionError(const QString& msg, Exiv2::AnyError& e)
0570 {
0571     qCCritical(DIGIKAM_METAENGINE_LOG) << msg.toLatin1().constData()
0572                                        << " (Error #" << (int)e.code() << ": " << QString::fromStdString(e.what());
0573 }
0574 
0575 void MetaEngine::Private::printExiv2MessageHandler(int lvl, const char* msg)
0576 {
0577     qCDebug(DIGIKAM_METAENGINE_LOG) << "Exiv2 (" << lvl << ") : " << msg;
0578 
0579     s_metaEngineWarnOrError |= ((lvl == Exiv2::LogMsg::warn) || (lvl == Exiv2::LogMsg::error));
0580 }
0581 
0582 QString MetaEngine::Private::convertCommentValue(const Exiv2::Exifdatum& exifDatum) const
0583 {
0584     QMutexLocker lock(&s_metaEngineMutex);
0585 
0586     try
0587     {
0588         std::string comment;
0589         std::string charset;
0590 
0591         comment = exifDatum.toString();
0592 
0593         // libexiv2 will prepend "charset=SomeCharset " if charset is specified
0594         // Before conversion to QString, we must know the charset, so we stay with std::string for a while
0595 
0596         if ((comment.length() > 8) && (comment.substr(0, 8) == "charset="))
0597         {
0598             // the prepended charset specification is followed by a blank
0599 
0600             std::string::size_type pos = comment.find_first_of(' ');
0601 
0602             if (pos != std::string::npos)
0603             {
0604                 // extract string between the = and the blank
0605 
0606                 charset = comment.substr(8, pos - 8);
0607 
0608                 // get the rest of the string after the charset specification
0609 
0610                 comment = comment.substr(pos + 1);
0611             }
0612         }
0613 
0614         if      (charset == "Unicode")
0615         {
0616 
0617 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
0618 
0619             QByteArray rawComment(exifDatum.size(), '\0');
0620             exifDatum.copy((Exiv2::byte*)rawComment.data(), Exiv2::bigEndian);
0621 
0622             // remove "UNICODE\0"
0623 
0624             rawComment = rawComment.mid(8);
0625 
0626             if ((rawComment.size() > 1) && (rawComment.at(1) == '\0'))
0627             {
0628                 QString utf16String = QString::fromUtf16((ushort*)rawComment.data());
0629 
0630                 if (utf16String.isValidUtf16())
0631                 {
0632                     return QString::fromUtf8(utf16String.toUtf8());
0633                 }
0634             }
0635 
0636 #endif
0637 
0638             return QString::fromUtf8(comment.data());
0639         }
0640         else if (charset == "Jis")
0641         {
0642 
0643 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0644 
0645             if (Exiv2::convertStringCharset(comment, charset.c_str(), "UTF-8"))
0646             {
0647                 return QString::fromUtf8(comment);
0648             }
0649             else
0650             {
0651                 return QLatin1String("");
0652             }
0653 
0654 #else
0655 
0656             QTextCodec* const codec = QTextCodec::codecForName("JIS7");
0657 
0658             if (codec)
0659             {
0660                 const char* tmp = comment.c_str();
0661 
0662                 if (tmp)
0663                 {
0664                     return codec->toUnicode(tmp);
0665                 }
0666             }
0667 
0668             return QLatin1String("");
0669 
0670 #endif
0671 
0672         }
0673         else if (charset == "Ascii")
0674         {
0675             return QString::fromStdString(comment);
0676         }
0677         else
0678         {
0679             return detectEncodingAndDecode(comment);
0680         }
0681     }
0682     catch (Exiv2::AnyError& e)
0683     {
0684         printExiv2ExceptionError(QLatin1String("Cannot convert Comment with Exiv2:"), e);
0685     }
0686     catch (...)
0687     {
0688         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0689     }
0690 
0691     return QString();
0692 }
0693 
0694 QString MetaEngine::Private::detectEncodingAndDecode(const std::string& value) const
0695 {
0696     // For charset autodetection, we could use sophisticated code
0697     // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent),
0698     // but that is probably too much.
0699     // We check for UTF8, Local encoding and ASCII.
0700     // Look like KEncodingDetector class can provide a full implementation for encoding detection.
0701 
0702     if (value.empty())
0703     {
0704         return QString();
0705     }
0706 
0707     if (isUtf8(value.c_str()))
0708     {
0709         return QString::fromStdString(value);
0710     }
0711 
0712     // Utf8 has a pretty unique byte pattern.
0713     // That's not true for ASCII, it is not possible
0714     // to reliably autodetect different ISO-8859 charsets.
0715     // So we can use either local encoding, or latin1.
0716 
0717     QString localString = QString::fromLocal8Bit(value.c_str());
0718 
0719     // To fix broken JFIF comment, replace non printable
0720     // characters from the string. See bug 39617
0721 
0722     for (int i = 0 ; i < localString.size() ; ++i)
0723     {
0724         if (!localString.at(i).isPrint()             &&
0725             (localString.at(i) != QLatin1Char('\n')) &&
0726             (localString.at(i) != QLatin1Char('\r')))
0727         {
0728             localString[i] = QLatin1Char('_');
0729         }
0730     }
0731 
0732     return localString;
0733 }
0734 
0735 bool MetaEngine::Private::isUtf8(const char* const buffer) const
0736 {
0737     int i, n;
0738     unsigned char c;
0739     bool gotone = false;
0740 
0741     if (!buffer)
0742     {
0743         return true;
0744     }
0745 
0746 // character never appears in text
0747 
0748 #define F 0
0749 
0750 // character appears in plain ASCII text
0751 
0752 #define T 1
0753 
0754 // character appears in ISO-8859 text
0755 
0756 #define I 2
0757 
0758 // character appears in non-ISO extended ASCII (Mac, IBM PC)
0759 
0760 #define X 3
0761 
0762     static const unsigned char text_chars[256] =
0763     {
0764         //                  BEL BS HT LF    FF CR
0765         F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F,  // 0x0X
0766         //                              ESC
0767         F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F,  // 0x1X
0768         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x2X
0769         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x3X
0770         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x4X
0771         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x5X
0772         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x6X
0773         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F,  // 0x7X
0774         //            NEL
0775         X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X,  // 0x8X
0776         X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,  // 0x9X
0777         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xaX
0778         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xbX
0779         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xcX
0780         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xdX
0781         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xeX
0782         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I   // 0xfX
0783     };
0784 
0785     for (i = 0 ; (c = buffer[i]) ; ++i)
0786     {
0787         if      ((c & 0x80) == 0)
0788         {
0789             // 0xxxxxxx is plain ASCII
0790 
0791             // Even if the whole file is valid UTF-8 sequences,
0792             // still reject it if it uses weird control characters.
0793 
0794             if (text_chars[c] != T)
0795             {
0796                 return false;
0797             }
0798         }
0799         else if ((c & 0x40) == 0)
0800         {
0801             // 10xxxxxx never 1st byte
0802 
0803             return false;
0804         }
0805         else
0806         {
0807             // 11xxxxxx begins UTF-8
0808 
0809             int following = 0;
0810 
0811             if      ((c & 0x20) == 0)
0812             {
0813                 // 110xxxxx
0814 
0815                 following = 1;
0816             }
0817             else if ((c & 0x10) == 0)
0818             {
0819                 // 1110xxxx
0820 
0821                 following = 2;
0822             }
0823             else if ((c & 0x08) == 0)
0824             {
0825                 // 11110xxx
0826 
0827                 following = 3;
0828             }
0829             else if ((c & 0x04) == 0)
0830             {
0831                 // 111110xx
0832 
0833                 following = 4;
0834             }
0835             else if ((c & 0x02) == 0)
0836             {
0837                 // 1111110x
0838 
0839                 following = 5;
0840             }
0841             else
0842             {
0843                 return false;
0844             }
0845 
0846             for (n = 0 ; n < following ; ++n)
0847             {
0848                 i++;
0849 
0850                 if (!(c = buffer[i]))
0851                 {
0852                     goto done;
0853                 }
0854 
0855                 if (((c & 0x80) == 0) || (c & 0x40))
0856                 {
0857                     return false;
0858                 }
0859             }
0860 
0861             gotone = true;
0862         }
0863     }
0864 
0865 done:
0866 
0867     return gotone;   // don't claim it's UTF-8 if it's all 7-bit.
0868 }
0869 
0870 #undef F
0871 #undef T
0872 #undef I
0873 #undef X
0874 
0875 bool MetaEngine::Private::decodeGPSCoordinate(const char* exifTagName, double* const coordinate) const
0876 {
0877     QMutexLocker lock(&s_metaEngineMutex);
0878 
0879     try
0880     {
0881         std::string exifkey(exifTagName);
0882         Exiv2::ExifKey gpsKey(exifkey);
0883         Exiv2::ExifData exifData(exifMetadata());
0884         Exiv2::ExifData::const_iterator it = exifData.findKey(gpsKey);
0885 
0886         if ((it != exifData.end()) && ((*it).count() == 3))
0887         {
0888             double deg;
0889             double min;
0890             double sec;
0891 
0892             deg = (double)((*it).toFloat(0));
0893 
0894             if (((*it).toRational(0).second == 0) || (deg == -1.0))
0895             {
0896                 return false;
0897             }
0898 
0899             *coordinate = deg;
0900 
0901             min = (double)((*it).toFloat(1));
0902 
0903             if (((*it).toRational(1).second == 0) || (min == -1.0))
0904             {
0905                 return false;
0906             }
0907 
0908             *coordinate = *coordinate + min / 60.0;
0909 
0910             sec = (double)((*it).toFloat(2));
0911 
0912             if (sec != -1.0)
0913             {
0914                 *coordinate = *coordinate + sec / 3600.0;
0915             }
0916 
0917             return true;
0918         }
0919     }
0920     catch (Exiv2::AnyError& e)
0921     {
0922         printExiv2ExceptionError(QLatin1String("Cannot get GPS tag with Exiv2:"), e);
0923     }
0924     catch (...)
0925     {
0926         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0927     }
0928 
0929     return false;
0930 }
0931 
0932 int MetaEngine::Private::getXMPTagsListFromPrefix(const QString& pf, MetaEngine::TagsMap& tagsMap) const
0933 {
0934     int i = 0;
0935 
0936 #ifdef _XMP_SUPPORT_
0937 
0938     QMutexLocker lock(&s_metaEngineMutex);
0939 
0940     try
0941     {
0942         QList<const Exiv2::XmpPropertyInfo*> tags;
0943         tags << Exiv2::XmpProperties::propertyList(pf.toLatin1().data());
0944 
0945         for (QList<const Exiv2::XmpPropertyInfo*>::iterator it = tags.begin() ; it != tags.end() ; ++it)
0946         {
0947             while ((*it) && !QString::fromLatin1((*it)->name_).isNull())
0948             {
0949                 QString     key = QLatin1String(Exiv2::XmpKey(pf.toLatin1().data(), (*it)->name_).key().c_str());
0950                 QStringList values;
0951                 values << QLatin1String((*it)->name_)
0952                        << QLatin1String((*it)->title_)
0953                        << QLatin1String((*it)->desc_);
0954                 tagsMap.insert(key, values);
0955                 ++(*it);
0956                 i++;
0957             }
0958         }
0959     }
0960     catch (Exiv2::AnyError& e)
0961     {
0962         printExiv2ExceptionError(QLatin1String("Cannot get Xmp tags list using Exiv2:"), e);
0963     }
0964     catch (...)
0965     {
0966         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0967     }
0968 
0969 #else
0970 
0971     Q_UNUSED(pf);
0972     Q_UNUSED(tagsMap);
0973 
0974 #endif // _XMP_SUPPORT_
0975 
0976     return i;
0977 }
0978 
0979 #ifdef _XMP_SUPPORT_
0980 
0981 void MetaEngine::Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar)
0982 {
0983     // Having a sidecar is a special situation.
0984     // The sidecar data often "dominates", see in particular bug 309058 for important aspects:
0985     // If a field is removed from the sidecar, we must ignore (older) data for this field in the file.
0986 
0987     // First: Ignore file XMP, only use sidecar XMP
0988 
0989     xmpMetadata()     = xmpsidecar->xmpData();
0990     loadedFromSidecar = true;
0991 
0992     // EXIF
0993     // Four groups of properties are mapped between EXIF and XMP:
0994     // Date/Time, Description, Copyright, Creator
0995     // A few more tags are defined "writeback" tags in the XMP specification, the sidecar value therefore overrides the Exif value.
0996     // The rest is kept side-by-side.
0997     // (to understand, remember that the xmpsidecar's Exif data is actually XMP data mapped back to Exif)
0998 
0999     // Description, Copyright and Creator is dominated by the sidecar: Remove file Exif fields, if field not in XMP.
1000 
1001     ExifMetaEngineMergeHelper exifDominatedHelper;
1002     exifDominatedHelper << QLatin1String("Exif.Image.ImageDescription")
1003                         << QLatin1String("Exif.Photo.UserComment")
1004                         << QLatin1String("Exif.Image.Copyright")
1005                         << QLatin1String("Exif.Image.Artist");
1006     exifDominatedHelper.exclusiveMerge(xmpsidecar->exifData(), exifMetadata());
1007 
1008     // Date/Time and "the few more" from the XMP spec are handled as writeback
1009     // Note that Date/Time mapping is slightly contradictory in latest specs.
1010 
1011     ExifMetaEngineMergeHelper exifWritebackHelper;
1012     exifWritebackHelper << QLatin1String("Exif.Image.DateTime")
1013                         << QLatin1String("Exif.Photo.DateTimeOriginal")
1014                         << QLatin1String("Exif.Photo.DateTimeDigitized")
1015                         << QLatin1String("Exif.Image.Orientation")
1016                         << QLatin1String("Exif.Image.XResolution")
1017                         << QLatin1String("Exif.Image.YResolution")
1018                         << QLatin1String("Exif.Image.ResolutionUnit")
1019                         << QLatin1String("Exif.Image.Software")
1020                         << QLatin1String("Exif.Photo.RelatedSoundFile");
1021     exifWritebackHelper.mergeFields(xmpsidecar->exifData(), exifMetadata());
1022 
1023     // IPTC
1024     // These fields cover almost all relevant IPTC data and are defined in the XMP specification for reconciliation.
1025 
1026     IptcMetaEngineMergeHelper iptcDominatedHelper;
1027     iptcDominatedHelper << QLatin1String("Iptc.Application2.ObjectName")
1028                         << QLatin1String("Iptc.Application2.Urgency")
1029                         << QLatin1String("Iptc.Application2.Category")
1030                         << QLatin1String("Iptc.Application2.SuppCategory")
1031                         << QLatin1String("Iptc.Application2.Keywords")
1032                         << QLatin1String("Iptc.Application2.SubLocation")
1033                         << QLatin1String("Iptc.Application2.SpecialInstructions")
1034                         << QLatin1String("Iptc.Application2.Byline")
1035                         << QLatin1String("Iptc.Application2.BylineTitle")
1036                         << QLatin1String("Iptc.Application2.City")
1037                         << QLatin1String("Iptc.Application2.ProvinceState")
1038                         << QLatin1String("Iptc.Application2.CountryCode")
1039                         << QLatin1String("Iptc.Application2.CountryName")
1040                         << QLatin1String("Iptc.Application2.TransmissionReference")
1041                         << QLatin1String("Iptc.Application2.Headline")
1042                         << QLatin1String("Iptc.Application2.Credit")
1043                         << QLatin1String("Iptc.Application2.Source")
1044                         << QLatin1String("Iptc.Application2.Copyright")
1045                         << QLatin1String("Iptc.Application2.Caption")
1046                         << QLatin1String("Iptc.Application2.Writer");
1047     iptcDominatedHelper.exclusiveMerge(xmpsidecar->iptcData(), iptcMetadata());
1048 
1049     IptcMetaEngineMergeHelper iptcWritebackHelper;
1050     iptcWritebackHelper << QLatin1String("Iptc.Application2.DateCreated")
1051                         << QLatin1String("Iptc.Application2.TimeCreated")
1052                         << QLatin1String("Iptc.Application2.DigitizationDate")
1053                         << QLatin1String("Iptc.Application2.DigitizationTime");
1054     iptcWritebackHelper.mergeFields(xmpsidecar->iptcData(), iptcMetadata());
1055 
1056     /*
1057      * TODO: Exiv2 (referring to 0.23) does not correctly synchronize all times values as given below.
1058      * Time values and their synchronization:
1059      * Original Date/Time – Creation date of the intellectual content (e.g. the photograph),
1060      * rather than the creatio*n date of the content being shown
1061      *  Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291)
1062      *  IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C)
1063      *  XMP (photoshop:DateCreated)
1064      * Digitized Date/Time – Creation date of the digital representation
1065      *  Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292)
1066      *  IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F)
1067      *  XMP (xmp:CreateDate)
1068      * Modification Date/Time – Modification date of the digital image file
1069      *  Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290)
1070      *  XMP (xmp:ModifyDate)
1071      */
1072 }
1073 
1074 #endif // _XMP_SUPPORT_
1075 
1076 QString MetaEngine::Private::extractIptcTagString(const Exiv2::IptcData& iptcData, const Exiv2::Iptcdatum& iptcTag) const
1077 {
1078     QString charSet = QLatin1String(iptcData.detectCharset());
1079     QString value;
1080 
1081     // NOTE: no need mutex lock here as this method is always called from a top level function by a parent mutex lock
1082 
1083     try
1084     {
1085         if ((iptcTag.typeId() == Exiv2::string) && !charSet.isNull())
1086         {
1087             // Perform Utf8 conversion from std::string
1088             // TODO: check if a parse of charset content can improve the string conversion if not Utf8 use.
1089 
1090             value = QString::fromStdString(iptcTag.toString());
1091         }
1092         else
1093         {
1094             // No characterset want mean ASCII-latin1
1095             // Decode the tag value with a user friendly output.
1096 
1097             std::ostringstream os;
1098             os << iptcTag;
1099             value = QString::fromStdString(os.str());
1100         }
1101     }
1102     catch (Exiv2::AnyError& e)
1103     {
1104         printExiv2ExceptionError(QLatin1String("Cannot decode Iptc tag string with right encoding using Exiv2:"), e);
1105     }
1106     catch (...)
1107     {
1108         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1109     }
1110 
1111     return value;
1112 }
1113 
1114 } // namespace Digikam
1115 
1116 // Restore warnings
1117 #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG)
1118 #   pragma GCC diagnostic pop
1119 #endif
1120 
1121 #if defined(Q_CC_CLANG)
1122 #   pragma clang diagnostic pop
1123 #endif