File indexing completed on 2025-04-27 04:04:20
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 }