KFileMetaData

exiv2extractor.cpp
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 "kfilemetadata_debug.h"
10#include <QTimeZone>
11#include <cmath>
12#include <exiv2/exiv2.hpp>
13
14using namespace KFileMetaData;
15
16namespace {
17void add(ExtractionResult* result, const Exiv2::ExifData& data,
18 Property::Property prop, const Exiv2::ExifKey& key,
19 QMetaType::Type type);
20
21double fetchGpsDouble(const Exiv2::ExifData& data, const Exiv2::ExifKey& key);
22double fetchGpsAltitude(const Exiv2::ExifData& data);
23QByteArray fetchByteArray(const Exiv2::ExifData& data, const Exiv2::ExifKey& key);
24}
25
26Exiv2Extractor::Exiv2Extractor(QObject* parent)
27 : ExtractorPlugin(parent)
28{
29#ifdef EXV_ENABLE_BMFF
30 Exiv2::enableBMFF(true);
31#endif
32}
33
34namespace
35{
36static const QStringList supportedMimeTypes = {
37 QStringLiteral("image/bmp"),
38 QStringLiteral("image/gif"),
39 QStringLiteral("image/jp2"),
40 QStringLiteral("image/jpeg"),
41 QStringLiteral("image/pgf"),
42 QStringLiteral("image/png"),
43 QStringLiteral("image/tiff"),
44 QStringLiteral("image/webp"),
45#ifdef EXV_ENABLE_BMFF
46 QStringLiteral("image/avif"),
47 QStringLiteral("image/heif"),
48 QStringLiteral("image/jxl"),
49 QStringLiteral("image/x-canon-cr3"),
50#endif
51 QStringLiteral("image/x-exv"),
52 QStringLiteral("image/x-canon-cr2"),
53 QStringLiteral("image/x-canon-crw"),
54 QStringLiteral("image/x-fuji-raf"),
55 QStringLiteral("image/x-minolta-mrw"),
56 QStringLiteral("image/x-nikon-nef"),
57 QStringLiteral("image/x-olympus-orf"),
58 QStringLiteral("image/x-panasonic-rw2"),
59 QStringLiteral("image/x-pentax-pef"),
60 QStringLiteral("image/x-photoshop"),
61 QStringLiteral("image/x-samsung-srw"),
62 QStringLiteral("image/x-tga"),
63};
64
65QString toString(const Exiv2::Value& value)
66{
67 const std::string str = value.toString();
68 return QString::fromUtf8(str.c_str(), str.length());
69}
70
71QVariant toVariantDateTime(const Exiv2::Value& value)
72{
73 if (value.typeId() == Exiv2::asciiString) {
75 if (val.isValid()) {
76 // Datetime is stored in exif as local time.
78 return QVariant(val);
79 }
80 }
81
82 return QVariant();
83}
84
85QVariant toVariantLong(const Exiv2::Value& value)
86{
87 if (value.typeId() == Exiv2::unsignedLong || value.typeId() == Exiv2::signedLong) {
88#if EXIV2_TEST_VERSION(0,28,0)
89 qlonglong val = value.toInt64();
90#else
91 qlonglong val = value.toLong();
92#endif
93 return QVariant(val);
94 }
95
96 QString str(toString(value));
97 bool ok = false;
98 int val = str.toInt(&ok);
99 if (ok) {
100 return QVariant(val);
101 }
102
103 return QVariant();
104}
105
106QVariant toVariantDouble(const Exiv2::Value& value)
107{
108 if (value.typeId() == Exiv2::tiffFloat || value.typeId() == Exiv2::tiffDouble
109 || value.typeId() == Exiv2::unsignedRational || value.typeId() == Exiv2::signedRational) {
110 return QVariant(static_cast<double>(value.toFloat()));
111 }
112
113 QString str(toString(value));
114 bool ok = false;
115 double val = str.toDouble(&ok);
116 if (ok) {
117 return QVariant(val);
118 }
119
120 return QVariant();
121}
122
123QVariant toVariantString(const Exiv2::Value& value)
124{
125 QString str = toString(value);
126 if (!str.isEmpty()) {
127 return QVariant(str);
128 }
129
130 return QVariant();
131}
132
133QVariant toVariant(const Exiv2::Value& value, QMetaType::Type type) {
134 if (value.count() == 0) {
135 return QVariant();
136 }
137 switch (type) {
138 case QMetaType::Int:
139 return toVariantLong(value);
140
142 return toVariantDateTime(value);
143
145 return toVariantDouble(value);
146
148 default:
149 return toVariantString(value);
150 }
151}
152}
153
154QStringList Exiv2Extractor::mimetypes() const
155{
156 return supportedMimeTypes;
157}
158
159void Exiv2Extractor::extract(ExtractionResult* result)
160{
161 using namespace std::string_literals;
162
163 QByteArray arr = result->inputUrl().toUtf8();
164 std::string fileString(arr.data(), arr.length());
165
166#if EXIV2_TEST_VERSION(0, 28, 0)
167 Exiv2::Image::UniquePtr image;
168#else
169 Exiv2::Image::AutoPtr image;
170#endif
171 try {
172 image = Exiv2::ImageFactory::open(fileString);
173 } catch (const std::exception&) {
174 return;
175 }
176 if (!image.get()) {
177 return;
178 }
179
180 try {
181 image->readMetadata();
182 } catch (const std::exception&) {
183 return;
184 }
185 result->addType(Type::Image);
186
187 if (!(result->inputFlags() & ExtractionResult::ExtractMetaData)) {
188 return;
189 }
190
191 if (image->pixelHeight()) {
192 result->add(Property::Height, image->pixelHeight());
193 }
194
195 if (image->pixelWidth()) {
196 result->add(Property::Width, image->pixelWidth());
197 }
198
199 std::string comment = image->comment();
200 if (!comment.empty()) {
201 result->add(Property::Comment, QString::fromUtf8(comment.c_str(), comment.length()));
202 }
203
204 const Exiv2::ExifData& data = image->exifData();
205
206 using EK = Exiv2::ExifKey;
207 add(result, data, Property::Manufacturer, EK{"Exif.Image.Make"s}, QMetaType::QString);
208 add(result, data, Property::Model, EK{"Exif.Image.Model"s}, QMetaType::QString);
209 add(result, data, Property::Description, EK{"Exif.Image.ImageDescription"s}, QMetaType::QString);
210 add(result, data, Property::Artist, EK{"Exif.Image.Artist"s}, QMetaType::QString);
211 add(result, data, Property::Copyright, EK{"Exif.Image.Copyright"s}, QMetaType::QString);
212 add(result, data, Property::Generator, EK{"Exif.Image.Software"s}, QMetaType::QString);
213 add(result, data, Property::ImageDateTime, EK{"Exif.Image.DateTime"s}, QMetaType::QDateTime);
214 add(result, data, Property::ImageOrientation, EK{"Exif.Image.Orientation"s}, QMetaType::Int);
215 add(result, data, Property::PhotoFlash, EK{"Exif.Photo.Flash"s}, QMetaType::Int);
216 add(result, data, Property::PhotoPixelXDimension, EK{"Exif.Photo.PixelXDimension"s}, QMetaType::Int);
217 add(result, data, Property::PhotoPixelYDimension, EK{"Exif.Photo.PixelYDimension"s}, QMetaType::Int);
218 add(result, data, Property::PhotoDateTimeOriginal, EK{"Exif.Photo.DateTimeOriginal"s}, QMetaType::QDateTime);
219 add(result, data, Property::PhotoFocalLength, EK{"Exif.Photo.FocalLength"s}, QMetaType::Double);
220 add(result, data, Property::PhotoFocalLengthIn35mmFilm, EK{"Exif.Photo.FocalLengthIn35mmFilm"s}, QMetaType::Double);
221 add(result, data, Property::PhotoExposureTime, EK{"Exif.Photo.ExposureTime"s}, QMetaType::Double);
222 add(result, data, Property::PhotoExposureBiasValue, EK{"Exif.Photo.ExposureBiasValue"s}, QMetaType::Double);
223 add(result, data, Property::PhotoFNumber, EK{"Exif.Photo.FNumber"s}, QMetaType::Double);
224 add(result, data, Property::PhotoApertureValue, EK{"Exif.Photo.ApertureValue"s}, QMetaType::Double);
225 add(result, data, Property::PhotoWhiteBalance, EK{"Exif.Photo.WhiteBalance"s}, QMetaType::Int);
226 add(result, data, Property::PhotoMeteringMode, EK{"Exif.Photo.MeteringMode"s}, QMetaType::Int);
227 add(result, data, Property::PhotoISOSpeedRatings, EK{"Exif.Photo.ISOSpeedRatings"s}, QMetaType::Int);
228 add(result, data, Property::PhotoSaturation, EK{"Exif.Photo.Saturation"s}, QMetaType::Int);
229 add(result, data, Property::PhotoSharpness, EK{"Exif.Photo.Sharpness"s}, QMetaType::Int);
230 // https://exiv2.org/tags.html "Exif.Photo.ImageTitle" not natively supported, use tag value
231 add(result, data, Property::Title, EK{0xa436, "Photo"s}, QMetaType::QString);
232
233 double latitude = fetchGpsDouble(data, EK{"Exif.GPSInfo.GPSLatitude"s});
234 double longitude = fetchGpsDouble(data, EK{"Exif.GPSInfo.GPSLongitude"s});
235 double altitude = fetchGpsAltitude(data);
236
237 QByteArray latRef = fetchByteArray(data, EK{"Exif.GPSInfo.GPSLatitudeRef"s});
238 if (!latRef.isEmpty() && latRef[0] == 'S') {
239 latitude *= -1;
240 }
241
242 QByteArray longRef = fetchByteArray(data, EK{"Exif.GPSInfo.GPSLongitudeRef"s});
243 if (!longRef.isEmpty() && longRef[0] == 'W') {
244 longitude *= -1;
245 }
246
247 if (!std::isnan(latitude)) {
248 result->add(Property::PhotoGpsLatitude, latitude);
249 }
250
251 if (!std::isnan(longitude)) {
252 result->add(Property::PhotoGpsLongitude, longitude);
253 }
254
255 if (!std::isnan(altitude)) {
256 result->add(Property::PhotoGpsAltitude, altitude);
257 }
258
259 const Exiv2::XmpData& xmpData = image->xmpData();
260 for (const auto& entry : xmpData) {
261 if (entry.groupName() != "dc"s) {
262 continue;
263 }
264
265 std::map<std::string, Property::Property> propMap = {
266 { "subject"s, Property::Subject },
267 { "title"s, Property::Title},
268 { "description"s, Property::Description},
269 };
270 if (auto type = propMap.find(entry.tagName()); type != propMap.end()) {
271 auto xmpType = Exiv2::XmpValue::xmpArrayType(entry.value().typeId());
272 size_t limit = ((xmpType == Exiv2::XmpValue::xaBag) || (xmpType == Exiv2::XmpValue::xaSeq)) ? entry.count() : 1;
273 for (size_t i = 0; i < limit; i++) {
274 auto value = QString::fromStdString(entry.toString(i));
275 if (!value.isEmpty()) {
276 result->add(type->second, value);
277 }
278 }
279 }
280 }
281}
282
283namespace {
284void add(ExtractionResult* result, const Exiv2::ExifData& data,
285 Property::Property prop, const Exiv2::ExifKey& key,
286 QMetaType::Type type)
287{
288 Exiv2::ExifData::const_iterator it = data.findKey(key);
289 if (it != data.end()) {
290 QVariant value = toVariant(it->value(), type);
291 if (!value.isNull()) {
292 result->add(prop, value);
293 }
294 }
295}
296
297double fetchGpsDouble(const Exiv2::ExifData& data, const Exiv2::ExifKey& key)
298{
299 Exiv2::ExifData::const_iterator it = data.findKey(key);
300 if (it != data.end() && it->count() == 3) {
301 double n = 0.0;
302 double d = 0.0;
303
304 n = (*it).toRational(0).first;
305 d = (*it).toRational(0).second;
306
307 if (d == 0.0) {
308 return std::numeric_limits<double>::quiet_NaN();
309 }
310
311 double deg = n / d;
312
313 n = (*it).toRational(1).first;
314 d = (*it).toRational(1).second;
315
316 if (d == 0.0) {
317 return deg;
318 }
319
320 double min = n / d;
321 if (min != -1.0) {
322 deg += min / 60.0;
323 }
324
325 n = (*it).toRational(2).first;
326 d = (*it).toRational(2).second;
327
328 if (d == 0.0) {
329 return deg;
330 }
331
332 double sec = n / d;
333 if (sec != -1.0) {
334 deg += sec / 3600.0;
335 }
336
337 return deg;
338 }
339
340 return std::numeric_limits<double>::quiet_NaN();
341}
342
343double fetchGpsAltitude(const Exiv2::ExifData& data)
344{
345 using namespace std::string_literals;
346
347 double alt = std::numeric_limits<double>::quiet_NaN();
348 Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"s));
349 if (it != data.end() && it->count() > 0 &&
350 (it->value().typeId() == Exiv2::unsignedRational || it->value().typeId() == Exiv2::signedRational)) {
351 auto ratio = it->value().toRational();
352 if (ratio.second == 0) {
353 return alt;
354 }
355 it = data.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"s));
356 if (it != data.end() && it->count() > 0 &&
357 (it->value().typeId() == Exiv2::unsignedByte || it->value().typeId() == Exiv2::signedByte)) {
358#if EXIV2_TEST_VERSION(0,28,0)
359 auto altRef = it->value().toInt64();
360#else
361 auto altRef = it->value().toLong();
362#endif
363 if (altRef) {
364 alt = -1.0 * ratio.first / ratio.second;
365 } else {
366 alt = 1.0 * ratio.first / ratio.second;
367 }
368 }
369 }
370 return alt;
371}
372QByteArray fetchByteArray(const Exiv2::ExifData& data, const Exiv2::ExifKey& key)
373{
374 Exiv2::ExifData::const_iterator it = data.findKey(key);
375 if (it != data.end() && it->count() > 0) {
376 std::string str = it->value().toString();
377 return QByteArray(str.c_str(), str.size());
378 }
379
380 return QByteArray();
381}
382} // <anonymous> namespace
383
384#include "moc_exiv2extractor.cpp"
The ExtractionResult class is where all the data extracted by the indexer is saved.
QString inputUrl() const
The input url which the plugins will use to locate the file.
virtual void addType(Type::Type type)=0
This function is called by the plugins.
virtual void add(Property::Property property, const QVariant &value)=0
This function is called by the plugins when they wish to add a key value pair which should be indexed...
Flags inputFlags() const
The flags which the extraction plugin should considering following when extracting metadata from the ...
The ExtractorPlugin is the base class for all file metadata extractors.
static QDateTime dateTimeFromString(const QString &dateString)
Tries to extract a valid date time from the string provided.
Type type(const QSqlDatabase &db)
char * toString(const EngineQuery &query)
KIOCORE_EXPORT void add(const QString &fileClass, const QString &directory)
Property
The Property enum contains all files property types that KFileMetaData manipulates.
Definition properties.h:23
char * data()
bool isEmpty() const const
qsizetype length() const const
bool isValid() const const
void setTimeZone(const QTimeZone &toZone)
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QByteArray toUtf8() const const
QTimeZone fromSecondsAheadOfUtc(int offset)
bool isNull() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:53 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.