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"