Coverage Report

Created: 2026-02-26 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libkexiv2/src/kexiv2_p.cpp
Line
Count
Source
1
/*
2
    SPDX-FileCopyrightText: 2006-2015 Gilles Caulier <caulier dot gilles at gmail dot com>
3
    SPDX-FileCopyrightText: 2006-2012 Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
4
5
    SPDX-License-Identifier: GPL-2.0-or-later
6
*/
7
8
#include "kexiv2_p.h"
9
10
// C ANSI includes
11
12
extern "C"
13
{
14
#include <sys/stat.h>
15
16
#ifndef _MSC_VER
17
#include <utime.h>
18
#else
19
#include <sys/utime.h>
20
#endif
21
}
22
23
// Qt includes
24
#include <QStringDecoder>
25
26
// Local includes
27
28
#include "libkexiv2_debug.h"
29
30
// Pragma directives to reduce warnings from Exiv2.
31
#if !defined(__APPLE__) && defined(__GNUC__)
32
#pragma GCC diagnostic push
33
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
34
#endif
35
36
#if defined(__APPLE__) && defined(__clang__)
37
#pragma clang diagnostic push
38
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
39
#endif
40
41
namespace KExiv2Iface
42
{
43
44
KExiv2Private::KExiv2Private()
45
38.8k
    : data(new KExiv2DataPrivate)
46
38.8k
{
47
38.8k
    writeRawFiles         = false;
48
38.8k
    updateFileTimeStamp   = false;
49
38.8k
    useXMPSidecar4Reading = false;
50
38.8k
    metadataWritingMode   = KExiv2::WRITETOIMAGEONLY;
51
38.8k
    loadedFromSidecar     = false;
52
38.8k
    Exiv2::LogMsg::setHandler(KExiv2Private::printExiv2MessageHandler);
53
38.8k
}
54
55
38.8k
KExiv2Private::~KExiv2Private() = default;
56
57
void KExiv2Private::copyPrivateData(const KExiv2Private* const other)
58
0
{
59
0
    data                  = other->data;
60
0
    filePath              = other->filePath;
61
0
    writeRawFiles         = other->writeRawFiles;
62
0
    updateFileTimeStamp   = other->updateFileTimeStamp;
63
0
    useXMPSidecar4Reading = other->useXMPSidecar4Reading;
64
0
    metadataWritingMode   = other->metadataWritingMode;
65
0
}
66
67
bool KExiv2Private::saveToXMPSidecar(const QFileInfo& finfo) const
68
0
{
69
0
    QString filePath = KExiv2::sidecarFilePathForFile(finfo.filePath());
70
71
0
    if (filePath.isEmpty())
72
0
    {
73
0
        return false;
74
0
    }
75
76
0
    try
77
0
    {
78
0
#if EXIV2_TEST_VERSION(0,28,0)
79
0
        Exiv2::Image::UniquePtr image;
80
#else
81
        Exiv2::Image::AutoPtr image;
82
#endif
83
0
        image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, (const char*)(QFile::encodeName(filePath).constData()));
84
0
#if EXIV2_TEST_VERSION(0,28,0)
85
0
        return saveOperations(finfo, std::move(image));
86
#else
87
        return saveOperations(finfo, image);
88
#endif
89
0
    }
90
0
    catch( Exiv2::Error& e )
91
0
    {
92
0
        printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata to XMP sidecar using Exiv2 "), e);
93
0
        return false;
94
0
    }
95
0
    catch(...)
96
0
    {
97
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
98
0
        return false;
99
0
    }
100
0
}
101
102
bool KExiv2Private::saveToFile(const QFileInfo& finfo) const
103
0
{
104
0
    if (!finfo.isWritable())
105
0
    {
106
0
        qCDebug(LIBKEXIV2_LOG) << "File '" << finfo.fileName().toLatin1().constData() << "' is read only. Metadata not written.";
107
0
        return false;
108
0
    }
109
110
0
    QStringList rawTiffBasedSupported, rawTiffBasedNotSupported;
111
112
    // Raw files supported by Exiv2 0.23
113
0
    rawTiffBasedSupported << QString::fromLatin1("dng")
114
0
                          << QString::fromLatin1("nef")
115
0
                          << QString::fromLatin1("pef")
116
0
                          << QString::fromLatin1("orf")
117
0
                          << QString::fromLatin1("srw")
118
0
                          << QString::fromLatin1("cr2");
119
120
    // Raw files not supported by Exiv2 0.23
121
0
    rawTiffBasedNotSupported << QString::fromLatin1("3fr")
122
0
                             << QString::fromLatin1("arw")
123
0
                             << QString::fromLatin1("dcr")
124
0
                             << QString::fromLatin1("erf")
125
0
                             << QString::fromLatin1("k25")
126
0
                             << QString::fromLatin1("kdc") 
127
0
                             << QString::fromLatin1("mos")
128
0
                             << QString::fromLatin1("raw")
129
0
                             << QString::fromLatin1("sr2")
130
0
                             << QString::fromLatin1("srf")
131
0
                             << QString::fromLatin1("rw2");
132
133
0
    QString ext = finfo.suffix().toLower();
134
135
0
    if (!writeRawFiles && (rawTiffBasedSupported.contains(ext) || rawTiffBasedNotSupported.contains(ext)) )
136
0
    {
137
0
        qCDebug(LIBKEXIV2_LOG) << finfo.fileName()
138
0
                               << "is a TIFF based RAW file, writing to such a file is disabled by current settings.";
139
0
        return false;
140
0
    }
141
142
/*
143
    if (rawTiffBasedNotSupported.contains(ext))
144
    {
145
        qCDebug(LIBKEXIV2_LOG) << finfo.fileName()
146
                               << "is TIFF based RAW file not yet supported. Metadata not saved.";
147
        return false;
148
    }
149
150
    if (rawTiffBasedSupported.contains(ext) && !writeRawFiles)
151
    {
152
        qCDebug(LIBKEXIV2_LOG) << finfo.fileName()
153
                               << "is TIFF based RAW file supported but writing mode is disabled. "
154
                               << "Metadata not saved.";
155
        return false;
156
    }
157
158
    qCDebug(LIBKEXIV2_LOG) << "File Extension: " << ext << " is supported for writing mode";
159
160
    bool ret = false;
161
*/
162
163
0
    try
164
0
    {
165
0
#if EXIV2_TEST_VERSION(0,28,0)
166
0
        Exiv2::Image::UniquePtr image;
167
#else
168
        Exiv2::Image::AutoPtr image;
169
#endif
170
0
        image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(finfo.filePath()).constData()));
171
0
#if EXIV2_TEST_VERSION(0,28,0)
172
0
        return saveOperations(finfo, std::move(image));
173
#else
174
        return saveOperations(finfo, image);
175
#endif
176
0
    }
177
0
    catch( Exiv2::Error& e )
178
0
    {
179
0
        printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata to image using Exiv2 "), e);
180
0
        return false;
181
0
    }
182
0
    catch(...)
183
0
    {
184
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
185
0
        return false;
186
0
    }
187
0
}
188
189
#if EXIV2_TEST_VERSION(0,28,0)
190
bool KExiv2Private::saveOperations(const QFileInfo& finfo, Exiv2::Image::UniquePtr image) const
191
#else
192
bool KExiv2Private::saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const
193
#endif
194
0
{
195
0
    try
196
0
    {
197
0
        Exiv2::AccessMode mode;
198
0
        bool wroteComment = false, wroteEXIF = false, wroteIPTC = false, wroteXMP = false;
199
200
        // We need to load target file metadata to merge with new one. It's mandatory with TIFF format:
201
        // like all tiff file structure is based on Exif.
202
0
        image->readMetadata();
203
204
        // Image Comments ---------------------------------
205
206
0
        mode = image->checkMode(Exiv2::mdComment);
207
208
0
        if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
209
0
        {
210
0
            image->setComment(imageComments());
211
0
            wroteComment = true;
212
0
        }
213
214
0
        qCDebug(LIBKEXIV2_LOG) << "wroteComment: " << wroteComment;
215
216
        // Exif metadata ----------------------------------
217
218
0
        mode = image->checkMode(Exiv2::mdExif);
219
220
0
        if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
221
0
        {
222
0
            if (image->mimeType() == "image/tiff")
223
0
            {
224
0
                Exiv2::ExifData orgExif = image->exifData();
225
0
                Exiv2::ExifData newExif;
226
0
                QStringList     untouchedTags;
227
228
                // With tiff image we cannot overwrite whole Exif data as well, because
229
                // image data are stored in Exif container. We need to take a care about
230
                // to not lost image data.
231
0
                untouchedTags << QString::fromLatin1("Exif.Image.ImageWidth");
232
0
                untouchedTags << QString::fromLatin1("Exif.Image.ImageLength");
233
0
                untouchedTags << QString::fromLatin1("Exif.Image.BitsPerSample");
234
0
                untouchedTags << QString::fromLatin1("Exif.Image.Compression");
235
0
                untouchedTags << QString::fromLatin1("Exif.Image.PhotometricInterpretation");
236
0
                untouchedTags << QString::fromLatin1("Exif.Image.FillOrder");
237
0
                untouchedTags << QString::fromLatin1("Exif.Image.SamplesPerPixel");
238
0
                untouchedTags << QString::fromLatin1("Exif.Image.StripOffsets");
239
0
                untouchedTags << QString::fromLatin1("Exif.Image.RowsPerStrip");
240
0
                untouchedTags << QString::fromLatin1("Exif.Image.StripByteCounts");
241
0
                untouchedTags << QString::fromLatin1("Exif.Image.XResolution");
242
0
                untouchedTags << QString::fromLatin1("Exif.Image.YResolution");
243
0
                untouchedTags << QString::fromLatin1("Exif.Image.PlanarConfiguration");
244
0
                untouchedTags << QString::fromLatin1("Exif.Image.ResolutionUnit");
245
246
0
                for (Exiv2::ExifData::iterator it = orgExif.begin(); it != orgExif.end(); ++it)
247
0
                {
248
0
                    if (untouchedTags.contains(QString::fromLatin1(it->key().c_str())))
249
0
                    {
250
0
                        newExif[it->key().c_str()] = orgExif[it->key().c_str()];
251
0
                    }
252
0
                }
253
254
0
                Exiv2::ExifData readedExif = exifMetadata();
255
256
0
                for (Exiv2::ExifData::iterator it = readedExif.begin(); it != readedExif.end(); ++it)
257
0
                {
258
0
                    if (!untouchedTags.contains(QString::fromLatin1(it->key().c_str())))
259
0
                    {
260
0
                        newExif[it->key().c_str()] = readedExif[it->key().c_str()];
261
0
                    }
262
0
                }
263
264
0
                image->setExifData(newExif);
265
0
            }
266
0
            else
267
0
            {
268
0
                image->setExifData(exifMetadata());
269
0
            }
270
271
0
            wroteEXIF = true;
272
0
        }
273
274
0
        qCDebug(LIBKEXIV2_LOG) << "wroteEXIF: " << wroteEXIF;
275
276
        // Iptc metadata ----------------------------------
277
278
0
        mode = image->checkMode(Exiv2::mdIptc);
279
280
0
        if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
281
0
        {
282
0
            image->setIptcData(iptcMetadata());
283
0
            wroteIPTC = true;
284
0
        }
285
286
0
        qCDebug(LIBKEXIV2_LOG) << "wroteIPTC: " << wroteIPTC;
287
288
        // Xmp metadata -----------------------------------
289
290
0
        mode = image->checkMode(Exiv2::mdXmp);
291
292
0
        if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite))
293
0
        {
294
0
#ifdef _XMP_SUPPORT_
295
0
            image->setXmpData(xmpMetadata());
296
0
            wroteXMP = true;
297
0
#endif
298
0
        }
299
300
0
        qCDebug(LIBKEXIV2_LOG) << "wroteXMP: " << wroteXMP;
301
302
0
        if (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP)
303
0
        {
304
0
            qCDebug(LIBKEXIV2_LOG) << "Writing metadata is not supported for file" << finfo.fileName();
305
0
            return false;
306
0
        }
307
0
        else if (!wroteEXIF || !wroteIPTC || !wroteXMP)
308
0
        {
309
0
            qCDebug(LIBKEXIV2_LOG) << "Support for writing metadata is limited for file" << finfo.fileName();
310
0
        }
311
312
0
        if (!updateFileTimeStamp)
313
0
        {
314
            // Don't touch access and modification timestamp of file.
315
0
            struct stat    st;
316
0
            struct utimbuf ut;
317
0
            int ret = ::stat(QFile::encodeName(filePath).constData(), &st);
318
319
0
            if (ret == 0)
320
0
            {
321
0
                ut.modtime = st.st_mtime;
322
0
                ut.actime  = st.st_atime;
323
0
            }
324
325
0
            image->writeMetadata();
326
327
0
            if (ret == 0)
328
0
            {
329
0
                ::utime(QFile::encodeName(filePath).constData(), &ut);
330
0
            }
331
332
0
            qCDebug(LIBKEXIV2_LOG) << "File time stamp restored";
333
0
        }
334
0
        else
335
0
        {
336
0
            image->writeMetadata();
337
0
        }
338
339
0
        return true;
340
0
    }
341
0
    catch( Exiv2::Error& e )
342
0
    {
343
0
        printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata using Exiv2 "), e);
344
0
    }
345
0
    catch(...)
346
0
    {
347
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
348
0
    }
349
350
0
    return false;
351
0
}
352
353
void KExiv2DataPrivate::clear()
354
0
{
355
0
    imageComments.clear();
356
0
    exifMetadata.clear();
357
0
    iptcMetadata.clear();
358
0
#ifdef _XMP_SUPPORT_
359
0
    xmpMetadata.clear();
360
0
#endif
361
0
}
362
363
void KExiv2Private::printExiv2ExceptionError(const QString& msg, Exiv2::Error& e)
364
14.2k
{
365
14.2k
    std::string s(e.what());
366
14.2k
    qCCritical(LIBKEXIV2_LOG) << msg.toLatin1().constData() << " (Error #"
367
14.2k
#if EXIV2_TEST_VERSION(0,28,0)
368
14.2k
                              << Exiv2::Error(e.code()).what()
369
#else
370
                              << e.code() << ": " << s.c_str()
371
#endif
372
14.2k
                              << ")";
373
14.2k
}
374
375
void KExiv2Private::printExiv2MessageHandler(int lvl, const char* msg)
376
1.10M
{
377
1.10M
    qCDebug(LIBKEXIV2_LOG) << "Exiv2 (" << lvl << ") : " << msg;
378
1.10M
}
379
380
QString KExiv2Private::convertCommentValue(const Exiv2::Exifdatum& exifDatum) const
381
0
{
382
0
    try
383
0
    {
384
0
        std::string comment;
385
0
        std::string charset;
386
387
0
        comment = exifDatum.toString();
388
389
        // libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified
390
        // Before conversion to QString, we must know the charset, so we stay with std::string for a while
391
0
        if (comment.length() > 8 && comment.substr(0, 8) == "charset=")
392
0
        {
393
            // the prepended charset specification is followed by a blank
394
0
            std::string::size_type pos = comment.find_first_of(' ');
395
396
0
            if (pos != std::string::npos)
397
0
            {
398
                // extract string between the = and the blank
399
0
                charset = comment.substr(8, pos-8);
400
                // get the rest of the string after the charset specification
401
0
                comment = comment.substr(pos+1);
402
0
            }
403
0
        }
404
405
0
        if (charset == "\"Unicode\"")
406
0
        {
407
0
            return QString::fromUtf8(comment.data());
408
0
        }
409
0
        else if (charset == "\"Jis\"")
410
0
        {
411
0
            QStringDecoder codec("JIS7");
412
0
            return codec.decode(comment.c_str());
413
0
        }
414
0
        else if (charset == "\"Ascii\"")
415
0
        {
416
0
            return QString::fromLatin1(comment.c_str());
417
0
        }
418
0
        else
419
0
        {
420
0
            return detectEncodingAndDecode(comment);
421
0
        }
422
0
    }
423
0
    catch( Exiv2::Error& e )
424
0
    {
425
0
        printExiv2ExceptionError(QString::fromLatin1("Cannot convert Comment using Exiv2 "), e);
426
0
    }
427
0
    catch(...)
428
0
    {
429
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
430
0
    }
431
432
0
    return QString();
433
0
}
434
435
QString KExiv2Private::detectEncodingAndDecode(const std::string& value) const
436
0
{
437
    // For charset autodetection, we could use sophisticated code
438
    // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent),
439
    // but that is probably too much.
440
    // We check for UTF8, Local encoding and ASCII.
441
    // Look like KEncodingDetector class can provide a full implementation for encoding detection.
442
443
0
    if (value.empty())
444
0
    {
445
0
        return QString();
446
0
    }
447
448
0
    if (isUtf8(value.c_str()))
449
0
    {
450
0
        return QString::fromUtf8(value.c_str());
451
0
    }
452
453
    // Utf8 has a pretty unique byte pattern.
454
    // Thats not true for ASCII, it is not possible
455
    // to reliably autodetect different ISO-8859 charsets.
456
    // So we can use either local encoding, or latin1.
457
458
0
    return QString::fromLocal8Bit(value.c_str());
459
0
}
460
461
bool KExiv2Private::isUtf8(const char* const buffer) const
462
0
{
463
0
    int i, n;
464
0
    unsigned char c;
465
0
    bool gotone = false;
466
467
0
    if (!buffer)
468
0
        return true;
469
470
// character never appears in text
471
0
#define F 0
472
// character appears in plain ASCII text
473
0
#define T 1
474
// character appears in ISO-8859 text
475
0
#define I 2
476
// character appears in non-ISO extended ASCII (Mac, IBM PC)
477
0
#define X 3
478
479
0
    static const unsigned char text_chars[256] =
480
0
    {
481
            //                  BEL BS HT LF    FF CR    
482
0
            F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F,  // 0x0X
483
            //                              ESC          
484
0
            F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F,  // 0x1X
485
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x2X
486
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x3X
487
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x4X
488
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x5X
489
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  // 0x6X
490
0
            T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F,  // 0x7X
491
            //            NEL                            
492
0
            X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X,  // 0x8X
493
0
            X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,  // 0x9X
494
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xaX
495
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xbX
496
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xcX
497
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xdX
498
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  // 0xeX
499
0
            I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I   // 0xfX
500
0
    };
501
502
0
    for (i = 0; (c = buffer[i]); ++i)
503
0
    {
504
0
        if ((c & 0x80) == 0)
505
0
        {
506
            // 0xxxxxxx is plain ASCII
507
508
            // Even if the whole file is valid UTF-8 sequences,
509
            // still reject it if it uses weird control characters.
510
511
0
            if (text_chars[c] != T)
512
0
                return false;
513
514
0
        }
515
0
        else if ((c & 0x40) == 0)
516
0
        {
517
            // 10xxxxxx never 1st byte
518
0
            return false;
519
0
        }
520
0
        else
521
0
        {
522
            // 11xxxxxx begins UTF-8
523
0
            int following = 0;
524
525
0
            if ((c & 0x20) == 0)
526
0
            {
527
                // 110xxxxx
528
0
                following = 1;
529
0
            }
530
0
            else if ((c & 0x10) == 0)
531
0
            {
532
                // 1110xxxx
533
0
                following = 2;
534
0
            }
535
0
            else if ((c & 0x08) == 0)
536
0
            {
537
                // 11110xxx
538
0
                following = 3;
539
0
            }
540
0
            else if ((c & 0x04) == 0)
541
0
            {
542
                // 111110xx
543
0
                following = 4;
544
0
            }
545
0
            else if ((c & 0x02) == 0)
546
0
            {
547
                // 1111110x
548
0
                following = 5;
549
0
            }
550
0
            else
551
0
            {
552
0
                return false;
553
0
            }
554
555
0
            for (n = 0; n < following; ++n)
556
0
            {
557
0
                i++;
558
559
0
                if (!(c = buffer[i]))
560
0
                    goto done;
561
562
0
                if ((c & 0x80) == 0 || (c & 0x40))
563
0
                    return false;
564
0
            }
565
            
566
0
            gotone = true;
567
0
        }
568
0
    }
569
570
0
done:
571
572
0
    return gotone;   // don't claim it's UTF-8 if it's all 7-bit.
573
0
}
574
575
#undef F
576
#undef T
577
#undef I
578
#undef X
579
580
int KExiv2Private::getXMPTagsListFromPrefix(const QString& pf, KExiv2::TagsMap& tagsMap) const
581
0
{
582
0
    int i = 0;
583
584
0
#ifdef _XMP_SUPPORT_
585
586
0
    try
587
0
    {
588
0
        QList<const Exiv2::XmpPropertyInfo*> tags;
589
0
        tags << Exiv2::XmpProperties::propertyList(pf.toLatin1().data());
590
591
0
        for (QList<const Exiv2::XmpPropertyInfo*>::iterator it = tags.begin(); it != tags.end(); ++it)
592
0
        {
593
0
            while ( (*it) && !QString::fromLatin1((*it)->name_).isNull() )
594
0
            {
595
0
                QString     key = QLatin1String( Exiv2::XmpKey( pf.toLatin1().data(), (*it)->name_ ).key().c_str() );
596
0
                QStringList values;
597
0
                values << QString::fromLatin1((*it)->name_)
598
0
                       << QString::fromLatin1((*it)->title_)
599
0
                       << QString::fromLatin1((*it)->desc_);
600
0
                tagsMap.insert(key, values);
601
0
                ++(*it);
602
0
                i++;
603
0
            }
604
0
        }
605
0
    }
606
0
    catch( Exiv2::Error& e )
607
0
    {
608
0
        printExiv2ExceptionError(QString::fromLatin1("Cannot get Xmp tags list using Exiv2 "), e);
609
0
    }
610
0
    catch(...)
611
0
    {
612
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
613
0
    }
614
615
#else
616
617
    Q_UNUSED(pf);
618
    Q_UNUSED(tagsMap);
619
620
#endif // _XMP_SUPPORT_
621
622
0
    return i;
623
0
}
624
625
#ifdef _XMP_SUPPORT_
626
#if EXIV2_TEST_VERSION(0,28,0)
627
void KExiv2Private::loadSidecarData(Exiv2::Image::UniquePtr xmpsidecar)
628
#else
629
void KExiv2Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar)
630
#endif
631
0
{
632
    // Having a sidecar is a special situation.
633
    // The sidecar data often "dominates", see in particular bug 309058 for important aspects:
634
    // If a field is removed from the sidecar, we must ignore (older) data for this field in the file.
635
636
    // First: Ignore file XMP, only use sidecar XMP
637
0
    xmpMetadata()     = xmpsidecar->xmpData();
638
0
    loadedFromSidecar = true;
639
640
    // EXIF
641
    // Four groups of properties are mapped between EXIF and XMP:
642
    // Date/Time, Description, Copyright, Creator
643
    // A few more tags are defined "writeback" tags in the XMP specification, the sidecar value therefore overrides the Exif value.
644
    // The rest is kept side-by-side.
645
    // (to understand, remember that the xmpsidecar's Exif data is actually XMP data mapped back to Exif)
646
647
    // Description, Copyright and Creator is dominated by the sidecar: Remove file Exif fields, if field not in XMP.
648
0
    ExifMergeHelper exifDominatedHelper;
649
0
    exifDominatedHelper << QLatin1String("Exif.Image.ImageDescription")
650
0
                        << QLatin1String("Exif.Photo.UserComment")
651
0
                        << QLatin1String("Exif.Image.Copyright")
652
0
                        << QLatin1String("Exif.Image.Artist");
653
0
    exifDominatedHelper.exclusiveMerge(xmpsidecar->exifData(), exifMetadata());
654
    // Date/Time and "the few more" from the XMP spec are handled as writeback
655
    // Note that Date/Time mapping is slightly contradictory in latest specs.
656
0
    ExifMergeHelper exifWritebackHelper;
657
0
    exifWritebackHelper << QLatin1String("Exif.Image.DateTime")
658
0
                        << QLatin1String("Exif.Image.DateTime")
659
0
                        << QLatin1String("Exif.Photo.DateTimeOriginal")
660
0
                        << QLatin1String("Exif.Photo.DateTimeDigitized")
661
0
                        << QLatin1String("Exif.Image.Orientation")
662
0
                        << QLatin1String("Exif.Image.XResolution")
663
0
                        << QLatin1String("Exif.Image.YResolution")
664
0
                        << QLatin1String("Exif.Image.ResolutionUnit")
665
0
                        << QLatin1String("Exif.Image.Software")
666
0
                        << QLatin1String("Exif.Photo.RelatedSoundFile");
667
0
    exifWritebackHelper.mergeFields(xmpsidecar->exifData(), exifMetadata());
668
669
    // IPTC
670
    // These fields cover almost all relevant IPTC data and are defined in the XMP specification for reconciliation.
671
0
    IptcMergeHelper iptcDominatedHelper;
672
0
    iptcDominatedHelper << QLatin1String("Iptc.Application2.ObjectName")
673
0
                        << QLatin1String("Iptc.Application2.Urgency")
674
0
                        << QLatin1String("Iptc.Application2.Category")
675
0
                        << QLatin1String("Iptc.Application2.SuppCategory")
676
0
                        << QLatin1String("Iptc.Application2.Keywords")
677
0
                        << QLatin1String("Iptc.Application2.SubLocation")
678
0
                        << QLatin1String("Iptc.Application2.SpecialInstructions")
679
0
                        << QLatin1String("Iptc.Application2.Byline")
680
0
                        << QLatin1String("Iptc.Application2.BylineTitle")
681
0
                        << QLatin1String("Iptc.Application2.City")
682
0
                        << QLatin1String("Iptc.Application2.ProvinceState")
683
0
                        << QLatin1String("Iptc.Application2.CountryCode")
684
0
                        << QLatin1String("Iptc.Application2.CountryName")
685
0
                        << QLatin1String("Iptc.Application2.TransmissionReference")
686
0
                        << QLatin1String("Iptc.Application2.Headline")
687
0
                        << QLatin1String("Iptc.Application2.Credit")
688
0
                        << QLatin1String("Iptc.Application2.Source")
689
0
                        << QLatin1String("Iptc.Application2.Copyright")
690
0
                        << QLatin1String("Iptc.Application2.Caption")
691
0
                        << QLatin1String("Iptc.Application2.Writer");
692
0
    iptcDominatedHelper.exclusiveMerge(xmpsidecar->iptcData(), iptcMetadata());
693
694
0
    IptcMergeHelper iptcWritebackHelper;
695
0
    iptcWritebackHelper << QLatin1String("Iptc.Application2.DateCreated")
696
0
                        << QLatin1String("Iptc.Application2.TimeCreated")
697
0
                        << QLatin1String("Iptc.Application2.DigitizationDate")
698
0
                        << QLatin1String("Iptc.Application2.DigitizationTime");
699
0
    iptcWritebackHelper.mergeFields(xmpsidecar->iptcData(), iptcMetadata());
700
701
    /*
702
     * TODO: Exiv2 (referring to 0.23) does not correctly synchronize all times values as given below.
703
     * Time values and their synchronization:
704
     * Original Date/Time – Creation date of the intellectual content (e.g. the photograph),
705
       rather than the creatio*n date of the content being shown
706
        Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291)
707
        IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C)
708
        XMP (photoshop:DateCreated)
709
     * Digitized Date/Time – Creation date of the digital representation
710
        Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292)
711
        IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F)
712
        XMP (xmp:CreateDate)
713
     * Modification Date/Time – Modification date of the digital image file
714
        Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290)
715
        XMP (xmp:ModifyDate)
716
     */
717
0
}
718
#endif // _XMP_SUPPORT_
719
720
}  // NameSpace KExiv2Iface
721
722
// Restore warnings
723
#if !defined(__APPLE__) && defined(__GNUC__)
724
#pragma GCC diagnostic pop
725
#endif
726
727
#if defined(__APPLE__) && defined(__clang__)
728
#pragma clang diagnostic pop
729
#endif