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 bool ExifToolParser::load(const QString& path) 0025 { 0026 QFileInfo fileInfo(path); 0027 0028 if (!fileInfo.exists()) 0029 { 0030 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0031 return false; 0032 } 0033 0034 d->prepareProcess(); 0035 0036 // Build command (get metadata as JSON array) 0037 0038 QByteArrayList cmdArgs; 0039 cmdArgs << QByteArray("-json"); 0040 cmdArgs << QByteArray("-G:0:1:2:4:6"); 0041 cmdArgs << QByteArray("-l"); 0042 0043 /* 0044 TODO: better i18n support from ExifTool. 0045 ExifTool Translations are limited to few languages, and passing a non supported code to ExifTool 0046 returns an error. So we needs a mechanism to get the complete list of cuurent i18n code supported, 0047 typically "exiftool -lang". 0048 0049 // Forward GUI language code to get translated string from ExifTool (typically tags descriptions) 0050 0051 QStringList langs = QLocale().uiLanguages(); 0052 QByteArray lg; 0053 0054 if (!langs.isEmpty()) 0055 { 0056 lg = langs.first().section(QLatin1Char('-'), 0, 0).toLatin1(); 0057 } 0058 0059 qCDebug(DIGIKAM_METAENGINE_LOG) << "UI language code:" << lg; 0060 0061 if (!lg.isEmpty()) 0062 { 0063 cmdArgs << QByteArray("-lang") << lg; 0064 } 0065 0066 */ 0067 0068 cmdArgs << d->filePathEncoding(fileInfo); 0069 d->currentPath = fileInfo.filePath(); 0070 0071 return (d->startProcess(cmdArgs, ExifToolProcess::LOAD_METADATA)); 0072 } 0073 0074 bool ExifToolParser::loadChunk(const QString& path, bool copyToAll) 0075 { 0076 QFileInfo fileInfo(path); 0077 0078 if (!fileInfo.exists()) 0079 { 0080 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0081 return false; 0082 } 0083 0084 d->prepareProcess(); 0085 0086 // Build command (get metadata as EXV container for Exiv2) 0087 0088 QByteArrayList cmdArgs; 0089 cmdArgs << QByteArray("-TagsFromFile"); 0090 cmdArgs << d->filePathEncoding(fileInfo); 0091 0092 QByteArray cpyOpt("-all"); 0093 0094 if (!copyToAll) 0095 { 0096 cpyOpt += ":all"; 0097 } 0098 0099 cmdArgs << cpyOpt; 0100 cmdArgs << QByteArray("-o"); 0101 cmdArgs << QByteArray("-.exv"); 0102 d->currentPath = fileInfo.filePath(); 0103 0104 return (d->startProcess(cmdArgs, ExifToolProcess::LOAD_CHUNKS)); 0105 } 0106 0107 bool ExifToolParser::applyChanges(const QString& path, const ExifToolData& newTags) 0108 { 0109 if (newTags.isEmpty()) 0110 { 0111 qCWarning(DIGIKAM_METAENGINE_LOG) << "List of tags to changes with ExifTool is empty"; 0112 0113 return false; 0114 } 0115 0116 QFileInfo fileInfo(path); 0117 0118 if (!fileInfo.exists()) 0119 { 0120 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0121 return false; 0122 } 0123 0124 d->prepareProcess(); 0125 0126 // Build command (set metadata) 0127 0128 QByteArrayList cmdArgs; 0129 cmdArgs << QByteArray("-json"); 0130 0131 for (ExifToolParser::ExifToolData::const_iterator it = newTags.constBegin() ; 0132 it != newTags.constEnd() ; ++it) 0133 { 0134 QString tagNameExifTool = it.key(); 0135 QString tagValue = it.value()[0].toString(); 0136 cmdArgs << QString::fromUtf8("-%1=%2").arg(tagNameExifTool).arg(tagValue).toUtf8(); 0137 } 0138 0139 cmdArgs << d->filePathEncoding(fileInfo); 0140 d->currentPath = fileInfo.filePath(); 0141 0142 return (d->startProcess(cmdArgs, ExifToolProcess::APPLY_CHANGES)); 0143 } 0144 0145 bool ExifToolParser::applyChanges(const QString& path, 0146 const QString& exvTempFile, 0147 bool hasExif, bool hasXmp, bool hasCSet) 0148 { 0149 if (exvTempFile.isEmpty()) 0150 { 0151 qCWarning(DIGIKAM_METAENGINE_LOG) << "EXV container files to apply changes with ExifTool is empty"; 0152 return false; 0153 } 0154 0155 QFileInfo fileInfo(path); 0156 0157 if (!fileInfo.exists()) 0158 { 0159 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0160 return false; 0161 } 0162 0163 d->prepareProcess(); 0164 0165 // QMimeDatabase mimeDB; 0166 QByteArrayList cmdArgs; 0167 /* 0168 QString suffix = fileInfo.suffix().toUpper(); 0169 0170 bool isJXLFile = (suffix == QLatin1String("JXL")); 0171 0172 bool isVideo = (mimeDB.mimeTypeForFile(fileInfo).name().startsWith(QLatin1String("video/"))); 0173 0174 if (isVideo) 0175 { 0176 cmdArgs << QByteArray("-api"); 0177 cmdArgs << QByteArray("QuickTimeHandler=1"); 0178 cmdArgs << QByteArray("-itemlist:title="); 0179 cmdArgs << QByteArray("-itemlist:comment="); 0180 cmdArgs << QByteArray("-microsoft:category="); 0181 cmdArgs << QByteArray("-itemlist:description="); 0182 } 0183 */ 0184 if (hasExif) 0185 { 0186 cmdArgs << QByteArray("-ifd0:all="); 0187 cmdArgs << QByteArray("-gps:all="); 0188 } 0189 else 0190 { 0191 cmdArgs << QByteArray("-exif:all="); 0192 } 0193 0194 cmdArgs << QByteArray("-iptc:all="); 0195 cmdArgs << QByteArray("-file:comment="); 0196 0197 if (hasXmp) 0198 { 0199 cmdArgs << QByteArray("-xmp-dc:all="); 0200 cmdArgs << QByteArray("-xmp-lr:all="); 0201 cmdArgs << QByteArray("-xmp-mp:all="); 0202 cmdArgs << QByteArray("-xmp-xmp:all="); 0203 cmdArgs << QByteArray("-xmp-exif:all="); 0204 cmdArgs << QByteArray("-xmp-tiff:all="); 0205 cmdArgs << QByteArray("-xmp-xmpdm:all="); 0206 cmdArgs << QByteArray("-xmp-acdsee:all="); 0207 cmdArgs << QByteArray("-xmp-mwg-rs:all="); 0208 cmdArgs << QByteArray("-xmp-digikam:all="); 0209 cmdArgs << QByteArray("-xmp-mediapro:all="); 0210 cmdArgs << QByteArray("-xmp-iptccore:all="); 0211 cmdArgs << QByteArray("-xmp-microsoft:all="); 0212 cmdArgs << QByteArray("-xmp-photoshop:all="); 0213 } 0214 else 0215 { 0216 cmdArgs << QByteArray("-xmp:all="); 0217 } 0218 0219 cmdArgs << QByteArray("-TagsFromFile"); 0220 cmdArgs << d->filePathEncoding(QFileInfo(exvTempFile)); 0221 cmdArgs << QByteArray("-all:all"); 0222 /* 0223 if (isVideo) 0224 { 0225 cmdArgs << QByteArray("-itemlist:title<xmp:title"); 0226 cmdArgs << QByteArray("-microsoft:category<xmp:tagslist"); 0227 cmdArgs << QByteArray("-itemlist:comment<xmp:description"); 0228 cmdArgs << QByteArray("-itemlist:description<xmp:description"); 0229 } 0230 */ 0231 if (hasCSet) 0232 { 0233 cmdArgs << QByteArray("-codedcharacterset=UTF8"); 0234 } 0235 0236 if (hasExif) 0237 { 0238 cmdArgs << QByteArray("-TagsFromFile"); 0239 cmdArgs << QByteArray("@"); 0240 cmdArgs << QByteArray("-makernotes"); 0241 } 0242 0243 cmdArgs << QByteArray("-overwrite_original"); 0244 cmdArgs << d->filePathEncoding(fileInfo); 0245 d->currentPath = fileInfo.filePath(); 0246 0247 return (d->startProcess(cmdArgs, ExifToolProcess::APPLY_CHANGES_EXV)); 0248 } 0249 0250 bool ExifToolParser::applyMetadataFile(const QString& path, const QString& meta) 0251 { 0252 QFileInfo metaInfo(meta); 0253 0254 if (!metaInfo.exists()) 0255 { 0256 qCWarning(DIGIKAM_METAENGINE_LOG) << "Metadata file to apply with ExifTool not exists"; 0257 return false; 0258 } 0259 0260 QFileInfo fileInfo(path); 0261 0262 if (!fileInfo.exists()) 0263 { 0264 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0265 return false; 0266 } 0267 0268 d->prepareProcess(); 0269 0270 QByteArrayList cmdArgs; 0271 0272 if (metaInfo.suffix().toUpper() == QLatin1String("JSON")) 0273 { 0274 cmdArgs << (QByteArray("-json=") + d->filePathEncoding(metaInfo)); 0275 } 0276 else 0277 { 0278 cmdArgs << QByteArray("-TagsFromFile"); 0279 cmdArgs << d->filePathEncoding(metaInfo); 0280 cmdArgs << QByteArray("-all:all"); 0281 } 0282 0283 cmdArgs << QByteArray("-overwrite_original"); 0284 cmdArgs << d->filePathEncoding(fileInfo); 0285 0286 return (d->startProcess(cmdArgs, ExifToolProcess::APPLY_METADATA_FILE)); 0287 } 0288 0289 bool ExifToolParser::readableFormats() 0290 { 0291 d->prepareProcess(); 0292 0293 // Build command 0294 0295 QByteArrayList cmdArgs; 0296 cmdArgs << QByteArray("-l"); 0297 cmdArgs << QByteArray("-listr"); 0298 0299 d->currentPath.clear(); 0300 0301 return (d->startProcess(cmdArgs, ExifToolProcess::READ_FORMATS)); 0302 } 0303 0304 bool ExifToolParser::writableFormats() 0305 { 0306 d->prepareProcess(); 0307 0308 // Build command 0309 0310 QByteArrayList cmdArgs; 0311 cmdArgs << QByteArray("-l"); 0312 cmdArgs << QByteArray("-listwf"); 0313 0314 d->currentPath.clear(); 0315 0316 return (d->startProcess(cmdArgs, ExifToolProcess::WRITE_FORMATS)); 0317 } 0318 0319 bool ExifToolParser::translationsList() 0320 { 0321 d->prepareProcess(); 0322 0323 // Build command 0324 0325 QByteArrayList cmdArgs; 0326 cmdArgs << QByteArray("-lang"); 0327 0328 d->currentPath.clear(); 0329 0330 return (d->startProcess(cmdArgs, ExifToolProcess::TRANSLATIONS_LIST)); 0331 } 0332 0333 bool ExifToolParser::tagsDatabase() 0334 { 0335 d->prepareProcess(); 0336 0337 // Build command 0338 0339 QByteArrayList cmdArgs; 0340 cmdArgs << QByteArray("-listx"); 0341 0342 d->currentPath.clear(); 0343 0344 return (d->startProcess(cmdArgs, ExifToolProcess::TAGS_DATABASE)); 0345 } 0346 0347 bool ExifToolParser::version() 0348 { 0349 d->prepareProcess(); 0350 0351 // Build command 0352 0353 QByteArrayList cmdArgs; 0354 cmdArgs << QByteArray("-ver"); 0355 0356 d->currentPath.clear(); 0357 0358 return (d->startProcess(cmdArgs, ExifToolProcess::VERSION_STRING)); 0359 } 0360 0361 bool ExifToolParser::copyTags(const QString& src, const QString& dst, 0362 unsigned char copyOps, 0363 unsigned char writeModes) 0364 { 0365 QFileInfo sfi(src); 0366 0367 if (!sfi.exists()) 0368 { 0369 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0370 return false; 0371 } 0372 0373 QFileInfo dfi(src); 0374 0375 if (!dfi.exists()) 0376 { 0377 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open destination file to process with ExifTool..."; 0378 return false; 0379 } 0380 0381 // --- 0382 0383 QByteArray wrtCmds; 0384 0385 qCDebug(DIGIKAM_METAENGINE_LOG) << "Copy Tags Modes:" 0386 << writeModes 0387 << "(" 0388 << QString::fromLatin1("%1").arg(writeModes, 0, 2) 0389 << ")"; 0390 0391 if (writeModes & ExifToolProcess::WRITE_EXISTING_TAGS) 0392 { 0393 wrtCmds.append(QByteArray("w")); 0394 } 0395 0396 if (writeModes & ExifToolProcess::CREATE_NEW_TAGS) 0397 { 0398 wrtCmds.append(QByteArray("c")); 0399 } 0400 0401 if (writeModes & ExifToolProcess::CREATE_NEW_GROUPS) 0402 { 0403 wrtCmds.append(QByteArray("g")); 0404 } 0405 0406 if (wrtCmds.isEmpty()) 0407 { 0408 qCWarning(DIGIKAM_METAENGINE_LOG) << "Copy tags writing modes list is empty!"; 0409 return false; 0410 } 0411 0412 // --- 0413 0414 QByteArrayList copyCmds; 0415 0416 qCDebug(DIGIKAM_METAENGINE_LOG) << "Copy Tags Operations:" 0417 << copyOps 0418 << "(" 0419 << QString::fromLatin1("%1").arg(copyOps, 0, 2) 0420 << ")"; 0421 0422 if (!(copyOps & ExifToolProcess::COPY_NONE)) 0423 { 0424 if (copyOps & ExifToolProcess::COPY_ALL) 0425 { 0426 copyCmds << QByteArray("-all:all"); 0427 } 0428 else 0429 { 0430 if (copyOps & ExifToolProcess::COPY_EXIF) 0431 { 0432 copyCmds << QByteArray("-exif"); 0433 } 0434 else 0435 { 0436 copyCmds << QByteArray("--exif"); 0437 } 0438 0439 if (copyOps & ExifToolProcess::COPY_MAKERNOTES) 0440 { 0441 copyCmds << QByteArray("-makernotes"); 0442 } 0443 else 0444 { 0445 copyCmds << QByteArray("--makernotes"); 0446 } 0447 0448 if (copyOps & ExifToolProcess::COPY_IPTC) 0449 { 0450 copyCmds << QByteArray("-iptc"); 0451 } 0452 else 0453 { 0454 copyCmds << QByteArray("--iptc"); 0455 } 0456 0457 if (copyOps & ExifToolProcess::COPY_XMP) 0458 { 0459 copyCmds << QByteArray("-xmp"); 0460 } 0461 else 0462 { 0463 copyCmds << QByteArray("--xmp"); 0464 } 0465 0466 if (copyOps & ExifToolProcess::COPY_ICC) 0467 { 0468 copyCmds << QByteArray("-icc_profile"); 0469 } 0470 else 0471 { 0472 copyCmds << QByteArray("--icc_profile"); 0473 } 0474 } 0475 } 0476 0477 // --- 0478 0479 d->prepareProcess(); 0480 0481 QByteArrayList cmdArgs; 0482 0483 cmdArgs << QByteArray("-wm") << wrtCmds; 0484 cmdArgs << QByteArray("-TagsFromFile"); 0485 cmdArgs << d->filePathEncoding(QFileInfo(src)); 0486 cmdArgs << copyCmds; 0487 cmdArgs << QByteArray("-overwrite_original"); 0488 cmdArgs << d->filePathEncoding(QFileInfo(dst)); 0489 d->currentPath = sfi.filePath(); 0490 0491 return (d->startProcess(cmdArgs, ExifToolProcess::COPY_TAGS)); 0492 } 0493 0494 bool ExifToolParser::translateTags(const QString& path, unsigned char transOps) 0495 { 0496 QFileInfo fi(path); 0497 0498 if (!fi.exists()) 0499 { 0500 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open source file to process with ExifTool..."; 0501 return false; 0502 } 0503 0504 // --- 0505 0506 if (!d->argsFile.isOpen() && d->argsFile.exists()) 0507 { 0508 d->argsFile.remove(); 0509 } 0510 0511 if (!d->argsFile.open()) 0512 { 0513 qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot open temporary file to write ExifTool tags translate config file..."; 0514 return false; 0515 } 0516 0517 QTextStream out(&d->argsFile); 0518 bool dirty = false; 0519 0520 qCDebug(DIGIKAM_METAENGINE_LOG) << "Translate Tags:" 0521 << transOps 0522 << "(" 0523 << QString::fromLatin1("%1").arg(transOps, 0, 2) 0524 << ")"; 0525 0526 if (transOps & ExifToolProcess::TRANS_ALL_XMP) 0527 { 0528 out << QLatin1String("-xmp:all<all:all") << QT_ENDL; 0529 dirty = true; 0530 } 0531 0532 if (transOps & ExifToolProcess::TRANS_ALL_IPTC) 0533 { 0534 out << QLatin1String("-iptc:all<all:all") << QT_ENDL; 0535 dirty = true; 0536 } 0537 0538 if (transOps & ExifToolProcess::TRANS_ALL_EXIF) 0539 { 0540 out << QLatin1String("-exif:all<all:all") << QT_ENDL; 0541 dirty = true; 0542 } 0543 0544 if (!dirty) 0545 { 0546 qCWarning(DIGIKAM_METAENGINE_LOG) << "Translate tags operations list is empty!"; 0547 return false; 0548 } 0549 0550 // --- 0551 0552 d->prepareProcess(); 0553 0554 QByteArrayList cmdArgs; 0555 0556 cmdArgs << QByteArray("-@") << d->filePathEncoding(QFileInfo(d->argsFile.fileName())); 0557 cmdArgs << QByteArray("-overwrite_original"); 0558 cmdArgs << d->filePathEncoding(QFileInfo(path)); 0559 d->currentPath = fi.filePath(); 0560 0561 return (d->startProcess(cmdArgs, ExifToolProcess::TRANS_TAGS)); 0562 } 0563 0564 } // namespace Digikam