Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: James Almer via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: James Almer <code@ffmpeg.org>
Subject: [FFmpeg-devel] [PATCH] avformat: add container level Exif metadata support (PR #20321)
Date: Sat, 23 Aug 2025 22:24:55 +0300 (EEST)
Message-ID: <20250823192455.456ED68D20D@ffbox0-bg.ffmpeg.org> (raw)

PR #20321 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20321
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20321.patch

Includes Exif packet side data, and starting with support in Heif.


From 962fea2d2cb56450d5325969cb655543895d4885 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 13:23:05 -0300
Subject: [PATCH 01/11] avcodec/packet: add an Exif side data type

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/avcodec.c       | 1 +
 libavcodec/options_table.h | 1 +
 libavcodec/packet.c        | 1 +
 libavcodec/packet.h        | 6 ++++++
 4 files changed, 9 insertions(+)

diff --git a/libavcodec/avcodec.c b/libavcodec/avcodec.c
index 834b7ad242..2181439113 100644
--- a/libavcodec/avcodec.c
+++ b/libavcodec/avcodec.c
@@ -66,6 +66,7 @@ const SideDataMap ff_sd_global_map[] = {
     { AV_PKT_DATA_ICC_PROFILE,                AV_FRAME_DATA_ICC_PROFILE },
     { AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT,AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT },
     { AV_PKT_DATA_3D_REFERENCE_DISPLAYS,      AV_FRAME_DATA_3D_REFERENCE_DISPLAYS },
+    { AV_PKT_DATA_EXIF,                       AV_FRAME_DATA_EXIF },
     { AV_PKT_DATA_NB },
 };
 
diff --git a/libavcodec/options_table.h b/libavcodec/options_table.h
index 25da169343..43aec402ba 100644
--- a/libavcodec/options_table.h
+++ b/libavcodec/options_table.h
@@ -406,6 +406,7 @@ static const AVOption avcodec_options[] = {
     {"mastering_display_metadata",  .default_val.i64 = AV_PKT_DATA_MASTERING_DISPLAY_METADATA,  .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
     {"content_light_level",         .default_val.i64 = AV_PKT_DATA_CONTENT_LIGHT_LEVEL,         .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
     {"icc_profile",                 .default_val.i64 = AV_PKT_DATA_ICC_PROFILE,                 .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
+    {"exif",                        .default_val.i64 = AV_PKT_DATA_EXIF,                        .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
 {NULL},
 };
 
diff --git a/libavcodec/packet.c b/libavcodec/packet.c
index 2d1f282927..9420c10be0 100644
--- a/libavcodec/packet.c
+++ b/libavcodec/packet.c
@@ -310,6 +310,7 @@ const char *av_packet_side_data_name(enum AVPacketSideDataType type)
     case AV_PKT_DATA_LCEVC:                      return "LCEVC NAL data";
     case AV_PKT_DATA_3D_REFERENCE_DISPLAYS:      return "3D Reference Displays Info";
     case AV_PKT_DATA_RTCP_SR:                    return "RTCP Sender Report";
+    case AV_PKT_DATA_EXIF:                       return "EXIF metadata";
     }
     return NULL;
 }
diff --git a/libavcodec/packet.h b/libavcodec/packet.h
index 55389a957d..5e27f9ceb5 100644
--- a/libavcodec/packet.h
+++ b/libavcodec/packet.h
@@ -362,6 +362,12 @@ enum AVPacketSideDataType {
      */
     AV_PKT_DATA_RTCP_SR,
 
+    /**
+     * 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.
+     */
+     AV_PKT_DATA_EXIF,
+
     /**
      * The number of side data types.
      * This is not part of the public API/ABI in the sense that it may
-- 
2.49.1


From 01896153bb13272cb6e1f1fb3c95b47a6c79771c Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 22:00:33 -0300
Subject: [PATCH 02/11] ffprobe: print EXIF packet side data size

Signed-off-by: James Almer <jamrial@gmail.com>
---
 fftools/ffprobe.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index 018111318e..f4eb87aefa 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -1063,6 +1063,8 @@ static void print_pkt_side_data(AVTextFormatContext *tfc,
         print_int("crop_right",  AV_RL32(sd->data + 12));
     } else if (sd->type == AV_PKT_DATA_AFD && sd->size > 0) {
         print_int("active_format", *sd->data);
+    } else if (sd->type == AV_PKT_DATA_EXIF) {
+        print_int("size", sd->size);
     }
 }
 
-- 
2.49.1


From 3e4e14d3e80ae61cb7d7c9c8f5e60e786eefd87c Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 13:23:49 -0300
Subject: [PATCH 03/11] avformat/dump: print side data type names generically

Based on vf_showinfo behavior.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/dump.c | 45 +++++++++++++++------------------------------
 1 file changed, 15 insertions(+), 30 deletions(-)

diff --git a/libavformat/dump.c b/libavformat/dump.c
index 02f69f9a3a..dfa33fd3c9 100644
--- a/libavformat/dump.c
+++ b/libavformat/dump.c
@@ -342,7 +342,7 @@ static void dump_mastering_display_metadata(void *ctx, const AVPacketSideData *s
 {
     const AVMasteringDisplayMetadata *metadata =
         (const AVMasteringDisplayMetadata *)sd->data;
-    av_log(ctx, log_level, "Mastering Display Metadata, "
+    av_log(ctx, log_level,
            "has_primaries:%d has_luminance:%d "
            "r(%5.4f,%5.4f) g(%5.4f,%5.4f) b(%5.4f %5.4f) wp(%5.4f, %5.4f) "
            "min_luminance=%f, max_luminance=%f",
@@ -362,7 +362,7 @@ static void dump_content_light_metadata(void *ctx, const AVPacketSideData *sd,
 {
     const AVContentLightMetadata *metadata =
         (const AVContentLightMetadata *)sd->data;
-    av_log(ctx, log_level, "Content Light Level Metadata, "
+    av_log(ctx, log_level,
            "MaxCLL=%d, MaxFALL=%d",
            metadata->MaxCLL, metadata->MaxFALL);
 }
@@ -371,7 +371,7 @@ static void dump_ambient_viewing_environment_metadata(void *ctx, const AVPacketS
 {
     const AVAmbientViewingEnvironment *ambient =
         (const AVAmbientViewingEnvironment *)sd->data;
-    av_log(ctx, AV_LOG_INFO, "Ambient Viewing Environment, "
+    av_log(ctx, AV_LOG_INFO,
            "ambient_illuminance=%f, ambient_light_x=%f, ambient_light_y=%f",
            av_q2d(ambient->ambient_illuminance),
            av_q2d(ambient->ambient_light_x),
@@ -481,81 +481,66 @@ static void dump_sidedata(void *ctx, const AVPacketSideData *side_data, int nb_s
 
     for (i = 0; i < nb_side_data; i++) {
         const AVPacketSideData *sd = &side_data[i];
-        av_log(ctx, log_level, "%s  ", indent);
+        const char *name = av_packet_side_data_name(sd->type);
 
+        av_log(ctx, log_level, "%s  ", indent);
+        if (name)
+            av_log(ctx, AV_LOG_INFO, "%s: ", name);
         switch (sd->type) {
-        case AV_PKT_DATA_PALETTE:
-            av_log(ctx, log_level, "palette");
-            break;
-        case AV_PKT_DATA_NEW_EXTRADATA:
-            av_log(ctx, log_level, "new extradata");
-            break;
         case AV_PKT_DATA_PARAM_CHANGE:
-            av_log(ctx, log_level, "paramchange: ");
             dump_paramchange(ctx, sd, log_level);
             break;
-        case AV_PKT_DATA_H263_MB_INFO:
-            av_log(ctx, log_level, "H.263 macroblock info");
-            break;
         case AV_PKT_DATA_REPLAYGAIN:
-            av_log(ctx, log_level, "replaygain: ");
             dump_replaygain(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_DISPLAYMATRIX:
-            av_log(ctx, log_level, "displaymatrix: rotation of %.2f degrees",
+            av_log(ctx, log_level, "rotation of %.2f degrees",
                    av_display_rotation_get((const int32_t *)sd->data));
             break;
         case AV_PKT_DATA_STEREO3D:
-            av_log(ctx, log_level, "stereo3d: ");
             dump_stereo3d(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_AUDIO_SERVICE_TYPE:
-            av_log(ctx, log_level, "audio service type: ");
             dump_audioservicetype(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_QUALITY_STATS:
-            av_log(ctx, log_level, "quality factor: %"PRId32", pict_type: %c",
+            av_log(ctx, log_level, "%"PRId32", pict_type: %c",
                    AV_RL32(sd->data), av_get_picture_type_char(sd->data[4]));
             break;
         case AV_PKT_DATA_CPB_PROPERTIES:
-            av_log(ctx, log_level, "cpb: ");
             dump_cpb(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_MASTERING_DISPLAY_METADATA:
             dump_mastering_display_metadata(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_SPHERICAL:
-            av_log(ctx, log_level, "spherical: ");
             dump_spherical(ctx, w, h, sd, log_level);
             break;
         case AV_PKT_DATA_CONTENT_LIGHT_LEVEL:
             dump_content_light_metadata(ctx, sd, log_level);
             break;
-        case AV_PKT_DATA_ICC_PROFILE:
-            av_log(ctx, log_level, "ICC Profile");
-            break;
         case AV_PKT_DATA_DOVI_CONF:
-            av_log(ctx, log_level, "DOVI configuration record: ");
             dump_dovi_conf(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_S12M_TIMECODE:
-            av_log(ctx, log_level, "SMPTE ST 12-1:2014: ");
             dump_s12m_timecode(ctx, avg_frame_rate, sd, log_level);
             break;
         case AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT:
             dump_ambient_viewing_environment_metadata(ctx, sd);
             break;
         case AV_PKT_DATA_FRAME_CROPPING:
-            av_log(ctx, AV_LOG_INFO, "Frame cropping: ");
             dump_cropping(ctx, sd);
             break;
         case AV_PKT_DATA_3D_REFERENCE_DISPLAYS:
-            av_log(ctx, log_level, "3D Reference Displays Information: ");
             dump_tdrdi(ctx, sd);
             break;
         default:
-            av_log(ctx, log_level, "unknown side data type %d "
-                   "(%"SIZE_SPECIFIER" bytes)", sd->type, sd->size);
+            if (name)
+                av_log(ctx, log_level,
+                       "(%"SIZE_SPECIFIER" bytes)", sd->size);
+            else
+                av_log(ctx, log_level, "unknown side data type %d "
+                       "(%"SIZE_SPECIFIER" bytes)", sd->type, sd->size);
             break;
         }
 
-- 
2.49.1


From 7b6874c4395f69b1b4bd130b4b188a6a52a1fae2 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 21:59:25 -0300
Subject: [PATCH 04/11] tests/mov: also print stream side data in
 fate-mov-heic-demux-still-image-multiple-thumb

This is in preparation for a following commit.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 tests/fate/mov.mak | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 421845969b..21c155ff2a 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -188,7 +188,7 @@ fate-mov-heic-demux-clap-irot-imir: CMD = stream_demux mov $(TARGET_SAMPLES)/hei
 FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC MJPEG, HEVC_PARSER) \
                            += fate-mov-heic-demux-still-image-multiple-thumb
 fate-mov-heic-demux-still-image-multiple-thumb: CMD = stream_demux mov $(TARGET_SAMPLES)/heif/P1001091.HIF "" "-c:v copy -map 0" \
-  "-show_entries stream=index,id:stream_disposition"
+  "-show_entries stream=index,id:stream_disposition:stream_side_data_list"
 
 # heic demuxing - still image with multiple items in a grid.
 FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) \
-- 
2.49.1


From db113a531f1a16906888285edec82e8cbe912d89 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Sat, 23 Aug 2025 11:04:48 -0300
Subject: [PATCH 05/11] avformat/mov: reduce code duplication when setting tile
 group properties

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/mov.c | 46 +++++++++++++++-------------------------------
 1 file changed, 15 insertions(+), 31 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index e9a582e5aa..c846e0d4e8 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -10188,21 +10188,6 @@ static int read_image_grid(AVFormatContext *s, const HEIFGrid *grid,
     tile_grid->width  = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
     tile_grid->height = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
 
-    /* ICC profile */
-    if (item->icc_profile_size) {
-        int ret = set_icc_profile_from_item(&tile_grid->coded_side_data,
-                                            &tile_grid->nb_coded_side_data, item);
-        if (ret < 0)
-            return ret;
-    }
-    /* rotation */
-    if (item->rotation || item->hflip || item->vflip) {
-        int ret = set_display_matrix_from_item(&tile_grid->coded_side_data,
-                                               &tile_grid->nb_coded_side_data, item);
-        if (ret < 0)
-            return ret;
-    }
-
     av_log(c->fc, AV_LOG_TRACE, "grid: grid_rows %d grid_cols %d output_width %d output_height %d\n",
            tile_rows, tile_cols, tile_grid->width, tile_grid->height);
 
@@ -10294,22 +10279,6 @@ static int read_image_iovl(AVFormatContext *s, const HEIFGrid *grid,
     tile_grid->height       =
     tile_grid->coded_height = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
 
-    /* rotation */
-    if (item->rotation || item->hflip || item->vflip) {
-        int ret = set_display_matrix_from_item(&tile_grid->coded_side_data,
-                                               &tile_grid->nb_coded_side_data, item);
-        if (ret < 0)
-            return ret;
-    }
-
-    /* ICC profile */
-    if (item->icc_profile_size) {
-        int ret = set_icc_profile_from_item(&tile_grid->coded_side_data,
-                                            &tile_grid->nb_coded_side_data, item);
-        if (ret < 0)
-            return ret;
-    }
-
     av_log(c->fc, AV_LOG_TRACE, "iovl: output_width %d, output_height %d\n",
            tile_grid->width, tile_grid->height);
 
@@ -10421,6 +10390,21 @@ static int mov_parse_tiles(AVFormatContext *s)
         if (err < 0)
             return err;
 
+        /* rotation */
+        if (grid->item->rotation || grid->item->hflip || grid->item->vflip) {
+            err = set_display_matrix_from_item(&tile_grid->coded_side_data,
+                                               &tile_grid->nb_coded_side_data, grid->item);
+            if (err < 0)
+                return err;
+        }
+
+        /* ICC profile */
+        if (grid->item->icc_profile_size) {
+            err = set_icc_profile_from_item(&tile_grid->coded_side_data,
+                                            &tile_grid->nb_coded_side_data, grid->item);
+            if (err < 0)
+                return err;
+        }
 
         if (grid->item->name)
             av_dict_set(&stg->metadata, "title", grid->item->name, 0);
-- 
2.49.1


From a62d48c823cc7e81ff5b94caaa07db36cbf01d5f Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 17:07:50 -0300
Subject: [PATCH 06/11] avformat/mov: make items referencing items generic

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/isom.h |  9 +++++--
 libavformat/mov.c  | 67 +++++++++++++++++++++-------------------------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/libavformat/isom.h b/libavformat/isom.h
index 94c9c65989..1329cb9188 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -286,8 +286,15 @@ typedef struct MOVStreamContext {
     int iamf_stream_offset;
 } MOVStreamContext;
 
+typedef struct HEIFItemRef {
+    unsigned type;
+    int item_id;
+} HEIFItemRef;
+
 typedef struct HEIFItem {
     AVStream *st;
+    HEIFItemRef *iref_list;
+    int nb_iref_list;
     char *name;
     int item_id;
     int64_t extent_length;
@@ -376,8 +383,6 @@ typedef struct MOVContext {
     int nb_heif_item;
     HEIFGrid *heif_grid;
     int nb_heif_grid;
-    int* thmb_item_id;
-    int nb_thmb_item;
     int64_t idat_offset;
     int interleaved_read;
 } MOVContext;
diff --git a/libavformat/mov.c b/libavformat/mov.c
index c846e0d4e8..2944daf97c 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -188,14 +188,14 @@ static int mov_read_mac_string(MOVContext *c, AVIOContext *pb, int len,
 }
 
 /**
- * Get the current item in the parsing process.
+ * Get the requested item.
  */
-static HEIFItem *heif_cur_item(MOVContext *c)
+static HEIFItem *get_heif_item(MOVContext *c, unsigned id)
 {
     HEIFItem *item = NULL;
 
     for (int i = 0; i < c->nb_heif_item; i++) {
-        if (!c->heif_item[i] || c->heif_item[i]->item_id != c->cur_item_id)
+        if (!c->heif_item[i] || c->heif_item[i]->item_id != id)
             continue;
 
         item = c->heif_item[i];
@@ -220,7 +220,7 @@ static AVStream *get_curr_st(MOVContext *c)
     if (c->cur_item_id == -1)
         return c->fc->streams[c->fc->nb_streams-1];
 
-    item = heif_cur_item(c);
+    item = get_heif_item(c, c->cur_item_id);
     if (item)
         st = item->st;
 
@@ -1245,7 +1245,7 @@ static int mov_read_clap(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     AVRational pc_x, pc_y;
     uint64_t top, bottom, left, right;
 
-    item = heif_cur_item(c);
+    item = get_heif_item(c, c->cur_item_id);
     st = get_curr_st(c);
     if (!st)
         return 0;
@@ -2078,7 +2078,7 @@ static int mov_read_colr(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
     st = get_curr_st(c);
     if (!st) {
-        item = heif_cur_item(c);
+        item = get_heif_item(c, c->cur_item_id);
         if (!item)
             return 0;
     }
@@ -9087,30 +9087,33 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version)
 
 static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
 {
-    int *thmb_item_id;
+    HEIFItem *from_item = NULL;
     int entries;
-    int to_item_id, from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
+    int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
+    const HEIFItemRef ref = { MKTAG('t','h','m','b'), from_item_id };
+
+    from_item = get_heif_item(c, from_item_id);
+    if (!from_item) {
+        av_log(c->fc, AV_LOG_ERROR, "Missing stream referenced by thmb item\n");
+        return AVERROR_INVALIDDATA;
+    }
 
     entries = avio_rb16(pb);
-    if (entries > 1) {
-        avpriv_request_sample(c->fc, "thmb in iref referencing several items");
-        return AVERROR_PATCHWELCOME;
-    }
     /* 'to' item ids */
-    to_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
+    for (int i = 0; i < entries; i++) {
+        HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
+        if (!item) {
+            av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by thmb item\n");
+            continue;
+        }
 
-    if (to_item_id != c->primary_item_id)
-        return 0;
+        if (!av_dynarray2_add((void **)&item->iref_list, &item->nb_iref_list,
+                              sizeof(*item->iref_list), (const uint8_t *)&ref))
+            return AVERROR(ENOMEM);
+    }
 
-    /* Put thumnbail IDs into an array */
-    thmb_item_id = av_dynarray2_add((void **)&c->thmb_item_id, &c->nb_thmb_item,
-                                    sizeof(*c->thmb_item_id),
-                                    (const void *)&from_item_id);
-    if (!thmb_item_id)
-        return AVERROR(ENOMEM);
-
-    av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d, nb_thmb: %d\n",
-           from_item_id, entries, c->nb_thmb_item);
+    av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d\n",
+           from_item_id, entries);
 
     return 0;
 }
@@ -9166,7 +9169,7 @@ static int mov_read_ispe(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     av_log(c->fc, AV_LOG_TRACE, "ispe: item_id %d, width %u, height %u\n",
            c->cur_item_id, width, height);
 
-    item = heif_cur_item(c);
+    item = get_heif_item(c, c->cur_item_id);
     if (item) {
         item->width  = width;
         item->height = height;
@@ -9185,7 +9188,7 @@ static int mov_read_irot(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     av_log(c->fc, AV_LOG_TRACE, "irot: item_id %d, angle %u\n",
            c->cur_item_id, angle);
 
-    item = heif_cur_item(c);
+    item = get_heif_item(c, c->cur_item_id);
     if (item) {
         // angle * 90 specifies the angle (in anti-clockwise direction)
         // in units of degrees.
@@ -9205,7 +9208,7 @@ static int mov_read_imir(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     av_log(c->fc, AV_LOG_TRACE, "imir: item_id %d, axis %u\n",
            c->cur_item_id, axis);
 
-    item = heif_cur_item(c);
+    item = get_heif_item(c, c->cur_item_id);
     if (item) {
         item->hflip =  axis;
         item->vflip = !axis;
@@ -9970,6 +9973,7 @@ static int mov_read_close(AVFormatContext *s)
         if (!mov->heif_item[i])
             continue;
         av_freep(&mov->heif_item[i]->name);
+        av_freep(&mov->heif_item[i]->iref_list);
         av_freep(&mov->heif_item[i]->icc_profile);
         av_freep(&mov->heif_item[i]);
     }
@@ -9980,7 +9984,6 @@ static int mov_read_close(AVFormatContext *s)
         av_freep(&mov->heif_grid[i].tile_item_list);
     }
     av_freep(&mov->heif_grid);
-    av_freep(&mov->thmb_item_id);
 
     return 0;
 }
@@ -10429,13 +10432,6 @@ static int mov_parse_heif_items(AVFormatContext *s)
         if (!item)
             continue;
         if (!item->st) {
-            for (int j = 0; j < mov->nb_thmb_item; j++) {
-                if (item->item_id == mov->thmb_item_id[j]) {
-                    av_log(s, AV_LOG_ERROR, "HEIF thumbnail ID %d doesn't reference a stream\n",
-                           item->item_id);
-                    return AVERROR_INVALIDDATA;
-                }
-            }
             continue;
         }
         if (item->is_idat_relative) {
@@ -10587,7 +10583,6 @@ static int mov_read_header(AVFormatContext *s)
 
     mov->fc = s;
     mov->trak_index = -1;
-    mov->thmb_item_id = NULL;
     mov->primary_item_id = -1;
     mov->cur_item_id = -1;
     /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
-- 
2.49.1


From c5d13a638f77131174233348e0fd96079be95e42 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Wed, 20 Aug 2025 19:53:53 -0300
Subject: [PATCH 07/11] avformat/mov: export Exif metadata from HEIF streams

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/mov.c                             | 133 +++++++++++++++++-
 .../mov-heic-demux-still-image-multiple-thumb |   4 +
 2 files changed, 131 insertions(+), 6 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 2944daf97c..4abd7e9397 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -51,6 +51,7 @@
 #include "libavutil/timecode.h"
 #include "libavutil/uuid.h"
 #include "libavcodec/ac3tab.h"
+#include "libavcodec/exif.h"
 #include "libavcodec/flac.h"
 #include "libavcodec/hevc/hevc.h"
 #include "libavcodec/mpegaudiodecheader.h"
@@ -9085,12 +9086,12 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version)
     return 0;
 }
 
-static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
+static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, unsigned type, int version)
 {
     HEIFItem *from_item = NULL;
     int entries;
     int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
-    const HEIFItemRef ref = { MKTAG('t','h','m','b'), from_item_id };
+    const HEIFItemRef ref = { type, from_item_id };
 
     from_item = get_heif_item(c, from_item_id);
     if (!from_item) {
@@ -9103,7 +9104,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
     for (int i = 0; i < entries; i++) {
         HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
         if (!item) {
-            av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by thmb item\n");
+            av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by %s item\n",
+                   av_fourcc2str(type));
             continue;
         }
 
@@ -9112,8 +9114,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
             return AVERROR(ENOMEM);
     }
 
-    av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d\n",
-           from_item_id, entries);
+    av_log(c->fc, AV_LOG_TRACE, "%s: from_item_id %d, entries %d\n",
+           av_fourcc2str(type), from_item_id, entries);
 
     return 0;
 }
@@ -9139,11 +9141,14 @@ static int mov_read_iref(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         next += size - 4;
         type = avio_rl32(pb);
         switch (type) {
+        case MKTAG('c','d','s','c'):
+            mov_read_iref_cdsc(c, pb, type, version);
+            break;
         case MKTAG('d','i','m','g'):
             mov_read_iref_dimg(c, pb, version);
             break;
         case MKTAG('t','h','m','b'):
-            mov_read_iref_thmb(c, pb, version);
+            mov_read_iref_cdsc(c, pb, type, version);
             break;
         default:
             av_log(c->fc, AV_LOG_DEBUG, "Unknown iref type %s size %"PRIu32"\n",
@@ -10308,6 +10313,90 @@ fail:
     return ret;
 }
 
+static int mov_parse_exif_item(AVFormatContext *s,
+                               AVPacketSideData **coded_side_data, int *nb_coded_side_data,
+                               const HEIFItem *ref)
+{
+    MOVContext *c = s->priv_data;
+    AVPacketSideData *sd;
+    AVExifMetadata ifd = { 0 };
+    AVExifEntry *entry = NULL;
+    AVBufferRef *buf;
+    int64_t offset = 0, pos = avio_tell(s->pb);
+    unsigned orientation_id = av_exif_get_tag_id("Orientation");
+    int err;
+
+    if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
+        av_log(c->fc, AV_LOG_WARNING, "Exif metadata with non seekable input\n");
+        return AVERROR_PATCHWELCOME;
+    }
+    if (ref->is_idat_relative) {
+        if (!c->idat_offset) {
+            av_log(c->fc, AV_LOG_ERROR, "missing idat box required by the Exif metadata\n");
+            return AVERROR_INVALIDDATA;
+        }
+        offset = c->idat_offset;
+    }
+
+    buf = av_buffer_alloc(ref->extent_length);
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    avio_seek(s->pb, ref->extent_offset + offset, SEEK_SET);
+    err = avio_read(s->pb, buf->data, ref->extent_length);
+    if (err != ref->extent_length) {
+        if (err > 0)
+            err = AVERROR_INVALIDDATA;
+        goto fail;
+    }
+
+    // HEIF spec states that Exif metadata is informative. The irot item property is
+    // the normative source of rotation information. So we remove any Orientation tag
+    // present in the Exif buffer.
+    err = av_exif_parse_buffer(s, buf->data, ref->extent_length, &ifd, AV_EXIF_T_OFF);
+    if (err < 0) {
+        av_log(s, AV_LOG_ERROR, "Unable to parse Exif metadata\n");
+        goto fail;
+    }
+
+    err = av_exif_get_entry(s, &ifd, orientation_id, 0, &entry);
+    if (err < 0)
+        goto fail;
+    else if (!err)
+        goto finish;
+
+    err = av_exif_remove_entry(s, &ifd, orientation_id, 0);
+    if (err < 0)
+        goto fail;
+
+    av_buffer_unref(&buf);
+    err = av_exif_write(s, &ifd, &buf, AV_EXIF_T_OFF);
+    if (err < 0)
+        goto fail;
+
+finish:
+    offset = AV_RB32(buf->data) + 4;
+    if (offset >= buf->size) {
+        err = AVERROR_INVALIDDATA;
+        goto fail;
+    }
+    sd = av_packet_side_data_new(coded_side_data, nb_coded_side_data,
+                                 AV_PKT_DATA_EXIF, buf->size - offset, 0);
+    if (!sd) {
+        err = AVERROR(ENOMEM);
+        goto fail;
+    }
+    memcpy(sd->data, buf->data + offset, buf->size - offset);
+
+    err = 0;
+fail:
+    av_buffer_unref(&buf);
+    av_exif_free(&ifd);
+    avio_seek(s->pb, pos, SEEK_SET);
+
+    return err;
+}
+
 static int mov_parse_tiles(AVFormatContext *s)
 {
     MOVContext *mov = s->priv_data;
@@ -10393,6 +10482,22 @@ static int mov_parse_tiles(AVFormatContext *s)
         if (err < 0)
             return err;
 
+        for (int j = 0; j < grid->item->nb_iref_list; j++) {
+            HEIFItem *ref = get_heif_item(mov, grid->item->iref_list[j].item_id);
+
+            av_assert0(ref);
+            switch(ref->type) {
+            case MKTAG('E','x','i','f'):
+                err = mov_parse_exif_item(s, &tile_grid->coded_side_data,
+                                             &tile_grid->nb_coded_side_data, ref);
+                if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
+                    return err;
+                break;
+            default:
+                break;
+            }
+        }
+
         /* rotation */
         if (grid->item->rotation || grid->item->hflip || grid->item->vflip) {
             err = set_display_matrix_from_item(&tile_grid->coded_side_data,
@@ -10459,6 +10564,22 @@ static int mov_parse_heif_items(AVFormatContext *s)
         if (item->item_id == mov->primary_item_id)
             st->disposition |= AV_DISPOSITION_DEFAULT;
 
+        for (int j = 0; j < item->nb_iref_list; j++) {
+            HEIFItem *ref = get_heif_item(mov, item->iref_list[j].item_id);
+
+            av_assert0(ref);
+            switch(ref->type) {
+            case MKTAG('E','x','i','f'):
+                err = mov_parse_exif_item(s, &st->codecpar->coded_side_data,
+                                             &st->codecpar->nb_coded_side_data, ref);
+                if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
+                    return err;
+                break;
+            default:
+                break;
+            }
+        }
+
         if (item->rotation || item->hflip || item->vflip) {
             err = set_display_matrix_from_item(&st->codecpar->coded_side_data,
                                                &st->codecpar->nb_coded_side_data, item);
diff --git a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
index 88263f0d0b..e5764f6de8 100644
--- a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
+++ b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
@@ -39,6 +39,10 @@ DISPOSITION:metadata=0
 DISPOSITION:dependent=0
 DISPOSITION:still_image=0
 DISPOSITION:multilayer=0
+[SIDE_DATA]
+side_data_type=EXIF metadata
+size=30308
+[/SIDE_DATA]
 [/STREAM]
 [STREAM]
 index=1
-- 
2.49.1


From 6e776057f868d4ceaff41719d74c657b3c58f9a1 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Fri, 22 Aug 2025 20:30:40 -0300
Subject: [PATCH 08/11] avcodec/decode: use av_exif_get_tag_id() where useful

Removes dependency on exif_internal.h

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/decode.c        | 5 +++--
 libavcodec/exif.c          | 7 +++++++
 libavcodec/exif_internal.h | 8 --------
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index b0ce94cadd..2877010682 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -47,7 +47,7 @@
 #include "codec_desc.h"
 #include "codec_internal.h"
 #include "decode.h"
-#include "exif_internal.h"
+#include "exif.h"
 #include "hwaccel_internal.h"
 #include "hwconfig.h"
 #include "internal.h"
@@ -2278,7 +2278,8 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
 
     for (size_t i = 0; i < ifd->count; i++) {
         const AVExifEntry *entry = &ifd->entries[i];
-        if (entry->id == ORIENTATION_TAG && entry->count > 0 && entry->type == AV_TIFF_SHORT) {
+        if (entry->id == av_exif_get_tag_id("Orientation") &&
+            entry->count > 0 && entry->type == AV_TIFF_SHORT) {
             orient = entry;
             break;
         }
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 1332fa68bb..2359566a47 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -46,6 +46,13 @@
 #define IFD_EXTRA_SIZE         6
 
 #define EXIF_TAG_NAME_LENGTH   32
+#define MAKERNOTE_TAG          0x927c
+#define ORIENTATION_TAG        0x112
+#define EXIFIFD_TAG            0x8769
+#define IMAGE_WIDTH_TAG        0x100
+#define IMAGE_LENGTH_TAG       0x101
+#define PIXEL_X_TAG            0xa002
+#define PIXEL_Y_TAG            0xa003
 
 struct exif_tag {
     const char name[EXIF_TAG_NAME_LENGTH];
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index 565e747353..0169fb3e00 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -42,14 +42,6 @@ int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
                            int le, int depth, AVDictionary **metadata);
 #endif /* FF_API_OLD_EXIF */
 
-#define MAKERNOTE_TAG          0x927c
-#define ORIENTATION_TAG        0x112
-#define EXIFIFD_TAG            0x8769
-#define IMAGE_WIDTH_TAG        0x100
-#define IMAGE_LENGTH_TAG       0x101
-#define PIXEL_X_TAG            0xa002
-#define PIXEL_Y_TAG            0xa003
-
 /**
  * Compares values in the IFD with data in the provided AVFrame and sets the values
  * in that IFD to match the ones in that AVFrame. This is mostly useful for an
-- 
2.49.1


From a0c32a9d6db522c46f9fe055669532576d7cb4a9 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Thu, 21 Aug 2025 20:03:03 -0300
Subject: [PATCH 09/11] avcodec/decode: use ff_frame_new_side_data() to export
 Exif side data

Otherwise, the user requested priority of packet side data will be ignored.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/decode.c | 29 +++++++++++++----------------
 libavcodec/decode.h |  7 +++----
 libavcodec/pngdec.c |  5 +----
 libavcodec/webp.c   |  4 +---
 4 files changed, 18 insertions(+), 27 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 2877010682..fa76dba51c 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -2268,12 +2268,10 @@ static int attach_displaymatrix(AVCodecContext *avctx, AVFrame *frame, int orien
     return ret;
 }
 
-static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef *og)
+static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef **pbuf)
 {
     const AVExifEntry *orient = NULL;
-    AVFrameSideData *sd;
     AVExifMetadata *cloned = NULL;
-    AVBufferRef *written = NULL;
     int ret;
 
     for (size_t i = 0; i < ifd->count; i++) {
@@ -2305,25 +2303,21 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
     if (ret < 0)
         goto end;
 
-    if (cloned || !og) {
-        ret = av_exif_write(avctx, ifd, &written, AV_EXIF_TIFF_HEADER);
+    if (cloned || !*pbuf) {
+        av_buffer_unref(pbuf);
+        ret = av_exif_write(avctx, ifd, pbuf, AV_EXIF_TIFF_HEADER);
         if (ret < 0)
             goto end;
     }
 
-    sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_EXIF, written ? written : og);
-    if (!sd) {
-        if (written)
-            av_buffer_unref(&written);
-        ret = AVERROR(ENOMEM);
+    ret = ff_frame_new_side_data_from_buf(avctx, frame, AV_FRAME_DATA_EXIF, pbuf);
+    if (ret < 0)
         goto end;
-    }
 
     ret = 0;
 
 end:
-    if (og && written && ret >= 0)
-        av_buffer_unref(&og); // as though we called new_side_data on og;
+    av_buffer_unref(pbuf);
     av_exif_free(cloned);
     av_free(cloned);
     return ret;
@@ -2331,22 +2325,25 @@ end:
 
 int ff_decode_exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd)
 {
-    return exif_attach_ifd(avctx, frame, ifd, NULL);
+    AVBufferRef *dummy = NULL;
+    return exif_attach_ifd(avctx, frame, ifd, &dummy);
 }
 
-int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef *data,
+int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef **pbuf,
                                  enum AVExifHeaderMode header_mode)
 {
     int ret;
+    AVBufferRef *data = *pbuf;
     AVExifMetadata ifd = { 0 };
 
     ret = av_exif_parse_buffer(avctx, data->data, data->size, &ifd, header_mode);
     if (ret < 0)
         goto end;
 
-    ret = exif_attach_ifd(avctx, frame, &ifd, data);
+    ret = exif_attach_ifd(avctx, frame, &ifd, pbuf);
 
 end:
+    av_buffer_unref(pbuf);
     av_exif_free(&ifd);
     return ret;
 }
diff --git a/libavcodec/decode.h b/libavcodec/decode.h
index f285da924d..561dc6a69c 100644
--- a/libavcodec/decode.h
+++ b/libavcodec/decode.h
@@ -230,11 +230,10 @@ enum AVExifHeaderMode;
  * attaches that information as an AV_FRAME_DATA_DISPLAYMATRIX instead
  * of including it in the AV_FRAME_DATA_EXIF side data buffer.
  *
- * On a success, the caller loses ownership of the data buffer. Either it is
- * unrefed, or its ownership is transferred to the frame directly. On failure,
- * the data buffer is left owned by the caller.
+ * *buf is ALWAYS consumed by this function and NULL written in its place, even
+ * on failure.
  */
-int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef *data,
+int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef **buf,
                                  enum AVExifHeaderMode header_mode);
 
 struct AVExifMetadata;
diff --git a/libavcodec/pngdec.c b/libavcodec/pngdec.c
index 8a63f0ad90..ce817d44fa 100644
--- a/libavcodec/pngdec.c
+++ b/libavcodec/pngdec.c
@@ -1753,15 +1753,12 @@ exit_loop:
     if (s->exif_data) {
         // we swap because ff_decode_exif_attach_buffer adds to p->metadata
         FFSWAP(AVDictionary *, p->metadata, s->frame_metadata);
-        ret = ff_decode_exif_attach_buffer(avctx, p, s->exif_data, AV_EXIF_TIFF_HEADER);
+        ret = ff_decode_exif_attach_buffer(avctx, p, &s->exif_data, AV_EXIF_TIFF_HEADER);
         FFSWAP(AVDictionary *, p->metadata, s->frame_metadata);
         if (ret < 0) {
             av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF buffer\n");
             return ret;
         }
-        // ff_decode_exif_attach_buffer takes ownership so
-        // we do not want to call av_buffer_unref here
-        s->exif_data = NULL;
     }
 
     if (s->color_type == PNG_COLOR_TYPE_PALETTE && avctx->codec_id == AV_CODEC_ID_APNG) {
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index 0dca130ff3..796f089437 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -1476,11 +1476,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
             s->has_exif = 1;
             memcpy(exif_buf->data, gb.buffer, chunk_size);
 
-            /* if this succeeds then exif_buf is either freed or transferred to the AVFrame */
-            ret = ff_decode_exif_attach_buffer(avctx, p, exif_buf, AV_EXIF_TIFF_HEADER);
+            ret = ff_decode_exif_attach_buffer(avctx, p, &exif_buf, AV_EXIF_TIFF_HEADER);
             if (ret < 0) {
                 av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF buffer\n");
-                av_buffer_unref(&exif_buf);
             }
 
 exif_end:
-- 
2.49.1


From 91b006364075467817a176c6280a528f1df6bd62 Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Thu, 21 Aug 2025 21:44:27 -0300
Subject: [PATCH 10/11] avcodec/decode: parse Exif packet side data before
 passing it to frames

Extract Orientation and export it as a display matrix if present, and set the
frame's metadata with the remaining Exif entries.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/decode.c | 87 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 83 insertions(+), 4 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index fa76dba51c..995d92c630 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -1389,6 +1389,75 @@ static int side_data_stereo3d_merge(AVFrameSideData *sd_frame,
     return 0;
 }
 
+static int side_data_exif_parse(AVFrame *dst, const AVPacketSideData *sd_pkt)
+{
+    AVExifMetadata ifd = { 0 };
+    AVExifEntry *entry = NULL;
+    AVBufferRef *buf = NULL;
+    AVFrameSideData *sd_frame;
+    int ret;
+
+    ret = av_exif_parse_buffer(NULL, sd_pkt->data, sd_pkt->size, &ifd,
+                               AV_EXIF_TIFF_HEADER);
+    if (ret < 0)
+        return ret;
+
+    ret = av_exif_get_entry(NULL, &ifd, av_exif_get_tag_id("Orientation"), 0, &entry);
+    if (ret < 0)
+        goto end;
+
+    if (!entry) {
+        ret = av_exif_ifd_to_dict(NULL, &ifd, &dst->metadata);
+        if (ret < 0)
+            goto end;
+
+        sd_frame = av_frame_side_data_new(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_EXIF,
+                                          sd_pkt->size, 0);
+        if (sd_frame)
+            memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+        ret = sd_frame ? 0 : AVERROR(ENOMEM);
+
+        goto end;
+    }
+
+    // If a display matrix already exists in the frame, give it priority
+    if (av_frame_side_data_get(dst->side_data, dst->nb_side_data, AV_FRAME_DATA_DISPLAYMATRIX))
+        goto finish;
+
+    sd_frame = av_frame_side_data_new(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_DISPLAYMATRIX,
+                                      sizeof(int32_t) * 9, 0);
+    if (!sd_frame) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    ret = av_exif_orientation_to_matrix((int32_t *)sd_frame->data, entry->value.uint[0]);
+    if (ret < 0)
+        goto end;
+
+finish:
+    av_exif_remove_entry(NULL, &ifd, entry->id, 0);
+
+    ret = av_exif_ifd_to_dict(NULL, &ifd, &dst->metadata);
+    if (ret < 0)
+        goto end;
+
+    ret = av_exif_write(NULL, &ifd, &buf, AV_EXIF_TIFF_HEADER);
+    if (ret < 0)
+        goto end;
+
+    if (!av_frame_side_data_add(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_EXIF, &buf, 0)) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    ret = 0;
+end:
+    av_buffer_unref(&buf);
+    av_exif_free(&ifd);
+    return ret;
+}
+
 static int side_data_map(AVFrame *dst,
                          const AVPacketSideData *sd_src, int nb_sd_src,
                          const SideDataMap *map)
@@ -1415,11 +1484,21 @@ static int side_data_map(AVFrame *dst,
             continue;
         }
 
-        sd_frame = av_frame_new_side_data(dst, type_frame, sd_pkt->size);
-        if (!sd_frame)
-            return AVERROR(ENOMEM);
+        switch (type_pkt) {
+        case AV_PKT_DATA_EXIF: {
+            int ret = side_data_exif_parse(dst, sd_pkt);
+            if (ret < 0)
+                return ret;
+            break;
+        }
+        default:
+            sd_frame = av_frame_new_side_data(dst, type_frame, sd_pkt->size);
+            if (!sd_frame)
+                return AVERROR(ENOMEM);
 
-        memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+            memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+            break;
+        }
     }
 
     return 0;
-- 
2.49.1


From 533845d0ccd8368e09bacc2d48288efaa29171ed Mon Sep 17 00:00:00 2001
From: James Almer <jamrial@gmail.com>
Date: Thu, 21 Aug 2025 23:39:58 -0300
Subject: [PATCH 11/11] avcodec/decode: always extract display matrix from Exif
 in frames

And ensure decoders don't export both Exif metadata containing an Orientation
tag as well as a display matrix from some other source.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/decode.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 995d92c630..b013f99ddb 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -2362,8 +2362,9 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
         }
     }
 
-    if (orient && orient->value.uint[0] > 1) {
-        av_log(avctx, AV_LOG_DEBUG, "found nontrivial EXIF orientation: %" PRIu64 "\n", orient->value.uint[0]);
+    if (orient) {
+        av_assert0(!av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX));
+        av_log(avctx, AV_LOG_DEBUG, "found EXIF orientation: %" PRIu64 "\n", orient->value.uint[0]);
         ret = attach_displaymatrix(avctx, frame, orient->value.uint[0]);
         if (ret < 0) {
             av_log(avctx, AV_LOG_WARNING, "unable to attach displaymatrix from EXIF\n");
-- 
2.49.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

                 reply	other threads:[~2025-08-23 19:24 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250823192455.456ED68D20D@ffbox0-bg.ffmpeg.org \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=code@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git