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