File indexing completed on 2024-05-12 15:54:49

0001 /*
0002  * SPDX-FileCopyrightText: (C) 2012-2015 Vishesh Handa <vhanda@kde.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "exiv2extractor.h"
0008 
0009 #include <KFileMetaData/UserMetaData>
0010 #include <QDebug>
0011 #include <QFile>
0012 #include <QFileInfo>
0013 #include <QStandardPaths>
0014 
0015 Exiv2Extractor::Exiv2Extractor(QObject *parent)
0016     : QObject(parent)
0017     , m_filePath(QString())
0018     , m_latitude(0)
0019     , m_longitude(0)
0020     , m_height(0)
0021     , m_width(0)
0022     , m_size(0)
0023     , m_model("")
0024     , m_time("")
0025     , m_favorite(false)
0026     , m_rating(0)
0027     , m_description("")
0028     , m_tags(QStringList())
0029     , m_error(false)
0030 {
0031 }
0032 
0033 Exiv2Extractor::~Exiv2Extractor()
0034 {
0035 }
0036 
0037 QUrl Exiv2Extractor::filePath() const
0038 {
0039     return QUrl::fromLocalFile(m_filePath);
0040 }
0041 
0042 QString Exiv2Extractor::simplifiedPath() const
0043 {
0044     auto url = filePath();
0045     QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
0046     if (QUrl::fromLocalFile(home).isParentOf(url)) {
0047         return QStringLiteral("~") + url.toLocalFile().remove(0, home.length());
0048     }
0049     return url.toLocalFile();
0050 }
0051 
0052 static QDateTime dateTimeFromString(const QString &dateString)
0053 {
0054     QDateTime dateTime;
0055 
0056     if (!dateTime.isValid()) {
0057         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy-MM-dd"));
0058         dateTime.setTimeSpec(Qt::UTC);
0059     }
0060     if (!dateTime.isValid()) {
0061         dateTime = QDateTime::fromString(dateString, QStringLiteral("dd-MM-yyyy"));
0062         dateTime.setTimeSpec(Qt::UTC);
0063     }
0064     if (!dateTime.isValid()) {
0065         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy-MM"));
0066         dateTime.setTimeSpec(Qt::UTC);
0067     }
0068     if (!dateTime.isValid()) {
0069         dateTime = QDateTime::fromString(dateString, QStringLiteral("MM-yyyy"));
0070         dateTime.setTimeSpec(Qt::UTC);
0071     }
0072     if (!dateTime.isValid()) {
0073         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy.MM.dd"));
0074         dateTime.setTimeSpec(Qt::UTC);
0075     }
0076     if (!dateTime.isValid()) {
0077         dateTime = QDateTime::fromString(dateString, QStringLiteral("dd.MM.yyyy"));
0078         dateTime.setTimeSpec(Qt::UTC);
0079     }
0080     if (!dateTime.isValid()) {
0081         dateTime = QDateTime::fromString(dateString, QStringLiteral("dd MMMM yyyy"));
0082         dateTime.setTimeSpec(Qt::UTC);
0083     }
0084     if (!dateTime.isValid()) {
0085         dateTime = QDateTime::fromString(dateString, QStringLiteral("MM.yyyy"));
0086         dateTime.setTimeSpec(Qt::UTC);
0087     }
0088     if (!dateTime.isValid()) {
0089         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy.MM"));
0090         dateTime.setTimeSpec(Qt::UTC);
0091     }
0092     if (!dateTime.isValid()) {
0093         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy"));
0094         dateTime.setTimeSpec(Qt::UTC);
0095     }
0096     if (!dateTime.isValid()) {
0097         dateTime = QDateTime::fromString(dateString, QStringLiteral("yy"));
0098         dateTime.setTimeSpec(Qt::UTC);
0099     }
0100     if (!dateTime.isValid()) {
0101         dateTime = QDateTime::fromString(dateString, Qt::ISODate);
0102     }
0103     if (!dateTime.isValid()) {
0104         dateTime = QDateTime::fromString(dateString, QStringLiteral("dddd d MMM yyyy h':'mm':'ss AP"));
0105         dateTime.setTimeSpec(Qt::LocalTime);
0106     }
0107     if (!dateTime.isValid()) {
0108         dateTime = QDateTime::fromString(dateString, QStringLiteral("yyyy:MM:dd hh:mm:ss"));
0109         dateTime.setTimeSpec(Qt::LocalTime);
0110     }
0111     if (!dateTime.isValid()) {
0112         dateTime = QLocale::system().toDateTime(dateString, QLocale::ShortFormat);
0113         dateTime.setTimeSpec(Qt::UTC);
0114     }
0115     if (!dateTime.isValid()) {
0116         dateTime = QLocale::system().toDateTime(dateString, QLocale::LongFormat);
0117         dateTime.setTimeSpec(Qt::UTC);
0118     }
0119     if (!dateTime.isValid()) {
0120         qWarning() << "Could not determine correct datetime format from:" << dateString;
0121         return QDateTime();
0122     }
0123 
0124     return dateTime;
0125 }
0126 static QDateTime toDateTime(const Exiv2::Value &value)
0127 {
0128     if (value.typeId() == Exiv2::asciiString) {
0129         QDateTime val = dateTimeFromString(value.toString().c_str());
0130         if (val.isValid()) {
0131             // Datetime is stored in exif as local time.
0132             val.setOffsetFromUtc(0);
0133             return val;
0134         }
0135     }
0136 
0137     return QDateTime();
0138 }
0139 
0140 void Exiv2Extractor::updateFavorite(const QString &filePath)
0141 {
0142     if (!QFileInfo::exists(filePath)) {
0143         return;
0144     }
0145 
0146     auto fileMetaData = KFileMetaData::UserMetaData(filePath);
0147 
0148     m_favorite = fileMetaData.hasAttribute("koko.favorite");
0149 
0150     Q_EMIT favoriteChanged();
0151 }
0152 
0153 void Exiv2Extractor::toggleFavorite(const QString &filePath)
0154 {
0155     if (!QFileInfo::exists(filePath)) {
0156         return;
0157     }
0158 
0159     auto fileMetaData = KFileMetaData::UserMetaData(filePath);
0160 
0161     if (fileMetaData.hasAttribute("koko.favorite")) {
0162         fileMetaData.setAttribute("koko.favorite", "");
0163     } else {
0164         fileMetaData.setAttribute("koko.favorite", "true");
0165     }
0166 
0167     m_favorite = fileMetaData.hasAttribute("koko.favorite");
0168 
0169     Q_EMIT favoriteChanged();
0170 }
0171 
0172 void Exiv2Extractor::setRating(const int &rating)
0173 {
0174     if (rating == m_rating) {
0175         return;
0176     }
0177 
0178     if (!QFileInfo::exists(m_filePath)) {
0179         return;
0180     }
0181 
0182     auto fileMetaData = KFileMetaData::UserMetaData(m_filePath);
0183 
0184     fileMetaData.setRating(rating);
0185 
0186     m_rating = rating;
0187 
0188     Q_EMIT filePathChanged();
0189 }
0190 
0191 void Exiv2Extractor::setDescription(const QString &description)
0192 {
0193     if (description == m_description) {
0194         return;
0195     }
0196 
0197     if (!QFileInfo::exists(m_filePath)) {
0198         return;
0199     }
0200 
0201     auto fileMetaData = KFileMetaData::UserMetaData(m_filePath);
0202 
0203     fileMetaData.setUserComment(description);
0204 
0205     m_description = description;
0206 
0207     Q_EMIT filePathChanged();
0208 }
0209 
0210 void Exiv2Extractor::setTags(const QStringList &tags)
0211 {
0212     if (tags == m_tags) {
0213         return;
0214     }
0215 
0216     if (!QFileInfo::exists(m_filePath)) {
0217         return;
0218     }
0219 
0220     auto fileMetaData = KFileMetaData::UserMetaData(m_filePath);
0221 
0222     fileMetaData.setTags(tags);
0223 
0224     m_tags = tags;
0225 
0226     Q_EMIT filePathChanged();
0227 }
0228 
0229 void Exiv2Extractor::extract(const QString &filePath)
0230 {
0231     if (filePath == m_filePath) {
0232         return;
0233     }
0234 
0235     // init values
0236     m_error = false;
0237     m_latitude = 0.0;
0238     m_longitude = 0.0;
0239     m_width = 0;
0240     m_height = 0;
0241     m_size = 0;
0242     m_model = "";
0243     m_time = "";
0244     m_favorite = false;
0245     m_dateTime = QDateTime();
0246     m_rating = 0;
0247     m_description = "";
0248     m_tags = QStringList();
0249     m_filePath = filePath;
0250 
0251     QByteArray arr = QFile::encodeName(filePath);
0252     std::string fileString(arr.data(), arr.length());
0253 
0254     Exiv2::LogMsg::setLevel(Exiv2::LogMsg::mute);
0255 #if EXIV2_TEST_VERSION(0, 27, 99)
0256     Exiv2::Image::UniquePtr image;
0257 #else
0258     Exiv2::Image::AutoPtr image;
0259 #endif
0260 
0261     QFileInfo file_info(m_filePath);
0262 
0263     if (!QFileInfo::exists(m_filePath)) {
0264         m_error = true; // only critical error (don't prevent indexing stuff without Exif metadata)
0265         Q_EMIT filePathChanged();
0266         Q_EMIT favoriteChanged();
0267         return;
0268     }
0269 
0270     m_size = file_info.size();
0271 
0272     auto fileMetaData = KFileMetaData::UserMetaData(m_filePath);
0273 
0274     m_favorite = fileMetaData.hasAttribute("koko.favorite");
0275     Q_EMIT favoriteChanged();
0276 
0277     m_rating = fileMetaData.rating();
0278     m_description = fileMetaData.userComment();
0279     m_tags = fileMetaData.tags();
0280 
0281     try {
0282         image = Exiv2::ImageFactory::open(fileString);
0283     } catch (const std::exception &) {
0284         Q_EMIT filePathChanged();
0285         return;
0286     }
0287     if (!image.get()) {
0288         Q_EMIT filePathChanged();
0289         return;
0290     }
0291 
0292     if (!image->good()) {
0293         Q_EMIT filePathChanged();
0294         return;
0295     }
0296 
0297     try {
0298         image->readMetadata();
0299     } catch (const std::exception &) {
0300         Q_EMIT filePathChanged();
0301         return;
0302     }
0303 
0304     const Exiv2::ExifData &data = image->exifData();
0305 
0306     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeOriginal"));
0307     if (it != data.end()) {
0308         m_dateTime = toDateTime(it->value());
0309         m_time = QString::fromStdString(it->toString());
0310     }
0311     if (m_dateTime.isNull()) {
0312         it = data.findKey(Exiv2::ExifKey("Exif.Image.DateTime"));
0313         if (it != data.end()) {
0314             m_dateTime = toDateTime(it->value());
0315         }
0316     }
0317 
0318     it = data.findKey(Exiv2::ExifKey("Exif.Image.Model"));
0319     if (it != data.end()) {
0320         m_model = QString::fromStdString(it->toString());
0321     }
0322 
0323     m_latitude = fetchGpsDouble(data, "Exif.GPSInfo.GPSLatitude");
0324     m_longitude = fetchGpsDouble(data, "Exif.GPSInfo.GPSLongitude");
0325 
0326     m_height = image->pixelHeight();
0327     m_width = image->pixelWidth();
0328 
0329     QByteArray latRef = fetchByteArray(data, "Exif.GPSInfo.GPSLatitudeRef");
0330     if (!latRef.isEmpty() && latRef[0] == 'S')
0331         m_latitude *= -1;
0332 
0333     QByteArray longRef = fetchByteArray(data, "Exif.GPSInfo.GPSLongitudeRef");
0334     if (!longRef.isEmpty() && longRef[0] == 'W')
0335         m_longitude *= -1;
0336 
0337     Q_EMIT filePathChanged();
0338 }
0339 
0340 double Exiv2Extractor::fetchGpsDouble(const Exiv2::ExifData &data, const char *name)
0341 {
0342     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
0343     if (it != data.end() && it->count() == 3) {
0344         double n = 0.0;
0345         double d = 0.0;
0346 
0347         n = (*it).toRational(0).first;
0348         d = (*it).toRational(0).second;
0349 
0350         if (d == 0) {
0351             return 0.0;
0352         }
0353 
0354         double deg = n / d;
0355 
0356         n = (*it).toRational(1).first;
0357         d = (*it).toRational(1).second;
0358 
0359         if (d == 0) {
0360             return deg;
0361         }
0362 
0363         double min = n / d;
0364         if (min != -1.0) {
0365             deg += min / 60.0;
0366         }
0367 
0368         n = (*it).toRational(2).first;
0369         d = (*it).toRational(2).second;
0370 
0371         if (d == 0) {
0372             return deg;
0373         }
0374 
0375         double sec = n / d;
0376         if (sec != -1.0) {
0377             deg += sec / 3600.0;
0378         }
0379 
0380         return deg;
0381     }
0382 
0383     return 0.0;
0384 }
0385 
0386 QByteArray Exiv2Extractor::fetchByteArray(const Exiv2::ExifData &data, const char *name)
0387 {
0388     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
0389     if (it != data.end()) {
0390         std::string str = it->value().toString();
0391         return QByteArray(str.c_str(), str.size());
0392     }
0393 
0394     return QByteArray();
0395 }
0396 
0397 bool Exiv2Extractor::error() const
0398 {
0399     return m_error;
0400 }