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