File indexing completed on 2024-05-12 15:55:36

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2023 Tobias Leupold <tl at stonemx dot de>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007 #include "Info.h"
0008 
0009 #include <kpabase/Logging.h>
0010 
0011 #include <kpabase/FileName.h>
0012 #include <kpabase/SettingsData.h>
0013 #include <kpabase/StringSet.h>
0014 
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QTextCodec>
0018 #include <exiv2/exv_conf.h>
0019 #include <exiv2/image.hpp>
0020 
0021 using namespace Exif;
0022 
0023 namespace
0024 {
0025 QString cStringWithEncoding(const char *c_str, const QString &charset)
0026 {
0027     QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1());
0028     if (!codec)
0029         codec = QTextCodec::codecForLocale();
0030     return codec->toUnicode(c_str);
0031 }
0032 
0033 } // namespace
0034 
0035 Info *Info::s_instance = nullptr;
0036 
0037 QMap<QString, QStringList> Info::info(const DB::FileName &fileName, StringSet wantedKeys, bool returnFullExifName, const QString &charset)
0038 {
0039     QMap<QString, QStringList> result;
0040 
0041     try {
0042         Metadata data = metadata(exifInfoFile(fileName));
0043 
0044         for (Exiv2::ExifData::const_iterator i = data.exif.begin(); i != data.exif.end(); ++i) {
0045             QString key = QString::fromLocal8Bit(i->key().c_str());
0046             m_keys.insert(key);
0047 
0048             if (wantedKeys.contains(key)) {
0049                 QString text = key;
0050                 if (!returnFullExifName)
0051                     text = key.split(QLatin1String(".")).last();
0052 
0053                 std::ostringstream stream;
0054                 stream << *i;
0055                 QString str(cStringWithEncoding(stream.str().c_str(), charset));
0056                 result[text] += str;
0057             }
0058         }
0059 
0060         for (Exiv2::IptcData::const_iterator i = data.iptc.begin(); i != data.iptc.end(); ++i) {
0061             QString key = QString::fromLatin1(i->key().c_str());
0062             m_keys.insert(key);
0063 
0064             if (wantedKeys.contains(key)) {
0065                 QString text = key;
0066                 if (!returnFullExifName)
0067                     text = key.split(QString::fromLatin1(".")).last();
0068 
0069                 std::ostringstream stream;
0070                 stream << *i;
0071                 QString str(cStringWithEncoding(stream.str().c_str(), charset));
0072                 result[text] += str;
0073             }
0074         }
0075     } catch (...) {
0076     }
0077 
0078     return result;
0079 }
0080 
0081 Info *Info::instance()
0082 {
0083     if (!s_instance)
0084         s_instance = new Info;
0085     return s_instance;
0086 }
0087 
0088 StringSet Info::availableKeys()
0089 {
0090     return m_keys;
0091 }
0092 
0093 QMap<QString, QStringList> Info::infoForViewer(const DB::FileName &fileName, const QString &charset)
0094 {
0095     return info(fileName, ::Settings::SettingsData::instance()->exifForViewer(), false, charset);
0096 }
0097 
0098 QMap<QString, QStringList> Info::infoForDialog(const DB::FileName &fileName, const QString &charset)
0099 {
0100     auto keys = ::Settings::SettingsData::instance()->exifForDialog();
0101     if (keys.isEmpty())
0102         keys = standardKeys();
0103     return info(fileName, keys, true, charset);
0104 }
0105 
0106 StringSet Info::standardKeys()
0107 {
0108     static StringSet res;
0109 
0110     if (!res.empty())
0111         return res;
0112 
0113     QList<const Exiv2::TagInfo *> tags;
0114     std::ostringstream s;
0115 
0116     const Exiv2::GroupInfo *gi = Exiv2::ExifTags::groupList();
0117     while (gi->tagList_ != nullptr) {
0118         Exiv2::TagListFct tl = gi->tagList_;
0119         const Exiv2::TagInfo *ti = tl();
0120 
0121         while (ti->tag_ != 0xFFFF) {
0122             tags << ti;
0123             ++ti;
0124         }
0125         ++gi;
0126     }
0127 
0128     for (QList<const Exiv2::TagInfo *>::iterator it = tags.begin(); it != tags.end(); ++it) {
0129         while ((*it)->tag_ != 0xffff) {
0130             res.insert(QString::fromLatin1(Exiv2::ExifKey(**it).key().c_str()));
0131             ++(*it);
0132         }
0133     }
0134 
0135     // IPTC tags use yet another format...
0136     Exiv2::IptcDataSets::dataSetList(s);
0137 
0138     QStringList lines = QString(QLatin1String(s.str().c_str())).split(QChar::fromLatin1('\n'));
0139     for (QStringList::const_iterator it = lines.constBegin(); it != lines.constEnd(); ++it) {
0140         if (it->isEmpty())
0141             continue;
0142         QStringList fields = it->split(QChar::fromLatin1('\t'));
0143         if (fields.size() == 7) {
0144             QString id = fields[4];
0145             if (id.endsWith(QChar::fromLatin1(',')))
0146                 id.chop(1);
0147             res.insert(id);
0148         } else {
0149             fields = it->split(QLatin1String(", "));
0150             if (fields.size() >= 11) {
0151                 res.insert(fields[8]);
0152             } else {
0153                 qCWarning(ExifLog) << "Unparsable output from exiv2 library: " << *it;
0154                 continue;
0155             }
0156         }
0157     }
0158     return res;
0159 }
0160 
0161 Info::Info()
0162 {
0163     m_keys = standardKeys();
0164 }
0165 
0166 void Exif::writeExifInfoToFile(const DB::FileName &srcName, const QString &destName, const QString &imageDescription)
0167 {
0168     // Load Exif from source image
0169     auto image = Exiv2::ImageFactory::open(QFile::encodeName(srcName.absolute()).data());
0170     image->readMetadata();
0171     Exiv2::ExifData data = image->exifData();
0172 
0173     // Modify Exif information from database.
0174     data["Exif.Image.ImageDescription"] = imageDescription.toLocal8Bit().data();
0175 
0176     image = Exiv2::ImageFactory::open(QFile::encodeName(destName).data());
0177     image->setExifData(data);
0178     image->writeMetadata();
0179 }
0180 
0181 /**
0182  * Some Canon cameras stores Exif info in files ending in .thm, so we need to use those files for fetching Exif info
0183  * if they exists.
0184  */
0185 DB::FileName Exif::Info::exifInfoFile(const DB::FileName &fileName)
0186 {
0187     QString dirName = QFileInfo(fileName.relative()).path();
0188     QString baseName = QFileInfo(fileName.relative()).baseName();
0189     DB::FileName name = DB::FileName::fromRelativePath(dirName + QString::fromLatin1("/") + baseName + QString::fromLatin1(".thm"));
0190     if (name.exists())
0191         return name;
0192 
0193     name = DB::FileName::fromRelativePath(dirName + QString::fromLatin1("/") + baseName + QString::fromLatin1(".THM"));
0194     if (name.exists())
0195         return name;
0196 
0197     return fileName;
0198 }
0199 
0200 Exif::Metadata Exif::Info::metadata(const DB::FileName &fileName)
0201 {
0202     try {
0203         Exif::Metadata result;
0204         auto image = Exiv2::ImageFactory::open(QFile::encodeName(fileName.absolute()).data());
0205         Q_ASSERT(image.get() != nullptr);
0206         image->readMetadata();
0207         result.exif = image->exifData();
0208         result.iptc = image->iptcData();
0209         result.comment = image->comment();
0210         return result;
0211     } catch (...) {
0212     }
0213     return Exif::Metadata();
0214 }
0215 
0216 // vi:expandtab:tabstop=4 shiftwidth=4: