File indexing completed on 2025-01-05 03:56:23
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-02-23 0007 * Description : item metadata interface - comments helpers. 0008 * 0009 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0011 * SPDX-FileCopyrightText: 2011 by Leif Huhn <leif at dkstat dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "dmetadata.h" 0018 0019 // Qt includes 0020 0021 #include <QLocale> 0022 0023 // Local includes 0024 0025 #include "digikam_version.h" 0026 #include "digikam_globals.h" 0027 #include "digikam_debug.h" 0028 0029 namespace Digikam 0030 { 0031 0032 CaptionsMap DMetadata::getItemComments(const DMetadataSettingsContainer& settings) const 0033 { 0034 CaptionsMap captionsMap; 0035 MetaEngine::AltLangMap authorsMap; 0036 MetaEngine::AltLangMap datesMap; 0037 MetaEngine::AltLangMap commentsMap; 0038 QString commonAuthor; 0039 0040 // In first try to get captions properties from digiKam XMP namespace 0041 0042 if (supportXmp()) 0043 { 0044 authorsMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", false); 0045 datesMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", false); 0046 0047 if (authorsMap.isEmpty() && commonAuthor.isEmpty()) 0048 { 0049 QString xmpAuthors = getXmpTagString("Xmp.acdsee.author", false); 0050 0051 if (!xmpAuthors.isEmpty()) 0052 { 0053 authorsMap.insert(QLatin1String("x-default"), xmpAuthors); 0054 } 0055 } 0056 } 0057 0058 // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. 0059 0060 QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); 0061 0062 if (!descriptionWriter.isNull()) 0063 { 0064 commonAuthor = descriptionWriter.toString(); 0065 } 0066 0067 // In first, we check XMP alternative language tags to create map of values. 0068 0069 bool xmpSupported = hasXmp(); 0070 bool iptcSupported = hasIptc(); 0071 bool exivSupported = hasExif(); 0072 0073 Q_FOREACH (const NamespaceEntry& entry, settings.getReadMapping(NamespaceEntry::DM_COMMENT_CONTAINER())) 0074 { 0075 if (entry.isDisabled) 0076 { 0077 continue; 0078 } 0079 0080 QString commentString; 0081 const std::string myStr = entry.namespaceName.toStdString(); 0082 const char* nameSpace = myStr.data(); 0083 0084 switch (entry.subspace) 0085 { 0086 case NamespaceEntry::XMP: 0087 { 0088 switch (entry.specialOpts) 0089 { 0090 case NamespaceEntry::COMMENT_ALTLANG: 0091 { 0092 if (xmpSupported) 0093 { 0094 commentString = getXmpTagStringLangAlt(nameSpace, QString(), false); 0095 } 0096 0097 break; 0098 } 0099 0100 case NamespaceEntry::COMMENT_ATLLANGLIST: 0101 { 0102 if (xmpSupported) 0103 { 0104 commentsMap = getXmpTagStringListLangAlt(nameSpace, false); 0105 } 0106 0107 break; 0108 } 0109 0110 case NamespaceEntry::COMMENT_XMP: 0111 { 0112 if (xmpSupported) 0113 { 0114 commentString = getXmpTagString(nameSpace, false); 0115 } 0116 0117 break; 0118 } 0119 0120 case NamespaceEntry::COMMENT_JPEG: 0121 { 0122 // Now, we trying to get image comments, outside of XMP. 0123 // For JPEG, string is extracted from JFIF Comments section. 0124 // For PNG, string is extracted from iTXt chunk. 0125 0126 commentString = getCommentsDecoded(); 0127 } 0128 0129 default: 0130 { 0131 break; 0132 } 0133 } 0134 0135 break; 0136 } 0137 0138 case NamespaceEntry::IPTC: 0139 { 0140 if (iptcSupported) 0141 { 0142 commentString = getIptcTagString(nameSpace, false); 0143 } 0144 0145 break; 0146 } 0147 0148 case NamespaceEntry::EXIF: 0149 { 0150 if (exivSupported) 0151 { 0152 commentString = getExifTagComment(nameSpace); 0153 0154 if (commentString.isEmpty() && !entry.alternativeName.isEmpty()) 0155 { 0156 const std::string altStr = entry.alternativeName.toStdString(); 0157 const char* alternateSpace = altStr.data(); 0158 0159 commentString = getExifTagComment(alternateSpace); 0160 } 0161 } 0162 0163 break; 0164 } 0165 0166 default: 0167 { 0168 break; 0169 } 0170 } 0171 0172 if (!commentString.isEmpty() && !commentString.trimmed().isEmpty()) 0173 { 0174 commentsMap.insert(QLatin1String("x-default"), commentString); 0175 captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); 0176 0177 return captionsMap; 0178 } 0179 0180 if (!commentsMap.isEmpty()) 0181 { 0182 captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); 0183 0184 return captionsMap; 0185 } 0186 } 0187 0188 return captionsMap; 0189 } 0190 0191 bool DMetadata::setItemComments(const CaptionsMap& comments, const DMetadataSettingsContainer& settings) const 0192 { 0193 /* 0194 // See bug #139313: An empty string is also a valid value 0195 0196 if (comments.isEmpty()) 0197 { 0198 return false; 0199 } 0200 */ 0201 0202 /* 0203 qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Comment: " << comments; 0204 */ 0205 // In first, set captions properties to digiKam XMP namespace 0206 0207 if (supportXmp()) 0208 { 0209 if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", comments.authorsList())) 0210 { 0211 return false; 0212 } 0213 0214 if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", comments.datesList())) 0215 { 0216 return false; 0217 } 0218 } 0219 0220 QString defaultComment = comments.value(QLatin1String("x-default")).caption; 0221 QList<NamespaceEntry> toWrite = settings.getReadMapping(NamespaceEntry::DM_COMMENT_CONTAINER()); 0222 0223 if (!settings.unifyReadWrite()) 0224 { 0225 toWrite = settings.getWriteMapping(NamespaceEntry::DM_COMMENT_CONTAINER()); 0226 } 0227 0228 for (const NamespaceEntry& entry : qAsConst(toWrite)) 0229 { 0230 if (entry.isDisabled) 0231 { 0232 continue; 0233 } 0234 0235 const std::string myStr = entry.namespaceName.toStdString(); 0236 const char* nameSpace = myStr.data(); 0237 0238 switch (entry.subspace) 0239 { 0240 case NamespaceEntry::XMP: 0241 { 0242 if (!supportXmp()) 0243 { 0244 continue; 0245 } 0246 0247 switch (entry.specialOpts) 0248 { 0249 case NamespaceEntry::COMMENT_ALTLANG: 0250 { 0251 removeXmpTag(nameSpace); 0252 0253 if (!defaultComment.isNull()) 0254 { 0255 if (!setXmpTagStringLangAlt(nameSpace, defaultComment, QString())) 0256 { 0257 qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image comment failed" << nameSpace; 0258 return false; 0259 } 0260 } 0261 0262 break; 0263 } 0264 0265 case NamespaceEntry::COMMENT_ATLLANGLIST: 0266 { 0267 // NOTE : setXmpTagStringListLangAlt remove xmp tag before to add new values 0268 0269 if (!setXmpTagStringListLangAlt(nameSpace, comments.toAltLangMap())) 0270 { 0271 return false; 0272 } 0273 0274 break; 0275 } 0276 0277 case NamespaceEntry::COMMENT_XMP: 0278 { 0279 removeXmpTag(nameSpace); 0280 0281 if (!defaultComment.isNull()) 0282 { 0283 if (!setXmpTagString(nameSpace, defaultComment)) 0284 { 0285 return false; 0286 } 0287 } 0288 0289 if (entry.namespaceName == QLatin1String("Xmp.acdsee.notes")) 0290 { 0291 QString defaultAuthor = comments.value(QLatin1String("x-default")).author; 0292 removeXmpTag("Xmp.acdsee.author"); 0293 0294 if (!defaultAuthor.isNull()) 0295 { 0296 if (!setXmpTagString("Xmp.acdsee.author", defaultAuthor)) 0297 { 0298 return false; 0299 } 0300 } 0301 } 0302 0303 break; 0304 } 0305 0306 case NamespaceEntry::COMMENT_JPEG: 0307 { 0308 // In first we set image comments, outside of Exif, XMP, and IPTC. 0309 0310 if (!setComments(defaultComment.toUtf8())) 0311 { 0312 return false; 0313 } 0314 0315 break; 0316 } 0317 0318 default: 0319 { 0320 break; 0321 } 0322 } 0323 0324 break; 0325 } 0326 0327 case NamespaceEntry::IPTC: 0328 { 0329 removeIptcTag(nameSpace); 0330 0331 if (!defaultComment.isNull()) 0332 { 0333 defaultComment.truncate(2000); 0334 0335 if (!setIptcTagString(nameSpace, defaultComment)) 0336 { 0337 return false; 0338 } 0339 } 0340 0341 break; 0342 } 0343 0344 case NamespaceEntry::EXIF: 0345 { 0346 if (entry.namespaceName == QLatin1String("Exif.Image.XPComment")) 0347 { 0348 if (writeWithExifTool() && !defaultComment.isEmpty()) 0349 { 0350 QByteArray xpData = QByteArray((char*)defaultComment.utf16(), defaultComment.size() * 2); 0351 xpData.append("\x00\x00"); 0352 0353 if (!setExifTagData(nameSpace, xpData)) 0354 { 0355 return false; 0356 } 0357 } 0358 else if (removeExifTag(nameSpace)) 0359 { 0360 qCDebug(DIGIKAM_METAENGINE_LOG) << "Remove image comment" << nameSpace; 0361 } 0362 } 0363 else if (entry.namespaceName == QLatin1String("Exif.Photo.UserComment")) 0364 { 0365 if (!setExifComment(defaultComment, false)) 0366 { 0367 return false; 0368 } 0369 } 0370 else if (!setExifTagString(nameSpace, defaultComment)) 0371 { 0372 return false; 0373 } 0374 0375 break; 0376 } 0377 0378 default: 0379 { 0380 break; 0381 } 0382 } 0383 } 0384 0385 return true; 0386 } 0387 0388 CaptionsMap DMetadata::getItemTitles(const DMetadataSettingsContainer& settings) const 0389 { 0390 CaptionsMap captionsMap; 0391 MetaEngine::AltLangMap authorsMap; 0392 MetaEngine::AltLangMap datesMap; 0393 MetaEngine::AltLangMap titlesMap; 0394 QString commonAuthor; 0395 0396 // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. 0397 0398 QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); 0399 0400 if (!descriptionWriter.isNull()) 0401 { 0402 commonAuthor = descriptionWriter.toString(); 0403 } 0404 0405 // In first, we check XMP alternative language tags to create map of values. 0406 0407 bool xmpSupported = hasXmp(); 0408 bool iptcSupported = hasIptc(); 0409 bool exivSupported = hasExif(); 0410 0411 Q_FOREACH (const NamespaceEntry& entry, settings.getReadMapping(NamespaceEntry::DM_TITLE_CONTAINER())) 0412 { 0413 if (entry.isDisabled) 0414 { 0415 continue; 0416 } 0417 0418 QString titleString; 0419 const std::string myStr = entry.namespaceName.toStdString(); 0420 const char* nameSpace = myStr.data(); 0421 0422 switch (entry.subspace) 0423 { 0424 case NamespaceEntry::XMP: 0425 { 0426 switch (entry.specialOpts) 0427 { 0428 case NamespaceEntry::COMMENT_ATLLANGLIST: 0429 { 0430 if (xmpSupported) 0431 { 0432 titlesMap = getXmpTagStringListLangAlt(nameSpace, false); 0433 } 0434 0435 break; 0436 } 0437 0438 case NamespaceEntry::COMMENT_XMP: 0439 { 0440 if (xmpSupported) 0441 { 0442 titleString = getXmpTagString(nameSpace, false); 0443 } 0444 0445 break; 0446 } 0447 0448 default: 0449 { 0450 break; 0451 } 0452 } 0453 0454 break; 0455 } 0456 0457 case NamespaceEntry::IPTC: 0458 { 0459 if (iptcSupported) 0460 { 0461 titleString = getIptcTagString(nameSpace, false); 0462 } 0463 0464 break; 0465 } 0466 0467 case NamespaceEntry::EXIF: 0468 { 0469 if (exivSupported) 0470 { 0471 titleString = getExifTagString(nameSpace); 0472 } 0473 0474 break; 0475 } 0476 0477 default: 0478 { 0479 break; 0480 } 0481 } 0482 0483 if (!titleString.isEmpty() && !titleString.trimmed().isEmpty()) 0484 { 0485 titlesMap.insert(QLatin1String("x-default"), titleString); 0486 captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); 0487 0488 return captionsMap; 0489 } 0490 0491 if (!titlesMap.isEmpty()) 0492 { 0493 captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); 0494 0495 return captionsMap; 0496 } 0497 } 0498 0499 return captionsMap; 0500 } 0501 0502 bool DMetadata::setItemTitles(const CaptionsMap& titles, const DMetadataSettingsContainer& settings) const 0503 { 0504 /* 0505 qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Title: " << titles; 0506 */ 0507 QString defaultTitle = titles[QLatin1String("x-default")].caption; 0508 QList<NamespaceEntry> toWrite = settings.getReadMapping(NamespaceEntry::DM_TITLE_CONTAINER()); 0509 0510 if (!settings.unifyReadWrite()) 0511 { 0512 toWrite = settings.getWriteMapping(NamespaceEntry::DM_TITLE_CONTAINER()); 0513 } 0514 0515 for (const NamespaceEntry& entry : qAsConst(toWrite)) 0516 { 0517 if (entry.isDisabled) 0518 { 0519 continue; 0520 } 0521 0522 const std::string myStr = entry.namespaceName.toStdString(); 0523 const char* nameSpace = myStr.data(); 0524 0525 switch (entry.subspace) 0526 { 0527 case NamespaceEntry::XMP: 0528 { 0529 if (!supportXmp()) 0530 { 0531 continue; 0532 } 0533 0534 switch (entry.specialOpts) 0535 { 0536 case NamespaceEntry::COMMENT_ATLLANGLIST: 0537 { 0538 // NOTE : setXmpTagStringListLangAlt remove xmp tag before to add new values 0539 0540 if (!setXmpTagStringListLangAlt(nameSpace, titles.toAltLangMap())) 0541 { 0542 return false; 0543 } 0544 0545 break; 0546 } 0547 0548 case NamespaceEntry::COMMENT_XMP: 0549 { 0550 removeXmpTag(nameSpace); 0551 0552 if (!defaultTitle.isNull()) 0553 { 0554 if (!setXmpTagString(nameSpace, defaultTitle)) 0555 { 0556 return false; 0557 } 0558 } 0559 0560 break; 0561 } 0562 0563 default: 0564 { 0565 break; 0566 } 0567 } 0568 0569 break; 0570 } 0571 0572 case NamespaceEntry::IPTC: 0573 { 0574 removeIptcTag(nameSpace); 0575 0576 if (!defaultTitle.isNull()) 0577 { 0578 // Note that Caption IPTC tag is limited to 64 char and ASCII charset. 0579 0580 defaultTitle.truncate(64); 0581 0582 // See if we have any non printable chars in there. If so, skip IPTC 0583 // to avoid confusing other apps and web services with invalid tags. 0584 0585 bool hasInvalidChar = false; 0586 0587 for (QString::const_iterator c = defaultTitle.constBegin() ; c != defaultTitle.constEnd() ; ++c) 0588 { 0589 if (!(*c).isPrint()) 0590 { 0591 hasInvalidChar = true; 0592 break; 0593 } 0594 } 0595 0596 if (!hasInvalidChar) 0597 { 0598 if (!setIptcTagString(nameSpace, defaultTitle)) 0599 { 0600 return false; 0601 } 0602 } 0603 } 0604 0605 break; 0606 } 0607 0608 case NamespaceEntry::EXIF: 0609 { 0610 if (entry.namespaceName == QLatin1String("Exif.Image.XPTitle")) 0611 { 0612 if (writeWithExifTool() && !defaultTitle.isEmpty()) 0613 { 0614 QByteArray xpData = QByteArray((char*)defaultTitle.utf16(), defaultTitle.size() * 2); 0615 xpData.append("\x00\x00"); 0616 0617 if (!setExifTagData(nameSpace, xpData)) 0618 { 0619 return false; 0620 } 0621 } 0622 else if (removeExifTag(nameSpace)) 0623 { 0624 qCDebug(DIGIKAM_METAENGINE_LOG) << "Remove image title" << nameSpace; 0625 } 0626 } 0627 else if (!setExifTagString(nameSpace, defaultTitle)) 0628 { 0629 return false; 0630 } 0631 0632 break; 0633 } 0634 0635 default: 0636 { 0637 break; 0638 } 0639 } 0640 } 0641 0642 return true; 0643 } 0644 0645 MetaEngine::AltLangMap DMetadata::toAltLangMap(const QVariant& var) 0646 { 0647 MetaEngine::AltLangMap map; 0648 0649 if (var.isNull()) 0650 { 0651 return map; 0652 } 0653 0654 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0655 0656 switch (var.typeId()) 0657 0658 #else 0659 0660 switch (var.type()) 0661 0662 #endif 0663 0664 { 0665 case QVariant::String: 0666 { 0667 map.insert(QLatin1String("x-default"), var.toString()); 0668 break; 0669 } 0670 0671 case QVariant::Map: 0672 { 0673 QMap<QString, QVariant> varMap = var.toMap(); 0674 0675 for (QMap<QString, QVariant>::const_iterator it = varMap.constBegin() ; it != varMap.constEnd() ; ++it) 0676 { 0677 map.insert(it.key(), it.value().toString()); 0678 } 0679 0680 break; 0681 } 0682 0683 default: 0684 { 0685 break; 0686 } 0687 } 0688 0689 return map; 0690 } 0691 0692 } // namespace Digikam