1 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
2 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
3 //
4 // SPDX-License-Identifier: GPL-2.0-or-later
5
6 #include "Info.h"
7
8 #include <kpabase/Logging.h>
9
10 #include <kpabase/FileName.h>
11 #include <kpabase/SettingsData.h>
12 #include <kpabase/StringSet.h>
13
14 #include <QFile>
15 #include <QFileInfo>
16 #include <QTextCodec>
17 #include <exiv2/exv_conf.h>
18 #include <exiv2/image.hpp>
19 #include <exiv2/version.hpp>
20
21 using namespace Exif;
22
23 namespace
24 {
cStringWithEncoding(const char * c_str,const QString & charset)25 QString cStringWithEncoding(const char *c_str, const QString &charset)
26 {
27 QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1());
28 if (!codec)
29 codec = QTextCodec::codecForLocale();
30 return codec->toUnicode(c_str);
31 }
32
33 } // namespace
34
35 Info *Info::s_instance = nullptr;
36
info(const DB::FileName & fileName,StringSet wantedKeys,bool returnFullExifName,const QString & charset)37 QMap<QString, QStringList> Info::info(const DB::FileName &fileName, StringSet wantedKeys, bool returnFullExifName, const QString &charset)
38 {
39 QMap<QString, QStringList> result;
40
41 try {
42 Metadata data = metadata(exifInfoFile(fileName));
43
44 for (Exiv2::ExifData::const_iterator i = data.exif.begin(); i != data.exif.end(); ++i) {
45 QString key = QString::fromLocal8Bit(i->key().c_str());
46 m_keys.insert(key);
47
48 if (wantedKeys.contains(key)) {
49 QString text = key;
50 if (!returnFullExifName)
51 text = key.split(QLatin1String(".")).last();
52
53 std::ostringstream stream;
54 stream << *i;
55 QString str(cStringWithEncoding(stream.str().c_str(), charset));
56 result[text] += str;
57 }
58 }
59
60 for (Exiv2::IptcData::const_iterator i = data.iptc.begin(); i != data.iptc.end(); ++i) {
61 QString key = QString::fromLatin1(i->key().c_str());
62 m_keys.insert(key);
63
64 if (wantedKeys.contains(key)) {
65 QString text = key;
66 if (!returnFullExifName)
67 text = key.split(QString::fromLatin1(".")).last();
68
69 std::ostringstream stream;
70 stream << *i;
71 QString str(cStringWithEncoding(stream.str().c_str(), charset));
72 result[text] += str;
73 }
74 }
75 } catch (...) {
76 }
77
78 return result;
79 }
80
instance()81 Info *Info::instance()
82 {
83 if (!s_instance)
84 s_instance = new Info;
85 return s_instance;
86 }
87
availableKeys()88 StringSet Info::availableKeys()
89 {
90 return m_keys;
91 }
92
infoForViewer(const DB::FileName & fileName,const QString & charset)93 QMap<QString, QStringList> Info::infoForViewer(const DB::FileName &fileName, const QString &charset)
94 {
95 return info(fileName, ::Settings::SettingsData::instance()->exifForViewer(), false, charset);
96 }
97
infoForDialog(const DB::FileName & fileName,const QString & charset)98 QMap<QString, QStringList> Info::infoForDialog(const DB::FileName &fileName, const QString &charset)
99 {
100 auto keys = ::Settings::SettingsData::instance()->exifForDialog();
101 if (keys.isEmpty())
102 keys = standardKeys();
103 return info(fileName, keys, true, charset);
104 }
105
standardKeys()106 StringSet Info::standardKeys()
107 {
108 static StringSet res;
109
110 if (!res.empty())
111 return res;
112
113 QList<const Exiv2::TagInfo *> tags;
114 std::ostringstream s;
115
116 const Exiv2::GroupInfo *gi = Exiv2::ExifTags::groupList();
117 while (gi->tagList_ != nullptr) {
118 Exiv2::TagListFct tl = gi->tagList_;
119 const Exiv2::TagInfo *ti = tl();
120
121 while (ti->tag_ != 0xFFFF) {
122 tags << ti;
123 ++ti;
124 }
125 ++gi;
126 }
127
128 for (QList<const Exiv2::TagInfo *>::iterator it = tags.begin(); it != tags.end(); ++it) {
129 while ((*it)->tag_ != 0xffff) {
130 res.insert(QString::fromLatin1(Exiv2::ExifKey(**it).key().c_str()));
131 ++(*it);
132 }
133 }
134
135 // IPTC tags use yet another format...
136 Exiv2::IptcDataSets::dataSetList(s);
137
138 QStringList lines = QString(QLatin1String(s.str().c_str())).split(QChar::fromLatin1('\n'));
139 for (QStringList::const_iterator it = lines.constBegin(); it != lines.constEnd(); ++it) {
140 if (it->isEmpty())
141 continue;
142 QStringList fields = it->split(QChar::fromLatin1('\t'));
143 if (fields.size() == 7) {
144 QString id = fields[4];
145 if (id.endsWith(QChar::fromLatin1(',')))
146 id.chop(1);
147 res.insert(id);
148 } else {
149 fields = it->split(QLatin1String(", "));
150 if (fields.size() >= 11) {
151 res.insert(fields[8]);
152 } else {
153 qCWarning(ExifLog) << "Unparsable output from exiv2 library: " << *it;
154 continue;
155 }
156 }
157 }
158 return res;
159 }
160
Info()161 Info::Info()
162 {
163 m_keys = standardKeys();
164 }
165
writeExifInfoToFile(const DB::FileName & srcName,const QString & destName,const QString & imageDescription)166 void Exif::writeExifInfoToFile(const DB::FileName &srcName, const QString &destName, const QString &imageDescription)
167 {
168 // Load Exif from source image
169 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(QFile::encodeName(srcName.absolute()).data());
170 image->readMetadata();
171 Exiv2::ExifData data = image->exifData();
172
173 // Modify Exif information from database.
174 data["Exif.Image.ImageDescription"] = imageDescription.toLocal8Bit().data();
175
176 image = Exiv2::ImageFactory::open(QFile::encodeName(destName).data());
177 image->setExifData(data);
178 image->writeMetadata();
179 }
180
181 /**
182 * Some Canon cameras stores Exif info in files ending in .thm, so we need to use those files for fetching Exif info
183 * if they exists.
184 */
exifInfoFile(const DB::FileName & fileName)185 DB::FileName Exif::Info::exifInfoFile(const DB::FileName &fileName)
186 {
187 QString dirName = QFileInfo(fileName.relative()).path();
188 QString baseName = QFileInfo(fileName.relative()).baseName();
189 DB::FileName name = DB::FileName::fromRelativePath(dirName + QString::fromLatin1("/") + baseName + QString::fromLatin1(".thm"));
190 if (name.exists())
191 return name;
192
193 name = DB::FileName::fromRelativePath(dirName + QString::fromLatin1("/") + baseName + QString::fromLatin1(".THM"));
194 if (name.exists())
195 return name;
196
197 return fileName;
198 }
199
metadata(const DB::FileName & fileName)200 Exif::Metadata Exif::Info::metadata(const DB::FileName &fileName)
201 {
202 try {
203 Exif::Metadata result;
204 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(QFile::encodeName(fileName.absolute()).data());
205 Q_ASSERT(image.get() != nullptr);
206 image->readMetadata();
207 result.exif = image->exifData();
208 result.iptc = image->iptcData();
209 result.comment = image->comment();
210 return result;
211 } catch (...) {
212 }
213 return Exif::Metadata();
214 }
215
216 // vi:expandtab:tabstop=4 shiftwidth=4:
217