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