21#include "moc_qgsexiftools.cpp"
24#include <exiv2/exiv2.hpp>
28#include <QRegularExpression>
33double readRational(
const Exiv2::Value &value,
long n = 0 )
35 const Exiv2::Rational rational = value.toRational( n );
36 const auto numerator = rational.first;
37 const auto denominator = rational.second;
39 if ( value.typeId() == Exiv2::unsignedRational )
41 res =
static_cast< double >(
static_cast<uint32_t
>( numerator ) ) /
static_cast<uint32_t
>( denominator );
45 res =
static_cast< double >( numerator ) / denominator;
50double readCoordinate(
const Exiv2::Value &value )
54 for (
int i = 0; i < 3; i++ )
56 res += readRational( value, i ) / div;
62QVariant decodeXmpData(
const QString &key, Exiv2::XmpData::const_iterator &it )
65 if ( key == QLatin1String(
"Xmp.xmp.MetadataDate" ) ||
66 key == QLatin1String(
"Xmp.xmp.CreateDate" ) ||
67 key == QLatin1String(
"Xmp.xmp.ModifyDate" ) )
69 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), Qt::ISODate ) );
73 switch ( it->typeId() )
75 case Exiv2::asciiString:
78 case Exiv2::directory:
80 val = QString::fromStdString( it->toString() );
83 case Exiv2::unsignedLong:
84 case Exiv2::signedLong:
85 case Exiv2::unsignedLongLong:
86 case Exiv2::signedLongLong:
87#if EXIV2_TEST_VERSION (0, 28, 0)
88 val = QVariant::fromValue( it->toUint32() );
90 val = QVariant::fromValue( it->toLong() );
94 case Exiv2::tiffDouble:
95 case Exiv2::tiffFloat:
96 val = QVariant::fromValue( it->toFloat() );
99 case Exiv2::unsignedShort:
100 case Exiv2::signedShort:
101 case Exiv2::unsignedByte:
102 case Exiv2::signedByte:
104 case Exiv2::tiffIfd8:
105#if EXIV2_TEST_VERSION (0, 28, 0)
106 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
108 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
114 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
115 val = QVariant::fromValue( QDate::fromString( QStringLiteral(
"%1-%2-%3" ).arg( date.year )
116 .arg( QString::number( date.month ).rightJustified( 2,
'0' ) )
117 .arg( QString::number( date.day ).rightJustified( 2,
'0' ) ), QLatin1String(
"yyyy-MM-dd" ) ) );
123 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
124 val = QVariant::fromValue( QTime::fromString( QStringLiteral(
"%1:%2:%3" ).arg( QString::number( time.hour ).rightJustified( 2,
'0' ) )
125 .arg( QString::number( time.minute ).rightJustified( 2,
'0' ) )
126 .arg( QString::number( time.second ).rightJustified( 2,
'0' ) ), QLatin1String(
"hh:mm:ss" ) ) );
130 case Exiv2::unsignedRational:
131 case Exiv2::signedRational:
133 if ( it->count() == 1 )
135 val = QVariant::fromValue( readRational( it->value() ) );
139 val = QString::fromStdString( it->toString() );
144 case Exiv2::undefined:
149 case Exiv2::invalidTypeId:
150 case Exiv2::lastTypeId:
151 val = QString::fromStdString( it->toString() );
159QVariant decodeExifData(
const QString &key, Exiv2::ExifData::const_iterator &it )
163 if ( key == QLatin1String(
"Exif.GPSInfo.GPSLatitude" ) ||
164 key == QLatin1String(
"Exif.GPSInfo.GPSLongitude" ) ||
165 key == QLatin1String(
"Exif.GPSInfo.GPSDestLatitude" ) ||
166 key == QLatin1String(
"Exif.GPSInfo.GPSDestLongitude" ) )
168 val = readCoordinate( it->value() );
170 else if ( key == QLatin1String(
"Exif.GPSInfo.GPSTimeStamp" ) )
172 const QStringList parts = QString::fromStdString( it->toString() ).split( QRegularExpression( QStringLiteral(
"\\s+" ) ) );
173 if ( parts.size() == 3 )
175 const int hour = std::max( 0, std::min( 23,
static_cast< int >( readRational( it->value(), 0 ) ) ) );
176 const int minute = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 1 ) ) ) );
177 const int second = std::max( 0, std::min( 59,
static_cast< int >( readRational( it->value(), 2 ) ) ) );
179 val = QVariant::fromValue( QTime::fromString( QStringLiteral(
"%1:%2:%3" )
180 .arg( QString::number( hour ).rightJustified( 2,
'0' ) )
181 .arg( QString::number( minute ).rightJustified( 2,
'0' ) )
182 .arg( QString::number( second ).rightJustified( 2,
'0' ) ), QLatin1String(
"hh:mm:ss" ) ) );
185 else if ( key == QLatin1String(
"Exif.GPSInfo.GPSDateStamp" ) )
187 val = QVariant::fromValue( QDate::fromString( QString::fromStdString( it->toString() ), QLatin1String(
"yyyy:MM:dd" ) ) );
189 else if ( key == QLatin1String(
"Exif.Image.DateTime" ) ||
190 key == QLatin1String(
"Exif.Image.DateTime" ) ||
191 key == QLatin1String(
"Exif.Photo.DateTimeDigitized" ) ||
192 key == QLatin1String(
"Exif.Photo.DateTimeOriginal" ) )
194 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), QLatin1String(
"yyyy:MM:dd hh:mm:ss" ) ) );
198 switch ( it->typeId() )
200 case Exiv2::asciiString:
203 case Exiv2::directory:
205 val = QString::fromStdString( it->toString() );
208 case Exiv2::unsignedLong:
209 case Exiv2::signedLong:
210 case Exiv2::unsignedLongLong:
211 case Exiv2::signedLongLong:
212#if EXIV2_TEST_VERSION (0, 28, 0)
213 val = QVariant::fromValue( it->toUint32() );
215 val = QVariant::fromValue( it->toLong() );
219 case Exiv2::tiffDouble:
220 case Exiv2::tiffFloat:
221 val = QVariant::fromValue( it->toFloat() );
224 case Exiv2::unsignedShort:
225 case Exiv2::signedShort:
226 case Exiv2::unsignedByte:
227 case Exiv2::signedByte:
229 case Exiv2::tiffIfd8:
230#if EXIV2_TEST_VERSION (0, 28, 0)
231 val = QVariant::fromValue(
static_cast< int >( it->toUint32() ) );
233 val = QVariant::fromValue(
static_cast< int >( it->toLong() ) );
239 const Exiv2::DateValue::Date date =
static_cast< const Exiv2::DateValue *
>( &it->value() )->getDate();
240 val = QVariant::fromValue( QDate::fromString( QStringLiteral(
"%1-%2-%3" ).arg( date.year )
241 .arg( QString::number( date.month ).rightJustified( 2,
'0' ) )
242 .arg( QString::number( date.day ).rightJustified( 2,
'0' ) ), QLatin1String(
"yyyy-MM-dd" ) ) );
248 const Exiv2::TimeValue::Time time =
static_cast< const Exiv2::TimeValue *
>( &it->value() )->getTime();
249 val = QVariant::fromValue( QTime::fromString( QStringLiteral(
"%1:%2:%3" ).arg( QString::number( time.hour ).rightJustified( 2,
'0' ) )
250 .arg( QString::number( time.minute ).rightJustified( 2,
'0' ) )
251 .arg( QString::number( time.second ).rightJustified( 2,
'0' ) ), QLatin1String(
"hh:mm:ss" ) ) );
255 case Exiv2::unsignedRational:
256 case Exiv2::signedRational:
258 if ( it->count() == 1 )
260 val = QVariant::fromValue( readRational( it->value() ) );
264 val = QString::fromStdString( it->toString() );
269 case Exiv2::undefined:
274 case Exiv2::invalidTypeId:
275 case Exiv2::lastTypeId:
276 val = QString::fromStdString( it->toString() );
283QString doubleToExifCoordinateString(
const double val )
285 const double d = std::abs( val );
286 const int degrees =
static_cast< int >( std::floor( d ) );
287 const double m = 60 * ( d - degrees );
288 const int minutes =
static_cast< int >( std::floor( m ) );
289 const double s = 60 * ( m - minutes );
290 const int seconds =
static_cast< int >( std::floor( s * 1000 ) );
291 return QStringLiteral(
"%1/1 %2/1 %3/1000" ).arg( degrees ).arg( minutes ).arg( seconds );
298 if ( !QFileInfo::exists( imagePath ) )
303 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
304 if ( !image || key.isEmpty() )
307 image->readMetadata();
309 if ( key.startsWith( QLatin1String(
"Xmp." ) ) )
311 Exiv2::XmpData &xmpData = image->xmpData();
312 if ( xmpData.empty() )
316 Exiv2::XmpData::const_iterator i = xmpData.findKey( Exiv2::XmpKey( key.toUtf8().constData() ) );
317 return i != xmpData.end() ? decodeXmpData( key, i ) : QVariant();
321 Exiv2::ExifData &exifData = image->exifData();
322 if ( exifData.empty() )
326 Exiv2::ExifData::const_iterator i = exifData.findKey( Exiv2::ExifKey( key.toUtf8().constData() ) );
327 return i != exifData.end() ? decodeExifData( key, i ) : QVariant();
335 Q_UNUSED( imagePath )
337 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
345 if ( !QFileInfo::exists( imagePath ) )
346 return QVariantMap();
351 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
353 return QVariantMap();
354 image->readMetadata();
356 Exiv2::ExifData &exifData = image->exifData();
357 if ( !exifData.empty() )
359 const Exiv2::ExifData::const_iterator end = exifData.end();
360 for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i )
362 const QString key = QString::fromStdString( i->key() );
363 res.insert( key, decodeExifData( key, i ) );
367 Exiv2::XmpData &xmpData = image->xmpData();
368 if ( !xmpData.empty() )
370 const Exiv2::XmpData::const_iterator end = xmpData.end();
371 for ( Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i )
373 const QString key = QString::fromStdString( i->key() );
374 res.insert( key, decodeXmpData( key, i ) );
382 return QVariantMap();
385 Q_UNUSED( imagePath )
386 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
387 return QVariantMap();
398 Q_UNUSED( imagePath )
399 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
408 if ( !QFileInfo::exists( imagePath ) )
412 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
416 image->readMetadata();
417 Exiv2::ExifData &exifData = image->exifData();
419 if ( exifData.empty() )
422 const Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitudeRef" ) );
423 const Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLatitude" ) );
424 const Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitudeRef" ) );
425 const Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSLongitude" ) );
427 if ( itLatRef == exifData.end() || itLatVal == exifData.end() ||
428 itLonRef == exifData.end() || itLonVal == exifData.end() )
431 double lat = readCoordinate( itLatVal->value() );
432 double lon = readCoordinate( itLonVal->value() );
434 const QString latRef = QString::fromStdString( itLatRef->value().toString() );
435 const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
436 if ( latRef.compare( QLatin1String(
"S" ), Qt::CaseInsensitive ) == 0 )
440 if ( lonRef.compare( QLatin1String(
"W" ), Qt::CaseInsensitive ) == 0 )
447 const Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitude" ) );
448 const Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey(
"Exif.GPSInfo.GPSAltitudeRef" ) );
449 if ( itElevVal != exifData.end() )
451 double elev = readRational( itElevVal->value() );
452 if ( itElevRefVal != exifData.end() )
454 const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
455 if ( elevRef.compare( QLatin1String(
"1" ), Qt::CaseInsensitive ) == 0 )
472 Q_UNUSED( imagePath )
474 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
484 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
488 image->readMetadata();
489 Exiv2::ExifData &exifData = image->exifData();
491 exifData[
"Exif.GPSInfo.GPSVersionID"] =
"2 0 0 0";
492 exifData[
"Exif.GPSInfo.GPSMapDatum"] =
"WGS-84";
493 exifData[
"Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinateString( location.
y() ).toStdString();
494 exifData[
"Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinateString( location.
x() ).toStdString();
497 const QString elevationString = QStringLiteral(
"%1/1000" ).arg(
static_cast< int>( std::floor( std::abs( details.
elevation ) * 1000 ) ) );
498 exifData[
"Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
499 exifData[
"Exif.GPSInfo.GPSAltitudeRef"] = details.
elevation < 0.0 ?
"1" :
"0";
501 exifData[
"Exif.GPSInfo.GPSLatitudeRef"] = location.
y() > 0 ?
"N" :
"S";
502 exifData[
"Exif.GPSInfo.GPSLongitudeRef"] = location.
x() > 0 ?
"E" :
"W";
503 exifData[
"Exif.Image.GPSTag"] = 4908;
504 image->writeMetadata();
512 Q_UNUSED( imagePath )
515 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
525 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
529 QVariant actualValue;
530 bool actualValueIsUShort =
false;
531 if ( tag == QLatin1String(
"Exif.GPSInfo.GPSLatitude" ) ||
532 tag == QLatin1String(
"Exif.GPSInfo.GPSLongitude" ) ||
533 tag == QLatin1String(
"Exif.GPSInfo.GPSDestLatitude" ) ||
534 tag == QLatin1String(
"Exif.GPSInfo.GPSDestLongitude" ) )
536 actualValue = doubleToExifCoordinateString( value.toDouble() );
538 else if ( tag == QLatin1String(
"Exif.GPSInfo.GPSAltitude" ) )
540 actualValue = QStringLiteral(
"%1/1000" ).arg(
static_cast< int>( std::floor( std::abs( value.toDouble() ) * 1000 ) ) );
542 else if ( tag == QLatin1String(
"Exif.Image.Orientation" ) )
544 actualValueIsUShort =
true;
547 else if ( value.userType() == QMetaType::Type::QDateTime )
549 const QDateTime dateTime = value.toDateTime();
550 if ( tag == QLatin1String(
"Exif.Image.DateTime" ) ||
551 tag == QLatin1String(
"Exif.Image.DateTime" ) ||
552 tag == QLatin1String(
"Exif.Photo.DateTimeDigitized" ) ||
553 tag == QLatin1String(
"Exif.Photo.DateTimeOriginal" ) )
555 actualValue = dateTime.toString( QStringLiteral(
"yyyy:MM:dd hh:mm:ss" ) );
559 actualValue = dateTime.toString( Qt::ISODate );
562 else if ( value.userType() == QMetaType::Type::QDate )
564 const QDate date = value.toDate();
565 if ( tag == QLatin1String(
"Exif.GPSInfo.GPSDateStamp" ) )
567 actualValue = date.toString( QStringLiteral(
"yyyy:MM:dd" ) );
571 actualValue = date.toString( QStringLiteral(
"yyyy-MM-dd" ) );
574 else if ( value.userType() == QMetaType::Type::QTime )
576 const QTime time = value.toTime();
577 if ( tag == QLatin1String(
"Exif.GPSInfo.GPSTimeStamp" ) )
579 actualValue = QStringLiteral(
"%1/1 %2/1 %3/1" ).arg( time.hour() ).arg( time.minute() ).arg( time.second() );
583 actualValue = time.toString( QStringLiteral(
"HH:mm:ss" ) );
591 const bool isXmp = tag.startsWith( QLatin1String(
"Xmp." ) );
592 image->readMetadata();
593 if ( actualValueIsUShort )
597 Exiv2::XmpData &xmpData = image->xmpData();
598 xmpData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
602 Exiv2::ExifData &exifData = image->exifData();
603 exifData[tag.toStdString()] =
static_cast<ushort
>( actualValue.toLongLong() );
606 else if ( actualValue.userType() == QMetaType::Type::Int ||
607 actualValue.userType() == QMetaType::Type::LongLong )
611 Exiv2::XmpData &xmpData = image->xmpData();
612 xmpData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
616 Exiv2::ExifData &exifData = image->exifData();
617 exifData[tag.toStdString()] =
static_cast<uint32_t
>( actualValue.toLongLong() );
620 else if ( actualValue.userType() == QMetaType::Type::UInt ||
621 actualValue.userType() == QMetaType::Type::ULongLong )
625 Exiv2::XmpData &xmpData = image->xmpData();
626 xmpData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
630 Exiv2::ExifData &exifData = image->exifData();
631 exifData[tag.toStdString()] =
static_cast<int32_t
>( actualValue.toULongLong() );
634 else if ( actualValue.userType() == QMetaType::Type::Double )
638 Exiv2::XmpData &xmpData = image->xmpData();
639 xmpData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
643 Exiv2::ExifData &exifData = image->exifData();
644 exifData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
651 Exiv2::XmpData &xmpData = image->xmpData();
652 xmpData[tag.toStdString()] = actualValue.toString().toStdString();
656 Exiv2::ExifData &exifData = image->exifData();
657 exifData[tag.toStdString()] = actualValue.toString().toStdString();
660 image->writeMetadata();
668 Q_UNUSED( imagePath )
671 QgsDebugError( QStringLiteral(
"QGIS is built without exiv2 support" ) );
Point geometry type, with support for z-dimension and m-values.
#define QgsDebugError(str)