File indexing completed on 2025-04-20 03:39:10
0001 /* 0002 SPDX-FileCopyrightText: 2012 Vishesh Handa <me@vhanda.in> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 0008 #include "exiv2extractor.h" 0009 #include "kfilemetadata_debug.h" 0010 #include <QTimeZone> 0011 #include <cmath> 0012 #include <exiv2/exiv2.hpp> 0013 0014 using namespace KFileMetaData; 0015 0016 namespace { 0017 void add(ExtractionResult* result, const Exiv2::ExifData& data, 0018 Property::Property prop, const Exiv2::ExifKey& key, 0019 QMetaType::Type type); 0020 0021 double fetchGpsDouble(const Exiv2::ExifData& data, const Exiv2::ExifKey& key); 0022 double fetchGpsAltitude(const Exiv2::ExifData& data); 0023 QByteArray fetchByteArray(const Exiv2::ExifData& data, const Exiv2::ExifKey& key); 0024 } 0025 0026 Exiv2Extractor::Exiv2Extractor(QObject* parent) 0027 : ExtractorPlugin(parent) 0028 { 0029 #ifdef EXV_ENABLE_BMFF 0030 Exiv2::enableBMFF(true); 0031 #endif 0032 } 0033 0034 namespace 0035 { 0036 static const QStringList supportedMimeTypes = { 0037 QStringLiteral("image/bmp"), 0038 QStringLiteral("image/gif"), 0039 QStringLiteral("image/jp2"), 0040 QStringLiteral("image/jpeg"), 0041 QStringLiteral("image/pgf"), 0042 QStringLiteral("image/png"), 0043 QStringLiteral("image/tiff"), 0044 QStringLiteral("image/webp"), 0045 #ifdef EXV_ENABLE_BMFF 0046 QStringLiteral("image/avif"), 0047 QStringLiteral("image/heif"), 0048 QStringLiteral("image/jxl"), 0049 QStringLiteral("image/x-canon-cr3"), 0050 #endif 0051 QStringLiteral("image/x-exv"), 0052 QStringLiteral("image/x-canon-cr2"), 0053 QStringLiteral("image/x-canon-crw"), 0054 QStringLiteral("image/x-fuji-raf"), 0055 QStringLiteral("image/x-minolta-mrw"), 0056 QStringLiteral("image/x-nikon-nef"), 0057 QStringLiteral("image/x-olympus-orf"), 0058 QStringLiteral("image/x-panasonic-rw2"), 0059 QStringLiteral("image/x-pentax-pef"), 0060 QStringLiteral("image/x-photoshop"), 0061 QStringLiteral("image/x-samsung-srw"), 0062 QStringLiteral("image/x-tga"), 0063 }; 0064 0065 QString toString(const Exiv2::Value& value) 0066 { 0067 const std::string str = value.toString(); 0068 return QString::fromUtf8(str.c_str(), str.length()); 0069 } 0070 0071 QVariant toVariantDateTime(const Exiv2::Value& value) 0072 { 0073 if (value.typeId() == Exiv2::asciiString) { 0074 QDateTime val = ExtractorPlugin::dateTimeFromString(QString::fromLatin1(value.toString().c_str())); 0075 if (val.isValid()) { 0076 // Datetime is stored in exif as local time. 0077 val.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(0)); 0078 return QVariant(val); 0079 } 0080 } 0081 0082 return QVariant(); 0083 } 0084 0085 QVariant toVariantLong(const Exiv2::Value& value) 0086 { 0087 if (value.typeId() == Exiv2::unsignedLong || value.typeId() == Exiv2::signedLong) { 0088 #if EXIV2_TEST_VERSION(0,28,0) 0089 qlonglong val = value.toInt64(); 0090 #else 0091 qlonglong val = value.toLong(); 0092 #endif 0093 return QVariant(val); 0094 } 0095 0096 QString str(toString(value)); 0097 bool ok = false; 0098 int val = str.toInt(&ok); 0099 if (ok) { 0100 return QVariant(val); 0101 } 0102 0103 return QVariant(); 0104 } 0105 0106 QVariant toVariantDouble(const Exiv2::Value& value) 0107 { 0108 if (value.typeId() == Exiv2::tiffFloat || value.typeId() == Exiv2::tiffDouble 0109 || value.typeId() == Exiv2::unsignedRational || value.typeId() == Exiv2::signedRational) { 0110 return QVariant(static_cast<double>(value.toFloat())); 0111 } 0112 0113 QString str(toString(value)); 0114 bool ok = false; 0115 double val = str.toDouble(&ok); 0116 if (ok) { 0117 return QVariant(val); 0118 } 0119 0120 return QVariant(); 0121 } 0122 0123 QVariant toVariantString(const Exiv2::Value& value) 0124 { 0125 QString str = toString(value); 0126 if (!str.isEmpty()) { 0127 return QVariant(str); 0128 } 0129 0130 return QVariant(); 0131 } 0132 0133 QVariant toVariant(const Exiv2::Value& value, QMetaType::Type type) { 0134 if (value.count() == 0) { 0135 return QVariant(); 0136 } 0137 switch (type) { 0138 case QMetaType::Int: 0139 return toVariantLong(value); 0140 0141 case QMetaType::QDateTime: 0142 return toVariantDateTime(value); 0143 0144 case QMetaType::Double: 0145 return toVariantDouble(value); 0146 0147 case QMetaType::QString: 0148 default: 0149 return toVariantString(value); 0150 } 0151 } 0152 } 0153 0154 QStringList Exiv2Extractor::mimetypes() const 0155 { 0156 return supportedMimeTypes; 0157 } 0158 0159 void Exiv2Extractor::extract(ExtractionResult* result) 0160 { 0161 using namespace std::string_literals; 0162 0163 QByteArray arr = result->inputUrl().toUtf8(); 0164 std::string fileString(arr.data(), arr.length()); 0165 0166 #if EXIV2_TEST_VERSION(0, 28, 0) 0167 Exiv2::Image::UniquePtr image; 0168 #else 0169 Exiv2::Image::AutoPtr image; 0170 #endif 0171 try { 0172 image = Exiv2::ImageFactory::open(fileString); 0173 } catch (const std::exception&) { 0174 return; 0175 } 0176 if (!image.get()) { 0177 return; 0178 } 0179 0180 try { 0181 image->readMetadata(); 0182 } catch (const std::exception&) { 0183 return; 0184 } 0185 result->addType(Type::Image); 0186 0187 if (!(result->inputFlags() & ExtractionResult::ExtractMetaData)) { 0188 return; 0189 } 0190 0191 if (image->pixelHeight()) { 0192 result->add(Property::Height, image->pixelHeight()); 0193 } 0194 0195 if (image->pixelWidth()) { 0196 result->add(Property::Width, image->pixelWidth()); 0197 } 0198 0199 std::string comment = image->comment(); 0200 if (!comment.empty()) { 0201 result->add(Property::Comment, QString::fromUtf8(comment.c_str(), comment.length())); 0202 } 0203 0204 const Exiv2::ExifData& data = image->exifData(); 0205 0206 using EK = Exiv2::ExifKey; 0207 add(result, data, Property::Manufacturer, EK{"Exif.Image.Make"s}, QMetaType::QString); 0208 add(result, data, Property::Model, EK{"Exif.Image.Model"s}, QMetaType::QString); 0209 add(result, data, Property::Description, EK{"Exif.Image.ImageDescription"s}, QMetaType::QString); 0210 add(result, data, Property::Artist, EK{"Exif.Image.Artist"s}, QMetaType::QString); 0211 add(result, data, Property::Copyright, EK{"Exif.Image.Copyright"s}, QMetaType::QString); 0212 add(result, data, Property::Generator, EK{"Exif.Image.Software"s}, QMetaType::QString); 0213 add(result, data, Property::ImageDateTime, EK{"Exif.Image.DateTime"s}, QMetaType::QDateTime); 0214 add(result, data, Property::ImageOrientation, EK{"Exif.Image.Orientation"s}, QMetaType::Int); 0215 add(result, data, Property::PhotoFlash, EK{"Exif.Photo.Flash"s}, QMetaType::Int); 0216 add(result, data, Property::PhotoPixelXDimension, EK{"Exif.Photo.PixelXDimension"s}, QMetaType::Int); 0217 add(result, data, Property::PhotoPixelYDimension, EK{"Exif.Photo.PixelYDimension"s}, QMetaType::Int); 0218 add(result, data, Property::PhotoDateTimeOriginal, EK{"Exif.Photo.DateTimeOriginal"s}, QMetaType::QDateTime); 0219 add(result, data, Property::PhotoFocalLength, EK{"Exif.Photo.FocalLength"s}, QMetaType::Double); 0220 add(result, data, Property::PhotoFocalLengthIn35mmFilm, EK{"Exif.Photo.FocalLengthIn35mmFilm"s}, QMetaType::Double); 0221 add(result, data, Property::PhotoExposureTime, EK{"Exif.Photo.ExposureTime"s}, QMetaType::Double); 0222 add(result, data, Property::PhotoExposureBiasValue, EK{"Exif.Photo.ExposureBiasValue"s}, QMetaType::Double); 0223 add(result, data, Property::PhotoFNumber, EK{"Exif.Photo.FNumber"s}, QMetaType::Double); 0224 add(result, data, Property::PhotoApertureValue, EK{"Exif.Photo.ApertureValue"s}, QMetaType::Double); 0225 add(result, data, Property::PhotoWhiteBalance, EK{"Exif.Photo.WhiteBalance"s}, QMetaType::Int); 0226 add(result, data, Property::PhotoMeteringMode, EK{"Exif.Photo.MeteringMode"s}, QMetaType::Int); 0227 add(result, data, Property::PhotoISOSpeedRatings, EK{"Exif.Photo.ISOSpeedRatings"s}, QMetaType::Int); 0228 add(result, data, Property::PhotoSaturation, EK{"Exif.Photo.Saturation"s}, QMetaType::Int); 0229 add(result, data, Property::PhotoSharpness, EK{"Exif.Photo.Sharpness"s}, QMetaType::Int); 0230 // https://exiv2.org/tags.html "Exif.Photo.ImageTitle" not natively supported, use tag value 0231 add(result, data, Property::Title, EK{0xa436, "Photo"s}, QMetaType::QString); 0232 0233 double latitude = fetchGpsDouble(data, EK{"Exif.GPSInfo.GPSLatitude"s}); 0234 double longitude = fetchGpsDouble(data, EK{"Exif.GPSInfo.GPSLongitude"s}); 0235 double altitude = fetchGpsAltitude(data); 0236 0237 QByteArray latRef = fetchByteArray(data, EK{"Exif.GPSInfo.GPSLatitudeRef"s}); 0238 if (!latRef.isEmpty() && latRef[0] == 'S') { 0239 latitude *= -1; 0240 } 0241 0242 QByteArray longRef = fetchByteArray(data, EK{"Exif.GPSInfo.GPSLongitudeRef"s}); 0243 if (!longRef.isEmpty() && longRef[0] == 'W') { 0244 longitude *= -1; 0245 } 0246 0247 if (!std::isnan(latitude)) { 0248 result->add(Property::PhotoGpsLatitude, latitude); 0249 } 0250 0251 if (!std::isnan(longitude)) { 0252 result->add(Property::PhotoGpsLongitude, longitude); 0253 } 0254 0255 if (!std::isnan(altitude)) { 0256 result->add(Property::PhotoGpsAltitude, altitude); 0257 } 0258 0259 const Exiv2::XmpData& xmpData = image->xmpData(); 0260 for (const auto& entry : xmpData) { 0261 if (entry.groupName() != "dc"s) { 0262 continue; 0263 } 0264 0265 std::map<std::string, Property::Property> propMap = { 0266 { "subject"s, Property::Subject }, 0267 { "title"s, Property::Title}, 0268 { "description"s, Property::Description}, 0269 }; 0270 if (auto type = propMap.find(entry.tagName()); type != propMap.end()) { 0271 auto xmpType = Exiv2::XmpValue::xmpArrayType(entry.value().typeId()); 0272 size_t limit = ((xmpType == Exiv2::XmpValue::xaBag) || (xmpType == Exiv2::XmpValue::xaSeq)) ? entry.count() : 1; 0273 for (size_t i = 0; i < limit; i++) { 0274 auto value = QString::fromStdString(entry.toString(i)); 0275 if (!value.isEmpty()) { 0276 result->add(type->second, value); 0277 } 0278 } 0279 } 0280 } 0281 } 0282 0283 namespace { 0284 void add(ExtractionResult* result, const Exiv2::ExifData& data, 0285 Property::Property prop, const Exiv2::ExifKey& key, 0286 QMetaType::Type type) 0287 { 0288 Exiv2::ExifData::const_iterator it = data.findKey(key); 0289 if (it != data.end()) { 0290 QVariant value = toVariant(it->value(), type); 0291 if (!value.isNull()) { 0292 result->add(prop, value); 0293 } 0294 } 0295 } 0296 0297 double fetchGpsDouble(const Exiv2::ExifData& data, const Exiv2::ExifKey& key) 0298 { 0299 Exiv2::ExifData::const_iterator it = data.findKey(key); 0300 if (it != data.end() && it->count() == 3) { 0301 double n = 0.0; 0302 double d = 0.0; 0303 0304 n = (*it).toRational(0).first; 0305 d = (*it).toRational(0).second; 0306 0307 if (d == 0.0) { 0308 return std::numeric_limits<double>::quiet_NaN(); 0309 } 0310 0311 double deg = n / d; 0312 0313 n = (*it).toRational(1).first; 0314 d = (*it).toRational(1).second; 0315 0316 if (d == 0.0) { 0317 return deg; 0318 } 0319 0320 double min = n / d; 0321 if (min != -1.0) { 0322 deg += min / 60.0; 0323 } 0324 0325 n = (*it).toRational(2).first; 0326 d = (*it).toRational(2).second; 0327 0328 if (d == 0.0) { 0329 return deg; 0330 } 0331 0332 double sec = n / d; 0333 if (sec != -1.0) { 0334 deg += sec / 3600.0; 0335 } 0336 0337 return deg; 0338 } 0339 0340 return std::numeric_limits<double>::quiet_NaN(); 0341 } 0342 0343 double fetchGpsAltitude(const Exiv2::ExifData& data) 0344 { 0345 using namespace std::string_literals; 0346 0347 double alt = std::numeric_limits<double>::quiet_NaN(); 0348 Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"s)); 0349 if (it != data.end() && it->count() > 0 && 0350 (it->value().typeId() == Exiv2::unsignedRational || it->value().typeId() == Exiv2::signedRational)) { 0351 auto ratio = it->value().toRational(); 0352 if (ratio.second == 0) { 0353 return alt; 0354 } 0355 it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"s)); 0356 if (it != data.end() && it->count() > 0 && 0357 (it->value().typeId() == Exiv2::unsignedByte || it->value().typeId() == Exiv2::signedByte)) { 0358 #if EXIV2_TEST_VERSION(0,28,0) 0359 auto altRef = it->value().toInt64(); 0360 #else 0361 auto altRef = it->value().toLong(); 0362 #endif 0363 if (altRef) { 0364 alt = -1.0 * ratio.first / ratio.second; 0365 } else { 0366 alt = 1.0 * ratio.first / ratio.second; 0367 } 0368 } 0369 } 0370 return alt; 0371 } 0372 QByteArray fetchByteArray(const Exiv2::ExifData& data, const Exiv2::ExifKey& key) 0373 { 0374 Exiv2::ExifData::const_iterator it = data.findKey(key); 0375 if (it != data.end() && it->count() > 0) { 0376 std::string str = it->value().toString(); 0377 return QByteArray(str.c_str(), str.size()); 0378 } 0379 0380 return QByteArray(); 0381 } 0382 } // <anonymous> namespace 0383 0384 #include "moc_exiv2extractor.cpp"