File indexing completed on 2025-01-19 03:55:48
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2012-01-18 0007 * Description : database worker interface 0008 * 0009 * SPDX-FileCopyrightText: 2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "fileworkeriface.h" 0016 0017 // Qt includes 0018 0019 #include <QScopedPointer> 0020 0021 // KDE includes 0022 0023 #include <klocalizedstring.h> 0024 0025 // Local includes 0026 0027 #include "digikam_debug.h" 0028 #include "digikam_globals.h" 0029 #include "fileactionmngr_p.h" 0030 #include "metaenginesettings.h" 0031 #include "itemattributeswatch.h" 0032 #include "iteminfotasksplitter.h" 0033 #include "filereadwritelock.h" 0034 #include "scancontroller.h" 0035 #include "facetagseditor.h" 0036 #include "jpegutils.h" 0037 #include "dimg.h" 0038 0039 namespace Digikam 0040 { 0041 0042 void FileActionMngrFileWorker::writeOrientationToFiles(const FileActionItemInfoList& infos, int orientation) 0043 { 0044 QStringList failedItems; 0045 0046 Q_FOREACH (const ItemInfo& info, infos) 0047 { 0048 if (state() == WorkerObject::Deactivating) 0049 { 0050 break; 0051 } 0052 0053 QString filePath = info.filePath(); 0054 QScopedPointer<DMetadata> metadata(new DMetadata(filePath)); 0055 DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; 0056 metadata->setItemOrientation(o); 0057 0058 if (!metadata->applyChanges()) 0059 { 0060 failedItems.append(info.name()); 0061 } 0062 else 0063 { 0064 Q_EMIT imageDataChanged(filePath, true, true); 0065 QUrl url = QUrl::fromLocalFile(filePath); 0066 ItemAttributesWatch::instance()->fileMetadataChanged(url); 0067 } 0068 0069 infos.writtenToOne(); 0070 } 0071 0072 if (!failedItems.isEmpty()) 0073 { 0074 Q_EMIT imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); 0075 } 0076 0077 infos.finishedWriting(); 0078 } 0079 0080 void FileActionMngrFileWorker::writeMetadataToFiles(const FileActionItemInfoList& infos) 0081 { 0082 d->startingToWrite(infos); 0083 0084 ScanController::instance()->suspendCollectionScan(); 0085 0086 Q_FOREACH (const ItemInfo& info, infos) 0087 { 0088 MetadataHub hub; 0089 0090 if (state() == WorkerObject::Deactivating) 0091 { 0092 break; 0093 } 0094 0095 hub.load(info); 0096 QString filePath = info.filePath(); 0097 0098 if (MetaEngineSettings::instance()->settings().useLazySync) 0099 { 0100 hub.write(filePath, MetadataHub::WRITE_ALL); 0101 } 0102 else 0103 { 0104 ScanController::FileMetadataWrite writeScope(info); 0105 writeScope.changed(hub.write(filePath, MetadataHub::WRITE_ALL)); 0106 } 0107 0108 // hub emits fileMetadataChanged 0109 0110 infos.writtenToOne(); 0111 } 0112 0113 ScanController::instance()->resumeCollectionScan(); 0114 0115 infos.finishedWriting(); 0116 } 0117 0118 void FileActionMngrFileWorker::writeMetadata(const FileActionItemInfoList& infos, int flags) 0119 { 0120 d->startingToWrite(infos); 0121 0122 ScanController::instance()->suspendCollectionScan(); 0123 0124 Q_FOREACH (const ItemInfo& info, infos) 0125 { 0126 MetadataHub hub; 0127 0128 if (state() == WorkerObject::Deactivating) 0129 { 0130 break; 0131 } 0132 0133 hub.load(info); 0134 0135 // apply to file metadata 0136 0137 if (MetaEngineSettings::instance()->settings().useLazySync) 0138 { 0139 hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); 0140 } 0141 else 0142 { 0143 ScanController::FileMetadataWrite writeScope(info); 0144 writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); 0145 } 0146 0147 // hub emits fileMetadataChanged 0148 0149 infos.writtenToOne(); 0150 } 0151 0152 ScanController::instance()->resumeCollectionScan(); 0153 0154 infos.finishedWriting(); 0155 } 0156 0157 void FileActionMngrFileWorker::transform(const FileActionItemInfoList& infos, int action) 0158 { 0159 d->startingToWrite(infos); 0160 0161 QStringList failedItems; 0162 ScanController::instance()->suspendCollectionScan(); 0163 0164 Q_FOREACH (const ItemInfo& info, infos) 0165 { 0166 if (state() == WorkerObject::Deactivating) 0167 { 0168 break; 0169 } 0170 0171 FileWriteLocker lock(info.filePath()); 0172 0173 QString format = info.format(); 0174 QString filePath = info.filePath(); 0175 QSize originalSize = info.dimensions(); 0176 MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); 0177 bool isRaw = info.format().startsWith(QLatin1String("RAW")); 0178 bool isDng = (info.format() == QLatin1String("RAW-DNG")); 0179 bool isWritable = QFileInfo(filePath).isWritable(); 0180 bool rotateAsJpeg = false; 0181 bool rotateLossy = false; 0182 0183 MetaEngineSettingsContainer::RotationBehaviorFlags behavior; 0184 behavior = MetaEngineSettings::instance()->settings().rotationBehavior; 0185 bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); 0186 0187 // Check if rotation by content, as desired, is feasible 0188 // We'll later check again if it was successful 0189 0190 if (isWritable && (behavior & MetaEngineSettingsContainer::RotatingPixels)) 0191 { 0192 if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(filePath)) 0193 { 0194 rotateAsJpeg = true; 0195 } 0196 0197 if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) 0198 { 0199 DImg::FORMAT frmt = DImg::fileFormat(filePath); 0200 0201 switch (frmt) 0202 { 0203 case DImg::JPEG: 0204 case DImg::PNG: 0205 case DImg::TIFF: 0206 case DImg::JP2K: 0207 case DImg::PGF: 0208 case DImg::HEIF: 0209 { 0210 rotateLossy = true; 0211 break; 0212 } 0213 0214 default: 0215 { 0216 // QImage and ImageMagick codecs support 0217 0218 if (format == QLatin1String("JXL")) 0219 { 0220 rotateLossy = true; 0221 } 0222 else if (format == QLatin1String("AVIF")) 0223 { 0224 rotateLossy = true; 0225 } 0226 else if (format == QLatin1String("WEBP")) 0227 { 0228 rotateLossy = true; 0229 } 0230 0231 break; 0232 } 0233 } 0234 } 0235 } 0236 0237 MetaEngineRotation matrix; 0238 matrix *= currentOrientation; 0239 matrix *= (MetaEngineRotation::TransformationAction)action; 0240 MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); 0241 bool rotatedPixels = false; 0242 0243 if (rotateAsJpeg) 0244 { 0245 JPEGUtils::JpegRotator rotator(filePath); 0246 rotator.setCurrentOrientation(currentOrientation); 0247 0248 if (action == MetaEngineRotation::NoTransformation) 0249 { 0250 rotatedPixels = rotator.autoExifTransform(); 0251 } 0252 else 0253 { 0254 rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); 0255 } 0256 0257 if (!rotatedPixels) 0258 { 0259 failedItems.append(info.name()); 0260 } 0261 } 0262 else if (rotateLossy) 0263 { 0264 // Non-JPEG image: DImg 0265 0266 DImg image; 0267 0268 if (!image.load(filePath)) 0269 { 0270 failedItems.append(info.name()); 0271 } 0272 else 0273 { 0274 if (action == MetaEngineRotation::NoTransformation) 0275 { 0276 image.rotateAndFlip(currentOrientation); 0277 } 0278 else 0279 { 0280 image.transform(action); 0281 } 0282 0283 // TODO: Atomic operation!! 0284 // prepare metadata, including to reset Exif tag 0285 0286 image.prepareMetadataToSave(filePath, image.format(), true); 0287 0288 if (image.save(filePath, image.detectedFormat())) 0289 { 0290 rotatedPixels = true; 0291 } 0292 else 0293 { 0294 failedItems.append(info.name()); 0295 } 0296 } 0297 } 0298 0299 MetaEngine::ImageOrientation metaOrientation = finalOrientation; 0300 0301 if (rotatedPixels) 0302 { 0303 metaOrientation = MetaEngine::ORIENTATION_NORMAL; 0304 } 0305 0306 // Setting the rotation flag on Raws with embedded JPEG is a mess 0307 // Can apply to the RAW data, or to the embedded JPEG, or to both. 0308 0309 if ((rotateByMetadata && !isRaw) || isDng) 0310 { 0311 QScopedPointer<DMetadata> metadata(new DMetadata(filePath)); 0312 metadata->setItemOrientation(metaOrientation); 0313 metadata->applyChanges(); 0314 } 0315 0316 // DB rotation 0317 0318 ItemInfo(info).setOrientation(metaOrientation); 0319 0320 ScanController::instance()->scannedInfo(filePath, 0321 CollectionScanner::ModifiedScan); 0322 0323 // Adjust faces in the DB 0324 0325 bool confirmed = FaceTagsEditor().rotateFaces(info.id(), originalSize, 0326 currentOrientation, finalOrientation); 0327 0328 // Write faces to metadata when confirmed names exists 0329 0330 if (confirmed) 0331 { 0332 MetadataHub hub; 0333 hub.load(info); 0334 0335 ScanController::FileMetadataWrite writeScope(info); 0336 writeScope.changed(hub.writeToMetadata(info, MetadataHub::WRITE_TAGS, true)); 0337 } 0338 0339 if (!failedItems.contains(info.name())) 0340 { 0341 Q_EMIT imageDataChanged(filePath, true, true); 0342 ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); 0343 } 0344 0345 infos.writtenToOne(); 0346 } 0347 0348 if (!failedItems.isEmpty()) 0349 { 0350 Q_EMIT imageChangeFailed(i18n("Failed to transform these files:"), failedItems); 0351 } 0352 0353 infos.finishedWriting(); 0354 0355 ScanController::instance()->resumeCollectionScan(); 0356 } 0357 0358 } // namespace Digikam 0359 0360 #include "moc_fileworkeriface.cpp"