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