File indexing completed on 2024-05-12 15:37:04

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