From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 7D63E4CC81 for ; Sun, 30 Nov 2025 13:19:05 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'8b/QqpuVfpq1EeXxPlncZtG1QM1pZ+4Exxw+h6qCqRs=', expected b'5bmUSry0vtywimVY4cVz/lXyZW2i0ce0TLXm6zCmoIY=')) header.d=ffmpeg.org header.i=@ffmpeg.org header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1764508724; h=mime-version : to : date : message-id : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=8b/QqpuVfpq1EeXxPlncZtG1QM1pZ+4Exxw+h6qCqRs=; b=hEfo1sC9lKNH+A/zzUfoWcIxf2c/wyM0bqi8ou1aKCCdxwH4RzrgtPak+u065U7toHJeI Hc0cl0jnz//nFYjL9prfBw8gcL+ibPatE3l90WFCC5Fg0oHk4r41QZrAC35gOFR5RVRFgEy NwkXVzVF4sOuxaaJMNlEc+5LXylMx4lwLT2cvK+Kuv7CVlK1DZ2Lk8BaBoVS2WffcitMnvx nPGbFsoT6y+pEVp4Rj3mDHIiA17XqtG8sioLOLTPnRqisTnNHOITx1sGQ5IX9/PaFwN9iKh FO88UrqBgNNVPKNWGsrX7Nhg+yhlLfR7HJRLZ1/xpAg0q1DCedb+gz3DFjgA== Received: from [172.19.0.3] (unknown [172.19.0.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 0BD366902D2; Sun, 30 Nov 2025 15:18:44 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1764508706; b=IZnOfipNgqoZURnRMT4KB6rVJPeSeejwjSc8gV5x3zVksClQBO7CKArF1IzwGw1E4cMAS ZAWgbbAb9pYCkdH6W5I1aP3Zx/QmZOXKigLkfwZ453ceGEArKWEcsOqZwiJONnErY3jXWF9 8x3OqakjIMeyrUB6rEFOQ/+xp5j8ssmyz2vAgxecfIWdxCRtrtF9iT1HypQC+1VPj4ffnK0 j0IxMrkDt+eBrIgKJ9dA1m5UUfT6TMtSZiEhraE831ylZB1916tNUtKOAoA72EhqBnbTqqn s9EIp16Fj8vQuQb+1S/ZScbtxRawGaBphDRege3eZXmGUq3zP+Na5KzOT3Ow== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1764508706; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=nUmHS4wEikA6UHLdpbJdC/4hKQlkkzBKEnUlIXaq1tQ=; b=pJo0Z+oCXFsHtYIuPdhHUJ5DKlcmCBcamGBCnDaD6+AnozElQixyMmq1maXEHPPfjEhyY cTioaVaPuFbxVbB1TauLOP4H8ZOkRKdViN9ZKsFgQQRloc/vgMg3HRJjUs2rOQDZ9waNLu+ Lhnt7gjuuPkWJ0LKIcF9rTd0Rpz4bwZ2g45+VpIWHmhQTipIgtKYDQ364bgM5qcDIivtP7H GbqaDmmlIZjWCjNX2cJ96hACNsDSXId46q0vxg7UMs+UkBLj029yQd1556qhPVveKU73j7Z oVHNbBNXsP4J38Rw3231TLLW5/GRhzXjl5oFrniR5LAXv0o2KS1VrezTQ30Q== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none; dmarc=pass header.from=ffmpeg.org policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=ffmpeg.org policy.dmarc=quarantine DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1764508698; h=content-type : mime-version : content-transfer-encoding : from : to : reply-to : subject : date : from; bh=5bmUSry0vtywimVY4cVz/lXyZW2i0ce0TLXm6zCmoIY=; b=CbkePWnychkQ4pRKJVA/CLfFewyEHX51EeRUqksr5ExfzufamBLC1BbtdyIgAo4SfCdfp xZ0HHWJxz8HuP+8EVNBt4/1FFUz0BW0hpzJcGkB8pcuzC5H+7ZaqRZfgbKTV5KxisUIAJhs nB/QQxOjCzHZ65LMgWliZzlYrHVPP1ahwzfety3xAXfj4P2c5erKCJ/6ZRVcAbVFTVk5lsC pmqzfntsauBnggxdIM6m3pdbeoCdvnjW2Nk2KFHb2y3wAliKZDdrM4/iWppwjt0EIHsmZCb I+a9MOQPygh+y9UZZf4cYWz/KzX8aSlVSPHSBWpkRtk54Gtu3ApjT2fYVGtw== Received: from 55ca25703178 (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id E3D3168EB85 for ; Sun, 30 Nov 2025 15:18:18 +0200 (EET) MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Date: Sun, 30 Nov 2025 13:18:18 -0000 Message-ID: <176450869904.39.10245069967752838866@2cb04c0e5124> Message-ID-Hash: RNV7FNX2U55E2MPGIIAXME7U3YZZG64Y X-Message-ID-Hash: RNV7FNX2U55E2MPGIIAXME7U3YZZG64Y X-MailFrom: code@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PATCH] avcodec/exif: parse additional EXIF IFDs (PR #21057) List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Leo Izen via ffmpeg-devel Cc: Leo Izen Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: PR #21057 opened by Leo Izen (Traneptora) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21057 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21057.patch Most EXIF metadata is in IFD0 and most EXIF payloads only contain one IFD, but it is possible for there to be more IFDs after the existing trailing one. exiftool and similar software report these IFDs as IFD1, IFD2, etc. This commit reads those additional IFDs and attaches them as dummy entries in the top-level IFD ranging from 0xFFFC down to 0xFFED, which are unused by the EXIF spec. The EXIF API is only able to return and work with a single IFD, so by attaching it as a subdirectory this metadata can be preserved. This is done transparently through the read/write process. Upon parsing an additional IFD1, it will be attached, but it will be written with av_exif_write after IFD0 rather than as a subdirectory, as intended. Existing files without more than one IFD, i.e. most files, will be unaffected by this change, as well as API clients looking to parse specific fields, but now more metadata is parsed and written, rather than simply being discarded as trailing data. Signed-off-by: Leo Izen >>From 3570ab1658f9ce87be88e71a82f666340885ca90 Mon Sep 17 00:00:00 2001 From: Leo Izen Date: Sun, 30 Nov 2025 06:55:16 -0500 Subject: [PATCH] avcodec/exif: parse additional EXIF IFDs Most EXIF metadata is in IFD0 and most EXIF payloads only contain one IFD, but it is possible for there to be more IFDs after the existing trailing one. exiftool and similar software report these IFDs as IFD1, IFD2, etc. This commit reads those additional IFDs and attaches them as dummy entries in the top-level IFD ranging from 0xFFFC down to 0xFFED, which are unused by the EXIF spec. The EXIF API is only able to return and work with a single IFD, so by attaching it as a subdirectory this metadata can be preserved. This is done transparently through the read/write process. Upon parsing an additional IFD1, it will be attached, but it will be written with av_exif_write after IFD0 rather than as a subdirectory, as intended. Existing files without more than one IFD, i.e. most files, will be unaffected by this change, as well as API clients looking to parse specific fields, but now more metadata is parsed and written, rather than simply being discarded as trailing data. Signed-off-by: Leo Izen --- libavcodec/exif.c | 106 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 93e1050d1f..50f56dd0c0 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -192,6 +192,24 @@ static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification: {"InteropIFD", 0xA005}, // <- Table 13 Interoperability IFD Attribute Information {"GlobalParametersIFD", 0x0190}, {"ProfileIFD", 0xc6f5}, + + /* Extra FFmpeg tags */ + { "IFD1", 0xFFFC}, + { "IFD2", 0xFFFB}, + { "IFD3", 0xFFFA}, + { "IFD4", 0xFFF9}, + { "IFD5", 0xFFF8}, + { "IFD6", 0xFFF7}, + { "IFD7", 0xFFF6}, + { "IFD8", 0xFFF5}, + { "IFD9", 0xFFF4}, + { "IFD10", 0xFFF3}, + { "IFD11", 0xFFF2}, + { "IFD12", 0xFFF1}, + { "IFD13", 0xFFF0}, + { "IFD14", 0xFFEF}, + { "IFD15", 0xFFEE}, + { "IFD16", 0xFFED}, }; /* same as type_sizes but with string == 1 */ @@ -635,7 +653,9 @@ static size_t exif_get_ifd_size(const AVExifMetadata *ifd) for (size_t i = 0; i < ifd->count; i++) { const AVExifEntry *entry = &ifd->entries[i]; if (entry->type == AV_TIFF_IFD) { - total_size += BASE_TAG_SIZE + exif_get_ifd_size(&entry->value.ifd) + entry->ifd_offset; + /* this is an extra IFD, not an entry, so we don't need to add base tag size */ + size_t base_size = entry->id > 0xFFECu && entry->id <= 0xFFFCu ? 0 : BASE_TAG_SIZE; + total_size += base_size + exif_get_ifd_size(&entry->value.ifd) + entry->ifd_offset; } else { size_t payload_size = entry->count * exif_sizes[entry->type]; total_size += BASE_TAG_SIZE + (payload_size > 4 ? payload_size : 0); @@ -708,12 +728,16 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, AVBufferRef *buf = NULL; size_t size, headsize = 8; PutByteContext pb; - int ret, off = 0; + int ret = 0, off = 0; + AVExifMetadata *ifd_new = NULL; + AVExifMetadata extra_ifds[16] = { 0 }; int le = 1; - if (*buffer) - return AVERROR(EINVAL); + if (*buffer) { + ret = AVERROR(EINVAL); + goto end; + } size = exif_get_ifd_size(ifd); switch (header_mode) { @@ -733,8 +757,10 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, break; } buf = av_buffer_alloc(size + off + headsize); - if (!buf) - return AVERROR(ENOMEM); + if (!buf) { + ret = AVERROR(ENOMEM); + goto end; + } if (header_mode == AV_EXIF_EXIF00) { AV_WL32(buf->data, MKTAG('E','x','i','f')); @@ -752,6 +778,30 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, tput32(&pb, le, 8); } + int extras; + for (extras = 0; extras < FF_ARRAY_ELEMS(extra_ifds); extras++) { + AVExifEntry *extra_entry = NULL; + ret = av_exif_get_entry(logctx, (AVExifMetadata *) ifd, 0xFFFCu - extras, 0, &extra_entry); + if (ret <= 0) + break; + if (!ifd_new) { + ifd_new = av_exif_clone_ifd(ifd); + if (!ifd_new) + break; + ifd = ifd_new; + } + /* calling remove_entry will call av_exif_free on the original */ + AVExifMetadata *cloned = av_exif_clone_ifd(&extra_entry->value.ifd); + if (!cloned) + break; + extra_ifds[extras] = *cloned; + /* don't use av_exif_free here, we want to preserve internals */ + av_free(cloned); + ret = av_exif_remove_entry(logctx, ifd_new, 0xFFFCu - extras, 0); + if (!cloned) + break; + } + ret = exif_write_ifd(logctx, &pb, le, 0, ifd); if (ret < 0) { av_buffer_unref(&buf); @@ -759,9 +809,26 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, return ret; } - *buffer = buf; + for (int i = 0; i < extras; i++) { + int tell = bytestream2_tell_p(&pb); + /* exif_write_ifd always writes 0 i.e. last ifd so we overwrite that here */ + bytestream2_seek_p(&pb, -4, SEEK_CUR); + tput32(&pb, le, tell); + ret = exif_write_ifd(logctx, &pb, le, 0, &extra_ifds[i]); + if (ret < 0) + break; + } - return 0; + *buffer = buf; + ret = 0; + +end: + av_exif_free(ifd_new); + av_freep(&ifd_new); + for (int i = 0; i < FF_ARRAY_ELEMS(extra_ifds); i++) + av_exif_free(&extra_ifds[i]); + + return ret; } int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size, @@ -820,8 +887,29 @@ int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size, av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data: %s\n", av_err2str(ret)); return ret; } + if (!ret) + goto finish; + int next = ret; + bytestream2_seek(&gbytes, next, SEEK_SET); - return bytestream2_tell(&gbytes); + /* cap at 16 extra IFDs for sanity/parse security */ + for (uint16_t extra_tag = 0xFFFCu; extra_tag > 0xFFECu; extra_tag--) { + AVExifMetadata extra_ifd = { 0 }; + ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, &extra_ifd, 1); + if (ret < 0) { + av_exif_free(&extra_ifd); + break; + } + next = ret; + bytestream2_seek(&gbytes, next, SEEK_SET); + ret = av_exif_set_entry(logctx, ifd, extra_tag, AV_TIFF_IFD, 1, NULL, 0, &extra_ifd); + av_exif_free(&extra_ifd); + if (ret < 0 || !next || bytestream2_get_bytes_left(&gbytes) <= 0) + break; + } + +finish: + return ret; } #define COLUMN_SEP(i, c) ((i) ? ((i) % (c) ? ", " : "\n") : "") -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org