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 || key ==
"Xmp.xmp.CreateDate"_L1 || key ==
"Xmp.xmp.ModifyDate"_L1 )
71 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), Qt::ISODate ) );
75 switch ( it->typeId() )
77 case Exiv2::asciiString:
80 case Exiv2::directory:
82 val = QString::fromStdString( it->toString() );
85 case Exiv2::unsignedLong:
86 case Exiv2::signedLong:
87 case Exiv2::unsignedLongLong:
88 case Exiv2::signedLongLong:
89#if EXIV2_TEST_VERSION( 0, 28, 0 )
90 val = QVariant::fromValue( it->toUint32() );
92 val = QVariant::fromValue( it->toLong() );
96 case Exiv2::tiffDouble:
97 case Exiv2::tiffFloat:
98 val = QVariant::fromValue( it->toFloat() );
101 case Exiv2::unsignedShort:
102 case Exiv2::signedShort:
103 case Exiv2::unsignedByte:
104 case Exiv2::signedByte:
106 case Exiv2::tiffIfd8:
107#if EXIV2_TEST_VERSION( 0, 28, 0 )
108 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
110 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
116 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
117 val = QVariant::fromValue(
118 QDate::fromString( u
"%1-%2-%3"_s.arg( date.year ).arg( QString::number( date.month ).rightJustified( 2,
'0' ) ).arg( QString::number( date.day ).rightJustified( 2,
'0' ) ),
"yyyy-MM-dd"_L1 )
125 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
126 val = QVariant::fromValue(
128 fromString( u
"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2,
'0' ) ).arg( QString::number( time.minute ).rightJustified( 2,
'0' ) ).arg( QString::number( time.second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 )
133 case Exiv2::unsignedRational:
134 case Exiv2::signedRational:
136 if ( it->count() == 1 )
138 val = QVariant::fromValue( readRational( it->value() ) );
142 val = QString::fromStdString( it->toString() );
147 case Exiv2::undefined:
152 case Exiv2::invalidTypeId:
153 case Exiv2::lastTypeId:
154 val = QString::fromStdString( it->toString() );
161QVariant decodeExifData(
const QString &key, Exiv2::ExifData::const_iterator &it )
165 if ( key ==
"Exif.GPSInfo.GPSLatitude"_L1 || key ==
"Exif.GPSInfo.GPSLongitude"_L1 || key ==
"Exif.GPSInfo.GPSDestLatitude"_L1 || key ==
"Exif.GPSInfo.GPSDestLongitude"_L1 )
167 val = readCoordinate( it->value() );
169 else if ( key ==
"Exif.GPSInfo.GPSTimeStamp"_L1 )
171 const QStringList parts = QString::fromStdString( it->toString() ).split( QRegularExpression( u
"\\s+"_s ) );
172 if ( parts.size() == 3 )
174 const int hour = std::max( 0, std::min( 23,
static_cast< int >( readRational( it->value(), 0 ) ) ) );
175 const int minute = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 1 ) ) ) );
176 const int second = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 2 ) ) ) );
178 val = QVariant::fromValue(
179 QTime::fromString( u
"%1:%2:%3"_s.arg( QString::number( hour ).rightJustified( 2,
'0' ) ).arg( QString::number( minute ).rightJustified( 2,
'0' ) ).arg( QString::number( second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 )
183 else if ( key ==
"Exif.GPSInfo.GPSDateStamp"_L1 )
185 val = QVariant::fromValue( QDate::fromString( QString::fromStdString( it->toString() ),
"yyyy:MM:dd"_L1 ) );
187 else if ( key ==
"Exif.Image.DateTime"_L1 || key ==
"Exif.Image.DateTime"_L1 || key ==
"Exif.Photo.DateTimeDigitized"_L1 || key ==
"Exif.Photo.DateTimeOriginal"_L1 )
189 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ),
"yyyy:MM:dd hh:mm:ss"_L1 ) );
193 switch ( it->typeId() )
195 case Exiv2::asciiString:
198 case Exiv2::directory:
200 val = QString::fromStdString( it->toString() );
203 case Exiv2::unsignedLong:
204 case Exiv2::signedLong:
205 case Exiv2::unsignedLongLong:
206 case Exiv2::signedLongLong:
207#if EXIV2_TEST_VERSION( 0, 28, 0 )
208 val = QVariant::fromValue( it->toUint32() );
210 val = QVariant::fromValue( it->toLong() );
214 case Exiv2::tiffDouble:
215 case Exiv2::tiffFloat:
216 val = QVariant::fromValue( it->toFloat() );
219 case Exiv2::unsignedShort:
220 case Exiv2::signedShort:
221 case Exiv2::unsignedByte:
222 case Exiv2::signedByte:
224 case Exiv2::tiffIfd8:
225#if EXIV2_TEST_VERSION( 0, 28, 0 )
226 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
228 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
234 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
235 val = QVariant::fromValue(
236 QDate::fromString( u
"%1-%2-%3"_s.arg( date.year ).arg( QString::number( date.month ).rightJustified( 2,
'0' ) ).arg( QString::number( date.day ).rightJustified( 2,
'0' ) ),
"yyyy-MM-dd"_L1 )
243 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
244 val = QVariant::fromValue(
246 fromString( u
"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2,
'0' ) ).arg( QString::number( time.minute ).rightJustified( 2,
'0' ) ).arg( QString::number( time.second ).rightJustified( 2,
'0' ) ),
"hh:mm:ss"_L1 )
251 case Exiv2::unsignedRational:
252 case Exiv2::signedRational:
254 if ( it->count() == 1 )
256 val = QVariant::fromValue( readRational( it->value() ) );
260 val = QString::fromStdString( it->toString() );
265 case Exiv2::undefined:
270 case Exiv2::invalidTypeId:
271 case Exiv2::lastTypeId:
272 val = QString::fromStdString( it->toString() );
279QString doubleToExifCoordinateString(
const double val )
281 const double d = std::abs( val );
282 const int degrees =
static_cast< int >( std::floor( d ) );
283 const double m = 60 * ( d - degrees );
284 const int minutes =
static_cast< int >( std::floor( m ) );
285 const double s = 60 * ( m - minutes );
286 const int seconds =
static_cast< int >( std::floor( s * 1000 ) );
287 return u
"%1/1 %2/1 %3/1000"_s.arg( degrees ).arg( minutes ).arg( seconds );
294 if ( !QFileInfo::exists( imagePath ) )
299 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
300 if ( !image || key.isEmpty() )
303 image->readMetadata();
305 if ( key.startsWith(
"Xmp."_L1 ) )
307 Exiv2::XmpData &xmpData = image->xmpData();
308 if ( xmpData.empty() )
312 Exiv2::XmpData::const_iterator i = xmpData.findKey( Exiv2::XmpKey( key.toUtf8().constData() ) );
313 return i != xmpData.end() ? decodeXmpData( key, i ) : QVariant();
317 Exiv2::ExifData &exifData = image->exifData();
318 if ( exifData.empty() )
322 Exiv2::ExifData::const_iterator i = exifData.findKey( Exiv2::ExifKey( key.toUtf8().constData() ) );
323 return i != exifData.end() ? decodeExifData( key, i ) : QVariant();
331 Q_UNUSED( imagePath )
341 if ( !QFileInfo::exists( imagePath ) )
342 return QVariantMap();
347 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
349 return QVariantMap();
350 image->readMetadata();
352 Exiv2::ExifData &exifData = image->exifData();
353 if ( !exifData.empty() )
355 const Exiv2::ExifData::const_iterator end = exifData.end();
356 for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i )
358 const QString key = QString::fromStdString( i->key() );
359 res.insert( key, decodeExifData( key, i ) );
363 Exiv2::XmpData &xmpData = image->xmpData();
364 if ( !xmpData.empty() )
366 const Exiv2::XmpData::const_iterator end = xmpData.end();
367 for ( Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i )
369 const QString key = QString::fromStdString( i->key() );
370 res.insert( key, decodeXmpData( key, i ) );
378 return QVariantMap();
381 Q_UNUSED( imagePath )
383 return QVariantMap();
394 Q_UNUSED( imagePath )
404 if ( !QFileInfo::exists( imagePath ) )
408 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
412 image->readMetadata();
413 Exiv2::ExifData &exifData = image->exifData();
415 if ( exifData.empty() )
418 const Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitudeRef" ) );
419 const Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitude" ) );
420 const Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitudeRef" ) );
421 const Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitude" ) );
423 if ( itLatRef == exifData.end() || itLatVal == exifData.end() || itLonRef == exifData.end() || itLonVal == exifData.end() )
426 double lat = readCoordinate( itLatVal->value() );
427 double lon = readCoordinate( itLonVal->value() );
429 const QString latRef = QString::fromStdString( itLatRef->value().toString() );
430 const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
431 if ( latRef.compare(
'S'_L1, Qt::CaseInsensitive ) == 0 )
435 if ( lonRef.compare(
'W'_L1, Qt::CaseInsensitive ) == 0 )
442 const Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitude" ) );
443 const Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitudeRef" ) );
444 if ( itElevVal != exifData.end() )
446 double elev = readRational( itElevVal->value() );
447 if ( itElevRefVal != exifData.end() )
449 const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
450 if ( elevRef.compare(
'1'_L1, Qt::CaseInsensitive ) == 0 )
467 Q_UNUSED( imagePath )
479 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
483 image->readMetadata();
484 Exiv2::ExifData &exifData = image->exifData();
486 exifData[
"Exif.GPSInfo.GPSVersionID"] =
"2 0 0 0";
487 exifData[
"Exif.GPSInfo.GPSMapDatum"] =
"WGS-84";
488 exifData[
"Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinateString( location.
y() ).toStdString();
489 exifData[
"Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinateString( location.
x() ).toStdString();
492 const QString elevationString = u
"%1/1000"_s.arg(
static_cast< int>( std::floor( std::abs( details.
elevation ) * 1000 ) ) );
493 exifData[
"Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
494 exifData[
"Exif.GPSInfo.GPSAltitudeRef"] = details.
elevation < 0.0 ?
"1" :
"0";
496 exifData[
"Exif.GPSInfo.GPSLatitudeRef"] = location.
y() > 0 ?
"N" :
"S";
497 exifData[
"Exif.GPSInfo.GPSLongitudeRef"] = location.
x() > 0 ?
"E" :
"W";
498 exifData[
"Exif.Image.GPSTag"] = 4908;
499 image->writeMetadata();
507 Q_UNUSED( imagePath )
520 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
524 QVariant actualValue;
525 bool actualValueIsUShort =
false;
526 if ( tag ==
"Exif.GPSInfo.GPSLatitude"_L1 || tag ==
"Exif.GPSInfo.GPSLongitude"_L1 || tag ==
"Exif.GPSInfo.GPSDestLatitude"_L1 || tag ==
"Exif.GPSInfo.GPSDestLongitude"_L1 )
528 actualValue = doubleToExifCoordinateString( value.toDouble() );
530 else if ( tag ==
"Exif.GPSInfo.GPSAltitude"_L1 )
532 actualValue = u
"%1/1000"_s.arg(
static_cast< int>( std::floor( std::abs( value.toDouble() ) * 1000 ) ) );
534 else if ( tag ==
"Exif.Image.Orientation"_L1 )
536 actualValueIsUShort =
true;
539 else if ( value.userType() == QMetaType::Type::QDateTime )
541 const QDateTime dateTime = value.toDateTime();
542 if ( tag ==
"Exif.Image.DateTime"_L1 || tag ==
"Exif.Image.DateTime"_L1 || tag ==
"Exif.Photo.DateTimeDigitized"_L1 || tag ==
"Exif.Photo.DateTimeOriginal"_L1 )
544 actualValue = dateTime.toString( u
"yyyy:MM:dd hh:mm:ss"_s );
548 actualValue = dateTime.toString( Qt::ISODate );
551 else if ( value.userType() == QMetaType::Type::QDate )
553 const QDate date = value.toDate();
554 if ( tag ==
"Exif.GPSInfo.GPSDateStamp"_L1 )
556 actualValue = date.toString( u
"yyyy:MM:dd"_s );
560 actualValue = date.toString( u
"yyyy-MM-dd"_s );
563 else if ( value.userType() == QMetaType::Type::QTime )
565 const QTime time = value.toTime();
566 if ( tag ==
"Exif.GPSInfo.GPSTimeStamp"_L1 )
568 actualValue = u
"%1/1 %2/1 %3/1"_s.arg( time.hour() ).arg( time.minute() ).arg( time.second() );
572 actualValue = time.toString( u
"HH:mm:ss"_s );
580 const bool isXmp = tag.startsWith(
"Xmp."_L1 );
581 image->readMetadata();
582 if ( actualValueIsUShort )
586 Exiv2::XmpData &xmpData = image->xmpData();
587 xmpData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
591 Exiv2::ExifData &exifData = image->exifData();
592 exifData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
595 else if ( actualValue.userType() == QMetaType::Type::Int || actualValue.userType() == QMetaType::Type::LongLong )
599 Exiv2::XmpData &xmpData = image->xmpData();
600 xmpData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
604 Exiv2::ExifData &exifData = image->exifData();
605 exifData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
608 else if ( actualValue.userType() == QMetaType::Type::UInt || actualValue.userType() == QMetaType::Type::ULongLong )
612 Exiv2::XmpData &xmpData = image->xmpData();
613 xmpData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
617 Exiv2::ExifData &exifData = image->exifData();
618 exifData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
621 else if ( actualValue.userType() == QMetaType::Type::Double )
625 Exiv2::XmpData &xmpData = image->xmpData();
626 xmpData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
630 Exiv2::ExifData &exifData = image->exifData();
631 exifData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
638 Exiv2::XmpData &xmpData = image->xmpData();
639 xmpData[tag.toStdString()] = actualValue.toString().toStdString();
643 Exiv2::ExifData &exifData = image->exifData();
644 exifData[tag.toStdString()] = actualValue.toString().toStdString();
647 image->writeMetadata();
655 Q_UNUSED( imagePath )
Point geometry type, with support for z-dimension and m-values.
#define QgsDebugError(str)