1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2006-09-15
7 * Description : Exiv2 library interface.
8 * Common metadata image information manipulation methods
9 *
10 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 * Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "metaengine_p.h"
27
28 // Qt includes
29
30 #include <QBuffer>
31
32 // Local includes
33
34 #include "metaengine_rotation.h"
35 #include "digikam_debug.h"
36
37 #if defined(Q_CC_CLANG)
38 # pragma clang diagnostic push
39 # pragma clang diagnostic ignored "-Wdeprecated-declarations"
40 #endif
41
42 namespace Digikam
43 {
44
setItemProgramId(const QString & program,const QString & version) const45 bool MetaEngine::setItemProgramId(const QString& program, const QString& version) const
46 {
47 QMutexLocker lock(&s_metaEngineMutex);
48
49 try
50 {
51 QString software(program);
52 software.append(QLatin1Char('-'));
53 software.append(version);
54
55 // Set program info into Exif.Image.ProcessingSoftware tag (only available with Exiv2 >= 0.14.0).
56
57 d->exifMetadata()["Exif.Image.ProcessingSoftware"] = std::string(software.toLatin1().constData());
58
59 // See B.K.O #142564: Check if Exif.Image.Software already exist. If yes, do not touch this tag.
60
61 if (!d->exifMetadata().empty())
62 {
63 Exiv2::ExifData exifData(d->exifMetadata());
64 Exiv2::ExifKey key("Exif.Image.Software");
65 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
66
67 if (it == exifData.end())
68 {
69 d->exifMetadata()["Exif.Image.Software"] = std::string(software.toLatin1().constData());
70 }
71 }
72
73 // set program info into XMP tags.
74
75 #ifdef _XMP_SUPPORT_
76
77 if (!d->xmpMetadata().empty())
78 {
79 // Only create Xmp.xmp.CreatorTool if it do not exist.
80 Exiv2::XmpData xmpData(d->xmpMetadata());
81 Exiv2::XmpKey key("Xmp.xmp.CreatorTool");
82 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
83
84 if (it == xmpData.end())
85 {
86 setXmpTagString("Xmp.xmp.CreatorTool", software);
87 }
88 }
89
90 setXmpTagString("Xmp.tiff.Software", software);
91
92 #endif // _XMP_SUPPORT_
93
94 // Set program info into IPTC tags.
95
96 d->iptcMetadata()["Iptc.Application2.Program"] = std::string(program.toLatin1().constData());
97 d->iptcMetadata()["Iptc.Application2.ProgramVersion"] = std::string(version.toLatin1().constData());
98
99 return true;
100 }
101 catch (Exiv2::AnyError& e)
102 {
103 d->printExiv2ExceptionError(QLatin1String("Cannot set Program identity into image with Exiv2:"), e);
104 }
105 catch (...)
106 {
107 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
108 }
109
110 return false;
111 }
112
getItemDimensions() const113 QSize MetaEngine::getItemDimensions() const
114 {
115 QMutexLocker lock(&s_metaEngineMutex);
116
117 try
118 {
119 long width = -1;
120 long height = -1;
121
122 // Try to get Exif.Photo tags
123
124 Exiv2::ExifData exifData(d->exifMetadata());
125 Exiv2::ExifKey key("Exif.Photo.PixelXDimension");
126 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
127
128 if ((it != exifData.end()) && it->count())
129 {
130 width = it->toLong();
131 }
132
133 Exiv2::ExifKey key2("Exif.Photo.PixelYDimension");
134 Exiv2::ExifData::const_iterator it2 = exifData.findKey(key2);
135
136 if ((it2 != exifData.end()) && it2->count())
137 {
138 height = it2->toLong();
139 }
140
141 if ((width != -1) && (height != -1))
142 {
143 return QSize(width, height);
144 }
145
146 // Try to get Exif.Image tags
147
148 width = -1;
149 height = -1;
150
151 Exiv2::ExifKey key3("Exif.Image.ImageWidth");
152 Exiv2::ExifData::const_iterator it3 = exifData.findKey(key3);
153
154 if ((it3 != exifData.end()) && it3->count())
155 {
156 width = it3->toLong();
157 }
158
159 Exiv2::ExifKey key4("Exif.Image.ImageLength");
160 Exiv2::ExifData::const_iterator it4 = exifData.findKey(key4);
161
162 if ((it4 != exifData.end()) && it4->count())
163 {
164 height = it4->toLong();
165 }
166
167 if ((width != -1) && (height != -1))
168 {
169 return QSize(width, height);
170 }
171
172 #ifdef _XMP_SUPPORT_
173
174 // Try to get Xmp.tiff tags
175
176 width = -1;
177 height = -1;
178 bool wOk = false;
179 bool hOk = false;
180
181 QString str = getXmpTagString("Xmp.tiff.ImageWidth");
182
183 if (!str.isEmpty())
184 {
185 width = str.toInt(&wOk);
186 }
187
188 str = getXmpTagString("Xmp.tiff.ImageLength");
189
190 if (!str.isEmpty())
191 {
192 height = str.toInt(&hOk);
193 }
194
195 if (wOk && hOk)
196 {
197 return QSize(width, height);
198 }
199
200 // Try to get Xmp.exif tags
201
202 width = -1;
203 height = -1;
204 wOk = false;
205 hOk = false;
206
207 str = getXmpTagString("Xmp.exif.PixelXDimension");
208
209 if (!str.isEmpty())
210 {
211 width = str.toInt(&wOk);
212 }
213
214 str = getXmpTagString("Xmp.exif.PixelYDimension");
215
216 if (!str.isEmpty())
217 {
218 height = str.toInt(&hOk);
219 }
220
221 if (wOk && hOk)
222 {
223 return QSize(width, height);
224 }
225
226 #endif // _XMP_SUPPORT_
227
228 }
229 catch (Exiv2::AnyError& e)
230 {
231 d->printExiv2ExceptionError(QLatin1String("Cannot parse image dimensions tag with Exiv2:"), e);
232 }
233 catch (...)
234 {
235 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
236 }
237
238 return QSize();
239 }
240
setItemDimensions(const QSize & size) const241 bool MetaEngine::setItemDimensions(const QSize& size) const
242 {
243 QMutexLocker lock(&s_metaEngineMutex);
244
245 try
246 {
247 // Set Exif values.
248
249 // NOTE: see B.K.O #144604: need to cast to record an unsigned integer value.
250
251 d->exifMetadata()["Exif.Image.ImageWidth"] = static_cast<uint32_t>(size.width());
252 d->exifMetadata()["Exif.Image.ImageLength"] = static_cast<uint32_t>(size.height());
253 d->exifMetadata()["Exif.Photo.PixelXDimension"] = static_cast<uint32_t>(size.width());
254 d->exifMetadata()["Exif.Photo.PixelYDimension"] = static_cast<uint32_t>(size.height());
255
256 // Set Xmp values.
257
258 #ifdef _XMP_SUPPORT_
259
260 setXmpTagString("Xmp.tiff.ImageWidth", QString::number(size.width()));
261 setXmpTagString("Xmp.tiff.ImageLength", QString::number(size.height()));
262 setXmpTagString("Xmp.exif.PixelXDimension", QString::number(size.width()));
263 setXmpTagString("Xmp.exif.PixelYDimension", QString::number(size.height()));
264
265 #endif // _XMP_SUPPORT_
266
267 return true;
268 }
269 catch (Exiv2::AnyError& e)
270 {
271 d->printExiv2ExceptionError(QLatin1String("Cannot set image dimensions with Exiv2:"), e);
272 }
273 catch (...)
274 {
275 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
276 }
277
278 return false;
279 }
280
getItemOrientation() const281 MetaEngine::ImageOrientation MetaEngine::getItemOrientation() const
282 {
283 QMutexLocker lock(&s_metaEngineMutex);
284
285 try
286 {
287 Exiv2::ExifData exifData(d->exifMetadata());
288 Exiv2::ExifData::iterator it;
289 long orientation;
290 ImageOrientation imageOrient = ORIENTATION_NORMAL;
291
292 // -- Standard Xmp tag --------------------------------
293
294 #ifdef _XMP_SUPPORT_
295
296 bool ok = false;
297 QString str = getXmpTagString("Xmp.tiff.Orientation");
298
299 if (!str.isEmpty())
300 {
301 orientation = str.toLong(&ok);
302
303 if (ok)
304 {
305 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Orientation => Xmp.tiff.Orientation =>" << (int)orientation;
306
307 return (ImageOrientation)orientation;
308 }
309 }
310
311 #endif // _XMP_SUPPORT_
312
313 // Because some camera set a wrong standard exif orientation tag,
314 // We need to check makernote tags in first!
315
316 // -- Minolta Cameras ----------------------------------
317
318 Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
319 it = exifData.findKey(minoltaKey1);
320
321 if ((it != exifData.end()) && it->count())
322 {
323 orientation = it->toLong();
324
325 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Orientation => Exif.MinoltaCs7D.Rotation =>" << (int)orientation;
326
327 switch (orientation)
328 {
329 case 76:
330 {
331 imageOrient = ORIENTATION_ROT_90;
332 break;
333 }
334
335 case 82:
336 {
337 imageOrient = ORIENTATION_ROT_270;
338 break;
339 }
340 }
341
342 return imageOrient;
343 }
344
345 Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
346 it = exifData.findKey(minoltaKey2);
347
348 if ((it != exifData.end()) && it->count())
349 {
350 orientation = it->toLong();
351
352 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Orientation => Exif.MinoltaCs5D.Rotation =>" << (int)orientation;
353
354 switch (orientation)
355 {
356 case 76:
357 {
358 imageOrient = ORIENTATION_ROT_90;
359 break;
360 }
361
362 case 82:
363 {
364 imageOrient = ORIENTATION_ROT_270;
365 break;
366 }
367 }
368
369 return imageOrient;
370 }
371
372 // -- Standard Exif tag --------------------------------
373
374 Exiv2::ExifKey keyStd("Exif.Image.Orientation");
375 it = exifData.findKey(keyStd);
376
377 if ((it != exifData.end()) && it->count())
378 {
379 orientation = it->toLong();
380
381 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Orientation => Exif.Image.Orientation =>" << (int)orientation;
382
383 return (ImageOrientation)orientation;
384 }
385
386 }
387 catch (Exiv2::AnyError& e)
388 {
389 d->printExiv2ExceptionError(QLatin1String("Cannot parse Exif Orientation tag with Exiv2:"), e);
390 }
391 catch (...)
392 {
393 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
394 }
395
396 return ORIENTATION_UNSPECIFIED;
397 }
398
setItemOrientation(ImageOrientation orientation) const399 bool MetaEngine::setItemOrientation(ImageOrientation orientation) const
400 {
401 QMutexLocker lock(&s_metaEngineMutex);
402
403 try
404 {
405 if (orientation < ORIENTATION_UNSPECIFIED || orientation > ORIENTATION_ROT_270)
406 {
407 qCDebug(DIGIKAM_METAENGINE_LOG) << "Image orientation value is not correct!";
408 return false;
409 }
410
411 // Set Exif values.
412
413 d->exifMetadata()["Exif.Image.Orientation"] = static_cast<uint16_t>(orientation);
414 qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif.Image.Orientation tag set to:" << (int)orientation;
415
416 // Set Xmp values.
417
418 #ifdef _XMP_SUPPORT_
419
420 setXmpTagString("Xmp.tiff.Orientation", QString::number((int)orientation));
421
422 #endif // _XMP_SUPPORT_
423
424 // -- Minolta/Sony Cameras ----------------------------------
425
426 // Minolta and Sony camera store image rotation in Makernote.
427 // We remove these information to prevent duplicate values.
428
429 Exiv2::ExifData::iterator it;
430
431 Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
432 it = d->exifMetadata().findKey(minoltaKey1);
433
434 if (it != d->exifMetadata().end())
435 {
436 d->exifMetadata().erase(it);
437 qCDebug(DIGIKAM_METAENGINE_LOG) << "Removing Exif.MinoltaCs7D.Rotation tag";
438 }
439
440 Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
441 it = d->exifMetadata().findKey(minoltaKey2);
442
443 if (it != d->exifMetadata().end())
444 {
445 d->exifMetadata().erase(it);
446 qCDebug(DIGIKAM_METAENGINE_LOG) << "Removing Exif.MinoltaCs5D.Rotation tag";
447 }
448
449 // -- Exif embedded thumbnail ----------------------------------
450
451 Exiv2::ExifKey thumbKey("Exif.Thumbnail.Orientation");
452 it = d->exifMetadata().findKey(thumbKey);
453
454 if ((it != d->exifMetadata().end()) && it->count())
455 {
456 (*it) = static_cast<uint16_t>(orientation);
457 }
458
459 return true;
460 }
461 catch (Exiv2::AnyError& e)
462 {
463 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Orientation tag with Exiv2:"), e);
464 }
465 catch (...)
466 {
467 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
468 }
469
470 return false;
471 }
472
getItemColorWorkSpace() const473 MetaEngine::ImageColorWorkSpace MetaEngine::getItemColorWorkSpace() const
474 {
475 // Check Exif values.
476
477 long exifColorSpace = -1;
478
479 if (!getExifTagLong("Exif.Photo.ColorSpace", exifColorSpace))
480 {
481 #ifdef _XMP_SUPPORT_
482
483 QVariant var = getXmpTagVariant("Xmp.exif.ColorSpace");
484
485 if (!var.isNull())
486 {
487 exifColorSpace = var.toInt();
488 }
489
490 #endif // _XMP_SUPPORT_
491 }
492
493 if (exifColorSpace == 1)
494 {
495 return WORKSPACE_SRGB; // as specified by standard
496 }
497 else if (exifColorSpace == 2)
498 {
499 return WORKSPACE_ADOBERGB; // not in the standard!
500 }
501 else
502 {
503 if (exifColorSpace == 65535)
504 {
505 // A lot of cameras set the Exif.Iop.InteroperabilityIndex,
506 // as documented for ExifTool
507
508 QString interopIndex = getExifTagString("Exif.Iop.InteroperabilityIndex");
509
510 if (!interopIndex.isNull())
511 {
512 if (interopIndex == QLatin1String("R03"))
513 {
514 return WORKSPACE_ADOBERGB;
515 }
516 else if (interopIndex == QLatin1String("R98"))
517 {
518 return WORKSPACE_SRGB;
519 }
520 }
521 }
522
523 // Note: Text EXIF ColorSpace tag may just not be present (NEF files)
524
525 // Nikon camera set Exif.Photo.ColorSpace to uncalibrated or just skip this field,
526 // then add additional information into the makernotes.
527 // Exif.Nikon3.ColorSpace: 1 => sRGB, 2 => AdobeRGB
528
529 long nikonColorSpace;
530
531 if (getExifTagLong("Exif.Nikon3.ColorSpace", nikonColorSpace))
532 {
533 if (nikonColorSpace == 1)
534 {
535 return WORKSPACE_SRGB;
536 }
537 else if (nikonColorSpace == 2)
538 {
539 return WORKSPACE_ADOBERGB;
540 }
541 }
542 // Exif.Nikon3.ColorMode is set to "MODE2" for AdobeRGB, but there are sometimes two ColorMode fields
543 // in a NEF, with the first one "COLOR" and the second one "MODE2"; but in this case, ColorSpace (above) was set.
544
545 if (getExifTagString("Exif.Nikon3.ColorMode").contains(QLatin1String("MODE2")))
546 {
547 return WORKSPACE_ADOBERGB;
548 }
549
550 // TODO: This makernote tag (0x00b4) must be added to libexiv2
551
552 /*
553 long canonColorSpace;
554
555 if (getExifTagLong("Exif.Canon.ColorSpace", canonColorSpace))
556 {
557 if (canonColorSpace == 1)
558 {
559 return WORKSPACE_SRGB;
560 }
561 else if (canonColorSpace == 2)
562 {
563 return WORKSPACE_ADOBERGB;
564 }
565 }
566 */
567
568 // TODO : add more Makernote parsing here ...
569
570 if (exifColorSpace == 65535)
571 {
572 return WORKSPACE_UNCALIBRATED;
573 }
574 }
575
576 return WORKSPACE_UNSPECIFIED;
577 }
578
setItemColorWorkSpace(ImageColorWorkSpace workspace) const579 bool MetaEngine::setItemColorWorkSpace(ImageColorWorkSpace workspace) const
580 {
581 QMutexLocker lock(&s_metaEngineMutex);
582
583 try
584 {
585 // Set Exif value.
586
587 d->exifMetadata()["Exif.Photo.ColorSpace"] = static_cast<uint16_t>(workspace);
588
589 // Set Xmp value.
590
591 #ifdef _XMP_SUPPORT_
592
593 setXmpTagString("Xmp.exif.ColorSpace", QString::number((int)workspace));
594
595 #endif // _XMP_SUPPORT_
596
597 return true;
598 }
599 catch (Exiv2::AnyError& e)
600 {
601 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif color workspace tag with Exiv2:"), e);
602 }
603 catch (...)
604 {
605 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
606 }
607
608 return false;
609 }
610
getItemDateTime() const611 QDateTime MetaEngine::getItemDateTime() const
612 {
613 QMutexLocker lock(&s_metaEngineMutex);
614
615 try
616 {
617 // In first, trying to get Date & time from Exif tags.
618
619 QMap<QDateTime, int> dateMap;
620
621 if (!d->exifMetadata().empty())
622 {
623 Exiv2::ExifData exifData(d->exifMetadata());
624 {
625 Exiv2::ExifKey key("Exif.Photo.DateTimeOriginal");
626 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
627
628 if (it != exifData.end())
629 {
630 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
631 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 2);
632
633 if (dateTime.isValid())
634 {
635 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Exif.Photo.DateTimeOriginal =>" << dateTime;
636 }
637 }
638 }
639 {
640 Exiv2::ExifKey key("Exif.Photo.DateTimeDigitized");
641 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
642
643 if (it != exifData.end())
644 {
645 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
646 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
647
648 if (dateTime.isValid() && dateMap.value(dateTime) > 2)
649 {
650 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Exif.Photo.DateTimeDigitized =>" << dateTime;
651
652 return dateTime;
653 }
654 }
655 }
656 {
657 Exiv2::ExifKey key("Exif.Image.DateTime");
658 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
659
660 if (it != exifData.end())
661 {
662 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
663 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
664
665 if (dateTime.isValid() && dateMap.value(dateTime) > 2)
666 {
667 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Exif.Image.DateTime =>" << dateTime;
668
669 return dateTime;
670 }
671 }
672 }
673 }
674
675 // In second, trying to get Date & time from Xmp tags.
676
677 #ifdef _XMP_SUPPORT_
678
679 if (!d->xmpMetadata().empty())
680 {
681 Exiv2::XmpData xmpData(d->xmpMetadata());
682 {
683 Exiv2::XmpKey key("Xmp.exif.DateTimeOriginal");
684 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
685
686 if (it != xmpData.end())
687 {
688 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
689 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 2);
690
691 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
692 {
693 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.exif.DateTimeOriginal =>" << dateTime;
694
695 return dateTime;
696 }
697 }
698 }
699 {
700 Exiv2::XmpKey key("Xmp.exif.DateTimeDigitized");
701 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
702
703 if (it != xmpData.end())
704 {
705 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
706 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
707
708 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
709 {
710 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.exif.DateTimeDigitized =>" << dateTime;
711
712 return dateTime;
713 }
714 }
715 }
716 {
717 Exiv2::XmpKey key("Xmp.photoshop.DateCreated");
718 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
719
720 if (it != xmpData.end())
721 {
722 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
723 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
724
725 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
726 {
727 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.photoshop.DateCreated =>" << dateTime;
728
729 return dateTime;
730 }
731 }
732 }
733 {
734 Exiv2::XmpKey key("Xmp.xmp.CreateDate");
735 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
736
737 if (it != xmpData.end())
738 {
739 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
740 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
741
742 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
743 {
744 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.xmp.CreateDate =>" << dateTime;
745
746 return dateTime;
747 }
748 }
749 }
750 {
751 Exiv2::XmpKey key("Xmp.tiff.DateTime");
752 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
753
754 if (it != xmpData.end())
755 {
756 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
757 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
758
759 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
760 {
761 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.tiff.DateTime =>" << dateTime;
762
763 return dateTime;
764 }
765 }
766 }
767 {
768 Exiv2::XmpKey key("Xmp.xmp.ModifyDate");
769 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
770
771 if (it != xmpData.end())
772 {
773 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
774 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
775
776 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
777 {
778 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.xmp.ModifyDate =>" << dateTime;
779
780 return dateTime;
781 }
782 }
783 }
784 {
785 Exiv2::XmpKey key("Xmp.xmp.MetadataDate");
786 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
787
788 if (it != xmpData.end())
789 {
790 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
791 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
792
793 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
794 {
795 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.xmp.MetadataDate =>" << dateTime;
796
797 return dateTime;
798 }
799 }
800 }
801
802 // Video files support
803
804 {
805 Exiv2::XmpKey key("Xmp.video.DateTimeOriginal");
806 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
807
808 if (it != xmpData.end())
809 {
810 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
811 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 2);
812
813 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
814 {
815 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.video.DateTimeOriginal =>" << dateTime;
816
817 return dateTime;
818 }
819 }
820 }
821 {
822 Exiv2::XmpKey key("Xmp.video.DateUTC");
823 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
824
825 if (it != xmpData.end())
826 {
827 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
828 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
829
830 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
831 {
832 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.video.DateUTC =>" << dateTime;
833
834 return dateTime;
835 }
836 }
837 }
838 {
839 Exiv2::XmpKey key("Xmp.video.ModificationDate");
840 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
841
842 if (it != xmpData.end())
843 {
844 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
845 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
846
847 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
848 {
849 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.video.ModificationDate =>" << dateTime;
850
851 return dateTime;
852 }
853 }
854 }
855 {
856 Exiv2::XmpKey key("Xmp.video.DateTimeDigitized");
857 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
858
859 if (it != xmpData.end())
860 {
861 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
862 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
863
864 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
865 {
866 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Xmp.video.DateTimeDigitized =>" << dateTime;
867
868 return dateTime;
869 }
870 }
871 }
872 }
873
874 #endif // _XMP_SUPPORT_
875
876 // In third, trying to get Date & time from Iptc tags.
877
878 if (!d->iptcMetadata().empty())
879 {
880 Exiv2::IptcData iptcData(d->iptcMetadata());
881
882 // Try creation Iptc date & time entries.
883
884 Exiv2::IptcKey keyDateCreated("Iptc.Application2.DateCreated");
885 Exiv2::IptcData::const_iterator it = iptcData.findKey(keyDateCreated);
886
887 if (it != iptcData.end())
888 {
889 QString IptcDateCreated(QString::fromStdString(it->toString()));
890 Exiv2::IptcKey keyTimeCreated("Iptc.Application2.TimeCreated");
891 Exiv2::IptcData::const_iterator it2 = iptcData.findKey(keyTimeCreated);
892
893 if (it2 != iptcData.end())
894 {
895 QString IptcTimeCreated(QString::fromStdString(it2->toString()));
896 QDate date = QDate::fromString(IptcDateCreated, Qt::ISODate);
897 QTime time = QTime::fromString(IptcTimeCreated, Qt::ISODate);
898 QDateTime dateTime = QDateTime(date, time);
899 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
900
901 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
902 {
903 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Iptc.Application2.DateCreated =>" << dateTime;
904
905 return dateTime;
906 }
907 }
908 }
909
910 // Try digitization Iptc date & time entries.
911
912 Exiv2::IptcKey keyDigitizationDate("Iptc.Application2.DigitizationDate");
913 Exiv2::IptcData::const_iterator it3 = iptcData.findKey(keyDigitizationDate);
914
915 if (it3 != iptcData.end())
916 {
917 QString IptcDateDigitization(QString::fromStdString(it3->toString()));
918 Exiv2::IptcKey keyDigitizationTime("Iptc.Application2.DigitizationTime");
919 Exiv2::IptcData::const_iterator it4 = iptcData.findKey(keyDigitizationTime);
920
921 if (it4 != iptcData.end())
922 {
923 QString IptcTimeDigitization(QString::fromStdString(it4->toString()));
924 QDate date = QDate::fromString(IptcDateDigitization, Qt::ISODate);
925 QTime time = QTime::fromString(IptcTimeDigitization, Qt::ISODate);
926 QDateTime dateTime = QDateTime(date, time);
927 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
928
929 if (dateTime.isValid() && (dateMap.value(dateTime) > 2))
930 {
931 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => Iptc.Application2.DigitizationDate =>" << dateTime;
932
933 return dateTime;
934 }
935 }
936 }
937 }
938
939 if (!dateMap.isEmpty())
940 {
941 int counter = 0;
942 QDateTime dateTime;
943 QMap<QDateTime, int>::const_iterator it;
944
945 for (it = dateMap.constBegin() ; it != dateMap.constEnd() ; ++it)
946 {
947 if (!it.key().isValid())
948 {
949 continue;
950 }
951 else if (it.value() > counter)
952 {
953 counter = it.value();
954 dateTime = it.key();
955 }
956 }
957
958 if (dateTime.isValid())
959 {
960 //qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime => create date =>" << dateTime;
961
962 return dateTime;
963 }
964 }
965 }
966 catch (Exiv2::AnyError& e)
967 {
968 d->printExiv2ExceptionError(QLatin1String("Cannot parse Exif date & time tag with Exiv2:"), e);
969 }
970 catch (...)
971 {
972 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
973 }
974
975 return QDateTime();
976 }
977
setImageDateTime(const QDateTime & dateTime,bool setDateTimeDigitized) const978 bool MetaEngine::setImageDateTime(const QDateTime& dateTime, bool setDateTimeDigitized) const
979 {
980 if (!dateTime.isValid())
981 {
982 return false;
983 }
984
985 QMutexLocker lock(&s_metaEngineMutex);
986
987 try
988 {
989 // In first we write date & time into Exif.
990
991 // DateTimeDigitized is set by slide scanners etc. when a picture is digitized.
992 // DateTimeOriginal specifies the date/time when the picture was taken.
993 // For digital cameras, these dates should be both set, and identical.
994 // Reference: https://www.exif.org/Exif2-2.PDF, chapter 4.6.5, table 4, section F.
995
996 const std::string& exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData());
997 d->exifMetadata()["Exif.Image.DateTime"] = exifdatetime;
998 d->exifMetadata()["Exif.Photo.DateTimeOriginal"] = exifdatetime;
999
1000 if (setDateTimeDigitized)
1001 {
1002 d->exifMetadata()["Exif.Photo.DateTimeDigitized"] = exifdatetime;
1003 }
1004
1005 #ifdef _XMP_SUPPORT_
1006
1007 // In second we write date & time into Xmp.
1008
1009 const std::string& xmpdatetime(dateTime.toString(Qt::ISODate).toLatin1().constData());
1010
1011 Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::xmpText);
1012 xmpTxtVal->read(xmpdatetime);
1013 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.exif.DateTimeOriginal"), xmpTxtVal.get());
1014 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.photoshop.DateCreated"), xmpTxtVal.get());
1015 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.tiff.DateTime"), xmpTxtVal.get());
1016 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.CreateDate"), xmpTxtVal.get());
1017 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.MetadataDate"), xmpTxtVal.get());
1018 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.ModifyDate"), xmpTxtVal.get());
1019
1020 if (setDateTimeDigitized)
1021 {
1022 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.exif.DateTimeDigitized"), xmpTxtVal.get());
1023 }
1024
1025 // We call this function from the FFmpeg parser and we don't have a filename yet.
1026 // Check getFilePath() for empty.
1027
1028 if (getFilePath().isEmpty() ||
1029 QMimeDatabase().mimeTypeForFile(getFilePath()).name().startsWith(QLatin1String("video/")))
1030 {
1031 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.video.DateTimeOriginal"), xmpTxtVal.get());
1032 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.video.DateUTC"), xmpTxtVal.get());
1033 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.video.ModificationDate"), xmpTxtVal.get());
1034
1035 if (setDateTimeDigitized)
1036 {
1037 d->xmpMetadata().add(Exiv2::XmpKey("Xmp.video.DateTimeDigitized"), xmpTxtVal.get());
1038 }
1039 }
1040
1041 // Tag not updated:
1042 // "Xmp.dc.DateTime" is a sequence of date relevant of dublin core change.
1043 // This is not the picture date as well
1044
1045 #endif // _XMP_SUPPORT_
1046
1047 // In third we write date & time into Iptc.
1048
1049 const std::string& iptcdate(dateTime.date().toString(Qt::ISODate).toLatin1().constData());
1050 const std::string& iptctime(dateTime.time().toString(Qt::ISODate).toLatin1().constData());
1051 d->iptcMetadata()["Iptc.Application2.DateCreated"] = iptcdate;
1052 d->iptcMetadata()["Iptc.Application2.TimeCreated"] = iptctime;
1053
1054 if (setDateTimeDigitized)
1055 {
1056 d->iptcMetadata()["Iptc.Application2.DigitizationDate"] = iptcdate;
1057 d->iptcMetadata()["Iptc.Application2.DigitizationTime"] = iptctime;
1058 }
1059
1060 return true;
1061 }
1062 catch (Exiv2::AnyError& e)
1063 {
1064 d->printExiv2ExceptionError(QLatin1String("Cannot set Date & Time into image with Exiv2:"), e);
1065 }
1066 catch (...)
1067 {
1068 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1069 }
1070
1071 return false;
1072 }
1073
getDigitizationDateTime(bool fallbackToCreationTime) const1074 QDateTime MetaEngine::getDigitizationDateTime(bool fallbackToCreationTime) const
1075 {
1076 QMutexLocker lock(&s_metaEngineMutex);
1077
1078 try
1079 {
1080 // In first, trying to get Date & time from Exif tags.
1081
1082 QMap<QDateTime, int> dateMap;
1083
1084 if (!d->exifMetadata().empty())
1085 {
1086 // Try Exif date time digitized.
1087
1088 Exiv2::ExifData exifData(d->exifMetadata());
1089 Exiv2::ExifKey key("Exif.Photo.DateTimeDigitized");
1090 Exiv2::ExifData::const_iterator it = exifData.findKey(key);
1091
1092 if (it != exifData.end())
1093 {
1094 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
1095 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
1096
1097 if (dateTime.isValid())
1098 {
1099 qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime (Exif digitalized):" << dateTime;
1100 }
1101 }
1102 }
1103
1104 // In second, we trying XMP
1105
1106 #ifdef _XMP_SUPPORT_
1107
1108 if (!d->xmpMetadata().empty())
1109 {
1110 Exiv2::XmpData xmpData(d->xmpMetadata());
1111 {
1112 Exiv2::XmpKey key("Xmp.exif.DateTimeDigitized");
1113 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
1114
1115 if (it != xmpData.end())
1116 {
1117 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
1118 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
1119
1120 if (dateTime.isValid() && (dateMap.value(dateTime) > 1))
1121 {
1122 qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime (XMP-Exif digitalized):" << dateTime;
1123
1124 return dateTime;
1125 }
1126 }
1127 }
1128 {
1129 Exiv2::XmpKey key("Xmp.video.DateTimeDigitized");
1130 Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
1131
1132 if (it != xmpData.end())
1133 {
1134 QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
1135 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
1136
1137 if (dateTime.isValid() && (dateMap.value(dateTime) > 1))
1138 {
1139 qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime (XMP-Video digitalized):" << dateTime;
1140
1141 return dateTime;
1142 }
1143 }
1144 }
1145 }
1146
1147 #endif // _XMP_SUPPORT_
1148
1149 // In third, trying to get Date & time from Iptc tags.
1150
1151 if (!d->iptcMetadata().empty())
1152 {
1153 // Try digitization Iptc date time entries.
1154
1155 Exiv2::IptcData iptcData(d->iptcMetadata());
1156 Exiv2::IptcKey keyDigitizationDate("Iptc.Application2.DigitizationDate");
1157 Exiv2::IptcData::const_iterator it = iptcData.findKey(keyDigitizationDate);
1158
1159 if (it != iptcData.end())
1160 {
1161 QString IptcDateDigitization(QString::fromStdString(it->toString()));
1162
1163 Exiv2::IptcKey keyDigitizationTime("Iptc.Application2.DigitizationTime");
1164 Exiv2::IptcData::const_iterator it2 = iptcData.findKey(keyDigitizationTime);
1165
1166 if (it2 != iptcData.end())
1167 {
1168 QString IptcTimeDigitization(QString::fromStdString(it2->toString()));
1169
1170 QDate date = QDate::fromString(IptcDateDigitization, Qt::ISODate);
1171 QTime time = QTime::fromString(IptcTimeDigitization, Qt::ISODate);
1172 QDateTime dateTime = QDateTime(date, time);
1173 dateMap.insert(dateTime, dateMap.value(dateTime, 0) + 1);
1174
1175 if (dateTime.isValid() && (dateMap.value(dateTime) > 1))
1176 {
1177 qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime (IPTC digitalized):" << dateTime;
1178
1179 return dateTime;
1180 }
1181 }
1182 }
1183 }
1184
1185 if (!dateMap.isEmpty())
1186 {
1187 int counter = 0;
1188 QDateTime dateTime;
1189 QMap<QDateTime, int>::const_iterator it;
1190
1191 for (it = dateMap.constBegin() ; it != dateMap.constEnd() ; ++it)
1192 {
1193 if (!it.key().isValid())
1194 {
1195 continue;
1196 }
1197 else if (it.value() > counter)
1198 {
1199 counter = it.value();
1200 dateTime = it.key();
1201 }
1202 }
1203
1204 if (dateTime.isValid())
1205 {
1206 qCDebug(DIGIKAM_METAENGINE_LOG) << "DateTime (digitization date):" << dateTime;
1207 return dateTime;
1208 }
1209 }
1210
1211 }
1212 catch (Exiv2::AnyError& e)
1213 {
1214 d->printExiv2ExceptionError(QLatin1String("Cannot parse Exif digitization date & time tag with Exiv2:"), e);
1215 }
1216 catch (...)
1217 {
1218 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1219 }
1220
1221 if (fallbackToCreationTime)
1222 {
1223 return getItemDateTime();
1224 }
1225 else
1226 {
1227 return QDateTime();
1228 }
1229 }
1230
getItemPreview(QImage & preview) const1231 bool MetaEngine::getItemPreview(QImage& preview) const
1232 {
1233 QMutexLocker lock(&s_metaEngineMutex);
1234
1235 try
1236 {
1237 // In first we trying to get from Iptc preview tag.
1238
1239 if (preview.loadFromData(getIptcTagData("Iptc.Application2.Preview")) )
1240 {
1241 return true;
1242 }
1243
1244 if (preview.loadFromData(QByteArray::fromBase64(getXmpTagString("Xmp.digiKam.Preview", false).toUtf8())))
1245 {
1246 return true;
1247 }
1248
1249 // TODO : Added here Makernotes preview extraction when Exiv2 will be fixed for that.
1250 }
1251 catch (Exiv2::AnyError& e)
1252 {
1253 d->printExiv2ExceptionError(QLatin1String("Cannot get image preview with Exiv2:"), e);
1254 }
1255 catch (...)
1256 {
1257 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1258 }
1259
1260 return false;
1261 }
1262
setItemPreview(const QImage & preview) const1263 bool MetaEngine::setItemPreview(const QImage& preview) const
1264 {
1265 if (preview.isNull())
1266 {
1267 removeIptcTag("Iptc.Application2.Preview");
1268 removeIptcTag("Iptc.Application2.PreviewFormat");
1269 removeIptcTag("Iptc.Application2.PreviewVersion");
1270 removeXmpTag("Xmp.digiKam.Preview");
1271 return true;
1272 }
1273
1274 QMutexLocker lock(&s_metaEngineMutex);
1275
1276 try
1277 {
1278 QByteArray data;
1279 QBuffer buffer(&data);
1280 buffer.open(QIODevice::WriteOnly);
1281
1282 // A little bit compressed preview jpeg image to limit IPTC size.
1283
1284 preview.save(&buffer, "JPEG");
1285 buffer.close();
1286 qCDebug(DIGIKAM_METAENGINE_LOG) << "JPEG image preview size: (" << preview.width() << "x"
1287 << preview.height() << ") pixels -" << data.size() << "bytes";
1288
1289 Exiv2::DataValue val;
1290 val.read((Exiv2::byte *)data.data(), data.size());
1291 d->iptcMetadata()["Iptc.Application2.Preview"] = val;
1292
1293 // See https://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf Appendix A for details.
1294
1295 d->iptcMetadata()["Iptc.Application2.PreviewFormat"] = 11; // JPEG
1296 d->iptcMetadata()["Iptc.Application2.PreviewVersion"] = 1;
1297
1298 setXmpTagString("Xmp.digiKam.Preview", QString::fromUtf8(data.toBase64()));
1299
1300 return true;
1301 }
1302 catch (Exiv2::AnyError& e)
1303 {
1304 d->printExiv2ExceptionError(QLatin1String("Cannot get image preview with Exiv2:"), e);
1305 }
1306 catch (...)
1307 {
1308 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1309 }
1310
1311 return false;
1312 }
1313
1314 } // namespace Digikam
1315
1316 #if defined(Q_CC_CLANG)
1317 # pragma clang diagnostic pop
1318 #endif
1319