PR #22710 opened by Leo Izen (Traneptora) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22710 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22710.patch
Various EXIF fixes. See: #22631 >From c9a77ecad1ac9777ddd622b70121d5cc41a3f4b6 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 10:53:11 -0400 Subject: [PATCH 01/11] avutil/frame.h: fix AV_EXIF_SIDE_DATA declaration This commit re-aligns the declaration by removing extra whitespace and fixes the comment above to have the correct acronym. It also documents what the four magic bytes indicate. Signed-off-by: Leo Izen <[email protected]> --- libavutil/frame.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libavutil/frame.h b/libavutil/frame.h index 771c9ce453..4ca39acaf4 100644 --- a/libavutil/frame.h +++ b/libavutil/frame.h @@ -256,10 +256,11 @@ enum AVFrameSideDataType { AV_FRAME_DATA_3D_REFERENCE_DISPLAYS, /** - * Extensible image file format metadata. The payload is a buffer containing - * EXIF metadata, starting with either 49 49 2a 00, or 4d 4d 00 2a. + * Exchangeable image file format metadata. The payload is a buffer containing + * EXIF metadata, starting with either 49 49 2a 00, or 4d 4d 00 2a. These four + * bytes signify the endianness, and occur as the first part of the TIFF header. */ - AV_FRAME_DATA_EXIF, + AV_FRAME_DATA_EXIF, }; enum AVActiveFormatDescription { -- 2.52.0 >From f39a8b28d485abf0db6f65464a0ab4c8c05f697e Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:22:28 -0400 Subject: [PATCH 02/11] avcodec/exif.h: fix documentation on av_exif_get_entry and similar Add additional documentation to av_exif_get_entry and also to av_exif_set_entry that was already part of the existing ABI but was insufficiently documented before this commit. Also clarifies that av_fast_realloc is used, instead of av_realloc on av_exif_set_entry. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/libavcodec/exif.h b/libavcodec/exif.h index 1824a38d1c..cbb7d3ba12 100644 --- a/libavcodec/exif.h +++ b/libavcodec/exif.h @@ -138,7 +138,7 @@ int32_t av_exif_get_tag_id(const char *name); * ID, it will set the existing one to have the other information provided. Otherwise, it * will allocate a new entry. * - * This function reallocates ifd->entries using av_realloc and allocates (using av_malloc) + * This function reallocates ifd->entries using av_fast_realloc and allocates (using av_malloc) * a new value member of the entry, then copies the contents of value into that buffer. */ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type, @@ -154,8 +154,15 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif * will be written into *value. * * If the entry was present and returned successfully, a positive number is returned. + * + * The positive number is equal to 1 plus the offset at which the entry was found. If the + * entry was found at the top level, this will be in the range of 1 to the number of entries + * in the IFD (inclusive). If the entry was found in a sub-IFD, then it will be that number, + * plus the number of entries in the parent IFD and all IFDs at the same level as the sub-IFD + * before it. This guarantees the offset value is unique. + * * If the entry was not found, *value is left untouched and zero is returned. - * If an error occurred, a negative number is returned. + * If an error occurred, a negative AVERROR is returned. */ int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags, AVExifEntry **value); @@ -163,8 +170,15 @@ int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags, * Remove an entry from the provided EXIF metadata struct. * * If the entry was present and removed successfully, a positive number is returned. + * + * The positive number is equal to 1 plus the offset at which the entry was found. If the + * entry was found at the top level, this will be in the range of 1 to the number of entries + * in the IFD (inclusive). If the entry was found in a sub-IFD, then it will be that number, + * plus the number of entries in the parent IFD and all IFDs at the same level as the sub-IFD + * before it. This guarantees the offset value is unique. + * * If the entry was not found, zero is returned. - * If an error occurred, a negative number is returned. + * If an error occurred, a negative AVERROR is returned. */ int av_exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags); -- 2.52.0 >From 9011673ef184eddd1124e94d437da06d1fc67533 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:15:26 -0400 Subject: [PATCH 03/11] avcodec/exif.c: pop entry off IFD if allocation fails In av_exif_set_entry, if cloning the entry fails because of an alloc failed, then we remove the entry from the IFD. If that entry exists in the middle of ifd->entries we need to shift everything to the left which this commit implements. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index e422b28b8d..9b7bb392d1 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1195,7 +1195,7 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value) { void *temp; - int ret = 0; + int ret, offset; AVExifEntry *entry = NULL; AVExifEntry src = { 0 }; @@ -1207,6 +1207,7 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif ret = av_exif_get_entry(logctx, ifd, id, 0, &entry); if (ret < 0) return ret; + offset = ret; if (entry) { exif_free_entry(entry); @@ -1234,8 +1235,15 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif ret = exif_clone_entry(entry, &src); - if (ret < 0) + if (ret < 0) { + /* offset is the actual offset + 1 */ + if (offset) { + size_t remaining = ifd->count - offset; + /* pop the entry off the IFD by shifting everything to the left */ + memmove(&ifd->entries[offset - 1], &ifd->entries[offset], sizeof(*ifd->entries) * remaining); + } ifd->count--; + } return ret; } -- 2.52.0 >From 10ea4a63b5c916e328f5d983b80b858ab4dbc054 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:33:12 -0400 Subject: [PATCH 04/11] avcodec/exif.c: use less than or equal for max width and height The max width and height for PIXEL_X_TAG and PIXEL_Y_TAG is 0xFFFFu because these are unsigned shorts, but we used < instead of <= erroneously. Fix that. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 9b7bb392d1..7a91cb9178 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1464,7 +1464,7 @@ int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd if (ret < 0) goto end; } - if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) { + if (!pw && w && w <= 0xFFFFu || !ph && h && h <= 0xFFFFu) { AVExifMetadata *exif; AVExifEntry *exif_entry; int exif_found = av_exif_get_entry(logctx, ifd, EXIFIFD_TAG, 0, &exif_entry); @@ -1482,12 +1482,12 @@ int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd } exif = &ifd->entries[ifd->count - 1].value.ifd; } - if (!pw && w && w < 0xFFFFu) { + if (!pw && w && w <= 0xFFFFu) { ret = av_exif_set_entry(logctx, exif, PIXEL_X_TAG, AV_TIFF_SHORT, 1, NULL, 0, &w); if (ret < 0) goto end; } - if (!ph && h && h < 0xFFFFu) { + if (!ph && h && h <= 0xFFFFu) { ret = av_exif_set_entry(logctx, exif, PIXEL_Y_TAG, AV_TIFF_SHORT, 1, NULL, 0, &h); if (ret < 0) goto end; -- 2.52.0 >From 6bbc914ad975465729329da90e9cd01a063f25e7 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:35:28 -0400 Subject: [PATCH 05/11] avcodec/exif.c: account for header_mode difference on rewrite When determining if we need to rewrite the exif buffer or can pass through as-is, account for a difference in header_mode requested from the one that is used internally. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 7a91cb9178..3f5c066aaf 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1525,6 +1525,13 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ goto end; } + /* + * we always have to rewrite if the requested header mode + * does not match the internal header mode, which is always + * AV_EXIF_TIFF_HEADER inside FFmpeg. + */ + rewrite = rewrite || header_mode != AV_EXIF_TIFF_HEADER; + if (rewrite) { ret = av_exif_write(logctx, &ifd, &buffer, header_mode); if (ret < 0) -- 2.52.0 >From b23d3a791194bef79a009eb3b634ce49491defde Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:41:20 -0400 Subject: [PATCH 06/11] avcodec/exif.c: add check for singular displaymatrix data If av_exif_matrix_to_orientation returns 0, then the display matrix is singular. In this case we should treat it as 1 and print a warning. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 3f5c066aaf..4c493aade7 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1393,6 +1393,10 @@ int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd if (sd_orient) orientation = av_exif_matrix_to_orientation((int32_t *) sd_orient->data); + if (!orientation) { + av_log(logctx, AV_LOG_WARNING, "display matrix is singular\n"); + orientation = 1; + } if (orientation != 1) av_log(logctx, AV_LOG_DEBUG, "matrix contains nontrivial EXIF orientation: %" PRIu64 "\n", orientation); -- 2.52.0 >From 9d1ae39b4789cda0ea0cab09f95e0c431cf31915 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:43:45 -0400 Subject: [PATCH 07/11] avcodec/exif.c: reset ifd->size when freeing ifd->entries If we free ifd->entries then we need to set ifd->size to 0 so another call to av_fast_realloc doesn't get confused. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 4c493aade7..7c8c3ea6da 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1273,8 +1273,10 @@ static int exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int exif_free_entry(&ifd->entries[index]); if (index == --ifd->count) { - if (!index) + if (!index) { av_freep(&ifd->entries); + ifd->size = 0; + } return 1; } -- 2.52.0 >From 19d2910e3c6a1834ac0c92618d612492f36f9dd7 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 11:59:10 -0400 Subject: [PATCH 08/11] avcodec/exif.c: synthesize EXIF data from frame metadata and matrix If the displaymatrix is present, we should synthesize EXIF data from the values there even if there is no EXIF attached to the frame. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 7c8c3ea6da..02a4034daf 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1518,12 +1518,14 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ return AVERROR(EINVAL); sd_exif = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF); - if (!sd_exif) + if (!sd_exif && !av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) return 0; - ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER); - if (ret < 0) - goto end; + if (sd_exif) { + ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER); + if (ret < 0) + goto end; + } rewrite = ff_exif_sanitize_ifd(logctx, frame, &ifd); if (rewrite < 0) { @@ -1535,8 +1537,11 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ * we always have to rewrite if the requested header mode * does not match the internal header mode, which is always * AV_EXIF_TIFF_HEADER inside FFmpeg. + * + * If ifd.count == 0 then there's no data to write at all. + * This is possible if the frame width and height are zero and the orientation is 1. */ - rewrite = rewrite || header_mode != AV_EXIF_TIFF_HEADER; + rewrite = (rewrite || header_mode != AV_EXIF_TIFF_HEADER) && ifd.count; if (rewrite) { ret = av_exif_write(logctx, &ifd, &buffer, header_mode); @@ -1544,7 +1549,7 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ goto end; *buffer_ptr = buffer; - } else { + } else if (sd_exif) { *buffer_ptr = av_buffer_ref(sd_exif->buf); if (!*buffer_ptr) { ret = AVERROR(ENOMEM); -- 2.52.0 >From 666224b20256ca9957dda09937ffdf946310dc58 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 12:04:07 -0400 Subject: [PATCH 09/11] avcodec/exif_internal.h: improve return docs for ff_exif_get_buffer This commit improves the documentation for the return value of the function ff_exif_get_buffer. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/exif_internal.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h index c0d2b3ef62..713b84ef62 100644 --- a/libavcodec/exif_internal.h +++ b/libavcodec/exif_internal.h @@ -56,11 +56,12 @@ int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd * side data types that are included in the frame data, such as possibly an * instance of AV_FRAME_DATA_DISPLAYMATRIX. It also sets width and height tags * to match those of the AVFrame if they are different. + * The *buffer argument must be NULL before calling. * - * Upon error, *buffer will be NULL. The buffer becomes owned by the caller upon - * success. The *buffer argument must be NULL before calling. If *buffer is NULL - * upon return then a negative return value indicates an error, and a zero return - * value indicates that there was no EXIF data to write. + * On success, a positive number is returned, and the buffer becomes owned by the caller. + * A negative AVERROR return value means that an error occurred. + * A zero return value means that there was no EXIF data to write. + * In both the negative and zero cases, *buffer will be NULL. */ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer, enum AVExifHeaderMode header_mode); -- 2.52.0 >From 6ec45c44a89a42b757c9a17b2da4b481e144d2c4 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 12:07:26 -0400 Subject: [PATCH 10/11] avcodec/libjxlenc: check orientation tag metadata before reading We need to check that entry->count is nonzero and that entry->type is AV_TIFF_SHORT before reading from the buffer, in case a maliciously constructed IFD uses a zero-count or an unusual type (e.g. IFD) for it. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/libjxlenc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c index ca46061545..228a8eb6ac 100644 --- a/libavcodec/libjxlenc.c +++ b/libavcodec/libjxlenc.c @@ -434,7 +434,8 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame, if (ret >= 0) ret = av_exif_get_entry(avctx, &ifd, tag, 0, &orient); if (ret >= 0 && orient) { - if (!have_matrix && orient->value.uint[0] >= 1 && orient->value.uint[0] <= 8) { + if (!have_matrix && orient->type == AV_TIFF_SHORT && orient->count + && orient->value.uint[0] >= 1 && orient->value.uint[0] <= 8) { av_exif_orientation_to_matrix(matrix, orient->value.uint[0]); have_matrix = 1; } -- 2.52.0 >From 0c19445e3d8d4297c32e476dd281432c813e1536 Mon Sep 17 00:00:00 2001 From: Leo Izen <[email protected]> Date: Sat, 4 Apr 2026 12:09:13 -0400 Subject: [PATCH 11/11] avcodec/libjxlenc: fix frame->linesize raw pointer read These should say frame->linesize[0] as it does everywhere else this variable is referenced. Fixes a typo bug. Signed-off-by: Leo Izen <[email protected]> --- libavcodec/libjxlenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c index 228a8eb6ac..a9eb1ffe10 100644 --- a/libavcodec/libjxlenc.c +++ b/libavcodec/libjxlenc.c @@ -456,7 +456,7 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame, /* av_display_matrix_flip is a right-multipilcation */ /* i.e. flip is applied before the previous matrix */ - if (frame->linesize < 0) + if (frame->linesize[0] < 0) av_display_matrix_flip(matrix, 0, 1); orientation = av_exif_matrix_to_orientation(matrix); @@ -469,7 +469,7 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame, } /* restore the previous value */ - if (frame->linesize < 0) + if (frame->linesize[0] < 0) av_display_matrix_flip(matrix, 0, 1); if (animated) { -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
