git repos / SagoImageBrowser

blame: src/exifreader.cpp

normal view · raw

53fdab93 Poul Sander 2026-05-18 18:45 1
#include "exifreader.h"
53fdab93 Poul Sander 2026-05-18 18:45 2
80a9926d Poul Sander 2026-05-19 20:28 3
#include <QFile>
53fdab93 Poul Sander 2026-05-18 18:45 4
#include <QImageReader>
4b975496 Poul Sander 2026-06-20 22:41 5
#include <QCoreApplication>
53fdab93 Poul Sander 2026-05-18 18:45 6
53fdab93 Poul Sander 2026-05-18 18:45 7
#include <exiv2/exiv2.hpp>
53fdab93 Poul Sander 2026-05-18 18:45 8
80a9926d Poul Sander 2026-05-19 20:28 9
#include <sys/stat.h>
80a9926d Poul Sander 2026-05-19 20:28 10
#include <fcntl.h>
700d6f2d Poul Sander 2026-06-06 15:44 11
#include <filesystem>
80a9926d Poul Sander 2026-05-19 20:28 12
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 13
0eae7b4d Poul Sander 2026-05-18 19:22 14
// Raw EXIF date format is "YYYY:MM:DD HH:MM:SS"; reformat date separators to dashes
0eae7b4d Poul Sander 2026-05-18 19:22 15
static QString formatDate(const QString &raw)
0eae7b4d Poul Sander 2026-05-18 19:22 16
{
0eae7b4d Poul Sander 2026-05-18 19:22 17
    if (raw.size() >= 10 && raw[4] == u':' && raw[7] == u':')
0eae7b4d Poul Sander 2026-05-18 19:22 18
        return raw.left(4) + u'-' + raw.mid(5, 2) + u'-' + raw.mid(8, 2) + raw.mid(10);
0eae7b4d Poul Sander 2026-05-18 19:22 19
    return raw;
0eae7b4d Poul Sander 2026-05-18 19:22 20
}
0eae7b4d Poul Sander 2026-05-18 19:22 21
0eae7b4d Poul Sander 2026-05-18 19:22 22
static QString findTag(const Exiv2::ExifData &exif, const std::string &key)
0eae7b4d Poul Sander 2026-05-18 19:22 23
{
0eae7b4d Poul Sander 2026-05-18 19:22 24
    try {
0eae7b4d Poul Sander 2026-05-18 19:22 25
        Exiv2::ExifData::const_iterator it = exif.findKey(Exiv2::ExifKey(key));
0eae7b4d Poul Sander 2026-05-18 19:22 26
        if (it != exif.end())
0eae7b4d Poul Sander 2026-05-18 19:22 27
            return QString::fromStdString(it->print(&exif)).trimmed();
0eae7b4d Poul Sander 2026-05-18 19:22 28
    } catch (...) {}
0eae7b4d Poul Sander 2026-05-18 19:22 29
    return {};
0eae7b4d Poul Sander 2026-05-18 19:22 30
}
0eae7b4d Poul Sander 2026-05-18 19:22 31
0eae7b4d Poul Sander 2026-05-18 19:22 32
static void addField(QList<QPair<QString, QString>> &result, const char *label, const QString &value)
0eae7b4d Poul Sander 2026-05-18 19:22 33
{
0eae7b4d Poul Sander 2026-05-18 19:22 34
    if (!value.isEmpty())
4b975496 Poul Sander 2026-06-20 22:41 35
        result.append({QCoreApplication::translate("ExifReader", label), value});
0eae7b4d Poul Sander 2026-05-18 19:22 36
}
0eae7b4d Poul Sander 2026-05-18 19:22 37
091a4f7d Poul Sander 2026-06-09 19:54 38
static double parseGpsCoordinate(const Exiv2::ExifData &exif, const std::string &key)
091a4f7d Poul Sander 2026-06-09 19:54 39
{
091a4f7d Poul Sander 2026-06-09 19:54 40
    try {
091a4f7d Poul Sander 2026-06-09 19:54 41
        Exiv2::ExifData::const_iterator it = exif.findKey(Exiv2::ExifKey(key));
091a4f7d Poul Sander 2026-06-09 19:54 42
        if (it != exif.end() && it->count() >= 3) {
091a4f7d Poul Sander 2026-06-09 19:54 43
            double d = it->toRational(0).first / static_cast<double>(it->toRational(0).second);
091a4f7d Poul Sander 2026-06-09 19:54 44
            double m = it->toRational(1).first / static_cast<double>(it->toRational(1).second);
091a4f7d Poul Sander 2026-06-09 19:54 45
            double s = it->toRational(2).first / static_cast<double>(it->toRational(2).second);
091a4f7d Poul Sander 2026-06-09 19:54 46
            return d + m / 60.0 + s / 3600.0;
091a4f7d Poul Sander 2026-06-09 19:54 47
        }
091a4f7d Poul Sander 2026-06-09 19:54 48
    } catch (...) {}
091a4f7d Poul Sander 2026-06-09 19:54 49
    return 0.0;
091a4f7d Poul Sander 2026-06-09 19:54 50
}
091a4f7d Poul Sander 2026-06-09 19:54 51
48534775 Poul Sander 2026-06-08 18:57 52
// EXIF orientation values run 1-8 and may encode mirroring. A 90-degree
48534775 Poul Sander 2026-06-08 18:57 53
// rotation maps each value to another; the tables below cover all eight so
48534775 Poul Sander 2026-06-08 18:57 54
// mirrored images stay correct. Unknown values are treated as 1 (normal).
48534775 Poul Sander 2026-06-08 18:57 55
static int orientationRotatedClockwise(int orientation)
48534775 Poul Sander 2026-06-08 18:57 56
{
48534775 Poul Sander 2026-06-08 18:57 57
    switch (orientation) {
48534775 Poul Sander 2026-06-08 18:57 58
    case 1: return 6;
48534775 Poul Sander 2026-06-08 18:57 59
    case 2: return 7;
48534775 Poul Sander 2026-06-08 18:57 60
    case 3: return 8;
48534775 Poul Sander 2026-06-08 18:57 61
    case 4: return 5;
48534775 Poul Sander 2026-06-08 18:57 62
    case 5: return 2;
48534775 Poul Sander 2026-06-08 18:57 63
    case 6: return 3;
48534775 Poul Sander 2026-06-08 18:57 64
    case 7: return 4;
48534775 Poul Sander 2026-06-08 18:57 65
    case 8: return 1;
48534775 Poul Sander 2026-06-08 18:57 66
    default: return 6;
48534775 Poul Sander 2026-06-08 18:57 67
    }
48534775 Poul Sander 2026-06-08 18:57 68
}
48534775 Poul Sander 2026-06-08 18:57 69
48534775 Poul Sander 2026-06-08 18:57 70
static int orientationRotatedCounterClockwise(int orientation)
48534775 Poul Sander 2026-06-08 18:57 71
{
48534775 Poul Sander 2026-06-08 18:57 72
    switch (orientation) {
48534775 Poul Sander 2026-06-08 18:57 73
    case 1: return 8;
48534775 Poul Sander 2026-06-08 18:57 74
    case 2: return 5;
48534775 Poul Sander 2026-06-08 18:57 75
    case 3: return 6;
48534775 Poul Sander 2026-06-08 18:57 76
    case 4: return 7;
48534775 Poul Sander 2026-06-08 18:57 77
    case 5: return 4;
48534775 Poul Sander 2026-06-08 18:57 78
    case 6: return 1;
48534775 Poul Sander 2026-06-08 18:57 79
    case 7: return 2;
48534775 Poul Sander 2026-06-08 18:57 80
    case 8: return 3;
48534775 Poul Sander 2026-06-08 18:57 81
    default: return 8;
48534775 Poul Sander 2026-06-08 18:57 82
    }
48534775 Poul Sander 2026-06-08 18:57 83
}
48534775 Poul Sander 2026-06-08 18:57 84
53fdab93 Poul Sander 2026-05-18 18:45 85
bool ExifData::isEmpty() const
53fdab93 Poul Sander 2026-05-18 18:45 86
{
beb4a823 Poul Sander 2026-05-19 21:53 87
    return captionAbstract.isEmpty() && description.isEmpty()
beb4a823 Poul Sander 2026-05-19 21:53 88
        && dateTime.isEmpty() && make.isEmpty() && model.isEmpty()
53fdab93 Poul Sander 2026-05-18 18:45 89
        && exposureTime.isEmpty() && fNumber.isEmpty() && iso.isEmpty()
53fdab93 Poul Sander 2026-05-18 18:45 90
        && focalLength.isEmpty() && flash.isEmpty()
091a4f7d Poul Sander 2026-06-09 19:54 91
        && filename.isEmpty() && dimensions.isEmpty() && fileSize.isEmpty()
091a4f7d Poul Sander 2026-06-09 19:54 92
        && orientation.isEmpty() && latitude.isEmpty() && longitude.isEmpty()
091a4f7d Poul Sander 2026-06-09 19:54 93
        && osmLink.isEmpty();
53fdab93 Poul Sander 2026-05-18 18:45 94
}
53fdab93 Poul Sander 2026-05-18 18:45 95
53fdab93 Poul Sander 2026-05-18 18:45 96
QList<QPair<QString, QString>> ExifData::toList() const
53fdab93 Poul Sander 2026-05-18 18:45 97
{
53fdab93 Poul Sander 2026-05-18 18:45 98
    QList<QPair<QString, QString>> result;
4b975496 Poul Sander 2026-06-20 22:41 99
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Caption"),      captionAbstract);
4b975496 Poul Sander 2026-06-20 22:41 100
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Description"),  description);
4b975496 Poul Sander 2026-06-20 22:41 101
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Date/Time"),    dateTime);
4b975496 Poul Sander 2026-06-20 22:41 102
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Camera Make"),  make);
4b975496 Poul Sander 2026-06-20 22:41 103
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Camera Model"), model);
4b975496 Poul Sander 2026-06-20 22:41 104
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Exposure"),     exposureTime);
4b975496 Poul Sander 2026-06-20 22:41 105
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Aperture"),     fNumber);
4b975496 Poul Sander 2026-06-20 22:41 106
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "ISO"),          iso);
4b975496 Poul Sander 2026-06-20 22:41 107
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Focal Length"), focalLength);
4b975496 Poul Sander 2026-06-20 22:41 108
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Flash"),        flash);
4b975496 Poul Sander 2026-06-20 22:41 109
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Filename"),     filename);
4b975496 Poul Sander 2026-06-20 22:41 110
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Dimensions"),   dimensions);
4b975496 Poul Sander 2026-06-20 22:41 111
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "File Size"),    fileSize);
4b975496 Poul Sander 2026-06-20 22:41 112
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Orientation"),  orientation);
4b975496 Poul Sander 2026-06-20 22:41 113
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Latitude"),     latitude);
4b975496 Poul Sander 2026-06-20 22:41 114
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "Longitude"),    longitude);
4b975496 Poul Sander 2026-06-20 22:41 115
    addField(result, QT_TRANSLATE_NOOP("ExifReader", "OpenStreetMap"), osmLink);
53fdab93 Poul Sander 2026-05-18 18:45 116
    return result;
53fdab93 Poul Sander 2026-05-18 18:45 117
}
53fdab93 Poul Sander 2026-05-18 18:45 118
80a9926d Poul Sander 2026-05-19 20:28 119
ExifData ExifReader::read(const QByteArray &path)
53fdab93 Poul Sander 2026-05-18 18:45 120
{
53fdab93 Poul Sander 2026-05-18 18:45 121
    ExifData data;
53fdab93 Poul Sander 2026-05-18 18:45 122
80a9926d Poul Sander 2026-05-19 20:28 123
    struct ::stat st{};
80a9926d Poul Sander 2026-05-19 20:28 124
    if (::stat(path.constData(), &st) != 0 || !S_ISREG(st.st_mode))
53fdab93 Poul Sander 2026-05-18 18:45 125
        return data;
53fdab93 Poul Sander 2026-05-18 18:45 126
700d6f2d Poul Sander 2026-06-06 15:44 127
    // Filename
700d6f2d Poul Sander 2026-06-06 15:44 128
    {
700d6f2d Poul Sander 2026-06-06 15:44 129
        const std::filesystem::path fsPath(path.toStdString());
700d6f2d Poul Sander 2026-06-06 15:44 130
        const std::string nativeName = fsPath.filename().native();
700d6f2d Poul Sander 2026-06-06 15:44 131
        data.filename = QString::fromLocal8Bit(
700d6f2d Poul Sander 2026-06-06 15:44 132
            nativeName.data(), static_cast<qsizetype>(nativeName.size()));
700d6f2d Poul Sander 2026-06-06 15:44 133
    }
700d6f2d Poul Sander 2026-06-06 15:44 134
53fdab93 Poul Sander 2026-05-18 18:45 135
    // File size
53fdab93 Poul Sander 2026-05-18 18:45 136
    {
80a9926d Poul Sander 2026-05-19 20:28 137
        qint64 size = static_cast<qint64>(st.st_size);
53fdab93 Poul Sander 2026-05-18 18:45 138
        if (size < 1024)
53fdab93 Poul Sander 2026-05-18 18:45 139
            data.fileSize = QString::number(size) + QLatin1String(" B");
53fdab93 Poul Sander 2026-05-18 18:45 140
        else if (size < 1024 * 1024)
53fdab93 Poul Sander 2026-05-18 18:45 141
            data.fileSize = QString::number(size / 1024.0, 'f', 1) + QLatin1String(" KiB");
53fdab93 Poul Sander 2026-05-18 18:45 142
        else
53fdab93 Poul Sander 2026-05-18 18:45 143
            data.fileSize = QString::number(size / (1024.0 * 1024.0), 'f', 1) + QLatin1String(" MiB");
53fdab93 Poul Sander 2026-05-18 18:45 144
    }
53fdab93 Poul Sander 2026-05-18 18:45 145
53fdab93 Poul Sander 2026-05-18 18:45 146
    // Image dimensions via Qt (reads only the header, no full decode needed)
53fdab93 Poul Sander 2026-05-18 18:45 147
    {
80a9926d Poul Sander 2026-05-19 20:28 148
        int fd = ::open(path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 149
        if (fd >= 0) {
80a9926d Poul Sander 2026-05-19 20:28 150
            QFile file;
80a9926d Poul Sander 2026-05-19 20:28 151
            if (file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
80a9926d Poul Sander 2026-05-19 20:28 152
                QImageReader reader(&file);
80a9926d Poul Sander 2026-05-19 20:28 153
                reader.setDecideFormatFromContent(true);
80a9926d Poul Sander 2026-05-19 20:28 154
                QSize sz = reader.size();
80a9926d Poul Sander 2026-05-19 20:28 155
                        if (sz.isValid())
80a9926d Poul Sander 2026-05-19 20:28 156
                    data.dimensions = QString::fromLatin1("%1 * %2")
80a9926d Poul Sander 2026-05-19 20:28 157
                                          .arg(sz.width()).arg(sz.height());
80a9926d Poul Sander 2026-05-19 20:28 158
            } else {
80a9926d Poul Sander 2026-05-19 20:28 159
                ::close(fd);
80a9926d Poul Sander 2026-05-19 20:28 160
            }
80a9926d Poul Sander 2026-05-19 20:28 161
        }
53fdab93 Poul Sander 2026-05-18 18:45 162
    }
53fdab93 Poul Sander 2026-05-18 18:45 163
53fdab93 Poul Sander 2026-05-18 18:45 164
    // EXIF tags via exiv2
53fdab93 Poul Sander 2026-05-18 18:45 165
    try {
80a9926d Poul Sander 2026-05-19 20:28 166
        std::unique_ptr<Exiv2::Image> image =
80a9926d Poul Sander 2026-05-19 20:28 167
            Exiv2::ImageFactory::open(path.toStdString());
53fdab93 Poul Sander 2026-05-18 18:45 168
        image->readMetadata();
53fdab93 Poul Sander 2026-05-18 18:45 169
        const Exiv2::ExifData &exif = image->exifData();
53fdab93 Poul Sander 2026-05-18 18:45 170
0eae7b4d Poul Sander 2026-05-18 19:22 171
        data.dateTime = formatDate(findTag(exif, "Exif.Photo.DateTimeOriginal"));
53fdab93 Poul Sander 2026-05-18 18:45 172
        if (data.dateTime.isEmpty())
0eae7b4d Poul Sander 2026-05-18 19:22 173
            data.dateTime = formatDate(findTag(exif, "Exif.Image.DateTime"));
0eae7b4d Poul Sander 2026-05-18 19:22 174
        data.make         = findTag(exif, "Exif.Image.Make");
0eae7b4d Poul Sander 2026-05-18 19:22 175
        data.model        = findTag(exif, "Exif.Image.Model");
0eae7b4d Poul Sander 2026-05-18 19:22 176
        data.exposureTime = findTag(exif, "Exif.Photo.ExposureTime");
0eae7b4d Poul Sander 2026-05-18 19:22 177
        data.fNumber      = findTag(exif, "Exif.Photo.FNumber");
0eae7b4d Poul Sander 2026-05-18 19:22 178
        data.iso          = findTag(exif, "Exif.Photo.ISOSpeedRatings");
0eae7b4d Poul Sander 2026-05-18 19:22 179
        data.focalLength  = findTag(exif, "Exif.Photo.FocalLength");
0eae7b4d Poul Sander 2026-05-18 19:22 180
        data.flash        = findTag(exif, "Exif.Photo.Flash");
beb4a823 Poul Sander 2026-05-19 21:53 181
        data.description  = findTag(exif, "Exif.Image.ImageDescription");
091a4f7d Poul Sander 2026-06-09 19:54 182
        data.orientation  = findTag(exif, "Exif.Image.Orientation");
091a4f7d Poul Sander 2026-06-09 19:54 183
091a4f7d Poul Sander 2026-06-09 19:54 184
        // GPS
091a4f7d Poul Sander 2026-06-09 19:54 185
        QString latStr = findTag(exif, "Exif.GPSInfo.GPSLatitude");
091a4f7d Poul Sander 2026-06-09 19:54 186
        QString latRef = findTag(exif, "Exif.GPSInfo.GPSLatitudeRef");
091a4f7d Poul Sander 2026-06-09 19:54 187
        if (!latStr.isEmpty() && !latRef.isEmpty()) {
e2a71efb Poul Sander 2026-06-09 21:42 188
            data.latitude = latStr + " " + latRef;
091a4f7d Poul Sander 2026-06-09 19:54 189
        }
091a4f7d Poul Sander 2026-06-09 19:54 190
091a4f7d Poul Sander 2026-06-09 19:54 191
        QString lonStr = findTag(exif, "Exif.GPSInfo.GPSLongitude");
091a4f7d Poul Sander 2026-06-09 19:54 192
        QString lonRef = findTag(exif, "Exif.GPSInfo.GPSLongitudeRef");
091a4f7d Poul Sander 2026-06-09 19:54 193
        if (!lonStr.isEmpty() && !lonRef.isEmpty()) {
e2a71efb Poul Sander 2026-06-09 21:42 194
            data.longitude = lonStr + " " + lonRef;
091a4f7d Poul Sander 2026-06-09 19:54 195
        }
091a4f7d Poul Sander 2026-06-09 19:54 196
091a4f7d Poul Sander 2026-06-09 19:54 197
        if (!data.latitude.isEmpty() && !data.longitude.isEmpty()) {
091a4f7d Poul Sander 2026-06-09 19:54 198
            double lat = parseGpsCoordinate(exif, "Exif.GPSInfo.GPSLatitude");
e2a71efb Poul Sander 2026-06-09 21:42 199
            if (latRef == "S") lat = -lat;
091a4f7d Poul Sander 2026-06-09 19:54 200
            double lon = parseGpsCoordinate(exif, "Exif.GPSInfo.GPSLongitude");
e2a71efb Poul Sander 2026-06-09 21:42 201
            if (lonRef == "W") lon = -lon;
091a4f7d Poul Sander 2026-06-09 19:54 202
            data.osmLink = QString::fromLatin1("https://www.openstreetmap.org/?mlat=%1&mlon=%2#map=16/%1/%2")
091a4f7d Poul Sander 2026-06-09 19:54 203
                               .arg(lat, 0, 'f', 6).arg(lon, 0, 'f', 6);
091a4f7d Poul Sander 2026-06-09 19:54 204
        }
beb4a823 Poul Sander 2026-05-19 21:53 205
beb4a823 Poul Sander 2026-05-19 21:53 206
        // IPTC Caption-Abstract
beb4a823 Poul Sander 2026-05-19 21:53 207
        const Exiv2::IptcData &iptc = image->iptcData();
beb4a823 Poul Sander 2026-05-19 21:53 208
        auto iptcIt = iptc.findKey(Exiv2::IptcKey("Iptc.Application2.Caption"));
beb4a823 Poul Sander 2026-05-19 21:53 209
        if (iptcIt != iptc.end())
beb4a823 Poul Sander 2026-05-19 21:53 210
            data.captionAbstract = QString::fromStdString(iptcIt->toString()).trimmed();
53fdab93 Poul Sander 2026-05-18 18:45 211
    } catch (const Exiv2::Error &) {
53fdab93 Poul Sander 2026-05-18 18:45 212
        // No EXIF or unsupported format — leave tags empty
53fdab93 Poul Sander 2026-05-18 18:45 213
    }
53fdab93 Poul Sander 2026-05-18 18:45 214
53fdab93 Poul Sander 2026-05-18 18:45 215
    return data;
53fdab93 Poul Sander 2026-05-18 18:45 216
}
beb4a823 Poul Sander 2026-05-19 21:53 217
beb4a823 Poul Sander 2026-05-19 21:53 218
bool ExifReader::saveCaption(const QByteArray &path,
beb4a823 Poul Sander 2026-05-19 21:53 219
                             const QString    &caption,
beb4a823 Poul Sander 2026-05-19 21:53 220
                             const ExifData   &oldData)
beb4a823 Poul Sander 2026-05-19 21:53 221
{
beb4a823 Poul Sander 2026-05-19 21:53 222
    try {
beb4a823 Poul Sander 2026-05-19 21:53 223
        std::unique_ptr<Exiv2::Image> image =
beb4a823 Poul Sander 2026-05-19 21:53 224
            Exiv2::ImageFactory::open(path.toStdString());
beb4a823 Poul Sander 2026-05-19 21:53 225
        image->readMetadata();
beb4a823 Poul Sander 2026-05-19 21:53 226
beb4a823 Poul Sander 2026-05-19 21:53 227
        // Erase all existing Caption-Abstract entries then add the new one
beb4a823 Poul Sander 2026-05-19 21:53 228
        Exiv2::IptcData &iptc = image->iptcData();
beb4a823 Poul Sander 2026-05-19 21:53 229
        {
beb4a823 Poul Sander 2026-05-19 21:53 230
            Exiv2::IptcKey key("Iptc.Application2.Caption");
beb4a823 Poul Sander 2026-05-19 21:53 231
            auto it = iptc.findKey(key);
beb4a823 Poul Sander 2026-05-19 21:53 232
            while (it != iptc.end()) {
beb4a823 Poul Sander 2026-05-19 21:53 233
                it = iptc.erase(it);
beb4a823 Poul Sander 2026-05-19 21:53 234
                it = iptc.findKey(key);
beb4a823 Poul Sander 2026-05-19 21:53 235
            }
beb4a823 Poul Sander 2026-05-19 21:53 236
        }
beb4a823 Poul Sander 2026-05-19 21:53 237
        Exiv2::Iptcdatum datum(Exiv2::IptcKey("Iptc.Application2.Caption"));
beb4a823 Poul Sander 2026-05-19 21:53 238
        datum.setValue(caption.toStdString());
beb4a823 Poul Sander 2026-05-19 21:53 239
        iptc.add(datum);
beb4a823 Poul Sander 2026-05-19 21:53 240
beb4a823 Poul Sander 2026-05-19 21:53 241
        // If Caption-Abstract and Description were previously in sync, keep them in sync
beb4a823 Poul Sander 2026-05-19 21:53 242
        if (oldData.captionAbstract == oldData.description) {
beb4a823 Poul Sander 2026-05-19 21:53 243
            image->exifData()["Exif.Image.ImageDescription"] = caption.toStdString();
beb4a823 Poul Sander 2026-05-19 21:53 244
        }
beb4a823 Poul Sander 2026-05-19 21:53 245
beb4a823 Poul Sander 2026-05-19 21:53 246
        image->writeMetadata();
beb4a823 Poul Sander 2026-05-19 21:53 247
        return true;
beb4a823 Poul Sander 2026-05-19 21:53 248
    } catch (const Exiv2::Error &) {
beb4a823 Poul Sander 2026-05-19 21:53 249
        return false;
beb4a823 Poul Sander 2026-05-19 21:53 250
    }
beb4a823 Poul Sander 2026-05-19 21:53 251
}
48534775 Poul Sander 2026-06-08 18:57 252
48534775 Poul Sander 2026-06-08 18:57 253
bool ExifReader::rotate(const QByteArray &path, bool clockwise)
48534775 Poul Sander 2026-06-08 18:57 254
{
48534775 Poul Sander 2026-06-08 18:57 255
    try {
48534775 Poul Sander 2026-06-08 18:57 256
        std::unique_ptr<Exiv2::Image> image =
48534775 Poul Sander 2026-06-08 18:57 257
            Exiv2::ImageFactory::open(path.toStdString());
48534775 Poul Sander 2026-06-08 18:57 258
        image->readMetadata();
48534775 Poul Sander 2026-06-08 18:57 259
        Exiv2::ExifData &exif = image->exifData();
48534775 Poul Sander 2026-06-08 18:57 260
48534775 Poul Sander 2026-06-08 18:57 261
        int current = 1; // EXIF default when the tag is absent
48534775 Poul Sander 2026-06-08 18:57 262
        auto it = exif.findKey(Exiv2::ExifKey("Exif.Image.Orientation"));
48534775 Poul Sander 2026-06-08 18:57 263
        if (it != exif.end())
48534775 Poul Sander 2026-06-08 18:57 264
            current = static_cast<int>(it->toInt64());
48534775 Poul Sander 2026-06-08 18:57 265
48534775 Poul Sander 2026-06-08 18:57 266
        const int updated = clockwise ? orientationRotatedClockwise(current)
48534775 Poul Sander 2026-06-08 18:57 267
                                      : orientationRotatedCounterClockwise(current);
48534775 Poul Sander 2026-06-08 18:57 268
        exif["Exif.Image.Orientation"] = static_cast<uint16_t>(updated);
48534775 Poul Sander 2026-06-08 18:57 269
48534775 Poul Sander 2026-06-08 18:57 270
        image->writeMetadata();
48534775 Poul Sander 2026-06-08 18:57 271
        return true;
48534775 Poul Sander 2026-06-08 18:57 272
    } catch (const Exiv2::Error &) {
48534775 Poul Sander 2026-06-08 18:57 273
        return false;
48534775 Poul Sander 2026-06-08 18:57 274
    }
48534775 Poul Sander 2026-06-08 18:57 275
}
1970-01-01 00:00 276