diff mbox series

[FFmpeg-devel,1/3] various: change EXIF metadata into AVFrameSideData

Message ID 20240213212456.167386-2-leo.izen@gmail.com
State New
Headers show
Series EXIF overhaul | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Leo Izen Feb. 13, 2024, 9:24 p.m. UTC
This patch centralizes much of the EXIF parsing and handling code for
libavcodec, and delegates its own AVFrameSideData type to containing
the buffer that holds EXIF metadata. This patch also adds exposes the
exif parsing routing so it can be called by ffprobe, and updates the
corresponding FATE tests to read the keys from the side data instead of
from the main frame metadata.

This commit also removes an avpriv_ function in exif.h, exposing the
parsing functionality as a public API in the exported libavcodec/exif.h
header file. As such, this commit requires an ABI break and can only be
applied during a major version bump.

Signed-off-by: Leo Izen <leo.izen@gmail.com>
---
 fftools/ffprobe.c                  |  27 ++-
 libavcodec/Makefile                |   1 +
 libavcodec/exif.c                  | 267 +++++++++++++++++++++++++++--
 libavcodec/exif.h                  |  21 ++-
 libavcodec/exif_internal.h         |  41 +++++
 libavcodec/mjpegdec.c              |  96 ++---------
 libavcodec/mjpegdec.h              |   2 +-
 libavcodec/tiff.c                  |  19 +-
 libavcodec/tiff.h                  |   1 +
 libavcodec/webp.c                  |  38 ++--
 libavformat/avidec.c               |   4 +-
 libavutil/frame.c                  |   1 +
 libavutil/frame.h                  |   6 +
 tests/ref/fate/exif-image-embedded |   5 +-
 tests/ref/fate/exif-image-jpg      |  91 +++++-----
 tests/ref/fate/exif-image-webp     |  91 +++++-----
 16 files changed, 482 insertions(+), 229 deletions(-)
 create mode 100644 libavcodec/exif_internal.h
diff mbox series

Patch

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index aa1153e709..7c3cd4a0ee 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -32,6 +32,7 @@ 
 #include "libavformat/avformat.h"
 #include "libavformat/version.h"
 #include "libavcodec/avcodec.h"
+#include "libavcodec/exif.h"
 #include "libavcodec/version.h"
 #include "libavutil/ambient_viewing_environment.h"
 #include "libavutil/avassert.h"
@@ -1982,19 +1983,30 @@  static void writer_register_all(void)
     memset( (ptr) + (cur_n), 0, ((new_n) - (cur_n)) * sizeof(*(ptr)) ); \
 }
 
-static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id)
+static inline int show_dict(WriterContext *w, const AVDictionary *tags)
 {
     const AVDictionaryEntry *tag = NULL;
     int ret = 0;
-
     if (!tags)
         return 0;
-    writer_print_section_header(w, NULL, section_id);
-
     while ((tag = av_dict_iterate(tags, tag))) {
-        if ((ret = print_str_validate(tag->key, tag->value)) < 0)
+        ret = print_str_validate(tag->key, tag->value);
+        if (ret < 0)
             break;
     }
+    return ret;
+}
+
+static inline int show_tags(WriterContext *w, const AVDictionary *tags, int section_id)
+{
+    int ret;
+
+    if (!tags)
+        return 0;
+    writer_print_section_header(w, NULL, section_id);
+
+    ret = show_dict(w, tags);
+
     writer_print_section_footer(w);
 
     return ret;
@@ -2700,6 +2712,11 @@  static void print_frame_side_data(WriterContext *w,
             print_dynamic_hdr_vivid(w, metadata);
         } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) {
             print_ambient_viewing_environment(w, (const AVAmbientViewingEnvironment *)sd->data);
+        } else if (sd->type == AV_FRAME_DATA_EXIF) {
+            AVDictionary *dict = NULL;
+            int ret = av_exif_parse_buffer(NULL, sd->data, sd->size, &dict, AV_EXIF_PARSE_TIFF_HEADER);
+            if (ret >= 0)
+                show_dict(w, dict);
         }
         writer_print_section_footer(w);
     }
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 470d7cb9b1..fea2f9b8b3 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -16,6 +16,7 @@  HEADERS = ac3_parser.h                                                  \
           dirac.h                                                       \
           dv_profile.h                                                  \
           dxva2.h                                                       \
+          exif.h                                                        \
           jni.h                                                         \
           mediacodec.h                                                  \
           packet.h                                                      \
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 959d114d09..0b7ad1c07c 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -1,6 +1,7 @@ 
 /*
  * EXIF metadata parser
  * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * Copyright (c) 2024 Leo Izen <leo.izen@gmail.com>
  *
  * This file is part of FFmpeg.
  *
@@ -23,9 +24,12 @@ 
  * @file
  * EXIF metadata parser
  * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen@gmail.com>
  */
 
-#include "exif.h"
+#include "libavutil/display.h"
+
+#include "exif_internal.h"
 #include "tiff_common.h"
 
 #define EXIF_TAG_NAME_LENGTH   32
@@ -197,6 +201,8 @@  static int exif_add_metadata(void *logctx, int count, int type,
     };
 }
 
+static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
+                               int depth, AVDictionary **metadata);
 
 static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
                            int depth, AVDictionary **metadata)
@@ -220,7 +226,7 @@  static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
     // store metadata or proceed with next IFD
     ret = ff_tis_ifd(id);
     if (ret) {
-        ret = ff_exif_decode_ifd(logctx, gbytes, le, depth + 1, metadata);
+        ret = exif_parse_ifd_list(logctx, gbytes, le, depth + 1, metadata);
     } else {
         const char *name = exif_get_tag_name(id);
         char buf[7];
@@ -239,35 +245,262 @@  static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
     return ret;
 }
 
+static int exif_get_collect_size(void *logctx, GetByteContext *gb, int le, int depth)
+{
+    int entries, total_size = 2;
+    GetByteContext gbytes;
+
+    if (depth > 2)
+        return 0;
+
+    gbytes = *gb;
+    entries = ff_tget_short(&gbytes, le);
+    if (bytestream2_get_bytes_left(&gbytes) < entries * 12)
+        return AVERROR_INVALIDDATA;
+
+    for (int i = 0; i < entries; i++) {
+        int cur_pos;
+        unsigned id, count;
+        enum TiffTypes type;
+        ff_tread_tag(&gbytes, le, &id, &type, &count, &cur_pos);
+        if (!bytestream2_tell(&gbytes)) {
+            bytestream2_seek(&gbytes, cur_pos, SEEK_SET);
+            continue;
+        }
+        if (ff_tis_ifd(id)) {
+            int ret = exif_get_collect_size(logctx, &gbytes, le, depth + 1);
+            if (ret < 0)
+                return ret;
+            total_size += ret + 12;
+        } else {
+            int payload_size = type == TIFF_STRING ? count : count * type_sizes[type];
+            if (payload_size > 4)
+                total_size += 12 + payload_size;
+            else
+                total_size += 12;
+        }
+        bytestream2_seek(&gbytes, cur_pos, SEEK_SET);
+    }
+
+    return total_size;
+}
+
+static inline void tput16(PutByteContext *pb, const int le, const unsigned int value)
+{
+    le ? bytestream2_put_le16(pb, value) : bytestream2_put_be16(pb, value);
+}
+
+static inline void tput32(PutByteContext *pb, const int le, const unsigned int value)
+{
+    le ? bytestream2_put_le32(pb, value) : bytestream2_put_be32(pb, value);
+}
+
+static int exif_collect_ifd_list(void *logctx, GetByteContext *gb, int le, int depth, PutByteContext *pb)
+{
+    int entries, ret = 0, offset;
+    GetByteContext gbytes;
+
+    if (depth > 2)
+        return 0;
+
+    gbytes = *gb;
+    entries = ff_tget_short(&gbytes, le);
+    if (bytestream2_get_bytes_left(&gbytes) < entries * 12)
+        return AVERROR_INVALIDDATA;
+
+    tput16(pb, le, entries);
+    offset = bytestream2_tell_p(pb) + entries * 12;
+    for (int i = 0; i < entries; i++) {
+        int cur_pos;
+        unsigned id, count;
+        enum TiffTypes type;
+        ff_tread_tag(&gbytes, le, &id, &type, &count, &cur_pos);
+        if (!bytestream2_tell(&gbytes)) {
+            bytestream2_seek(&gbytes, cur_pos, SEEK_SET);
+            continue;
+        }
+        if (bytestream2_get_bytes_left_p(pb) < 12)
+            return AVERROR_BUFFER_TOO_SMALL;
+        tput16(pb, le, id);
+        tput16(pb, le, type);
+        tput32(pb, le, count);
+        if (ff_tis_ifd(id)) {
+            int tell = bytestream2_tell_p(pb);
+            tput32(pb, le, offset);
+            bytestream2_seek_p(pb, offset, SEEK_SET);
+            ret = exif_collect_ifd_list(logctx, &gbytes, le, depth + 1, pb);
+            if (ret < 0)
+                return ret;
+            offset += ret;
+            bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+        } else  {
+            int payload_size = type == TIFF_STRING ? count : count * type_sizes[type];
+            if (payload_size > 4) {
+                int tell = bytestream2_tell_p(pb);
+                tput32(pb, le, offset);
+                bytestream2_seek_p(pb, offset, SEEK_SET);
+                if (bytestream2_get_bytes_left(&gbytes) < payload_size)
+                    return AVERROR_INVALIDDATA;
+                bytestream2_put_buffer(pb, gbytes.buffer, payload_size);
+                offset += payload_size;
+                bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+            } else {
+                bytestream2_put_ne32(pb, bytestream2_get_ne32(&gbytes));
+            }
+        }
+        bytestream2_seek(&gbytes, cur_pos, SEEK_SET);
+    }
+
+    return offset;
+}
 
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes,
-                       int le, int depth, AVDictionary **metadata)
+int ff_exif_collect_ifd(void *logctx, GetByteContext *gb, int le, AVBufferRef **buffer)
 {
-    int i, ret;
-    int entries;
+    AVBufferRef *ref = NULL;
+    int total_size, ret;
+    PutByteContext pb;
+    if (!buffer)
+        return 0;
+
+    total_size = exif_get_collect_size(logctx, gb, le, 0);
+    if (total_size <= 0)
+        return total_size;
+    total_size += 8;
+    ref = av_buffer_alloc(total_size);
+    if (!ref)
+        return AVERROR(ENOMEM);
+    bytestream2_init_writer(&pb, ref->data, total_size);
+    bytestream2_put_be32(&pb, le ? 0x49492a00 : 0x4d4d002a);
+    tput32(&pb, le, 8);
+
+    ret = exif_collect_ifd_list(logctx, gb, le, 0, &pb);
+    if (ret < 0)
+        av_buffer_unref(&ref);
 
-    entries = ff_tget_short(gbytes, le);
+    *buffer = ref;
+    return ret;
+}
 
-    if (bytestream2_get_bytes_left(gbytes) < entries * 12) {
+static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
+                               int depth, AVDictionary **metadata)
+{
+    int entries = ff_tget_short(gb, le);
+    if (bytestream2_get_bytes_left(gb) < entries * 12)
         return AVERROR_INVALIDDATA;
+
+    for (int i = 0; i < entries; i++) {
+        int ret = exif_decode_tag(logctx, gb, le, depth, metadata);
+        if (ret < 0)
+            return ret;
     }
 
-    for (i = 0; i < entries; i++) {
-        if ((ret = exif_decode_tag(logctx, gbytes, le, depth, metadata)) < 0) {
+    // return next IDF offset or 0x000000000 or a value < 0 for failure
+    return ff_tget_long(gb, le);
+}
+
+int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
+                         AVDictionary **metadata, enum AVExifParseMode parse_mode)
+{
+    int ret, le;
+    GetByteContext gbytes;
+    if (size > INT_MAX)
+        return AVERROR(EINVAL);
+    bytestream2_init(&gbytes, buf, size);
+    if (parse_mode == AV_EXIF_PARSE_TIFF_HEADER) {
+        int ifd_offset;
+        // read TIFF header
+        ret = ff_tdecode_header(&gbytes, &le, &ifd_offset);
+        if (ret < 0) {
+            av_log(logctx, AV_LOG_ERROR, "invalid TIFF header in EXIF data\n");
             return ret;
         }
+        bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
+    } else {
+        le = parse_mode == AV_EXIF_ASSUME_LE;
     }
 
-    // return next IDF offset or 0x000000000 or a value < 0 for failure
-    return ff_tget_long(gbytes, le);
+    // read 0th IFD and store the metadata
+    // (return values > 0 indicate the presence of subimage metadata)
+    ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, metadata);
+    if (ret < 0) {
+        av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data\n");
+        return ret;
+    }
+
+    return bytestream2_tell(&gbytes);
+}
+
+static int attach_displaymatrix(void *logctx, AVFrame *frame, const char *value)
+{
+    char *endptr;
+    AVFrameSideData *sd;
+    long orientation = strtol(value, &endptr, 0);
+    int32_t *matrix;
+    /* invalid string */
+    if (*endptr || endptr == value)
+        return 0;
+    /* invalid orientation */
+    if (orientation < 2 || orientation > 8)
+        return 0;
+    sd = av_frame_new_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9);
+    if (!sd) {
+        av_log(logctx, AV_LOG_ERROR, "Could not allocate frame side data\n");
+        return AVERROR(ENOMEM);
+    }
+    matrix = (int32_t *) sd->data;
+
+    switch (orientation) {
+    case 2:
+        av_display_rotation_set(matrix, 0.0);
+        av_display_matrix_flip(matrix, 1, 0);
+        break;
+    case 3:
+        av_display_rotation_set(matrix, 180.0);
+        break;
+    case 4:
+        av_display_rotation_set(matrix, 180.0);
+        av_display_matrix_flip(matrix, 1, 0);
+        break;
+    case 5:
+        av_display_rotation_set(matrix, 90.0);
+        av_display_matrix_flip(matrix, 1, 0);
+        break;
+    case 6:
+        av_display_rotation_set(matrix, 90.0);
+        break;
+    case 7:
+        av_display_rotation_set(matrix, -90.0);
+        av_display_matrix_flip(matrix, 1, 0);
+        break;
+    case 8:
+        av_display_rotation_set(matrix, -90.0);
+        break;
+    default:
+        av_assert0(0);
+    }
+
+    return 0;
 }
 
-int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
-                           int le, int depth, AVDictionary **metadata)
+int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data)
 {
-    GetByteContext gb;
+    const AVDictionaryEntry *e = NULL;
+    int ret;
+    AVDictionary *m = NULL;
+    AVBufferRef *buffer = *data;
+    AVFrameSideData *sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_EXIF, buffer);
+    if (!sd)
+        return AVERROR(ENOMEM);
+    *data = NULL;
+    ret = av_exif_parse_buffer(logctx, buffer->data, buffer->size, &m, AV_EXIF_PARSE_TIFF_HEADER);
+    if (ret < 0)
+        return ret;
 
-    bytestream2_init(&gb, buf, size);
+    if ((e = av_dict_get(m, "Orientation", e, AV_DICT_IGNORE_SUFFIX))) {
+        ret = attach_displaymatrix(logctx, frame, e->value);
+        if (ret < 0)
+            return ret;
+    }
 
-    return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata);
+    return 0;
 }
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index f70d21391a..284a6deffa 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -1,6 +1,7 @@ 
 /*
  * EXIF metadata parser
  * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * Copyright (c) 2024 Leo Izen <leo.izen@gmail.com>
  *
  * This file is part of FFmpeg.
  *
@@ -23,21 +24,27 @@ 
  * @file
  * EXIF metadata parser
  * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen@gmail.com>
  */
 
 #ifndef AVCODEC_EXIF_H
 #define AVCODEC_EXIF_H
 
 #include <stdint.h>
+
 #include "libavutil/dict.h"
-#include "bytestream.h"
 
-/** Recursively decodes all IFD's and
- *  adds included TAGS into the metadata dictionary. */
-int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
-                           int le, int depth, AVDictionary **metadata);
+enum AVExifParseMode {
+    AV_EXIF_PARSE_TIFF_HEADER,
+    AV_EXIF_ASSUME_LE,
+    AV_EXIF_ASSUME_BE,
+};
 
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes, int le,
-                       int depth, AVDictionary **metadata);
+/**
+ * Recursively decodes all IFD's and
+ * adds included TAGS into the metadata dictionary.
+ */
+int av_exif_parse_buffer(void *logctx, const uint8_t *data, size_t size,
+                         AVDictionary **metadata, enum AVExifParseMode parse_mode);
 
 #endif /* AVCODEC_EXIF_H */
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
new file mode 100644
index 0000000000..e0e04e0020
--- /dev/null
+++ b/libavcodec/exif_internal.h
@@ -0,0 +1,41 @@ 
+/*
+ * EXIF metadata parser - internal functions
+ * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * Copyright (c) 2024 Leo Izen <leo.izen@gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * EXIF metadata parser - internal functions
+ * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen@gmail.com>
+ */
+
+#ifndef AVCODEC_EXIF_INTERNAL_H
+#define AVCODEC_EXIF_INTERNAL_H
+
+#include "libavutil/buffer.h"
+#include "libavutil/frame.h"
+#include "bytestream.h"
+#include "exif.h"
+
+int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data);
+int ff_exif_collect_ifd(void *logctx, GetByteContext *gb, int le, AVBufferRef **buffer);
+
+#endif /* AVCODEC_EXIF_INTERNAL_H */
diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index 81f724d230..c34dd0db40 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -42,6 +42,7 @@ 
 #include "codec_internal.h"
 #include "copy_block.h"
 #include "decode.h"
+#include "exif_internal.h"
 #include "hwaccel_internal.h"
 #include "hwconfig.h"
 #include "idctdsp.h"
@@ -52,11 +53,6 @@ 
 #include "jpeglsdec.h"
 #include "profiles.h"
 #include "put_bits.h"
-#include "tiff.h"
-#include "exif.h"
-#include "bytestream.h"
-#include "tiff_common.h"
-
 
 static int init_default_huffman_tables(MJpegDecodeContext *s)
 {
@@ -2040,8 +2036,6 @@  static int mjpeg_decode_app(MJpegDecodeContext *s)
 
     /* EXIF metadata */
     if (s->start_code == APP1 && id == AV_RB32("Exif") && len >= 2) {
-        GetByteContext gbytes;
-        int ret, le, ifd_offset, bytes_read;
         const uint8_t *aligned;
 
         skip_bits(&s->gb, 16); // skip padding
@@ -2049,27 +2043,12 @@  static int mjpeg_decode_app(MJpegDecodeContext *s)
 
         // init byte wise reading
         aligned = align_get_bits(&s->gb);
-        bytestream2_init(&gbytes, aligned, len);
-
-        // read TIFF header
-        ret = ff_tdecode_header(&gbytes, &le, &ifd_offset);
-        if (ret) {
-            av_log(s->avctx, AV_LOG_ERROR, "mjpeg: invalid TIFF header in EXIF data\n");
-        } else {
-            bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
-
-            // read 0th IFD and store the metadata
-            // (return values > 0 indicate the presence of subimage metadata)
-            ret = ff_exif_decode_ifd(s->avctx, &gbytes, le, 0, &s->exif_metadata);
-            if (ret < 0) {
-                av_log(s->avctx, AV_LOG_ERROR, "mjpeg: error decoding EXIF data\n");
-            }
-        }
-
-        bytes_read = bytestream2_tell(&gbytes);
-        skip_bits(&s->gb, bytes_read << 3);
-        len -= bytes_read;
-
+        s->exif_buffer = av_buffer_alloc(len);
+        if (!s->exif_buffer)
+            return AVERROR(ENOMEM);
+        memcpy(s->exif_buffer->data, aligned, len);
+        skip_bits(&s->gb, len << 3);
+        len = 0;
         goto out;
     }
 
@@ -2381,13 +2360,12 @@  int ff_mjpeg_decode_frame_from_buf(AVCodecContext *avctx, AVFrame *frame,
     int i, index;
     int ret = 0;
     int is16bit;
-    AVDictionaryEntry *e = NULL;
 
     s->force_pal8 = 0;
 
     s->buf_size = buf_size;
 
-    av_dict_free(&s->exif_metadata);
+    av_buffer_unref(&s->exif_buffer);
     av_freep(&s->stereo3d);
     s->adobe_transform = -1;
 
@@ -2853,60 +2831,12 @@  the_end:
         }
     }
 
-    if (e = av_dict_get(s->exif_metadata, "Orientation", e, AV_DICT_IGNORE_SUFFIX)) {
-        char *value = e->value + strspn(e->value, " \n\t\r"), *endptr;
-        int orientation = strtol(value, &endptr, 0);
-
-        if (!*endptr) {
-            AVFrameSideData *sd = NULL;
-
-            if (orientation >= 2 && orientation <= 8) {
-                int32_t *matrix;
-
-                sd = av_frame_new_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9);
-                if (!sd) {
-                    av_log(avctx, AV_LOG_ERROR, "Could not allocate frame side data\n");
-                    return AVERROR(ENOMEM);
-                }
-
-                matrix = (int32_t *)sd->data;
-
-                switch (orientation) {
-                case 2:
-                    av_display_rotation_set(matrix, 0.0);
-                    av_display_matrix_flip(matrix, 1, 0);
-                    break;
-                case 3:
-                    av_display_rotation_set(matrix, 180.0);
-                    break;
-                case 4:
-                    av_display_rotation_set(matrix, 180.0);
-                    av_display_matrix_flip(matrix, 1, 0);
-                    break;
-                case 5:
-                    av_display_rotation_set(matrix, 90.0);
-                    av_display_matrix_flip(matrix, 1, 0);
-                    break;
-                case 6:
-                    av_display_rotation_set(matrix, 90.0);
-                    break;
-                case 7:
-                    av_display_rotation_set(matrix, -90.0);
-                    av_display_matrix_flip(matrix, 1, 0);
-                    break;
-                case 8:
-                    av_display_rotation_set(matrix, -90.0);
-                    break;
-                default:
-                    av_assert0(0);
-                }
-            }
-        }
+    if (s->exif_buffer) {
+        ret = ff_exif_attach(s->avctx, frame, &s->exif_buffer);
+        if (ret < 0)
+            return ret;
     }
 
-    av_dict_copy(&frame->metadata, s->exif_metadata, 0);
-    av_dict_free(&s->exif_metadata);
-
     if (avctx->codec_id != AV_CODEC_ID_SMVJPEG &&
         (avctx->codec_tag == MKTAG('A', 'V', 'R', 'n') ||
          avctx->codec_tag == MKTAG('A', 'V', 'D', 'J')) &&
@@ -2961,7 +2891,7 @@  av_cold int ff_mjpeg_decode_end(AVCodecContext *avctx)
         av_freep(&s->blocks[i]);
         av_freep(&s->last_nnz[i]);
     }
-    av_dict_free(&s->exif_metadata);
+    av_buffer_unref(&s->exif_buffer);
 
     reset_icc_profile(s);
 
diff --git a/libavcodec/mjpegdec.h b/libavcodec/mjpegdec.h
index 13c524d597..780381445f 100644
--- a/libavcodec/mjpegdec.h
+++ b/libavcodec/mjpegdec.h
@@ -138,7 +138,7 @@  typedef struct MJpegDecodeContext {
     unsigned int ljpeg_buffer_size;
 
     int extern_huff;
-    AVDictionary *exif_metadata;
+    AVBufferRef *exif_buffer;
 
     AVStereo3D *stereo3d; ///!< stereoscopic information (cached, since it is read before frame allocation)
 
diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index 3ce441aa2c..47b6173907 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -44,6 +44,7 @@ 
 #include "bytestream.h"
 #include "codec_internal.h"
 #include "decode.h"
+#include "exif_internal.h"
 #include "faxcompr.h"
 #include "lzw.h"
 #include "tiff.h"
@@ -1271,7 +1272,7 @@  static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
         s->last_tag = tag;
 
     off = bytestream2_tell(&s->gb);
-    if (count == 1) {
+    if (count == 1 && tag != TIFF_EXIFTAG) {
         switch (type) {
         case TIFF_BYTE:
         case TIFF_SHORT:
@@ -1751,6 +1752,22 @@  static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
     case TIFF_SOFTWARE_NAME:
         ADD_METADATA(count, "software", NULL);
         break;
+    case TIFF_EXIFTAG: {
+        AVBufferRef *exif = NULL;
+        int next;
+        gb_temp = s->gb;
+        next = ff_exif_collect_ifd(s->avctx, &gb_temp, s->le, &exif);
+        if (next < 0)
+            av_log(s->avctx, AV_LOG_ERROR, "Error parsing TIFF exif tags: %d\n", next);
+        else if (next)
+            bytestream2_seek(&s->gb, next, SEEK_SET);
+        if (exif) {
+            ret = ff_exif_attach(s->avctx, frame, &exif);
+            if (ret < 0)
+                return ret;
+        }
+        break;
+    }
     case DNG_VERSION:
         if (count == 4) {
             unsigned int ver[4];
diff --git a/libavcodec/tiff.h b/libavcodec/tiff.h
index e67c59abad..b8d0228570 100644
--- a/libavcodec/tiff.h
+++ b/libavcodec/tiff.h
@@ -97,6 +97,7 @@  enum TiffTags {
     TIFF_GEO_KEY_DIRECTORY  = 0x87AF,
     TIFF_GEO_DOUBLE_PARAMS  = 0x87B0,
     TIFF_GEO_ASCII_PARAMS   = 0x87B1,
+    TIFF_EXIFTAG            = 0x8769,
 };
 
 /** abridged list of DNG tags */
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index 54b3fde6dc..e8eb748ea1 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -35,6 +35,9 @@ 
  * Exif metadata
  * ICC profile
  *
+ * @author Leo Izen <leo.izen@gmail.com>
+ * Exif metadata
+ *
  * Unimplemented:
  *   - Animation
  *   - XMP metadata
@@ -47,7 +50,7 @@ 
 #include "bytestream.h"
 #include "codec_internal.h"
 #include "decode.h"
-#include "exif.h"
+#include "exif_internal.h"
 #include "get_bits.h"
 #include "thread.h"
 #include "tiff_common.h"
@@ -1451,13 +1454,12 @@  static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p,
             break;
         }
         case MKTAG('E', 'X', 'I', 'F'): {
-            int le, ifd_offset, exif_offset = bytestream2_tell(&gb);
-            AVDictionary *exif_metadata = NULL;
-            GetByteContext exif_gb;
+            AVBufferRef *buf;
 
             if (s->has_exif) {
                 av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra EXIF chunk\n");
-                goto exif_end;
+                bytestream2_skip(&gb, chunk_size);
+                break;
             }
             if (!(vp8x_flags & VP8X_FLAG_EXIF_METADATA))
                 av_log(avctx, AV_LOG_WARNING,
@@ -1465,25 +1467,13 @@  static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p,
                        "VP8X header\n");
 
             s->has_exif = 1;
-            bytestream2_init(&exif_gb, avpkt->data + exif_offset,
-                             avpkt->size - exif_offset);
-            if (ff_tdecode_header(&exif_gb, &le, &ifd_offset) < 0) {
-                av_log(avctx, AV_LOG_ERROR, "invalid TIFF header "
-                       "in Exif data\n");
-                goto exif_end;
-            }
-
-            bytestream2_seek(&exif_gb, ifd_offset, SEEK_SET);
-            if (ff_exif_decode_ifd(avctx, &exif_gb, le, 0, &exif_metadata) < 0) {
-                av_log(avctx, AV_LOG_ERROR, "error decoding Exif data\n");
-                goto exif_end;
-            }
-
-            av_dict_copy(&p->metadata, exif_metadata, 0);
-
-exif_end:
-            av_dict_free(&exif_metadata);
-            bytestream2_skip(&gb, chunk_size);
+            buf = av_buffer_alloc(chunk_size);
+            if (!buf)
+                return AVERROR(ENOMEM);
+            bytestream2_get_buffer(&gb, buf->data, chunk_size);
+            ret = ff_exif_attach(avctx, p, &buf);
+            if (ret < 0)
+                return ret;
             break;
         }
         case MKTAG('I', 'C', 'C', 'P'): {
diff --git a/libavformat/avidec.c b/libavformat/avidec.c
index 00bd7a98a9..2e89e1dfda 100644
--- a/libavformat/avidec.c
+++ b/libavformat/avidec.c
@@ -431,8 +431,8 @@  static int avi_extract_stream_metadata(AVFormatContext *s, AVStream *st)
         offset = bytestream2_tell(&gb);
 
         // decode EXIF tags from IFD, AVI is always little-endian
-        return avpriv_exif_decode_ifd(s, data + offset, data_size - offset,
-                                      1, 0, &st->metadata);
+        return av_exif_parse_buffer(s, data + offset, data_size - offset,
+                                    &st->metadata, AV_EXIF_ASSUME_LE);
         break;
     case MKTAG('C', 'A', 'S', 'I'):
         avpriv_request_sample(s, "RIFF stream data tag type CASI (%u)", tag);
diff --git a/libavutil/frame.c b/libavutil/frame.c
index a3f07ca089..856d16cb9b 100644
--- a/libavutil/frame.c
+++ b/libavutil/frame.c
@@ -958,6 +958,7 @@  const char *av_frame_side_data_name(enum AVFrameSideDataType type)
     case AV_FRAME_DATA_DOVI_RPU_BUFFER:             return "Dolby Vision RPU Data";
     case AV_FRAME_DATA_DOVI_METADATA:               return "Dolby Vision Metadata";
     case AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT: return "Ambient viewing environment";
+    case AV_FRAME_DATA_EXIF:                        return "EXIF metadata";
     }
     return NULL;
 }
diff --git a/libavutil/frame.h b/libavutil/frame.h
index c0c1b23db7..d1ad086dfb 100644
--- a/libavutil/frame.h
+++ b/libavutil/frame.h
@@ -224,6 +224,12 @@  enum AVFrameSideDataType {
      * encoding.
      */
     AV_FRAME_DATA_VIDEO_HINT,
+
+    /**
+     * 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_FRAME_DATA_EXIF,
 };
 
 enum AVActiveFormatDescription {
diff --git a/tests/ref/fate/exif-image-embedded b/tests/ref/fate/exif-image-embedded
index d5937e9f86..98e7782025 100644
--- a/tests/ref/fate/exif-image-embedded
+++ b/tests/ref/fate/exif-image-embedded
@@ -33,8 +33,11 @@  color_space=bt470bg
 color_primaries=unknown
 color_transfer=unknown
 chroma_location=center
-TAG:UserComment=AppleMark
+[SIDE_DATA]
+side_data_type=EXIF metadata
+UserComment=AppleMark
 
+[/SIDE_DATA]
 [/FRAME]
 [FRAME]
 media_type=audio
diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg
index bed265b717..f5f4fcee6b 100644
--- a/tests/ref/fate/exif-image-jpg
+++ b/tests/ref/fate/exif-image-jpg
@@ -33,31 +33,33 @@  color_space=bt470bg
 color_primaries=unknown
 color_transfer=unknown
 chroma_location=center
-TAG:ImageDescription=                               
-TAG:Make=Canon
-TAG:Model=Canon PowerShot SX200 IS
-TAG:Orientation=    1
-TAG:XResolution=    180:1      
-TAG:YResolution=    180:1      
-TAG:ResolutionUnit=    2
-TAG:DateTime=2013:07:18 13:12:03
-TAG:YCbCrPositioning=    2
-TAG:ExposureTime=      1:1250   
-TAG:FNumber=     40:10     
-TAG:ISOSpeedRatings=  160
-TAG:ExifVersion= 48,  50,  50,  49
-TAG:DateTimeOriginal=2013:07:18 13:12:03
-TAG:DateTimeDigitized=2013:07:18 13:12:03
-TAG:ComponentsConfiguration=  1,   2,   3,   0
-TAG:CompressedBitsPerPixel=      3:1      
-TAG:ShutterSpeedValue=    329:32     
-TAG:ApertureValue=    128:32     
-TAG:ExposureBiasValue=      0:3      
-TAG:MaxApertureValue=    113:32     
-TAG:MeteringMode=    5
-TAG:Flash=   16
-TAG:FocalLength=   5000:1000   
-TAG:MakerNote=
+[SIDE_DATA]
+side_data_type=EXIF metadata
+ImageDescription=                               
+Make=Canon
+Model=Canon PowerShot SX200 IS
+Orientation=    1
+XResolution=    180:1      
+YResolution=    180:1      
+ResolutionUnit=    2
+DateTime=2013:07:18 13:12:03
+YCbCrPositioning=    2
+ExposureTime=      1:1250   
+FNumber=     40:10     
+ISOSpeedRatings=  160
+ExifVersion= 48,  50,  50,  49
+DateTimeOriginal=2013:07:18 13:12:03
+DateTimeDigitized=2013:07:18 13:12:03
+ComponentsConfiguration=  1,   2,   3,   0
+CompressedBitsPerPixel=      3:1      
+ShutterSpeedValue=    329:32     
+ApertureValue=    128:32     
+ExposureBiasValue=      0:3      
+MaxApertureValue=    113:32     
+MeteringMode=    5
+Flash=   16
+FocalLength=   5000:1000   
+MakerNote=
  25,   0,   1,   0,   3,   0,  48,   0,   0,   0,  28,   4,   0,   0,   2,   0
   3,   0,   4,   0,   0,   0, 124,   4,   0,   0,   3,   0,   3,   0,   4,   0
   0,   0, 132,   4,   0,   0,   4,   0,   3,   0,  34,   0,   0,   0, 140,   4
@@ -199,7 +201,7 @@  TAG:MakerNote=
   0,   0,   0,   0,   8,   0,   0,   0,   0,   0,   0,   0,  10,   0,   0,   0
 255, 255,   0,   0,   0,   0, 239, 154, 237, 228, 191, 235,  20, 171,  30,   6
   2, 129,  88, 251,  56,  49,  73,  73,  42,   0, 222,   2,   0,   0
-TAG:UserComment=
+UserComment=
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
@@ -217,22 +219,23 @@  TAG:UserComment=
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0
-TAG:FlashpixVersion= 48,  49,  48,  48
-TAG:ColorSpace=    1
-TAG:PixelXDimension= 4000
-TAG:PixelYDimension= 2248
-TAG:GPSLatitudeRef=R98
-TAG:GPSLatitude= 48,  49,  48,  48
-TAG:0x1001= 4000
-TAG:0x1002= 2248
-TAG:FocalPlaneXResolution=4000000:244    
-TAG:FocalPlaneYResolution=2248000:183    
-TAG:FocalPlaneResolutionUnit=    2
-TAG:SensingMethod=    2
-TAG:FileSource=  3
-TAG:CustomRendered=    0
-TAG:ExposureMode=    0
-TAG:WhiteBalance=    0
-TAG:DigitalZoomRatio=   4000:4000   
-TAG:SceneCaptureType=    0
+FlashpixVersion= 48,  49,  48,  48
+ColorSpace=    1
+PixelXDimension= 4000
+PixelYDimension= 2248
+GPSLatitudeRef=R98
+GPSLatitude= 48,  49,  48,  48
+0x1001= 4000
+0x1002= 2248
+FocalPlaneXResolution=4000000:244    
+FocalPlaneYResolution=2248000:183    
+FocalPlaneResolutionUnit=    2
+SensingMethod=    2
+FileSource=  3
+CustomRendered=    0
+ExposureMode=    0
+WhiteBalance=    0
+DigitalZoomRatio=   4000:4000   
+SceneCaptureType=    0
+[/SIDE_DATA]
 [/FRAME]
diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp
index 783abefc60..4cebb326e2 100644
--- a/tests/ref/fate/exif-image-webp
+++ b/tests/ref/fate/exif-image-webp
@@ -33,31 +33,33 @@  color_space=bt470bg
 color_primaries=unknown
 color_transfer=unknown
 chroma_location=unspecified
-TAG:ImageDescription=                               
-TAG:Make=Canon
-TAG:Model=Canon PowerShot SX200 IS
-TAG:Orientation=    1
-TAG:XResolution=    180:1      
-TAG:YResolution=    180:1      
-TAG:ResolutionUnit=    2
-TAG:DateTime=2013:07:18 13:12:03
-TAG:YCbCrPositioning=    2
-TAG:ExposureTime=      1:1250   
-TAG:FNumber=     40:10     
-TAG:ISOSpeedRatings=  160
-TAG:ExifVersion= 48,  50,  50,  49
-TAG:DateTimeOriginal=2013:07:18 13:12:03
-TAG:DateTimeDigitized=2013:07:18 13:12:03
-TAG:ComponentsConfiguration=  1,   2,   3,   0
-TAG:CompressedBitsPerPixel=      3:1      
-TAG:ShutterSpeedValue=    329:32     
-TAG:ApertureValue=    128:32     
-TAG:ExposureBiasValue=      0:3      
-TAG:MaxApertureValue=    113:32     
-TAG:MeteringMode=    5
-TAG:Flash=   16
-TAG:FocalLength=   5000:1000   
-TAG:MakerNote=
+[SIDE_DATA]
+side_data_type=EXIF metadata
+ImageDescription=                               
+Make=Canon
+Model=Canon PowerShot SX200 IS
+Orientation=    1
+XResolution=    180:1      
+YResolution=    180:1      
+ResolutionUnit=    2
+DateTime=2013:07:18 13:12:03
+YCbCrPositioning=    2
+ExposureTime=      1:1250   
+FNumber=     40:10     
+ISOSpeedRatings=  160
+ExifVersion= 48,  50,  50,  49
+DateTimeOriginal=2013:07:18 13:12:03
+DateTimeDigitized=2013:07:18 13:12:03
+ComponentsConfiguration=  1,   2,   3,   0
+CompressedBitsPerPixel=      3:1      
+ShutterSpeedValue=    329:32     
+ApertureValue=    128:32     
+ExposureBiasValue=      0:3      
+MaxApertureValue=    113:32     
+MeteringMode=    5
+Flash=   16
+FocalLength=   5000:1000   
+MakerNote=
  25,   0,   1,   0,   3,   0,  48,   0,   0,   0,  28,   4,   0,   0,   2,   0
   3,   0,   4,   0,   0,   0, 124,   4,   0,   0,   3,   0,   3,   0,   4,   0
   0,   0, 132,   4,   0,   0,   4,   0,   3,   0,  34,   0,   0,   0, 140,   4
@@ -199,7 +201,7 @@  TAG:MakerNote=
   0,   0,   0,   0,   8,   0,   0,   0,   0,   0,   0,   0,  10,   0,   0,   0
 255, 255,   0,   0,   0,   0, 239, 154, 237, 228, 191, 235,  20, 171,  30,   6
   2, 129,  88, 251,  56,  49,  73,  73,  42,   0, 222,   2,   0,   0
-TAG:UserComment=
+UserComment=
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
@@ -217,22 +219,23 @@  TAG:UserComment=
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
   0,   0,   0,   0,   0,   0,   0,   0
-TAG:FlashpixVersion= 48,  49,  48,  48
-TAG:ColorSpace=    1
-TAG:PixelXDimension= 4000
-TAG:PixelYDimension= 2248
-TAG:GPSLatitudeRef=R98
-TAG:GPSLatitude= 48,  49,  48,  48
-TAG:0x1001= 4000
-TAG:0x1002= 2248
-TAG:FocalPlaneXResolution=4000000:244    
-TAG:FocalPlaneYResolution=2248000:183    
-TAG:FocalPlaneResolutionUnit=    2
-TAG:SensingMethod=    2
-TAG:FileSource=  3
-TAG:CustomRendered=    0
-TAG:ExposureMode=    0
-TAG:WhiteBalance=    0
-TAG:DigitalZoomRatio=   4000:4000   
-TAG:SceneCaptureType=    0
+FlashpixVersion= 48,  49,  48,  48
+ColorSpace=    1
+PixelXDimension= 4000
+PixelYDimension= 2248
+GPSLatitudeRef=R98
+GPSLatitude= 48,  49,  48,  48
+0x1001= 4000
+0x1002= 2248
+FocalPlaneXResolution=4000000:244    
+FocalPlaneYResolution=2248000:183    
+FocalPlaneResolutionUnit=    2
+SensingMethod=    2
+FileSource=  3
+CustomRendered=    0
+ExposureMode=    0
+WhiteBalance=    0
+DigitalZoomRatio=   4000:4000   
+SceneCaptureType=    0
+[/SIDE_DATA]
 [/FRAME]