diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index bf5ebe3ce0..cdd7af6e82 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"
@@ -2040,19 +2041,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;
@@ -2920,6 +2932,11 @@ static void print_frame_side_data(WriterContext *w,
         } else if (sd->type == AV_FRAME_DATA_FILM_GRAIN_PARAMS) {
             AVFilmGrainParams *fgp = (AVFilmGrainParams *)sd->data;
             print_film_grain_params(w, fgp);
+        } 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 502be8b09b..4736126b75 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..8442e9f174 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,12 +24,17 @@
  * @file
  * EXIF metadata parser
  * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen@gmail.com>
  */
 
-#include "exif.h"
+#include "libavutil/attributes.h"
+#include "libavutil/display.h"
+
+#include "exif_internal.h"
 #include "tiff_common.h"
 
 #define EXIF_TAG_NAME_LENGTH   32
+#define MAKERNOTE_TAG 0x927c
 
 struct exif_tag {
     char      name[EXIF_TAG_NAME_LENGTH];
@@ -175,11 +181,6 @@ static int exif_add_metadata(void *logctx, int count, int type,
                              AVDictionary **metadata)
 {
     switch(type) {
-    case 0:
-        av_log(logctx, AV_LOG_WARNING,
-               "Invalid TIFF tag type 0 found for %s with size %d\n",
-               name, count);
-        return 0;
     case TIFF_DOUBLE   : return ff_tadd_doubles_metadata(count, name, sep, gb, le, metadata);
     case TIFF_SSHORT   : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 1, metadata);
     case TIFF_SHORT    : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 0, metadata);
@@ -192,12 +193,20 @@ static int exif_add_metadata(void *logctx, int count, int type,
     case TIFF_SLONG    :
     case TIFF_LONG     : return ff_tadd_long_metadata(count, name, sep, gb, le, metadata);
     default:
-        avpriv_request_sample(logctx, "TIFF tag type (%u)", type);
+        av_log(logctx, AV_LOG_WARNING,
+            "Invalid TIFF tag type %d found for %s with size %d\n", type, name, count);
         return 0;
     };
 }
 
+static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
+                               int depth, AVDictionary **metadata);
 
+/**
+ * Decodes a tag and stores it in the dictionary **metadata.
+ * GetByteContext *gbytes is seeked to the next tag.
+ * If the tag is an IFD, it is recursively decoded.
+ */
 static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
                            int depth, AVDictionary **metadata)
 {
@@ -220,7 +229,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 +248,374 @@ static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
     return ret;
 }
 
+static const uint8_t casio_header[] = {
+    'Q', 'V', 'C', 0, 0, 0,
+};
+
+static const uint8_t fuji_header[] = {
+    'F', 'U', 'J', 'I',
+};
+
+static const uint8_t nikon_header[] = {
+    'N', 'i', 'k', 'o', 'n', 0,
+};
 
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes,
-                       int le, int depth, AVDictionary **metadata)
+static const uint8_t olympus1_header[] = {
+    'O', 'L', 'Y', 'M', 'P', 0,
+};
+
+static const uint8_t olympus2_header[] = {
+    'O', 'L', 'Y', 'M', 'P', 'U', 'S', 0, 'I', 'I',
+};
+
+static const uint8_t panosonic_header[] = {
+    'P', 'a', 'n', 'o', 's', 'o', 'n',  'i', 'c', 0, 0, 0,
+};
+
+static const uint8_t aoc_header[] = {
+    'A', 'O', 'C', 0,
+};
+
+static const uint8_t sigma_header[] = {
+    'S', 'I', 'G', 'M', 'A', 0, 0, 0,
+};
+
+static const uint8_t foveon_header[] = {
+    'F', 'O', 'V', 'E', 'O', 'N', 0, 0,
+};
+
+static const uint8_t sony_header[] = {
+    'S', 'O', 'N', 'Y', ' ', 'D', 'S', 'C', ' ', 0, 0, 0,
+};
+
+/**
+ * Get the offset for IFD inside MakerNote.
+ *
+ * Returns -1 if this makernote isn't an IFD or if it contains
+ * and IFD that's relative to the start of the MakerNote, otherwise
+ * returns the offset.
+ *
+ * Most manufacturers use IFDs for these with no header (e.g. Canon)
+ * so the default is to assume the MakerNote is an IFD with offset 0.
+ */
+static int exif_get_makernote_offset(GetByteContext *gb) {
+    if (!memcmp(gb->buffer, casio_header, sizeof(casio_header))) {
+        return -1;
+    } else if (!memcmp(gb->buffer, fuji_header, sizeof(fuji_header))) {
+        return -1;
+    } else if (!memcmp(gb->buffer, olympus2_header, sizeof(olympus2_header))) {
+        return -1;
+    } else if (!memcmp(gb->buffer, olympus1_header, sizeof(olympus1_header)))  {
+        return 8;
+    } else if (!memcmp(gb->buffer, nikon_header, sizeof(nikon_header))) {
+        if (bytestream2_get_bytes_left(gb) < 14)
+            return -1;
+        else if (AV_RB32(gb->buffer + 10) == 0x49492a00 || AV_RB32(gb->buffer + 10) == 0x4d4d002a)
+            return -1;
+        return 8;
+    } else if (!memcmp(gb->buffer, panosonic_header, sizeof(panosonic_header))) {
+        return 12;
+    } else if (!memcmp(gb->buffer, aoc_header, sizeof(aoc_header))) {
+        return 6;
+    } else if (!memcmp(gb->buffer, sigma_header, sizeof(sigma_header))) {
+        return 10;
+    } else if (!memcmp(gb->buffer, foveon_header, sizeof(foveon_header))) {
+        return 10;
+    } else if (!memcmp(gb->buffer, sony_header, sizeof(sony_header))) {
+        return 12;
+    }
+
+    return 0;
+}
+
+/**
+ * Calculate the size of an IFD, so exif_collect_ifd_list knows how big of a space it has
+ * to allocate. It does so by adding the tag sizes, and recursively adding the sizes of
+ * IFD tags encounted.
+ */
+static int exif_get_collect_size(void *logctx, GetByteContext *gb, int le, int depth)
 {
-    int i, ret;
-    int entries;
+    int entries, total_size = 2;
+    GetByteContext gbytes;
 
-    entries = ff_tget_short(gbytes, le);
+    if (depth > 2)
+        return 0;
 
-    if (bytestream2_get_bytes_left(gbytes) < entries * 12) {
+    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, makernote_ifd = -1;
+        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 (id == MAKERNOTE_TAG)
+            makernote_ifd = exif_get_makernote_offset(&gbytes);
+        if (id != MAKERNOTE_TAG && ff_tis_ifd(id) || makernote_ifd >= 0) {
+            int ret, makernote_off = makernote_ifd >= 0 ? makernote_ifd : 0;
+            bytestream2_seek(&gbytes, makernote_off, SEEK_CUR);
+            ret = exif_get_collect_size(logctx, &gbytes, le, depth + 1);
+            if (ret < 0)
+                return ret;
+            total_size += ret + 12 + makernote_off;
+        } 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);
     }
 
-    for (i = 0; i < entries; i++) {
-        if ((ret = exif_decode_tag(logctx, gbytes, le, depth, metadata)) < 0) {
-            return ret;
+    return total_size;
+}
+
+static inline void tput16(PutByteContext *pb, const int le, const uint16_t value)
+{
+    le ? bytestream2_put_le16(pb, value) : bytestream2_put_be16(pb, value);
+}
+
+static inline void tput32(PutByteContext *pb, const int le, const uint32_t value)
+{
+    le ? bytestream2_put_le32(pb, value) : bytestream2_put_be32(pb, value);
+}
+
+/**
+ * Takes an IFD and collects it into a compact buffer with initial offset of 0.
+ * This is useful if ExifTag occurs inside TIFF, or another file with initial
+ * nonzero offsets.
+ */
+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, makernote_ifd = -1;
+        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 (id == MAKERNOTE_TAG)
+            makernote_ifd = exif_get_makernote_offset(&gbytes);
+        if (id != MAKERNOTE_TAG && ff_tis_ifd(id) || makernote_ifd >= 0) {
+            int tell = bytestream2_tell_p(pb);
+            int makernote_off = makernote_ifd >= 0 ? makernote_ifd : 0;
+            tput32(pb, le, offset);
+            bytestream2_seek_p(pb, offset, SEEK_SET);
+            if (makernote_off)
+                bytestream2_copy_buffer(pb, &gbytes, makernote_off);
+            ret = exif_collect_ifd_list(logctx, &gbytes, le, depth + 1, pb);
+            if (ret < 0)
+                return ret;
+            offset += ret + makernote_off;
+            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_collect_ifd(void *logctx, GetByteContext *gb, int le, AVBufferRef **buffer)
+{
+    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);
+
+    *buffer = ref;
+    return ret;
+}
+
+/**
+ * Parse an IFD and read it into the dictonary **metadata.
+ */
+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;
     }
 
     // return next IDF offset or 0x000000000 or a value < 0 for failure
-    return ff_tget_long(gbytes, le);
+    return ff_tget_long(gb, le);
 }
 
-int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
-                           int le, int depth, AVDictionary **metadata)
+int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
+                         AVDictionary **metadata, enum AVExifParseMode parse_mode)
 {
-    GetByteContext gb;
+    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;
+    }
+
+    // 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);
+}
+
+/**
+ * Attach an AV_FRAME_DATA_DISPLAYMATRIX based on the orientation string value.
+ */
+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);
+    }
 
-    bytestream2_init(&gb, buf, size);
+    return 0;
+}
 
-    return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata);
+int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data)
+{
+    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;
+
+    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 0;
+}
+
+attribute_deprecated
+int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
+                           int le, int depth, AVDictionary **metadata)
+{
+    return av_exif_parse_buffer(logctx, buf, size, metadata, le ? AV_EXIF_ASSUME_LE : AV_EXIF_ASSUME_BE);
 }
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index f70d21391a..cf54be68ba 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,40 @@
  * @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 <stddef.h>
 #include <stdint.h>
+
+#include "libavutil/attributes.h"
 #include "libavutil/dict.h"
-#include "bytestream.h"
 
-/** Recursively decodes all IFD's and
- *  adds included TAGS into the metadata dictionary. */
+enum AVExifParseMode {
+    AV_EXIF_PARSE_TIFF_HEADER,
+    AV_EXIF_ASSUME_LE,
+    AV_EXIF_ASSUME_BE,
+};
+
+/**
+ * Parse an EXIF metadata buffer into the AVDictionary **metadata.
+ *
+ * @param logctx A log context for error logging.
+ * @param buf This buffer contains an EXIF blob.
+ * @param size The size of the buffer.
+ * @param metadata A metadata dictionary, into which the key/value tags are written.
+ * @param parse_mode An enum indicating whether to parse the TIFF header,
+ *      or to assume it's LE/BE and skip it.
+ * @return negative upon failure, otherwise total bytes read from the buffer.
+ */
+int av_exif_parse_buffer(void *logctx, const uint8_t *data, size_t size,
+                         AVDictionary **metadata, enum AVExifParseMode parse_mode);
+
+attribute_deprecated
 int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
                            int le, int depth, AVDictionary **metadata);
 
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes, int le,
-                       int depth, AVDictionary **metadata);
-
 #endif /* AVCODEC_EXIF_H */
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
new file mode 100644
index 0000000000..2c788856d6
--- /dev/null
+++ b/libavcodec/exif_internal.h
@@ -0,0 +1,55 @@
+/*
+ * 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"
+
+/**
+ * Attach an AVBufferRef containing EXIF metadata to a frame.
+ * This function allocates the necessary AVFrameSideData and attaches it,
+ * and also attaches any necessary other sidedata that can be read from the,
+ * EXIF metadata, such as a display matrix.
+ */
+int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data);
+
+/**
+ * Collects an IFD into a buffer. **buffer points to an AVBufferRef *, which will
+ * be allocated by this function. The caller needs to unref the buffer when it is
+ * done with it. This function also writes the EXIF/TIFF header into the buffer
+ * based on the endianness, so encoders can pass the buffer as-is. This function can be
+ * used by TIFF or other codecs that have non-zero IFD offsets on their EXIF metadata.
+ */
+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 86ec58713c..9ed71976df 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -43,6 +43,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"
@@ -2039,8 +2040,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
@@ -2048,27 +2047,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;
     }
 
@@ -2380,13 +2364,12 @@ int ff_mjpeg_decode_frame_from_buf(AVCodecContext *avctx, AVFrame *frame,
     int 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 +2836,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 +2896,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 37b56e9757..45c3ae8c97 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -46,6 +46,7 @@
 #include "bytestream.h"
 #include "codec_internal.h"
 #include "decode.h"
+#include "exif_internal.h"
 #include "faxcompr.h"
 #include "lzw.h"
 #include "tiff.h"
@@ -1268,7 +1269,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:
@@ -1770,6 +1771,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 12afcfa6e5..2628f9885f 100644
--- a/libavcodec/tiff.h
+++ b/libavcodec/tiff.h
@@ -94,6 +94,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/version.h b/libavcodec/version.h
index 7531c6c42a..9b8c267529 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 
 #include "version_major.h"
 
-#define LIBAVCODEC_VERSION_MINOR  14
+#define LIBAVCODEC_VERSION_MINOR  15
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index 7c2a5f0111..659f641652 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
@@ -48,7 +51,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"
@@ -1452,13 +1455,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,
@@ -1466,25 +1468,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 1ae09efc15..1cea72471f 100644
--- a/libavformat/avidec.c
+++ b/libavformat/avidec.c
@@ -433,8 +433,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 5cbfc6a48b..f668ed87fd 100644
--- a/libavutil/frame.c
+++ b/libavutil/frame.c
@@ -56,6 +56,8 @@ static const AVSideDataDescriptor sd_props[] = {
     [AV_FRAME_DATA_SPHERICAL]                   = { "Spherical Mapping",                            AV_SIDE_DATA_PROP_GLOBAL },
     [AV_FRAME_DATA_ICC_PROFILE]                 = { "ICC profile",                                  AV_SIDE_DATA_PROP_GLOBAL },
     [AV_FRAME_DATA_SEI_UNREGISTERED]            = { "H.26[45] User Data Unregistered SEI message",  AV_SIDE_DATA_PROP_MULTI },
+    [AV_FRAME_DATA_EXIF]                        = { "EXIF metadata",
+    AV_SIDE_DATA_PROP_GLOBAL },
 };
 
 static void get_frame_defaults(AVFrame *frame)
diff --git a/libavutil/frame.h b/libavutil/frame.h
index 60bb966f8b..3614b412bb 100644
--- a/libavutil/frame.h
+++ b/libavutil/frame.h
@@ -228,6 +228,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 98b6ec5a44..1edda0fc92 100644
--- a/tests/ref/fate/exif-image-embedded
+++ b/tests/ref/fate/exif-image-embedded
@@ -29,8 +29,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 2e314078da..bcc6184085 100644
--- a/tests/ref/fate/exif-image-jpg
+++ b/tests/ref/fate/exif-image-jpg
@@ -29,31 +29,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
@@ -195,7 +197,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
@@ -213,22 +215,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 73560e8ba0..d1fd7ea4e3 100644
--- a/tests/ref/fate/exif-image-webp
+++ b/tests/ref/fate/exif-image-webp
@@ -29,31 +29,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
@@ -195,7 +197,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
@@ -213,22 +215,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]
