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