diff --git a/core/libs/fileactionmanager/databaseworkeriface.cpp b/core/libs/fileactionmanager/databaseworkeriface.cpp index 2b20218a31..4850f3e3a3 100644 --- a/core/libs/fileactionmanager/databaseworkeriface.cpp +++ b/core/libs/fileactionmanager/databaseworkeriface.cpp @@ -1,388 +1,389 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "databaseworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscanner.h" #include "coredboperationgroup.h" #include "iteminfotasksplitter.h" #include "fileactionmngr_p.h" #include "scancontroller.h" #include "disjointmetadata.h" +#include "faceutils.h" namespace Digikam { void FileActionMngrDatabaseWorker::assignTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, true); } void FileActionMngrDatabaseWorker::removeTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, false); } void FileActionMngrDatabaseWorker::changeTags(FileActionItemInfoList infos, const QList& tagIDs, bool addOrRemove) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); for (QList::const_iterator tagIt = tagIDs.constBegin() ; tagIt != tagIDs.constEnd() ; ++tagIt) { if (addOrRemove) { hub.setTag(*tagIt, DisjointMetadata::MetadataAvailable); } else { hub.setTag(*tagIt, DisjointMetadata::MetadataInvalid); } } hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); qCDebug(DIGIKAM_GENERAL_LOG) << "Scheduled to write"; for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_TAGS); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignPickLabel(FileActionItemInfoList infos, int pickId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setPickLabel(pickId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_PICKLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignColorLabel(FileActionItemInfoList infos, int colorId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setColorLabel(colorId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_COLORLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignRating(FileActionItemInfoList infos, int rating) { DisjointMetadata hub; QList forWriting; rating = qMin(RatingMax, qMax(RatingMin, rating)); { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setRating(rating); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_RATING); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::editGroup(int groupAction, const ItemInfo& pick, FileActionItemInfoList infos) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& constInfo, infos) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo info(constInfo); switch (groupAction) { case AddToGroup: info.addToGroup(pick); break; case RemoveFromGroup: info.removeFromGroup(); break; case Ungroup: info.clearGroup(); break; } infos.dbProcessedOne(); group.allowLift(); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::setExifOrientation(FileActionItemInfoList infos, int orientation) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (ItemInfo info, infos) { if (state() == WorkerObject::Deactivating) { break; } - MetadataHub hub; - hub.adjustFaceRectangles(info, false, - orientation, - info.orientation()); + // Adjust Faces + + FaceUtils().rotateFaces(info, orientation, + info.orientation()); info.setOrientation(orientation); } } infos.dbProcessed(infos.count()); infos.schedulingForWrite(infos.count(), i18n("Revising Exif Orientation tags"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeOrientationToFiles(FileActionItemInfoList(splitter.next()), orientation); } infos.dbFinished(); } void FileActionMngrDatabaseWorker::applyMetadata(FileActionItemInfoList infos, DisjointMetadata *hub) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } // apply to database hub->write(info); infos.dbProcessedOne(); group.allowLift(); } } if (hub->willWriteMetadata(DisjointMetadata::FullWriteIfChanged), Qt::DirectConnection) { int flags = hub->changedFlags(); // don't filter by shallSendForWriting here; we write from the hub, not from freshly loaded data infos.schedulingForWrite(infos.size(), i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), flags); } } delete hub; infos.dbFinished(); } void FileActionMngrDatabaseWorker::copyAttributes(FileActionItemInfoList infos, const QStringList& derivedPaths) { if (infos.size() == 1) { foreach (const QString& path, derivedPaths) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo dest = ScanController::instance()->scannedInfo(path); CollectionScanner::copyFileProperties(infos.first(), dest); } infos.dbProcessedOne(); } infos.dbFinished(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/fileworkeriface.cpp b/core/libs/fileactionmanager/fileworkeriface.cpp index 09ab9d9a6e..068a232119 100644 --- a/core/libs/fileactionmanager/fileworkeriface.cpp +++ b/core/libs/fileactionmanager/fileworkeriface.cpp @@ -1,331 +1,342 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "fileworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "fileactionmngr_p.h" #include "metaenginesettings.h" #include "itemattributeswatch.h" #include "iteminfotasksplitter.h" #include "collectionscanner.h" #include "scancontroller.h" +#include "faceutils.h" #include "jpegutils.h" #include "dimg.h" namespace Digikam { void FileActionMngrFileWorker::writeOrientationToFiles(FileActionItemInfoList infos, int orientation) { QStringList failedItems; foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); DMetadata metadata(path); DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; metadata.setItemOrientation(o); if (!metadata.applyChanges()) { failedItems.append(info.name()); } else { emit imageDataChanged(path, true, true); QUrl url = QUrl::fromLocalFile(path); ItemAttributesWatch::instance()->fileMetadataChanged(url); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); } infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadataToFiles(FileActionItemInfoList infos) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); QString path = info.filePath(); if (MetaEngineSettings::instance()->settings().useLazySync) { hub.write(path, MetadataHub::WRITE_ALL); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.write(path, MetadataHub::WRITE_ALL)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadata(FileActionItemInfoList infos, int flags) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); // apply to file metadata if (MetaEngineSettings::instance()->settings().useLazySync) { hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::transform(FileActionItemInfoList infos, int action) { d->startingToWrite(infos); QStringList failedItems; ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); QString format = info.format(); MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); bool isRaw = info.format().startsWith(QLatin1String("RAW")); bool rotateAsJpeg = false; bool rotateLossy = false; MetaEngineSettingsContainer::RotationBehaviorFlags behavior; behavior = MetaEngineSettings::instance()->settings().rotationBehavior; bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); // Check if rotation by content, as desired, is feasible // We'll later check again if it was successful if (behavior & MetaEngineSettingsContainer::RotatingPixels) { if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(path)) { rotateAsJpeg = true; } if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) { DImg::FORMAT frmt = DImg::fileFormat(path); switch (frmt) { case DImg::JPEG: case DImg::PNG: case DImg::TIFF: case DImg::JP2K: case DImg::PGF: case DImg::HEIF: rotateLossy = true; default: break; } } } MetaEngineRotation matrix; matrix *= currentOrientation; matrix *= (MetaEngineRotation::TransformationAction)action; MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); bool rotatedPixels = false; if (rotateAsJpeg) { JPEGUtils::JpegRotator rotator(path); rotator.setCurrentOrientation(currentOrientation); if (action == MetaEngineRotation::NoTransformation) { rotatedPixels = rotator.autoExifTransform(); } else { rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); } if (!rotatedPixels) { failedItems.append(info.name()); } } else if (rotateLossy) { // Non-JPEG image: DImg DImg image; if (!image.load(path)) { failedItems.append(info.name()); } else { if (action == MetaEngineRotation::NoTransformation) { image.rotateAndFlip(currentOrientation); } else { image.transform(action); } // TODO: Atomic operation!! // prepare metadata, including to reset Exif tag image.prepareMetadataToSave(path, image.format(), true); if (image.save(path, image.detectedFormat())) { rotatedPixels = true; } else { failedItems.append(info.name()); } } } int newOrientation = finalOrientation; if (rotatedPixels) { finalOrientation = MetaEngine::ORIENTATION_NORMAL; } // DB rotation ItemInfo(info).setOrientation(finalOrientation); // Adjust Faces - MetadataHub hub; - hub.adjustFaceRectangles(info, rotatedPixels, - newOrientation, - currentOrientation); + QSize newSize = FaceUtils().rotateFaces(info, newOrientation, + currentOrientation); + if (newSize.isValid()) + { + MetadataHub hub; + hub.load(info); + + // Adjusted newSize + + newSize = rotatedPixels ? newSize : info.dimensions(); + + hub.loadFaceTags(info, newSize); + hub.write(info.filePath(), MetadataHub::WRITE_TAGS, true); + } if (rotateByMetadata) { // Setting the rotation flag on Raws with embedded JPEG is a mess // Can apply to the RAW data, or to the embedded JPEG, or to both. if (!isRaw) { DMetadata metadata(path); metadata.setItemOrientation(finalOrientation); metadata.applyChanges(); } } CollectionScanner scanner; scanner.scanFile(info, CollectionScanner::NormalScan); if (!failedItems.contains(info.name())) { emit imageDataChanged(path, true, true); ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to transform these files:"), failedItems); } infos.finishedWriting(); ScanController::instance()->resumeCollectionScan(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/metadatahub.cpp b/core/libs/fileactionmanager/metadatahub.cpp index 3f44b1ec59..a94df4ae02 100644 --- a/core/libs/fileactionmanager/metadatahub.cpp +++ b/core/libs/fileactionmanager/metadatahub.cpp @@ -1,909 +1,824 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-01-05 * Description : Metadata handling * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2020 by Gilles Caulier * Copyright (C) 2014-2015 by Veaceslav Munteanu * * This program is free software you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metadatahub.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbwatch.h" #include "iteminfo.h" #include "itemcomments.h" #include "itemposition.h" #include "template.h" #include "templatemanager.h" #include "applicationsettings.h" #include "itemattributeswatch.h" #include "tagscache.h" #include "facetagseditor.h" #include "metadatahubmngr.h" #ifdef HAVE_KFILEMETADATA # include "baloowrap.h" #endif namespace Digikam { class Q_DECL_HIDDEN MetadataHub::Private { public: explicit Private() : pickLabel(-1), colorLabel(-1), rating(-1), count(0), dateTimeStatus(MetadataHub::MetadataInvalid), titlesStatus(MetadataHub::MetadataInvalid), commentsStatus(MetadataHub::MetadataInvalid), pickLabelStatus(MetadataHub::MetadataInvalid), colorLabelStatus(MetadataHub::MetadataInvalid), ratingStatus(MetadataHub::MetadataInvalid), templateStatus(MetadataHub::MetadataInvalid) { } public: int pickLabel; int colorLabel; int rating; int count; QDateTime dateTime; CaptionsMap titles; CaptionsMap comments; Template metadataTemplate; QMap tags; QStringList tagList; QMultiMap faceTagsList; ItemPosition itemPosition; MetadataHub::Status dateTimeStatus; MetadataHub::Status titlesStatus; MetadataHub::Status commentsStatus; MetadataHub::Status pickLabelStatus; MetadataHub::Status colorLabelStatus; MetadataHub::Status ratingStatus; MetadataHub::Status templateStatus; public: template void loadSingleValue(const T& data, T& storage, MetadataHub::Status& status); }; // ------------------------------------------------------------------------------------------ MetadataHub::MetadataHub() : d(new Private) { } MetadataHub::MetadataHub(const MetadataHub& other) : d(new Private(*other.d)) { } MetadataHub::~MetadataHub() { delete d; } MetadataHub& MetadataHub::operator=(const MetadataHub& other) { (*d) = (*other.d); return *this; } MetadataHub* MetadataHub::clone() const { return (new MetadataHub(*this)); } void MetadataHub::reset() { (*d) = Private(); } // -------------------------------------------------- void MetadataHub::load(const ItemInfo& info) { d->count++; //qCDebug(DIGIKAM_GENERAL_LOG) << "---------------------------------Load from ItemInfo ----------------"; CaptionsMap commentMap; CaptionsMap titleMap; { CoreDbAccess access; ItemComments comments = info.imageComments(access); commentMap = comments.toCaptionsMap(); titleMap = comments.toCaptionsMap(DatabaseComment::Title); } Template tref = info.metadataTemplate(); Template t = TemplateManager::defaultManager()->findByContents(tref); //qCDebug(DIGIKAM_GENERAL_LOG) << "Found Metadata Template: " << t.templateTitle(); load(info.dateTime(), titleMap, commentMap, info.colorLabel(), info.pickLabel(), info.rating(), t.isNull() ? tref : t); QList tagIds = info.tagIds(); loadTags(tagIds); loadFaceTags(info, info.dimensions()); d->itemPosition = info.imagePosition(); } // private common code to merge tags void MetadataHub::loadTags(const QList& loadedTags) { d->tags.clear(); foreach (int tagId, loadedTags) { if (TagsCache::instance()->isInternalTag(tagId)) { continue; } d->tags[tagId] = MetadataAvailable; } } // private common code to load dateTime, comment, color label, pick label, rating void MetadataHub::load(const QDateTime& dateTime, const CaptionsMap& titles, const CaptionsMap& comments, int colorLabel, int pickLabel, int rating, const Template& t) { if (dateTime.isValid()) { d->loadSingleValue(dateTime, d->dateTime, d->dateTimeStatus); } d->loadSingleValue(pickLabel, d->pickLabel, d->pickLabelStatus); d->loadSingleValue(colorLabel, d->colorLabel, d->colorLabelStatus); d->loadSingleValue(rating, d->rating, d->ratingStatus); d->loadSingleValue(titles, d->titles, d->titlesStatus); d->loadSingleValue(comments, d->comments, d->commentsStatus); d->loadSingleValue