File indexing completed on 2025-01-19 03:55:57

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2019-09-26
0007  * Description : item metadata interface - libheif helpers.
0008  *
0009  * SPDX-FileCopyrightText: 2020-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dmetadata.h"
0016 
0017 // Qt includes
0018 
0019 #include <QFile>
0020 #include <QVariant>
0021 #include <QByteArray>
0022 #include <QTextStream>
0023 #include <QDataStream>
0024 #include <QFileInfo>
0025 #include <qplatformdefs.h>
0026 
0027 // Libheif includes
0028 
0029 #include <libheif/heif.h>
0030 
0031 // Local includes
0032 
0033 #include "digikam_config.h"
0034 #include "digikam_debug.h"
0035 
0036 namespace Digikam
0037 {
0038 
0039 static int64_t heifQIODeviceMetaGetPosition(void* userdata)      // krazy:exclude=typedefs
0040 {
0041     QFile* const file = static_cast<QFile*>(userdata);
0042 
0043     return (int64_t)file->pos();
0044 }
0045 
0046 static int heifQIODeviceMetaRead(void* data, size_t size, void* userdata)
0047 {
0048     QFile* const file = static_cast<QFile*>(userdata);
0049 
0050     if ((file->pos() + (qint64)size) > file->size())
0051     {
0052         return 0;
0053     }
0054 
0055     qint64 bytes = file->read((char*)data, size);
0056 
0057     return (int)((file->error() != QFileDevice::NoError) || bytes != (qint64)size);
0058 }
0059 
0060 static int heifQIODeviceMetaSeek(int64_t position, void* userdata)  // krazy:exclude=typedefs
0061 {
0062     QFile* const file = static_cast<QFile*>(userdata);
0063 
0064     return (int)file->seek(position);
0065 }
0066 
0067 static heif_reader_grow_status heifQIODeviceMetaWait(int64_t target_size, void* userdata)   // krazy:exclude=typedefs
0068 {
0069     QFile* const file = static_cast<QFile*>(userdata);
0070 
0071     if ((qint64)target_size > file->size())
0072     {
0073         return heif_reader_grow_status_size_beyond_eof;
0074     }
0075 
0076     return heif_reader_grow_status_size_reached;
0077 }
0078 
0079 bool s_isHeifSuccess(struct heif_error* const error)
0080 {
0081     if (error->code == 0)
0082     {
0083         return true;
0084     }
0085 
0086     qCWarning(DIGIKAM_METAENGINE_LOG) << "Error while processing HEIF image:" << error->message;
0087 
0088     return false;
0089 }
0090 
0091 void s_readHEICMetadata(struct heif_context* const heif_context, heif_item_id image_id,
0092                         QByteArray& exif, QByteArray& iptc, QByteArray& xmp)
0093 {
0094     struct heif_image_handle* image_handle = nullptr;
0095     struct heif_error error1               = heif_context_get_image_handle(heif_context,
0096                                                                            image_id,
0097                                                                            &image_handle);
0098 
0099     if (!s_isHeifSuccess(&error1))
0100     {
0101         heif_image_handle_release(image_handle);
0102 
0103         return;
0104     }
0105 
0106     heif_item_id dataIds[10];
0107 
0108     int count = heif_image_handle_get_list_of_metadata_block_IDs(image_handle,
0109                                                                  nullptr,
0110                                                                  dataIds,
0111                                                                  10);
0112 
0113     qDebug(DIGIKAM_METAENGINE_LOG) << "Found" << count << "HEIF metadata chunk";
0114 
0115     if (count > 0)
0116     {
0117         for (int i = 0 ; i < count ; ++i)
0118         {
0119             qDebug(DIGIKAM_METAENGINE_LOG) << "Parsing HEIF metadata chunk:" << heif_image_handle_get_metadata_type(image_handle, dataIds[i]);
0120 
0121             if (QLatin1String(heif_image_handle_get_metadata_type(image_handle, dataIds[i])) == QLatin1String("Exif"))
0122             {
0123                 // Read Exif chunk.
0124 
0125                 size_t length = heif_image_handle_get_metadata_size(image_handle, dataIds[i]);
0126 
0127                 QByteArray exifChunk;
0128                 exifChunk.resize((int)length);
0129 
0130                 struct heif_error error2 = heif_image_handle_get_metadata(image_handle,
0131                                                                           dataIds[i],
0132                                                                           exifChunk.data());
0133 
0134                 if ((error2.code == 0) && (length > 4))
0135                 {
0136                     // The first 4 bytes indicate the
0137                     // offset to the start of the TIFF header of the Exif data.
0138 
0139                     int skip = ((exifChunk.constData()[0] << 24) |
0140                                 (exifChunk.constData()[1] << 16) |
0141                                 (exifChunk.constData()[2] << 8)  |
0142                                  exifChunk.constData()[3]) + 4;
0143 
0144                     if (exifChunk.size() > skip)
0145                     {
0146                         // Copy the real exif data into the byte array
0147 
0148                         qDebug(DIGIKAM_METAENGINE_LOG) << "HEIF exif container found with size:" << length - skip;
0149                         exif.append((char*)(exifChunk.data() + skip), exifChunk.size() - skip);
0150                     }
0151                 }
0152             }
0153 
0154             if (QLatin1String(heif_image_handle_get_metadata_type(image_handle, dataIds[i])) == QLatin1String("iptc"))
0155             {
0156                 // Read Iptc chunk.
0157 
0158                 size_t length = heif_image_handle_get_metadata_size(image_handle, dataIds[i]);
0159 
0160                 iptc.resize((int)length);
0161 
0162                 struct heif_error error3 = heif_image_handle_get_metadata(image_handle,
0163                                                                           dataIds[i],
0164                                                                           iptc.data());
0165 
0166                 if (error3.code == 0)
0167                 {
0168                     qDebug(DIGIKAM_METAENGINE_LOG) << "HEIF iptc container found with size:" << length;
0169                 }
0170                 else
0171                 {
0172                     iptc = QByteArray();
0173                 }
0174             }
0175 
0176             if (
0177                 (QLatin1String(heif_image_handle_get_metadata_type(image_handle, dataIds[i]))         == QLatin1String("mime")) &&
0178                 (QLatin1String(heif_image_handle_get_metadata_content_type(image_handle, dataIds[i])) == QLatin1String("application/rdf+xml"))
0179                )
0180             {
0181                 // Read Xmp chunk.
0182 
0183                 size_t length = heif_image_handle_get_metadata_size(image_handle, dataIds[i]);
0184 
0185                 xmp.resize((int)length);
0186 
0187                 struct heif_error error4 = heif_image_handle_get_metadata(image_handle,
0188                                                                           dataIds[i],
0189                                                                           xmp.data());
0190 
0191                 if (error4.code == 0)
0192                 {
0193                     qDebug(DIGIKAM_METAENGINE_LOG) << "HEIF xmp container found with size:" << length;
0194                 }
0195                 else
0196                 {
0197                     xmp = QByteArray();
0198                 }
0199             }
0200         }
0201     }
0202 
0203     heif_image_handle_release(image_handle);
0204 }
0205 
0206 bool DMetadata::loadUsingLibheif(const QString& filePath)
0207 {
0208     QFileInfo fileInfo(filePath);
0209     QString ext = fileInfo.suffix().toUpper();
0210 
0211     if (
0212         !fileInfo.exists() || ext.isEmpty() ||
0213         (ext != QLatin1String("HEIF"))      ||
0214         (ext != QLatin1String("HEIC"))      ||
0215         (ext != QLatin1String("HIF"))
0216        )
0217     {
0218         return false;
0219     }
0220 
0221     QFile readFile(filePath);
0222 
0223     if (!readFile.open(QIODevice::ReadOnly))
0224     {
0225         qCWarning(DIGIKAM_METAENGINE_LOG) << "Error: Could not open source file.";
0226 
0227         return false;
0228     }
0229 
0230     const qint64 headerLen = 12;
0231 
0232     QByteArray header(headerLen, '\0');
0233 
0234     if (readFile.read((char*)header.data(), headerLen) != headerLen)
0235     {
0236         qCWarning(DIGIKAM_METAENGINE_LOG) << "Error: Could not parse magic identifier.";
0237 
0238         return false;
0239     }
0240 
0241     if ((memcmp(&header.data()[4], "ftyp", 4) != 0) &&
0242         (memcmp(&header.data()[8], "heic", 4) != 0) &&
0243         (memcmp(&header.data()[8], "heix", 4) != 0) &&
0244         (memcmp(&header.data()[8], "mif1", 4) != 0))
0245     {
0246         qCWarning(DIGIKAM_METAENGINE_LOG) << "Error: source file is not HEIF image.";
0247 
0248         return false;
0249     }
0250 
0251     readFile.reset();
0252 
0253     // -------------------------------------------------------------------
0254     // Initialize HEIF API.
0255 
0256     heif_item_id primary_image_id;
0257 
0258     struct heif_context* const heif_context = heif_context_alloc();
0259 
0260     heif_reader reader;
0261     reader.reader_api_version = 1;
0262     reader.get_position       = heifQIODeviceMetaGetPosition;
0263     reader.read               = heifQIODeviceMetaRead;
0264     reader.seek               = heifQIODeviceMetaSeek;
0265     reader.wait_for_file_size = heifQIODeviceMetaWait;
0266 
0267     struct heif_error error   = heif_context_read_from_reader(heif_context,
0268                                                               &reader,
0269                                                               (void*)&readFile,
0270                                                               nullptr);
0271 
0272     if (!s_isHeifSuccess(&error))
0273     {
0274         qCWarning(DIGIKAM_METAENGINE_LOG) << "Error: Could not read source file.";
0275         heif_context_free(heif_context);
0276 
0277         return false;
0278     }
0279 
0280     error = heif_context_get_primary_image_ID(heif_context, &primary_image_id);
0281 
0282     if (!s_isHeifSuccess(&error))
0283     {
0284         qCWarning(DIGIKAM_METAENGINE_LOG) << "Error: Could not load image data.";
0285         heif_context_free(heif_context);
0286 
0287         return false;
0288     }
0289 
0290     QByteArray exif;
0291     QByteArray iptc;
0292     QByteArray xmp;
0293     bool ret = false;
0294 
0295     s_readHEICMetadata(heif_context, primary_image_id, exif, iptc, xmp);
0296 
0297     if (!exif.isEmpty() || !iptc.isEmpty() || !xmp.isEmpty())
0298     {
0299         if (!exif.isEmpty())
0300         {
0301             setExif(exif);
0302         }
0303 
0304         if (!iptc.isEmpty())
0305         {
0306             setIptc(iptc);
0307         }
0308 
0309         if (!xmp.isEmpty())
0310         {
0311             setXmp(xmp);
0312         }
0313 
0314         ret = true;
0315     }
0316 
0317     heif_context_free(heif_context);
0318 
0319     return ret;
0320 }
0321 
0322 } // namespace Digikam