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]

Reply via email to