File indexing completed on 2025-01-19 03:56:03
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2020-11-28 0007 * Description : ExifTool process stream parser. 0008 * 0009 * SPDX-FileCopyrightText: 2020-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "exiftoolparser_p.h" 0016 0017 // Local includes 0018 0019 #include "digikam_globals.h" 0020 0021 namespace Digikam 0022 { 0023 0024 void ExifToolParser::printExifToolOutput(const QByteArray& stdOut) 0025 { 0026 qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifTool output:"; 0027 qCDebug(DIGIKAM_METAENGINE_LOG) << "---"; 0028 qCDebug(DIGIKAM_METAENGINE_LOG).noquote() << stdOut; 0029 qCDebug(DIGIKAM_METAENGINE_LOG) << "---"; 0030 } 0031 0032 void ExifToolParser::cmdCompleted(const ExifToolProcess::Result& result) 0033 { 0034 qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifTool complete command for action" 0035 << d->actionString(result.cmdAction) 0036 << "with elasped time (ms):" 0037 << result.elapsed; 0038 0039 ExifToolData exifToolData; 0040 0041 switch (result.cmdAction) 0042 { 0043 case ExifToolProcess::LOAD_METADATA: 0044 { 0045 // Convert JSON array as QVariantMap 0046 0047 QJsonDocument jsonDoc = QJsonDocument::fromJson(result.output); 0048 QJsonArray jsonArray = jsonDoc.array(); 0049 0050 if (jsonArray.size() == 0) 0051 { 0052 qCDebug(DIGIKAM_METAENGINE_LOG) << "Json Array size is null"; 0053 0054 Q_EMIT signalExifToolDataAvailable(); 0055 0056 if (d->async) 0057 { 0058 Q_EMIT signalExifToolAsyncData(exifToolData); 0059 } 0060 0061 return; 0062 } 0063 0064 QJsonObject jsonObject = jsonArray.at(0).toObject(); 0065 QVariantMap metadataMap = jsonObject.toVariantMap(); 0066 0067 qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifTool Json map size:" << metadataMap.size(); 0068 0069 for (QVariantMap::const_iterator it = metadataMap.constBegin() ; 0070 it != metadataMap.constEnd() ; ++it) 0071 { 0072 QString tagNameExifTool; 0073 QString tagType; 0074 QStringList sections = it.key().split(QLatin1Char(':')); 0075 0076 if (sections.size() == 6) // With ExifTool > 12.00 (at least under Windows or MacOS), groups are return with 6 sections. 0077 { 0078 tagNameExifTool = QString::fromLatin1("%1.%2.%3.%4") 0079 .arg(sections[0]) 0080 .arg(sections[1]) 0081 .arg(sections[2]) 0082 .arg(sections[5]); 0083 tagType = sections[4]; 0084 } 0085 else if (sections.size() == 5) // ExifTool 12.00 under Linux return 5 or 4 sections. 0086 { 0087 tagNameExifTool = QString::fromLatin1("%1.%2.%3.%4") 0088 .arg(sections[0]) 0089 .arg(sections[1]) 0090 .arg(sections[2]) 0091 .arg(sections[4]); 0092 tagType = sections[3]; 0093 } 0094 else if (sections.size() == 4) 0095 { 0096 tagNameExifTool = QString::fromLatin1("%1.%2.%3.%4") 0097 .arg(sections[0]) 0098 .arg(sections[1]) 0099 .arg(sections[2]) 0100 .arg(sections[3]); 0101 } 0102 else if (sections[0] == QLatin1String("SourceFile")) 0103 { 0104 d->currentPath = it.value().toString(); 0105 continue; 0106 } 0107 else 0108 { 0109 continue; 0110 } 0111 0112 QVariantMap propsMap = it.value().toMap(); 0113 QString data; 0114 0115 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0116 0117 if (propsMap.find(QLatin1String("val")).value().typeId() == QVariant::List) 0118 0119 #else 0120 0121 if (propsMap.find(QLatin1String("val")).value().type() == QVariant::List) 0122 0123 #endif 0124 0125 { 0126 QStringList list = propsMap.find(QLatin1String("val")).value().toStringList(); 0127 data = list.join(QLatin1String(", ")); 0128 } 0129 else 0130 { 0131 data = propsMap.find(QLatin1String("val")).value().toString(); 0132 } 0133 0134 QString desc = propsMap.find(QLatin1String("desc")).value().toString(); 0135 0136 // Optional numerical value extraction, if any 0137 0138 QString num; 0139 QVariantMap::iterator it2 = propsMap.find(QLatin1String("num")); 0140 0141 if (it2 != propsMap.end()) 0142 { 0143 num = it2.value().toString(); 0144 } 0145 /* 0146 qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifTool json property:" << tagNameExifTool << data; 0147 */ 0148 0149 if (data.startsWith(QLatin1String("(Binary data ")) && 0150 data.endsWith(QLatin1String(", use -b option to extract)"))) 0151 { 0152 data = data.section(QLatin1Char(','), 0, 0); 0153 data.remove(QLatin1Char('(')); 0154 } 0155 0156 if (exifToolData.contains(tagNameExifTool)) 0157 { 0158 QString existData = exifToolData[tagNameExifTool][0].toString(); 0159 existData += QLatin1String(", ") + data; 0160 exifToolData[tagNameExifTool][0] = existData; 0161 } 0162 else 0163 { 0164 exifToolData.insert(tagNameExifTool, QVariantList() 0165 << data // ExifTool Raw data as string. 0166 << tagType // ExifTool data type. 0167 << desc // ExifTool tag description. 0168 << num); // ExifTool numeral value if any. 0169 } 0170 } 0171 0172 break; 0173 } 0174 0175 case ExifToolProcess::LOAD_CHUNKS: 0176 { 0177 qCDebug(DIGIKAM_METAENGINE_LOG) << "EXV chunk size:" << result.output.size(); 0178 0179 exifToolData.insert(QLatin1String("EXV"), QVariantList() << result.output); // Exv chunk as bytearray. 0180 break; 0181 } 0182 0183 case ExifToolProcess::APPLY_CHANGES: 0184 { 0185 printExifToolOutput(result.output); 0186 break; 0187 } 0188 0189 case ExifToolProcess::APPLY_CHANGES_EXV: 0190 { 0191 printExifToolOutput(result.output); 0192 break; 0193 } 0194 0195 case ExifToolProcess::APPLY_METADATA_FILE: 0196 { 0197 printExifToolOutput(result.output); 0198 break; 0199 } 0200 0201 case ExifToolProcess::COPY_TAGS: 0202 { 0203 printExifToolOutput(result.output); 0204 break; 0205 } 0206 0207 case ExifToolProcess::TRANS_TAGS: 0208 { 0209 printExifToolOutput(result.output); 0210 0211 if (!d->argsFile.isOpen() && d->argsFile.exists()) 0212 { 0213 d->argsFile.remove(); 0214 } 0215 0216 break; 0217 } 0218 0219 case ExifToolProcess::READ_FORMATS: 0220 { 0221 // Remove first line 0222 0223 QString out = QString::fromUtf8(result.output).section(QLatin1Char('\n'), 1, -1); 0224 0225 // Get extensions and descriptions as pair of strings 0226 0227 QStringList lines = out.split(QLatin1Char('\n'), QT_SKIP_EMPTY_PARTS); 0228 QStringList lst; 0229 QString s; 0230 0231 Q_FOREACH (const QString& ln, lines) 0232 { 0233 s = ln.simplified(); 0234 QString ext = s.section(QLatin1Char(' '), 0, 0); 0235 QString desc = s.section(QLatin1Char(' '), 1, -1); 0236 lst << ext << desc; 0237 } 0238 0239 0240 exifToolData.insert(QLatin1String("READ_FORMATS"), QVariantList() << lst); 0241 break; 0242 } 0243 0244 case ExifToolProcess::WRITE_FORMATS: 0245 { 0246 // Remove first line 0247 0248 QString out = QString::fromUtf8(result.output).section(QLatin1Char('\n'), 1, -1); 0249 0250 // Get extensions and descriptions as pair of strings 0251 0252 QStringList lines = out.split(QLatin1Char('\n'), QT_SKIP_EMPTY_PARTS); 0253 QStringList lst; 0254 QString s; 0255 0256 Q_FOREACH (const QString& ln, lines) 0257 { 0258 s = ln.simplified(); 0259 QString ext = s.section(QLatin1Char(' '), 0, 0); 0260 QString desc = s.section(QLatin1Char(' '), 1, -1); 0261 lst << ext << desc; 0262 } 0263 0264 exifToolData.insert(QLatin1String("WRITE_FORMATS"), QVariantList() << lst); 0265 break; 0266 } 0267 0268 case ExifToolProcess::TRANSLATIONS_LIST: 0269 { 0270 // Remove first line 0271 0272 QString out = QString::fromUtf8(result.output).section(QLatin1Char('\n'), 1, -1); 0273 0274 // Get i18n list 0275 0276 QStringList lines = out.split(QLatin1Char('\n'), QT_SKIP_EMPTY_PARTS); 0277 QStringList lst; 0278 0279 Q_FOREACH (const QString& ln, lines) 0280 { 0281 lst << ln.simplified().section(QLatin1String(" - "), 0, 0); 0282 } 0283 0284 exifToolData.insert(QLatin1String("TRANSLATIONS_LIST"), QVariantList() << lst); 0285 break; 0286 } 0287 0288 case ExifToolProcess::TAGS_DATABASE: 0289 { 0290 QString xml = QString::fromUtf8(result.output); 0291 0292 QDomDocument doc; 0293 0294 if (doc.setContent(xml)) 0295 { 0296 QDomElement docElem = doc.documentElement(); 0297 0298 if (docElem.tagName() == QLatin1String("taginfo")) 0299 { 0300 for (QDomNode n1 = docElem.firstChild() ; !n1.isNull() ; n1 = n1.nextSibling()) 0301 { 0302 QDomElement e1 = n1.toElement(); 0303 0304 if (!e1.isNull()) 0305 { 0306 if (e1.tagName() == QLatin1String("table")) // Top level group 0307 { 0308 QString g0 = e1.attribute(QLatin1String("g0")); 0309 QString g1 = e1.attribute(QLatin1String("g1")); 0310 QString g2 = e1.attribute(QLatin1String("g2")); 0311 QString type; 0312 QString writable; 0313 QString tag; 0314 QString mainDesc; 0315 QString desc; 0316 0317 for (QDomNode n2 = e1.firstChild() ; !n2.isNull() ; n2 = n2.nextSibling()) 0318 { 0319 QDomElement e2 = n2.toElement(); 0320 0321 if (!e2.isNull()) 0322 { 0323 if (e2.tagName() == QLatin1String("desc")) // Main description of group 0324 { 0325 if (e2.attribute(QLatin1String("lang")) == QLatin1String("en")) 0326 { 0327 mainDesc = e2.text(); 0328 } 0329 0330 continue; 0331 } 0332 else if (e2.tagName() == QLatin1String("tag")) // One tag from group 0333 { 0334 QString a1 = e2.attribute(QLatin1String("g1")); 0335 QString a2 = e2.attribute(QLatin1String("g2")); 0336 QString name = e2.attribute(QLatin1String("name")); 0337 tag = QString::fromLatin1("%1.%2.%3.%4").arg(g0) 0338 .arg(a1.isNull() ? g1 : a1) 0339 .arg(a2.isNull() ? g2 : a2) 0340 .arg(name); 0341 type = e2.attribute(QLatin1String("type")); 0342 writable = e2.attribute(QLatin1String("writable")); 0343 0344 for (QDomNode n3 = e2.firstChild() ; !n3.isNull() ; n3 = n3.nextSibling()) 0345 { 0346 QDomElement e3 = n3.toElement(); 0347 0348 if (!e3.isNull()) 0349 { 0350 if (e3.tagName() == QLatin1String("desc")) // Description of tag 0351 { 0352 if (e3.attribute(QLatin1String("lang")) == QLatin1String("en")) 0353 { 0354 desc = e3.text(); 0355 break; 0356 } 0357 } 0358 } 0359 } 0360 } 0361 } 0362 0363 exifToolData.insert(tag, 0364 QVariantList() 0365 << QString::fromLatin1("%1 - %2").arg(mainDesc).arg(desc) 0366 << type 0367 << writable 0368 ); 0369 } 0370 } 0371 } 0372 } 0373 } 0374 } 0375 0376 break; 0377 } 0378 0379 case ExifToolProcess::VERSION_STRING: 0380 { 0381 QString out = QString::fromUtf8(result.output); 0382 QStringList lines = out.split(QLatin1Char('\n'), QT_SKIP_EMPTY_PARTS); 0383 0384 if (!lines.isEmpty()) 0385 { 0386 exifToolData.insert(QLatin1String("VERSION_STRING"), QVariantList() << lines.first()); 0387 } 0388 0389 break; 0390 } 0391 0392 default: 0393 { 0394 break; 0395 } 0396 } 0397 0398 qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifTool parsed command for action" << d->actionString(result.cmdAction) 0399 << exifToolData.count() << "properties decoded"; 0400 0401 d->exifToolData = exifToolData; 0402 0403 Q_EMIT signalExifToolDataAvailable(); 0404 0405 if (d->async) 0406 { 0407 Q_EMIT signalExifToolAsyncData(exifToolData); 0408 } 0409 } 0410 0411 void ExifToolParser::errorOccurred(const ExifToolProcess::Result& result, 0412 QProcess::ProcessError error, 0413 const QString& description) 0414 { 0415 qCWarning(DIGIKAM_METAENGINE_LOG) << "ExifTool process for action" 0416 << d->actionString(result.cmdAction) 0417 << "exited with error:" << error; 0418 0419 d->errorString = description; 0420 0421 Q_EMIT signalExifToolDataAvailable(); 0422 } 0423 0424 void ExifToolParser::finished() 0425 { 0426 Q_EMIT signalExifToolDataAvailable(); 0427 } 0428 0429 void ExifToolParser::slotExifToolResult(int cmdId) 0430 { 0431 { 0432 QMutexLocker locker(&d->mutex); 0433 0434 if (!d->asyncRunning.contains(cmdId)) 0435 { 0436 return; 0437 } 0438 0439 d->asyncRunning.removeAll(cmdId); 0440 } 0441 0442 d->jumpToResultCommand(d->proc->getExifToolResult(cmdId), cmdId); 0443 } 0444 0445 void ExifToolParser::setOutputStream(int cmdAction, 0446 const QByteArray& cmdOutputChannel, 0447 const QByteArray& /*cmdErrorChannel*/) 0448 { 0449 ExifToolProcess::Result result; 0450 0451 result.cmdAction = cmdAction; 0452 result.output = cmdOutputChannel; 0453 0454 cmdCompleted(result); 0455 } 0456 0457 } // namespace Digikam