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