23#include "moc_qgsexiftools.cpp"
25using namespace Qt::StringLiterals;
28#include <exiv2/exiv2.hpp>
32#include <QRegularExpression>
37double readRational(
const Exiv2::Value &value,
long n = 0 )
39 const Exiv2::Rational rational = value.toRational( n );
40 const auto numerator = rational.first;
41 const auto denominator = rational.second;
43 if ( value.typeId() == Exiv2::unsignedRational )
45 res =
static_cast< double >(
static_cast<uint32_t
>( numerator ) ) /
static_cast<uint32_t
>( denominator );
49 res =
static_cast< double >( numerator ) / denominator;
54double readCoordinate(
const Exiv2::Value &value )
58 for (
int i = 0; i < 3; i++ )
60 res += readRational( value, i ) / div;
66QVariant decodeXmpData(
const QString &key, Exiv2::XmpData::const_iterator &it )
69 if ( key ==
"Xmp.xmp.MetadataDate"_L1 ||
70 key ==
"Xmp.xmp.CreateDate"_L1 ||
71 key ==
"Xmp.xmp.ModifyDate"_L1 )
73 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), Qt::ISODate ) );
77 switch ( it->typeId() )
79 case Exiv2::asciiString:
82 case Exiv2::directory:
84 val = QString::fromStdString( it->toString() );
87 case Exiv2::unsignedLong:
88 case Exiv2::signedLong:
89 case Exiv2::unsignedLongLong:
90 case Exiv2::signedLongLong:
91#if EXIV2_TEST_VERSION (0, 28, 0)
92 val = QVariant::fromValue( it->toUint32() );
94 val = QVariant::fromValue( it->toLong() );
98 case Exiv2::tiffDouble:
99 case Exiv2::tiffFloat:
100 val = QVariant::fromValue( it->toFloat() );
103 case Exiv2::unsignedShort:
104 case Exiv2::signedShort:
105 case Exiv2::unsignedByte:
106 case Exiv2::signedByte:
108 case Exiv2::tiffIfd8:
109#if EXIV2_TEST_VERSION (0, 28, 0)
110 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
112 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
118 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
119 val = QVariant::fromValue( QDate::fromString( u
"%1-%2-%3"_s.arg( date.year )
120 .arg( QString::number( date.month ).rightJustified( 2,
'0' ) )
121 .arg( QString::number( date.day ).rightJustified( 2,
'0' ) ),
"yyyy-MM-dd"_L1 ) );
127 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
128 val = QVariant::fromValue( QTime::fromString( u
"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2,
'0' ) )
129 .arg( QString::number( time.minute ).rightJustified( 2,
'0' ) )
130 .arg( QString::number( time.second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 ) );
134 case Exiv2::unsignedRational:
135 case Exiv2::signedRational:
137 if ( it->count() == 1 )
139 val = QVariant::fromValue( readRational( it->value() ) );
143 val = QString::fromStdString( it->toString() );
148 case Exiv2::undefined:
153 case Exiv2::invalidTypeId:
154 case Exiv2::lastTypeId:
155 val = QString::fromStdString( it->toString() );
163QVariant decodeExifData(
const QString &key, Exiv2::ExifData::const_iterator &it )
167 if ( key ==
"Exif.GPSInfo.GPSLatitude"_L1 ||
168 key ==
"Exif.GPSInfo.GPSLongitude"_L1 ||
169 key ==
"Exif.GPSInfo.GPSDestLatitude"_L1 ||
170 key ==
"Exif.GPSInfo.GPSDestLongitude"_L1 )
172 val = readCoordinate( it->value() );
174 else if ( key ==
"Exif.GPSInfo.GPSTimeStamp"_L1 )
176 const QStringList parts = QString::fromStdString( it->toString() ).split( QRegularExpression( u
"\\s+"_s ) );
177 if ( parts.size() == 3 )
179 const int hour = std::max( 0, std::min( 23,
static_cast< int >( readRational( it->value(), 0 ) ) ) );
180 const int minute = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 1 ) ) ) );
181 const int second = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 2 ) ) ) );
183 val = QVariant::fromValue( QTime::fromString( u
"%1:%2:%3"_s
184 .arg( QString::number( hour ).rightJustified( 2,
'0' ) )
185 .arg( QString::number( minute ).rightJustified( 2,
'0' ) )
186 .arg( QString::number( second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 ) );
189 else if ( key ==
"Exif.GPSInfo.GPSDateStamp"_L1 )
191 val = QVariant::fromValue( QDate::fromString( QString::fromStdString( it->toString() ),
"yyyy:MM:dd"_L1 ) );
193 else if ( key ==
"Exif.Image.DateTime"_L1 ||
194 key ==
"Exif.Image.DateTime"_L1 ||
195 key ==
"Exif.Photo.DateTimeDigitized"_L1 ||
196 key ==
"Exif.Photo.DateTimeOriginal"_L1 )
198 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ),
"yyyy:MM:dd hh:mm:ss"_L1 ) );
202 switch ( it->typeId() )
204 case Exiv2::asciiString:
207 case Exiv2::directory:
209 val = QString::fromStdString( it->toString() );
212 case Exiv2::unsignedLong:
213 case Exiv2::signedLong:
214 case Exiv2::unsignedLongLong:
215 case Exiv2::signedLongLong:
216#if EXIV2_TEST_VERSION (0, 28, 0)
217 val = QVariant::fromValue( it->toUint32() );
219 val = QVariant::fromValue( it->toLong() );
223 case Exiv2::tiffDouble:
224 case Exiv2::tiffFloat:
225 val = QVariant::fromValue( it->toFloat() );
228 case Exiv2::unsignedShort:
229 case Exiv2::signedShort:
230 case Exiv2::unsignedByte:
231 case Exiv2::signedByte:
233 case Exiv2::tiffIfd8:
234#if EXIV2_TEST_VERSION (0, 28, 0)
235 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
237 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
243 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
244 val = QVariant::fromValue( QDate::fromString( u
"%1-%2-%3"_s.arg( date.year )
245 .arg( QString::number( date.month ).rightJustified( 2,
'0' ) )
246 .arg( QString::number( date.day ).rightJustified( 2,
'0' ) ),
"yyyy-MM-dd"_L1 ) );
252 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
253 val = QVariant::fromValue( QTime::fromString( u
"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2,
'0' ) )
254 .arg( QString::number( time.minute ).rightJustified( 2,
'0' ) )
255 .arg( QString::number( time.second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 ) );
259 case Exiv2::unsignedRational:
260 case Exiv2::signedRational:
262 if ( it->count() == 1 )
264 val = QVariant::fromValue( readRational( it->value() ) );
268 val = QString::fromStdString( it->toString() );
273 case Exiv2::undefined:
278 case Exiv2::invalidTypeId:
279 case Exiv2::lastTypeId:
280 val = QString::fromStdString( it->toString() );
287QString doubleToExifCoordinateString(
const double val )
289 const double d = std::abs( val );
290 const int degrees =
static_cast< int >( std::floor( d ) );
291 const double m = 60 * ( d - degrees );
292 const int minutes =
static_cast< int >( std::floor( m ) );
293 const double s = 60 * ( m - minutes );
294 const int seconds =
static_cast< int >( std::floor( s * 1000 ) );
295 return u
"%1/1 %2/1 %3/1000"_s.arg( degrees ).arg( minutes ).arg( seconds );
302 if ( !QFileInfo::exists( imagePath ) )
307 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
308 if ( !image || key.isEmpty() )
311 image->readMetadata();
313 if ( key.startsWith(
"Xmp."_L1 ) )
315 Exiv2::XmpData &xmpData = image->xmpData();
316 if ( xmpData.empty() )
320 Exiv2::XmpData::const_iterator i = xmpData.findKey( Exiv2::XmpKey( key.toUtf8().constData() ) );
321 return i != xmpData.end() ? decodeXmpData( key, i ) : QVariant();
325 Exiv2::ExifData &exifData = image->exifData();
326 if ( exifData.empty() )
330 Exiv2::ExifData::const_iterator i = exifData.findKey( Exiv2::ExifKey( key.toUtf8().constData() ) );
331 return i != exifData.end() ? decodeExifData( key, i ) : QVariant();
339 Q_UNUSED( imagePath )
349 if ( !QFileInfo::exists( imagePath ) )
350 return QVariantMap();
355 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
357 return QVariantMap();
358 image->readMetadata();
360 Exiv2::ExifData &exifData = image->exifData();
361 if ( !exifData.empty() )
363 const Exiv2::ExifData::const_iterator end = exifData.end();
364 for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i )
366 const QString key = QString::fromStdString( i->key() );
367 res.insert( key, decodeExifData( key, i ) );
371 Exiv2::XmpData &xmpData = image->xmpData();
372 if ( !xmpData.empty() )
374 const Exiv2::XmpData::const_iterator end = xmpData.end();
375 for ( Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i )
377 const QString key = QString::fromStdString( i->key() );
378 res.insert( key, decodeXmpData( key, i ) );
386 return QVariantMap();
389 Q_UNUSED( imagePath )
391 return QVariantMap();
402 Q_UNUSED( imagePath )
412 if ( !QFileInfo::exists( imagePath ) )
416 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
420 image->readMetadata();
421 Exiv2::ExifData &exifData = image->exifData();
423 if ( exifData.empty() )
426 const Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitudeRef" ) );
427 const Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitude" ) );
428 const Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitudeRef" ) );
429 const Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitude" ) );
431 if ( itLatRef == exifData.end() || itLatVal == exifData.end() ||
432 itLonRef == exifData.end() || itLonVal == exifData.end() )
435 double lat = readCoordinate( itLatVal->value() );
436 double lon = readCoordinate( itLonVal->value() );
438 const QString latRef = QString::fromStdString( itLatRef->value().toString() );
439 const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
440 if ( latRef.compare(
'S'_L1, Qt::CaseInsensitive ) == 0 )
444 if ( lonRef.compare(
'W'_L1, Qt::CaseInsensitive ) == 0 )
451 const Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitude" ) );
452 const Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitudeRef" ) );
453 if ( itElevVal != exifData.end() )
455 double elev = readRational( itElevVal->value() );
456 if ( itElevRefVal != exifData.end() )
458 const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
459 if ( elevRef.compare(
'1'_L1, Qt::CaseInsensitive ) == 0 )
476 Q_UNUSED( imagePath )
488 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
492 image->readMetadata();
493 Exiv2::ExifData &exifData = image->exifData();
495 exifData[
"Exif.GPSInfo.GPSVersionID"] =
"2 0 0 0";
496 exifData[
"Exif.GPSInfo.GPSMapDatum"] =
"WGS-84";
497 exifData[
"Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinateString( location.
y() ).toStdString();
498 exifData[
"Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinateString( location.
x() ).toStdString();
501 const QString elevationString = u
"%1/1000"_s.arg(
static_cast< int>( std::floor( std::abs( details.
elevation ) * 1000 ) ) );
502 exifData[
"Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
503 exifData[
"Exif.GPSInfo.GPSAltitudeRef"] = details.
elevation < 0.0 ?
"1" :
"0";
505 exifData[
"Exif.GPSInfo.GPSLatitudeRef"] = location.
y() > 0 ?
"N" :
"S";
506 exifData[
"Exif.GPSInfo.GPSLongitudeRef"] = location.
x() > 0 ?
"E" :
"W";
507 exifData[
"Exif.Image.GPSTag"] = 4908;
508 image->writeMetadata();
516 Q_UNUSED( imagePath )
529 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
533 QVariant actualValue;
534 bool actualValueIsUShort =
false;
535 if ( tag ==
"Exif.GPSInfo.GPSLatitude"_L1 ||
536 tag ==
"Exif.GPSInfo.GPSLongitude"_L1 ||
537 tag ==
"Exif.GPSInfo.GPSDestLatitude"_L1 ||
538 tag ==
"Exif.GPSInfo.GPSDestLongitude"_L1 )
540 actualValue = doubleToExifCoordinateString( value.toDouble() );
542 else if ( tag ==
"Exif.GPSInfo.GPSAltitude"_L1 )
544 actualValue = u
"%1/1000"_s.arg(
static_cast< int>( std::floor( std::abs( value.toDouble() ) * 1000 ) ) );
546 else if ( tag ==
"Exif.Image.Orientation"_L1 )
548 actualValueIsUShort =
true;
551 else if ( value.userType() == QMetaType::Type::QDateTime )
553 const QDateTime dateTime = value.toDateTime();
554 if ( tag ==
"Exif.Image.DateTime"_L1 ||
555 tag ==
"Exif.Image.DateTime"_L1 ||
556 tag ==
"Exif.Photo.DateTimeDigitized"_L1 ||
557 tag ==
"Exif.Photo.DateTimeOriginal"_L1 )
559 actualValue = dateTime.toString( u
"yyyy:MM:dd hh:mm:ss"_s );
563 actualValue = dateTime.toString( Qt::ISODate );
566 else if ( value.userType() == QMetaType::Type::QDate )
568 const QDate date = value.toDate();
569 if ( tag ==
"Exif.GPSInfo.GPSDateStamp"_L1 )
571 actualValue = date.toString( u
"yyyy:MM:dd"_s );
575 actualValue = date.toString( u
"yyyy-MM-dd"_s );
578 else if ( value.userType() == QMetaType::Type::QTime )
580 const QTime time = value.toTime();
581 if ( tag ==
"Exif.GPSInfo.GPSTimeStamp"_L1 )
583 actualValue = u
"%1/1 %2/1 %3/1"_s.arg( time.hour() ).arg( time.minute() ).arg( time.second() );
587 actualValue = time.toString( u
"HH:mm:ss"_s );
595 const bool isXmp = tag.startsWith(
"Xmp."_L1 );
596 image->readMetadata();
597 if ( actualValueIsUShort )
601 Exiv2::XmpData &xmpData = image->xmpData();
602 xmpData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
606 Exiv2::ExifData &exifData = image->exifData();
607 exifData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
610 else if ( actualValue.userType() == QMetaType::Type::Int ||
611 actualValue.userType() == QMetaType::Type::LongLong )
615 Exiv2::XmpData &xmpData = image->xmpData();
616 xmpData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
620 Exiv2::ExifData &exifData = image->exifData();
621 exifData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
624 else if ( actualValue.userType() == QMetaType::Type::UInt ||
625 actualValue.userType() == QMetaType::Type::ULongLong )
629 Exiv2::XmpData &xmpData = image->xmpData();
630 xmpData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
634 Exiv2::ExifData &exifData = image->exifData();
635 exifData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
638 else if ( actualValue.userType() == QMetaType::Type::Double )
642 Exiv2::XmpData &xmpData = image->xmpData();
643 xmpData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
647 Exiv2::ExifData &exifData = image->exifData();
648 exifData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
655 Exiv2::XmpData &xmpData = image->xmpData();
656 xmpData[tag.toStdString()] = actualValue.toString().toStdString();
660 Exiv2::ExifData &exifData = image->exifData();
661 exifData[tag.toStdString()] = actualValue.toString().toStdString();
664 image->writeMetadata();
672 Q_UNUSED( imagePath )
Point geometry type, with support for z-dimension and m-values.
#define QgsDebugError(str)