1 /*
2     SPDX-FileCopyrightText: 2012 Vishesh Handa <me@vhanda.in>
3 
4     SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6 
7 
8 #include "exiv2extractor.h"
9 #include <cmath>
10 
11 using namespace KFileMetaData;
12 
13 
Exiv2Extractor(QObject * parent)14 Exiv2Extractor::Exiv2Extractor(QObject* parent)
15     : ExtractorPlugin(parent)
16 {
17 }
18 
19 namespace
20 {
21 static const QStringList supportedMimeTypes = {
22     QStringLiteral("image/bmp"),
23     QStringLiteral("image/gif"),
24     QStringLiteral("image/jp2"),
25     QStringLiteral("image/jpeg"),
26     QStringLiteral("image/pgf"),
27     QStringLiteral("image/png"),
28     QStringLiteral("image/tiff"),
29 #ifdef HAVE_WEBP_SUPPORT
30     QStringLiteral("image/webp"),
31 #endif
32     QStringLiteral("image/x-exv"),
33     QStringLiteral("image/x-canon-cr2"),
34     QStringLiteral("image/x-canon-crw"),
35     QStringLiteral("image/x-fuji-raf"),
36     QStringLiteral("image/x-minolta-mrw"),
37     QStringLiteral("image/x-nikon-nef"),
38     QStringLiteral("image/x-olympus-orf"),
39     QStringLiteral("image/x-panasonic-rw2"),
40     QStringLiteral("image/x-pentax-pef"),
41     QStringLiteral("image/x-photoshop"),
42     QStringLiteral("image/x-samsung-srw"),
43     QStringLiteral("image/x-tga"),
44 };
45 
toString(const Exiv2::Value & value)46 QString toString(const Exiv2::Value& value)
47 {
48     const std::string str = value.toString();
49     return QString::fromUtf8(str.c_str(), str.length());
50 }
51 
toVariantDateTime(const Exiv2::Value & value)52 QVariant toVariantDateTime(const Exiv2::Value& value)
53 {
54     if (value.typeId() == Exiv2::asciiString) {
55         QDateTime val = ExtractorPlugin::dateTimeFromString(QString::fromLatin1(value.toString().c_str()));
56         if (val.isValid()) {
57             // Datetime is stored in exif as local time.
58             val.setOffsetFromUtc(0);
59             return QVariant(val);
60         }
61     }
62 
63     return QVariant();
64 }
65 
toVariantLong(const Exiv2::Value & value)66 QVariant toVariantLong(const Exiv2::Value& value)
67 {
68     if (value.typeId() == Exiv2::unsignedLong || value.typeId() == Exiv2::signedLong) {
69         qlonglong val = value.toLong();
70         return QVariant(val);
71     }
72 
73     QString str(toString(value));
74     bool ok = false;
75     int val = str.toInt(&ok);
76     if (ok) {
77         return QVariant(val);
78     }
79 
80     return QVariant();
81 }
82 
toVariantDouble(const Exiv2::Value & value)83 QVariant toVariantDouble(const Exiv2::Value& value)
84 {
85     if (value.typeId() == Exiv2::tiffFloat || value.typeId() == Exiv2::tiffDouble
86         || value.typeId() == Exiv2::unsignedRational || value.typeId() == Exiv2::signedRational) {
87         return QVariant(static_cast<double>(value.toFloat()));
88     }
89 
90     QString str(toString(value));
91     bool ok = false;
92     double val = str.toDouble(&ok);
93     if (ok) {
94         return QVariant(val);
95     }
96 
97     return QVariant();
98 }
99 
toVariantString(const Exiv2::Value & value)100 QVariant toVariantString(const Exiv2::Value& value)
101 {
102     QString str = toString(value);
103     if (!str.isEmpty()) {
104         return QVariant(str);
105     }
106 
107     return QVariant();
108 }
109 
toVariant(const Exiv2::Value & value,QVariant::Type type)110 QVariant toVariant(const Exiv2::Value& value, QVariant::Type type) {
111     if (value.count() == 0) {
112         return QVariant();
113     }
114     switch (type) {
115     case QVariant::Int:
116         return toVariantLong(value);
117 
118     case QVariant::DateTime:
119         return toVariantDateTime(value);
120 
121     case QVariant::Double:
122         return toVariantDouble(value);
123 
124     case QVariant::String:
125     default:
126         return toVariantString(value);
127     }
128 }
129 }
130 
mimetypes() const131 QStringList Exiv2Extractor::mimetypes() const
132 {
133     return supportedMimeTypes;
134 }
135 
extract(ExtractionResult * result)136 void Exiv2Extractor::extract(ExtractionResult* result)
137 {
138     QByteArray arr = result->inputUrl().toUtf8();
139     std::string fileString(arr.data(), arr.length());
140 
141 #if EXIV2_TEST_VERSION(0, 28, 0)
142     Exiv2::Image::UniquePtr image;
143 #else
144     Exiv2::Image::AutoPtr image;
145 #endif
146     try {
147         image = Exiv2::ImageFactory::open(fileString);
148     } catch (const std::exception&) {
149         return;
150     }
151     if (!image.get()) {
152         return;
153     }
154 
155     try {
156         image->readMetadata();
157     } catch (const std::exception&) {
158         return;
159     }
160     result->addType(Type::Image);
161 
162     if (!(result->inputFlags() & ExtractionResult::ExtractMetaData)) {
163         return;
164     }
165 
166     if (image->pixelHeight()) {
167         result->add(Property::Height, image->pixelHeight());
168     }
169 
170     if (image->pixelWidth()) {
171         result->add(Property::Width, image->pixelWidth());
172     }
173 
174     std::string comment = image->comment();
175     if (!comment.empty()) {
176         result->add(Property::Comment, QString::fromUtf8(comment.c_str(), comment.length()));
177     }
178 
179     const Exiv2::ExifData& data = image->exifData();
180 
181     add(result, data, Property::Manufacturer, "Exif.Image.Make", QVariant::String);
182     add(result, data, Property::Model, "Exif.Image.Model", QVariant::String);
183     add(result, data, Property::Description, "Exif.Image.ImageDescription", QVariant::String);
184     add(result, data, Property::Artist, "Exif.Image.Artist", QVariant::String);
185     add(result, data, Property::Copyright, "Exif.Image.Copyright", QVariant::String);
186     add(result, data, Property::Generator, "Exif.Image.Software", QVariant::String);
187     add(result, data, Property::ImageDateTime, "Exif.Image.DateTime", QVariant::DateTime);
188     add(result, data, Property::ImageOrientation, "Exif.Image.Orientation", QVariant::Int);
189     add(result, data, Property::PhotoFlash, "Exif.Photo.Flash", QVariant::Int);
190     add(result, data, Property::PhotoPixelXDimension, "Exif.Photo.PixelXDimension", QVariant::Int);
191     add(result, data, Property::PhotoPixelYDimension, "Exif.Photo.PixelYDimension", QVariant::Int);
192     add(result, data, Property::PhotoDateTimeOriginal, "Exif.Photo.DateTimeOriginal", QVariant::DateTime);
193     add(result, data, Property::PhotoFocalLength, "Exif.Photo.FocalLength", QVariant::Double);
194     add(result, data, Property::PhotoFocalLengthIn35mmFilm, "Exif.Photo.FocalLengthIn35mmFilm", QVariant::Double);
195     add(result, data, Property::PhotoExposureTime, "Exif.Photo.ExposureTime", QVariant::Double);
196     add(result, data, Property::PhotoExposureBiasValue, "Exif.Photo.ExposureBiasValue", QVariant::Double);
197     add(result, data, Property::PhotoFNumber, "Exif.Photo.FNumber", QVariant::Double);
198     add(result, data, Property::PhotoApertureValue, "Exif.Photo.ApertureValue", QVariant::Double);
199     add(result, data, Property::PhotoWhiteBalance, "Exif.Photo.WhiteBalance", QVariant::Int);
200     add(result, data, Property::PhotoMeteringMode, "Exif.Photo.MeteringMode", QVariant::Int);
201     add(result, data, Property::PhotoISOSpeedRatings, "Exif.Photo.ISOSpeedRatings", QVariant::Int);
202     add(result, data, Property::PhotoSaturation, "Exif.Photo.Saturation", QVariant::Int);
203     add(result, data, Property::PhotoSharpness, "Exif.Photo.Sharpness", QVariant::Int);
204 
205     double latitude = fetchGpsDouble(data, "Exif.GPSInfo.GPSLatitude");
206     double longitude = fetchGpsDouble(data, "Exif.GPSInfo.GPSLongitude");
207     double altitude = fetchGpsAltitude(data);
208 
209     QByteArray latRef = fetchByteArray(data, "Exif.GPSInfo.GPSLatitudeRef");
210     if (!latRef.isEmpty() && latRef[0] == 'S') {
211         latitude *= -1;
212     }
213 
214     QByteArray longRef = fetchByteArray(data, "Exif.GPSInfo.GPSLongitudeRef");
215     if (!longRef.isEmpty() && longRef[0] == 'W') {
216         longitude *= -1;
217     }
218 
219     if (!std::isnan(latitude)) {
220         result->add(Property::PhotoGpsLatitude, latitude);
221     }
222 
223     if (!std::isnan(longitude)) {
224         result->add(Property::PhotoGpsLongitude, longitude);
225     }
226 
227     if (!std::isnan(altitude)) {
228         result->add(Property::PhotoGpsAltitude, altitude);
229     }
230 }
231 
add(ExtractionResult * result,const Exiv2::ExifData & data,Property::Property prop,const char * name,QVariant::Type type)232 void Exiv2Extractor::add(ExtractionResult* result, const Exiv2::ExifData& data,
233                          Property::Property prop, const char* name,
234                          QVariant::Type type)
235 {
236     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
237     if (it != data.end()) {
238         QVariant value = toVariant(it->value(), type);
239         if (!value.isNull()) {
240             result->add(prop, value);
241         }
242     }
243 }
244 
fetchGpsDouble(const Exiv2::ExifData & data,const char * name)245 double Exiv2Extractor::fetchGpsDouble(const Exiv2::ExifData& data, const char* name)
246 {
247     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
248     if (it != data.end() && it->count() == 3) {
249         double n = 0.0;
250         double d = 0.0;
251 
252         n = (*it).toRational(0).first;
253         d = (*it).toRational(0).second;
254 
255         if (d == 0.0) {
256             return std::numeric_limits<double>::quiet_NaN();
257         }
258 
259         double deg = n / d;
260 
261         n = (*it).toRational(1).first;
262         d = (*it).toRational(1).second;
263 
264         if (d == 0.0) {
265             return deg;
266         }
267 
268         double min = n / d;
269         if (min != -1.0) {
270             deg += min / 60.0;
271         }
272 
273         n = (*it).toRational(2).first;
274         d = (*it).toRational(2).second;
275 
276         if (d == 0.0) {
277             return deg;
278         }
279 
280         double sec = n / d;
281         if (sec != -1.0) {
282             deg += sec / 3600.0;
283         }
284 
285         return deg;
286     }
287 
288     return std::numeric_limits<double>::quiet_NaN();
289 }
290 
fetchGpsAltitude(const Exiv2::ExifData & data)291 double Exiv2Extractor::fetchGpsAltitude(const Exiv2::ExifData& data)
292 {
293     double alt = std::numeric_limits<double>::quiet_NaN();
294     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"));
295     if (it != data.end() && it->count() > 0 &&
296         (it->value().typeId() == Exiv2::unsignedRational || it->value().typeId() == Exiv2::signedRational)) {
297         auto ratio = it->value().toRational();
298         if (ratio.second == 0) {
299             return alt;
300         }
301         it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"));
302         if (it != data.end() && it->count() > 0 &&
303             (it->value().typeId() == Exiv2::unsignedByte || it->value().typeId() == Exiv2::signedByte)) {
304             auto altRef = it->value().toLong();
305             if (altRef) {
306                 alt = -1.0 * ratio.first / ratio.second;
307             } else {
308                 alt = 1.0 * ratio.first / ratio.second;
309             }
310         }
311     }
312     return alt;
313 }
314 
fetchByteArray(const Exiv2::ExifData & data,const char * name)315 QByteArray Exiv2Extractor::fetchByteArray(const Exiv2::ExifData& data, const char* name)
316 {
317     Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
318     if (it != data.end() && it->count() > 0) {
319         std::string str = it->value().toString();
320         return QByteArray(str.c_str(), str.size());
321     }
322 
323     return QByteArray();
324 }
325