File indexing completed on 2025-03-09 03:52:56
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-09-19 0007 * Description : Scanning a single item - photo metadata helper. 0008 * 0009 * SPDX-FileCopyrightText: 2007-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2013-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "itemscanner_p.h" 0017 0018 namespace Digikam 0019 { 0020 0021 QString ItemScanner::iptcCorePropertyName(MetadataInfo::Field field) 0022 { 0023 // These strings are specified in DBSCHEMA.ods 0024 0025 switch (field) 0026 { 0027 // Copyright table krazy:exclude=copyright 0028 0029 case MetadataInfo::IptcCoreCopyrightNotice: 0030 return QLatin1String("copyrightNotice"); // krazy:exclude=copyright 0031 case MetadataInfo::IptcCoreCreator: 0032 return QLatin1String("creator"); 0033 case MetadataInfo::IptcCoreProvider: 0034 return QLatin1String("provider"); 0035 case MetadataInfo::IptcCoreRightsUsageTerms: 0036 return QLatin1String("rightsUsageTerms"); 0037 case MetadataInfo::IptcCoreSource: 0038 return QLatin1String("source"); 0039 case MetadataInfo::IptcCoreCreatorJobTitle: 0040 return QLatin1String("creatorJobTitle"); 0041 case MetadataInfo::IptcCoreInstructions: 0042 return QLatin1String("instructions"); 0043 0044 // ImageProperties table 0045 0046 case MetadataInfo::IptcCoreCountryCode: 0047 return QLatin1String("countryCode"); 0048 case MetadataInfo::IptcCoreCountry: 0049 return QLatin1String("country"); 0050 case MetadataInfo::IptcCoreCity: 0051 return QLatin1String("city"); 0052 case MetadataInfo::IptcCoreLocation: 0053 return QLatin1String("location"); 0054 case MetadataInfo::IptcCoreProvinceState: 0055 return QLatin1String("provinceState"); 0056 case MetadataInfo::IptcCoreIntellectualGenre: 0057 return QLatin1String("intellectualGenre"); 0058 case MetadataInfo::IptcCoreJobID: 0059 return QLatin1String("jobId"); 0060 case MetadataInfo::IptcCoreScene: 0061 return QLatin1String("scene"); 0062 case MetadataInfo::IptcCoreSubjectCode: 0063 return QLatin1String("subjectCode"); 0064 case MetadataInfo::IptcCoreContactInfoCity: 0065 return QLatin1String("creatorContactInfo.city"); 0066 case MetadataInfo::IptcCoreContactInfoCountry: 0067 return QLatin1String("creatorContactInfo.country"); 0068 case MetadataInfo::IptcCoreContactInfoAddress: 0069 return QLatin1String("creatorContactInfo.address"); 0070 case MetadataInfo::IptcCoreContactInfoPostalCode: 0071 return QLatin1String("creatorContactInfo.postalCode"); 0072 case MetadataInfo::IptcCoreContactInfoProvinceState: 0073 return QLatin1String("creatorContactInfo.provinceState"); 0074 case MetadataInfo::IptcCoreContactInfoEmail: 0075 return QLatin1String("creatorContactInfo.email"); 0076 case MetadataInfo::IptcCoreContactInfoPhone: 0077 return QLatin1String("creatorContactInfo.phone"); 0078 case MetadataInfo::IptcCoreContactInfoWebUrl: 0079 return QLatin1String("creatorContactInfo.webUrl"); 0080 default: 0081 return QString(); 0082 } 0083 } 0084 0085 QString ItemScanner::detectImageFormat() const 0086 { 0087 DImg::FORMAT dimgFormat = d->img.detectedFormat(); 0088 0089 switch (dimgFormat) 0090 { 0091 case DImg::JPEG: 0092 return QLatin1String("JPG"); 0093 case DImg::PNG: 0094 return QLatin1String("PNG"); 0095 case DImg::TIFF: 0096 return QLatin1String("TIFF"); 0097 case DImg::JP2K: 0098 return QLatin1String("JP2"); 0099 case DImg::PGF: 0100 return QLatin1String("PGF"); 0101 case DImg::HEIF: 0102 return QLatin1String("HEIF"); 0103 case DImg::RAW: 0104 { 0105 QString format = QLatin1String("RAW-"); 0106 format += d->fileInfo.suffix().toUpper(); 0107 return format; 0108 } 0109 case DImg::NONE: 0110 case DImg::QIMAGE: 0111 { 0112 QString ext = d->fileInfo.suffix().toUpper(); 0113 0114 if ( 0115 (ext == QLatin1String("AVIF")) || // See bug #109060 0116 (ext == QLatin1String("JPX")) 0117 ) 0118 { 0119 return ext; 0120 } 0121 0122 QByteArray format = QImageReader::imageFormat(d->fileInfo.filePath()); 0123 0124 if (!format.isEmpty()) 0125 { 0126 return QString::fromUtf8(format).toUpper(); 0127 } 0128 0129 break; 0130 } 0131 } 0132 0133 // See BUG #339341: Take file name suffix instead type mime analyze. 0134 0135 return d->fileInfo.suffix().toUpper(); 0136 } 0137 0138 void ItemScanner::scanImageMetadata() 0139 { 0140 QVariantList metadataInfos = d->metadata->getMetadataFields(allImageMetadataFields()); 0141 0142 if (hasValidField(metadataInfos)) 0143 { 0144 d->commit.commitImageMetadata = true; 0145 d->commit.imageMetadataInfos = metadataInfos; 0146 } 0147 } 0148 0149 void ItemScanner::commitImageMetadata() 0150 { 0151 CoreDbAccess().db()->addImageMetadata(d->scanInfo.id, d->commit.imageMetadataInfos); 0152 } 0153 0154 void ItemScanner::scanItemPosition() 0155 { 0156 // This list must reflect the order required by CoreDB::addItemPosition 0157 0158 MetadataFields fields; 0159 fields << MetadataInfo::Latitude 0160 << MetadataInfo::LatitudeNumber 0161 << MetadataInfo::Longitude 0162 << MetadataInfo::LongitudeNumber 0163 << MetadataInfo::Altitude 0164 << MetadataInfo::PositionOrientation 0165 << MetadataInfo::PositionTilt 0166 << MetadataInfo::PositionRoll 0167 << MetadataInfo::PositionAccuracy 0168 << MetadataInfo::PositionDescription; 0169 0170 QVariantList metadataInfos = d->metadata->getMetadataFields(fields); 0171 0172 if (hasValidField(metadataInfos)) 0173 { 0174 d->commit.commitItemPosition = true; 0175 d->commit.imagePositionInfos = metadataInfos; 0176 } 0177 } 0178 0179 void ItemScanner::commitItemPosition() 0180 { 0181 CoreDbAccess().db()->addItemPosition(d->scanInfo.id, d->commit.imagePositionInfos); 0182 } 0183 0184 void ItemScanner::scanItemComments() 0185 { 0186 MetadataFields fields; 0187 fields << MetadataInfo::Headline; 0188 0189 QVariantList metadataInfos = d->metadata->getMetadataFields(fields); 0190 0191 // handles all possible fields, multi-language, author, date 0192 0193 CaptionsMap captions = d->metadata->getItemComments(); 0194 CaptionsMap titles = d->metadata->getItemTitles(); 0195 0196 if (titles.isEmpty() && 0197 captions.isEmpty() && 0198 !hasValidField(metadataInfos)) 0199 { 0200 return; 0201 } 0202 0203 d->commit.commitItemComments = true; 0204 d->commit.captions = captions; 0205 d->commit.titles = titles; 0206 0207 // Headline 0208 0209 if (!metadataInfos.at(0).isNull()) 0210 { 0211 d->commit.headline = metadataInfos.at(0).toString(); 0212 } 0213 } 0214 0215 void ItemScanner::commitItemComments() 0216 { 0217 CoreDbAccess access; 0218 ItemComments comments(access, d->scanInfo.id); 0219 0220 // Description 0221 0222 if (!d->commit.captions.isEmpty()) 0223 { 0224 CaptionsMap::const_iterator it; 0225 0226 for (it = d->commit.captions.constBegin() ; it != d->commit.captions.constEnd() ; ++it) 0227 { 0228 CaptionValues val = it.value(); 0229 comments.addComment(val.caption, it.key(), val.author, val.date); 0230 } 0231 } 0232 0233 // Headline 0234 0235 if (!d->commit.headline.isNull()) 0236 { 0237 comments.addHeadline(d->commit.headline); 0238 } 0239 0240 // Title 0241 0242 if (!d->commit.titles.isEmpty()) 0243 { 0244 CaptionsMap::const_iterator it; 0245 0246 for (it = d->commit.titles.constBegin() ; it != d->commit.titles.constEnd() ; ++it) 0247 { 0248 CaptionValues val = it.value(); 0249 comments.addTitle(val.caption, it.key(), val.author, val.date); 0250 } 0251 } 0252 } 0253 0254 void ItemScanner::scanItemCopyright() 0255 { 0256 Template t; 0257 0258 if (!d->metadata->getCopyrightInformation(t)) 0259 { 0260 return; 0261 } 0262 0263 d->commit.commitItemCopyright = true; 0264 d->commit.copyrightTemplate = t; 0265 } 0266 0267 void ItemScanner::commitItemCopyright() 0268 { 0269 ItemCopyright copyright(d->scanInfo.id); 0270 0271 // It is not clear if removeAll() should be called if d->scanMode == Rescan 0272 0273 copyright.removeAll(); 0274 copyright.setFromTemplate(d->commit.copyrightTemplate); 0275 } 0276 0277 void ItemScanner::scanIPTCCore() 0278 { 0279 MetadataFields fields; 0280 fields << MetadataInfo::IptcCoreLocationInfo 0281 << MetadataInfo::IptcCoreIntellectualGenre 0282 << MetadataInfo::IptcCoreJobID 0283 << MetadataInfo::IptcCoreScene 0284 << MetadataInfo::IptcCoreSubjectCode; 0285 0286 QVariantList metadataInfos = d->metadata->getMetadataFields(fields); 0287 0288 if (!hasValidField(metadataInfos)) 0289 { 0290 return; 0291 } 0292 0293 d->commit.commitIPTCCore = true; 0294 d->commit.iptcCoreMetadataInfos = metadataInfos; 0295 } 0296 0297 void ItemScanner::commitIPTCCore() 0298 { 0299 ItemExtendedProperties props(d->scanInfo.id); 0300 0301 if (!d->commit.iptcCoreMetadataInfos.at(0).isNull()) 0302 { 0303 IptcCoreLocationInfo loc = d->commit.iptcCoreMetadataInfos.at(0).value<IptcCoreLocationInfo>(); 0304 0305 if (!loc.isNull()) 0306 { 0307 props.setLocation(loc); 0308 } 0309 } 0310 0311 if (!d->commit.iptcCoreMetadataInfos.at(1).isNull()) 0312 { 0313 props.setIntellectualGenre(d->commit.iptcCoreMetadataInfos.at(1).toString()); 0314 } 0315 0316 if (!d->commit.iptcCoreMetadataInfos.at(2).isNull()) 0317 { 0318 props.setJobId(d->commit.iptcCoreMetadataInfos.at(2).toString()); 0319 } 0320 0321 if (!d->commit.iptcCoreMetadataInfos.at(3).isNull()) 0322 { 0323 props.setScene(d->commit.iptcCoreMetadataInfos.at(3).toStringList()); 0324 } 0325 0326 if (!d->commit.iptcCoreMetadataInfos.at(4).isNull()) 0327 { 0328 props.setSubjectCode(d->commit.iptcCoreMetadataInfos.at(4).toStringList()); 0329 } 0330 } 0331 0332 void ItemScanner::scanTags() 0333 { 0334 // Check Keywords tag paths. 0335 0336 QVariant var = d->metadata->getMetadataField(MetadataInfo::Keywords); 0337 QStringList keywords = var.toStringList(); 0338 QStringList filteredKeywords; 0339 0340 // Extra empty tags check, empty tag = root tag which is not asignable 0341 0342 for (int index = 0 ; index < keywords.size() ; ++index) 0343 { 0344 QString keyword = keywords.at(index); 0345 0346 if (!keyword.isEmpty()) 0347 { 0348 0349 // _Digikam_root_tag_ is present in some photos tagged with older 0350 // version of digiKam, must be removed 0351 0352 if (keyword.contains(QRegularExpression(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) 0353 { 0354 keyword = keyword.replace(QRegularExpression(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")), 0355 QLatin1String("")); 0356 } 0357 0358 filteredKeywords.append(keyword); 0359 } 0360 } 0361 0362 if (!filteredKeywords.isEmpty()) 0363 { 0364 // get tag ids, create if necessary 0365 0366 QList<int> tagIds = TagsCache::instance()->getOrCreateTags(filteredKeywords); 0367 d->commit.tagIds += tagIds; 0368 } 0369 0370 // Check Pick Label tag. 0371 0372 int pickId = d->metadata->getItemPickLabel(); 0373 0374 if (pickId != -1) 0375 { 0376 qCDebug(DIGIKAM_DATABASE_LOG) << "Pick Label found :" << pickId; 0377 0378 int tagId = TagsCache::instance()->tagForPickLabel((PickLabel)pickId); 0379 0380 if (tagId) 0381 { 0382 d->commit.tagIds << tagId; 0383 d->commit.hasPickTag = true; 0384 qCDebug(DIGIKAM_DATABASE_LOG) << "Assigned Pick Label Tag :" << tagId; 0385 } 0386 else 0387 { 0388 qCDebug(DIGIKAM_DATABASE_LOG) << "Cannot find Pick Label Tag for :" << pickId; 0389 } 0390 } 0391 0392 // Check Color Label tag. 0393 0394 int colorId = d->metadata->getItemColorLabel(); 0395 0396 if (colorId != -1) 0397 { 0398 qCDebug(DIGIKAM_DATABASE_LOG) << "Color Label found :" << colorId; 0399 0400 int tagId = TagsCache::instance()->tagForColorLabel((ColorLabel)colorId); 0401 0402 if (tagId) 0403 { 0404 d->commit.tagIds << tagId; 0405 d->commit.hasColorTag = true; 0406 qCDebug(DIGIKAM_DATABASE_LOG) << "Assigned Color Label Tag :" << tagId; 0407 } 0408 else 0409 { 0410 qCDebug(DIGIKAM_DATABASE_LOG) << "Cannot find Color Label Tag for :" << colorId; 0411 } 0412 } 0413 } 0414 0415 void ItemScanner::commitTags() 0416 { 0417 const QList<int>& currentTags = CoreDbAccess().db()->getItemTagIDs(d->scanInfo.id); 0418 const QVector<int>& colorTags = TagsCache::instance()->colorLabelTags(); 0419 const QVector<int>& pickTags = TagsCache::instance()->pickLabelTags(); 0420 QList<int> removeTags; 0421 0422 if (d->commit.hasColorTag || d->commit.hasPickTag) 0423 { 0424 Q_FOREACH (int tag, currentTags) 0425 { 0426 if (colorTags.contains(tag) || pickTags.contains(tag)) 0427 { 0428 removeTags << tag; 0429 } 0430 } 0431 0432 } 0433 0434 if (!removeTags.isEmpty()) 0435 { 0436 CoreDbAccess().db()->removeTagsFromItems(QList<qlonglong>() << d->scanInfo.id, removeTags); 0437 } 0438 0439 CoreDbAccess().db()->addTagsToItems(QList<qlonglong>() << d->scanInfo.id, d->commit.tagIds); 0440 } 0441 0442 void ItemScanner::scanFaces() 0443 { 0444 QSize size = d->img.size(); 0445 0446 if (!size.isValid()) 0447 { 0448 return; 0449 } 0450 0451 QMultiMap<QString, QVariant> metadataFacesMap; 0452 0453 if (!d->metadata->getItemFacesMap(metadataFacesMap)) 0454 { 0455 return; 0456 } 0457 0458 d->commit.commitFaces = true; 0459 d->commit.metadataFacesMap = metadataFacesMap; 0460 } 0461 0462 void ItemScanner::commitFaces() 0463 { 0464 FaceTagsEditor editor; 0465 QList<QRect> assignedRects; 0466 QMultiMap<QString, QVariant>::const_iterator it; 0467 QSize size = d->img.size(); 0468 int orientation = d->img.orientation(); 0469 QList<FaceTagsIface> databaseFaces = editor.databaseFaces(d->scanInfo.id); 0470 0471 for (it = d->commit.metadataFacesMap.constBegin() ; it != d->commit.metadataFacesMap.constEnd() ; ++it) 0472 { 0473 QString name = it.key(); 0474 QRectF rectF = it.value().toRectF(); 0475 0476 if (!rectF.isValid()) 0477 { 0478 int tagId = FaceTags::getOrCreateTagForPerson(name); 0479 0480 if (tagId) 0481 { 0482 ItemInfo(d->scanInfo.id).setTag(tagId); 0483 } 0484 else 0485 { 0486 qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to create a person tag for name" << name; 0487 } 0488 0489 continue; 0490 } 0491 0492 QRect rect = TagRegion::relativeToAbsolute(rectF, size); 0493 TagRegion::adjustToOrientation(rect, orientation, size); 0494 TagRegion newRegion(rect); 0495 0496 if (assignedRects.contains(rect)) 0497 { 0498 continue; 0499 } 0500 0501 assignedRects << rect; 0502 QList<FaceTagsIface>::iterator it1; 0503 0504 for (it1 = databaseFaces.begin() ; it1 != databaseFaces.end() ; ) 0505 { 0506 double minOverlap = (*it1).isConfirmedName() ? 0.25 : 0.5; 0507 0508 if ((*it1).region().intersects(newRegion, minOverlap)) 0509 { 0510 // Remove the duplicate face in the database. 0511 0512 editor.removeFace((*it1)); 0513 it1 = databaseFaces.erase(it1); 0514 0515 continue; 0516 } 0517 0518 ++it1; 0519 } 0520 0521 if (name.isEmpty()) 0522 { 0523 int tagId = FaceTags::unknownPersonTagId(); 0524 FaceTagsIface face(FaceTagsIface::UnknownName, d->scanInfo.id, tagId, newRegion); 0525 0526 editor.addManually(face); 0527 } 0528 else 0529 { 0530 int tagId = FaceTags::getOrCreateTagForPerson(name); 0531 0532 if (tagId) 0533 { 0534 editor.add(d->scanInfo.id, tagId, newRegion, false); 0535 } 0536 else 0537 { 0538 qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to create a person tag for name" << name; 0539 } 0540 } 0541 } 0542 } 0543 0544 void ItemScanner::checkCreationDateFromMetadata(QVariant& dateFromMetadata) const 0545 { 0546 // creation date: fall back to file system property 0547 0548 if (dateFromMetadata.isNull() || !dateFromMetadata.toDateTime().isValid()) 0549 { 0550 dateFromMetadata = creationDateFromFilesystem(d->fileInfo); 0551 } 0552 } 0553 0554 bool ItemScanner::checkRatingFromMetadata(const QVariant& ratingFromMetadata) const 0555 { 0556 // should only be overwritten if set in metadata 0557 0558 if ((d->scanMode == Rescan) || (d->scanMode == CleanScan)) 0559 { 0560 if (ratingFromMetadata.isNull() || (ratingFromMetadata.toInt() == -1)) 0561 { 0562 return false; 0563 } 0564 } 0565 0566 return true; 0567 } 0568 0569 MetadataFields ItemScanner::allImageMetadataFields() 0570 { 0571 // This list must reflect the order required by CoreDB::addImageMetadata 0572 0573 MetadataFields fields; 0574 fields << MetadataInfo::Make 0575 << MetadataInfo::Model 0576 << MetadataInfo::Lens 0577 << MetadataInfo::Aperture 0578 << MetadataInfo::FocalLength 0579 << MetadataInfo::FocalLengthIn35mm 0580 << MetadataInfo::ExposureTime 0581 << MetadataInfo::ExposureProgram 0582 << MetadataInfo::ExposureMode 0583 << MetadataInfo::Sensitivity 0584 << MetadataInfo::FlashMode 0585 << MetadataInfo::WhiteBalance 0586 << MetadataInfo::WhiteBalanceColorTemperature 0587 << MetadataInfo::MeteringMode 0588 << MetadataInfo::SubjectDistance 0589 << MetadataInfo::SubjectDistanceCategory; 0590 return fields; 0591 } 0592 0593 } // namespace Digikam