KFileMetaData

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

KDE's Doxygen guidelines are available online.