[FFmpeg-cvslog] [ffmpeg] branch master updated. 4c3f94f265 doc/APIChanges: document EXIF API additions
ffmpeg-git at ffmpeg.org
ffmpeg-git at ffmpeg.org
Tue Aug 19 18:33:03 EEST 2025
The branch, master has been updated
via 4c3f94f2651ce4d6834fbef10e5c287f25720ac9 (commit)
via fe496b0308f1911138698b92bbafac582fa455d7 (commit)
via 535a07d14e16eb2930d2b1e16605eafa95959139 (commit)
via 8132ee046d1a03c5913759c50fce7beda8d3d627 (commit)
via 5d4f873ff31f86f9ae1927f37ca0fcb78da70512 (commit)
via 1e816ebefee0ef479fa76bd2a1fb9ec5d5e807df (commit)
via 93a8091015978c44462626409af71789080bbef4 (commit)
via 303f60684f25ce63951480ef0f759acf41aac938 (commit)
via 5caaadee7954f2014d9e0a17d1edfb961893bcf1 (commit)
via d3190a64c366a79091fe47fddf93c09a7d988802 (commit)
via 44af3829796427b89c4b76b56652cc5932ada616 (commit)
via f5ad1c910c168d05cb01315773ab0bd094c9372f (commit)
via e3aa1154aab2656c91ce61915f79516d9b563b61 (commit)
via c6cc2115f45ac26ae42442f8796996eb410f4028 (commit)
via 52dba25661305e3c4a6209d46aea43cd327c960e (commit)
via bfb17d26306592c85cf0c4e909099c621177b062 (commit)
via ba2ea285e0f270a0a885b414cafaace6a89b9a91 (commit)
via ad77345a5d14862f4701e5ad422b03b14934a5b9 (commit)
via bb90b262d6d23f1bca3587a48abc15b951cbbf05 (commit)
via a99fff4e2d4058c57599ba0b968862af82bdad5b (commit)
from a6b5a382dd7ecdd27c5d0ebba688e1db409d18fd (commit)
- Log -----------------------------------------------------------------
commit 4c3f94f2651ce4d6834fbef10e5c287f25720ac9
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Fri Jul 25 10:14:37 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:32:06 2025 -0400
doc/APIChanges: document EXIF API additions
Many of these additions are in separate commits in one set, so in the
interest of clarity, the API changes are all documented in one commmit
here.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/doc/APIchanges b/doc/APIchanges
index 1ed8337d04..4b32279e35 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,23 @@ The last version increases of all libraries were on 2025-03-28
API changes, most recent first:
+2025-08-19 - ad77345a5d1..fe496b0308f - lavc 62.13.100 - exif.h
+ Add:
+ - enum AVTiffDataType, enum AVExifHeaderMode
+ - struct AVExifMetadata, struct AVExifEntry
+ - av_exif_get_tag_name, av_exif_get_tag_id,
+ av_exif_set_entry, av_exif_get_entry,
+ av_exif_remove_entry, av_exif_parse_buffer,
+ av_exif_write, av_exif_free,
+ av_exif_ifd_to_dict, av_exif_clone_ifd,
+ av_exif_matrix_to_orientation,
+ av_exif_orientation_to_matrix,
+ Deprecate:
+ - avpriv_exif_decode_ifd (deprecated behind FF_API_OLD_EXIF)
+
+2025-08-19 - bb90b262d6d - lavu 60.10.100 - frame.h
+ Add AV_FRAME_DATA_EXIF.
+
2025-07-29 - 1c85a3832af - lavc 62.10.100 - smpte_436m.h
Add a new public header smpte_436m.h with API for
manipulating AV_CODEC_ID_SMPTE_436M_ANC data.
commit fe496b0308f1911138698b92bbafac582fa455d7
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Sun Apr 13 05:47:54 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/libjxlenc: apply displaymatrix side data orientation to output
If the output is tagged with AV_FRAME_DATA_DISPLAYMATRIX we will read
it and apply it to the output file.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c
index 40d5f760b1..4f05f015e2 100644
--- a/libavcodec/libjxlenc.c
+++ b/libavcodec/libjxlenc.c
@@ -28,6 +28,7 @@
#include "libavutil/avutil.h"
#include "libavutil/csp.h"
+#include "libavutil/display.h"
#include "libavutil/error.h"
#include "libavutil/frame.h"
#include "libavutil/libm.h"
@@ -40,6 +41,7 @@
#include "avcodec.h"
#include "encode.h"
#include "codec_internal.h"
+#include "exif_internal.h"
#include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h>
@@ -322,10 +324,14 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame,
{
LibJxlEncodeContext *ctx = avctx->priv_data;
AVFrameSideData *sd;
+ int32_t *matrix = (int32_t[9]){ 0 };
+ int ret = 0;
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
JxlBasicInfo info;
JxlPixelFormat *jxl_fmt = &ctx->jxl_fmt;
int bits_per_sample;
+ int orientation;
+ AVBufferRef *exif_buffer = NULL;
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
JxlBitDepth jxl_bit_depth;
#endif
@@ -368,8 +374,51 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame,
/* bitexact lossless requires there to be no XYB transform */
info.uses_original_profile = ctx->distance == 0.0 || !ctx->xyb;
- /* libjxl doesn't support negative linesizes so we use orientation to work around this */
- info.orientation = frame->linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF);
+ if (sd) {
+ AVExifMetadata ifd = { 0 };
+ AVExifEntry *orient = NULL;
+ uint16_t tag = av_exif_get_tag_id("Orientation");
+ ret = av_exif_parse_buffer(avctx, sd->data, sd->size, &ifd, AV_EXIF_TIFF_HEADER);
+ if (ret >= 0)
+ ret = ff_exif_sanitize_ifd(avctx, frame, &ifd);
+ if (ret >= 0)
+ ret = av_exif_get_entry(avctx, &ifd, tag, 0, &orient);
+ if (ret >= 0 && orient && orient->value.uint[0] >= 1 && orient->value.uint[0] <= 8) {
+ av_exif_orientation_to_matrix(matrix, orient->value.uint[0]);
+ ret = av_exif_remove_entry(avctx, &ifd, tag, 0);
+ } else {
+ av_exif_orientation_to_matrix(matrix, 1);
+ }
+ if (ret >= 0)
+ ret = av_exif_write(avctx, &ifd, &exif_buffer, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_WARNING, "unable to process EXIF frame data\n");
+ } else {
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX);
+ if (sd)
+ matrix = (int32_t *) sd->data;
+ else
+ av_exif_orientation_to_matrix(matrix, 1);
+ }
+
+ /* av_display_matrix_flip is a right-multipilcation */
+ /* i.e. flip is applied before the previous matrix */
+ if (frame->linesize < 0)
+ av_display_matrix_flip(matrix, 0, 1);
+
+ orientation = av_exif_matrix_to_orientation(matrix);
+ /* JPEG XL orientation flag agrees with EXIF for values 1-8 */
+ if (orientation) {
+ info.orientation = orientation;
+ } else {
+ av_log(avctx, AV_LOG_WARNING, "singular displaymatrix data\n");
+ info.orientation = frame->linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
+ }
+
+ /* restore the previous value */
+ if (frame->linesize < 0)
+ av_display_matrix_flip(matrix, 0, 1);
if (animated) {
info.have_animation = 1;
@@ -382,7 +431,8 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame,
if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
- return AVERROR_EXTERNAL;
+ ret = AVERROR_EXTERNAL;
+ goto end;
}
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
@@ -399,6 +449,11 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame,
av_log(avctx, AV_LOG_WARNING, "Failed to set JxlBitDepth\n");
#endif
+ if (exif_buffer) {
+ if (JxlEncoderUseBoxes(ctx->encoder) != JXL_ENC_SUCCESS)
+ av_log(avctx, AV_LOG_WARNING, "Couldn't enable UseBoxes\n");
+ }
+
/* depending on basic info, level 10 might
* be required instead of level 5 */
if (JxlEncoderGetRequiredCodestreamLevel(ctx->encoder) > 5) {
@@ -406,7 +461,9 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame,
av_log(avctx, AV_LOG_WARNING, "Could not increase codestream level\n");
}
- return 0;
+end:
+ av_buffer_unref(&exif_buffer);
+ return ret;
}
/**
commit 535a07d14e16eb2930d2b1e16605eafa95959139
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Fri Jul 25 11:56:36 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/exif: add ff_exif_sanitize_ifd
This commit takes some of the sanitize code used by ff_exif_get_buffer
and exposes it as an ff_ API, so encoders who wish to modify the
sanitized IFD before calling av_exif_write can do so.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 15bbe7749b..48959eb9b3 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -1307,13 +1307,10 @@ int av_exif_orientation_to_matrix(int32_t *matrix, int orientation)
return 0;
}
-int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ptr, enum AVExifHeaderMode header_mode)
+int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd)
{
int ret = 0;
- AVBufferRef *buffer = NULL;
- AVFrameSideData *sd_exif = NULL;
AVFrameSideData *sd_orient = NULL;
- AVExifMetadata ifd = { 0 };
AVExifEntry *or = NULL;
AVExifEntry *iw = NULL;
AVExifEntry *ih = NULL;
@@ -1324,28 +1321,15 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
uint64_t h = frame->height;
int rewrite = 0;
- if (!buffer_ptr || *buffer_ptr)
- return AVERROR(EINVAL);
-
- sd_exif = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF);
sd_orient = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX);
- if (!sd_exif && !sd_orient)
- return 0;
-
if (sd_orient)
orientation = av_exif_matrix_to_orientation((int32_t *) sd_orient->data);
if (orientation != 1)
av_log(logctx, AV_LOG_DEBUG, "matrix contains nontrivial EXIF orientation: %" PRIu64 "\n", orientation);
- if (sd_exif) {
- ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER);
- if (ret < 0)
- goto end;
- }
-
- for (size_t i = 0; i < ifd.count; i++) {
- AVExifEntry *entry = &ifd.entries[i];
+ for (size_t i = 0; i < ifd->count; i++) {
+ AVExifEntry *entry = &ifd->entries[i];
if (entry->id == ORIENTATION_TAG && entry->count > 0 && entry->type == AV_TIFF_SHORT) {
or = entry;
continue;
@@ -1396,26 +1380,26 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
}
if (!or && orientation != 1) {
rewrite = 1;
- ret = av_exif_set_entry(logctx, &ifd, ORIENTATION_TAG, AV_TIFF_SHORT, 1, NULL, 0, &orientation);
+ ret = av_exif_set_entry(logctx, ifd, ORIENTATION_TAG, AV_TIFF_SHORT, 1, NULL, 0, &orientation);
if (ret < 0)
goto end;
}
if (!iw && w) {
rewrite = 1;
- ret = av_exif_set_entry(logctx, &ifd, IMAGE_WIDTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &w);
+ ret = av_exif_set_entry(logctx, ifd, IMAGE_WIDTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &w);
if (ret < 0)
goto end;
}
if (!ih && h) {
rewrite = 1;
- ret = av_exif_set_entry(logctx, &ifd, IMAGE_LENGTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &h);
+ ret = av_exif_set_entry(logctx, ifd, IMAGE_LENGTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &h);
if (ret < 0)
goto end;
}
if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) {
AVExifMetadata *exif;
AVExifEntry *exif_entry;
- int exif_found = av_exif_get_entry(logctx, &ifd, EXIFIFD_TAG, 0, &exif_entry);
+ int exif_found = av_exif_get_entry(logctx, ifd, EXIFIFD_TAG, 0, &exif_entry);
rewrite = 1;
if (exif_found < 0)
goto end;
@@ -1423,12 +1407,12 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
exif = &exif_entry->value.ifd;
} else {
AVExifMetadata exif_new = { 0 };
- ret = av_exif_set_entry(logctx, &ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
+ ret = av_exif_set_entry(logctx, ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
if (ret < 0) {
av_exif_free(&exif_new);
goto end;
}
- exif = &ifd.entries[ifd.count - 1].value.ifd;
+ exif = &ifd->entries[ifd->count - 1].value.ifd;
}
if (!pw && w && w < 0xFFFFu) {
ret = av_exif_set_entry(logctx, exif, PIXEL_X_TAG, AV_TIFF_SHORT, 1, NULL, 0, &w);
@@ -1442,6 +1426,37 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
}
}
+ return rewrite;
+
+end:
+ return ret;
+}
+
+int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ptr, enum AVExifHeaderMode header_mode)
+{
+ AVFrameSideData *sd_exif = NULL;
+ AVBufferRef *buffer = NULL;
+ AVExifMetadata ifd = { 0 };
+ int ret = 0;
+ int rewrite = 0;
+
+ if (!buffer_ptr || *buffer_ptr)
+ return AVERROR(EINVAL);
+
+ sd_exif = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF);
+ if (!sd_exif)
+ return 0;
+
+ ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ goto end;
+
+ rewrite = ff_exif_sanitize_ifd(logctx, frame, &ifd);
+ if (rewrite < 0) {
+ ret = rewrite;
+ goto end;
+ }
+
if (rewrite) {
ret = av_exif_write(logctx, &ifd, &buffer, header_mode);
if (ret < 0)
@@ -1456,6 +1471,9 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
}
}
+ av_exif_free(&ifd);
+ return rewrite;
+
end:
av_exif_free(&ifd);
return ret;
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index 4fd147fb99..7d8e225250 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -59,6 +59,14 @@ int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum
*/
int ff_exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd);
+/**
+ * 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
+ * encoder that wishes to use ff_exif_get_buffer, but would prefer to modify the
+ * IFD after it is sanitized and call av_exif_write afterward.
+ */
+int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd);
+
/**
* Gets all relevant side data, collects it into an IFD, and writes it into the
* corresponding buffer pointer. This includes both AV_FRAME_DATA_EXIF and other
commit 8132ee046d1a03c5913759c50fce7beda8d3d627
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Sat Apr 12 23:44:42 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/libjxldec: read EXIF metadata from container JPEG XL files
libjxl provides the ability to get EXIF metadata from an ISO18181-2
JPEG XL container file. This commit enables reading it and attaching
it to an AVFrame using the new EXIF API.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c
index 96c338d1b4..7ea5fc4787 100644
--- a/libavcodec/libjxldec.c
+++ b/libavcodec/libjxldec.c
@@ -29,6 +29,7 @@
#include "libavutil/common.h"
#include "libavutil/csp.h"
#include "libavutil/error.h"
+#include "libavutil/intreadwrite.h"
#include "libavutil/mem.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"
@@ -37,6 +38,7 @@
#include "avcodec.h"
#include "codec_internal.h"
#include "decode.h"
+#include "exif_internal.h"
#include "internal.h"
#include <jxl/decode.h>
@@ -59,6 +61,8 @@ typedef struct LibJxlDecodeContext {
int prev_is_last;
AVRational anim_timebase;
AVFrame *frame;
+ AVBufferRef *exif;
+ size_t exif_pos;
} LibJxlDecodeContext;
static int libjxl_init_jxl_decoder(AVCodecContext *avctx)
@@ -66,17 +70,24 @@ static int libjxl_init_jxl_decoder(AVCodecContext *avctx)
LibJxlDecodeContext *ctx = avctx->priv_data;
ctx->events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE
- | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME;
+ | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME
+ | JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE;
if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) {
av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events\n");
return AVERROR_EXTERNAL;
}
+ if (JxlDecoderSetDecompressBoxes(ctx->decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error setting compress box mode\n");
+ return AVERROR_EXTERNAL;
+ }
+
if (JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner) != JXL_DEC_SUCCESS) {
av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
return AVERROR_EXTERNAL;
}
+ av_buffer_unref(&ctx->exif);
memset(&ctx->basic_info, 0, sizeof(JxlBasicInfo));
memset(&ctx->jxl_pixfmt, 0, sizeof(JxlPixelFormat));
ctx->prev_is_last = 1;
@@ -487,6 +498,28 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
if (ret < 0)
return ret;
}
+ if (ctx->exif) {
+ AVExifMetadata ifd = { 0 };
+ /* size may be larger than exif_pos due to the realloc loop */
+ ret = av_exif_parse_buffer(avctx, ctx->exif->data, ctx->exif_pos, &ifd, AV_EXIF_T_OFF);
+ av_buffer_unref(&ctx->exif);
+ if (ret < 0) {
+ av_log(avctx, AV_LOG_ERROR, "Unable to parse EXIF buffer\n");
+ continue;
+ }
+ /*
+ * JPEG XL Codestream orientation overrides EXIF orientation in all cases.
+ * As a result, we remove the EXIF Orientation tag rather than just zeroing it
+ * in order to prevent any ambiguity. libjxl autorotates the image for us so we
+ * do not need to worry about that.
+ */
+ ret = av_exif_remove_entry(avctx, &ifd, av_exif_get_tag_id("Orientation"), 0);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_WARNING, "Unable to remove orientation from EXIF buffer\n");
+ ret = ff_exif_attach_ifd(avctx, ctx->frame, &ifd);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_ERROR, "Unable to attach EXIF ifd\n");
+ }
if (ctx->basic_info.have_animation) {
ctx->frame->pts = av_rescale_q(ctx->accumulated_pts, ctx->anim_timebase, avctx->pkt_timebase);
ctx->frame->duration = av_rescale_q(ctx->frame_duration, ctx->anim_timebase, avctx->pkt_timebase);
@@ -511,6 +544,48 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
JxlDecoderReset(ctx->decoder);
libjxl_init_jxl_decoder(avctx);
continue;
+ case JXL_DEC_BOX: {
+ char type[4];
+ av_log(avctx, AV_LOG_DEBUG, "BOX event emitted\n");
+ if (JxlDecoderGetBoxType(ctx->decoder, type, JXL_TRUE) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error getting box type\n");
+ return AVERROR_EXTERNAL;
+ }
+ if (AV_RL32(type) != MKTAG('E','x','i','f'))
+ continue;
+ av_buffer_unref(&ctx->exif);
+ ctx->exif_pos = 0;
+ // 4k buffer should usually be enough
+ ret = av_buffer_realloc(&ctx->exif, 4096);
+ if (ret < 0)
+ return AVERROR(ENOMEM);
+ if (JxlDecoderSetBoxBuffer(ctx->decoder, ctx->exif->data, ctx->exif->size) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error setting box buffer\n");
+ return AVERROR_EXTERNAL;
+ }
+ continue;
+ }
+ case JXL_DEC_BOX_NEED_MORE_OUTPUT: {
+ av_log(avctx, AV_LOG_DEBUG, "BOX_NEED_MORE_OUTPUT event emitted\n");
+ size_t remainder = JxlDecoderReleaseBoxBuffer(ctx->decoder);
+ ctx->exif_pos = ctx->exif->size - remainder;
+ size_t new_size = ctx->exif->size << 1;
+ ret = av_buffer_realloc(&ctx->exif, new_size);
+ if (ret < 0)
+ return AVERROR(ENOMEM);
+ if (JxlDecoderSetBoxBuffer(ctx->decoder, ctx->exif->data + ctx->exif_pos,
+ ctx->exif->size - ctx->exif_pos) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error setting box buffer\n");
+ return AVERROR_EXTERNAL;
+ }
+ continue;
+ }
+ case JXL_DEC_BOX_COMPLETE: {
+ av_log(avctx, AV_LOG_DEBUG, "BOX_COMPLETE event emitted\n");
+ size_t remainder = JxlDecoderReleaseBoxBuffer(ctx->decoder);
+ ctx->exif_pos = ctx->exif->size - remainder;
+ continue;
+ }
default:
av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret);
return AVERROR_EXTERNAL;
@@ -528,6 +603,7 @@ static av_cold int libjxl_decode_close(AVCodecContext *avctx)
if (ctx->decoder)
JxlDecoderDestroy(ctx->decoder);
ctx->decoder = NULL;
+ av_buffer_unref(&ctx->exif);
av_buffer_unref(&ctx->iccp);
av_frame_free(&ctx->frame);
commit 5d4f873ff31f86f9ae1927f37ca0fcb78da70512
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Sat Apr 12 12:00:52 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
fftools/ffprobe: print EXIF side data size
We don't need to print the tags here because they're added as dict
elements to AVFrame->metadata and are printed elsewhere with ffprobe
-show_frames.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index b8d51071e6..018111318e 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -1342,6 +1342,8 @@ static void print_frame_side_data(AVTextFormatContext *tfc,
print_film_grain_params(tfc, fgp);
} else if (sd->type == AV_FRAME_DATA_VIEW_ID) {
print_int("view_id", *(int*)sd->data);
+ } else if (sd->type == AV_FRAME_DATA_EXIF) {
+ print_int("size", sd->size);
}
avtext_print_section_footer(tfc);
}
diff --git a/tests/ref/fate/exif-image-embedded b/tests/ref/fate/exif-image-embedded
index a6617bd89f..22d4b03d1c 100644
--- a/tests/ref/fate/exif-image-embedded
+++ b/tests/ref/fate/exif-image-embedded
@@ -34,6 +34,7 @@ TAG:ExifIFD/UserComment=AppleMark
[SIDE_DATA]
side_data_type=EXIF metadata
+size=55
[/SIDE_DATA]
[/FRAME]
[FRAME]
diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg
index 5135ae32f8..d847ae7c7f 100644
--- a/tests/ref/fate/exif-image-jpg
+++ b/tests/ref/fate/exif-image-jpg
@@ -199,5 +199,6 @@ TAG:ExifIFD/DigitalZoomRatio= 4000:4000
TAG:ExifIFD/SceneCaptureType= 0
[SIDE_DATA]
side_data_type=EXIF metadata
+size=3297
[/SIDE_DATA]
[/FRAME]
diff --git a/tests/ref/fate/exif-image-tiff b/tests/ref/fate/exif-image-tiff
index 81737cb983..f0c0946c76 100644
--- a/tests/ref/fate/exif-image-tiff
+++ b/tests/ref/fate/exif-image-tiff
@@ -54,5 +54,6 @@ TAG:ResolutionUnit= 3
TAG:Software=ImageMagick 6.5.8-0 2010-02-09 Q16 http://www.imagemagick.org
[SIDE_DATA]
side_data_type=EXIF metadata
+size=403
[/SIDE_DATA]
[/FRAME]
diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp
index 7c01ae3d66..54c8294362 100644
--- a/tests/ref/fate/exif-image-webp
+++ b/tests/ref/fate/exif-image-webp
@@ -199,5 +199,6 @@ TAG:ExifIFD/DigitalZoomRatio= 4000:4000
TAG:ExifIFD/SceneCaptureType= 0
[SIDE_DATA]
side_data_type=EXIF metadata
+size=9718
[/SIDE_DATA]
[/FRAME]
commit 1e816ebefee0ef479fa76bd2a1fb9ec5d5e807df
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Apr 9 13:07:49 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/tiff: decode TIFF non-image-data tags into EXIF metadata struct
This commit will cause TIFF files to store their tags in the EXIF
struct so tags such as orientation can be transfered to other formats
(such as PNG) in a way that doesn't corrupt the IFD.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index b468598fbb..d15bc497ec 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -47,6 +47,7 @@
#include "bytestream.h"
#include "codec_internal.h"
#include "decode.h"
+#include "exif_internal.h"
#include "faxcompr.h"
#include "lzw.h"
#include "tiff.h"
@@ -124,6 +125,8 @@ typedef struct TiffContext {
int geotag_count;
TiffGeoTag *geotags;
+
+ AVExifMetadata exif_meta;
} TiffContext;
static const float d65_white[3] = { 0.950456f, 1.f, 1.088754f };
@@ -1937,6 +1940,12 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *p,
int is_dng;
int has_tile_bits, has_strip_bits;
+ av_exif_free(&s->exif_meta);
+ /* this will not parse the image data */
+ ret = av_exif_parse_buffer(avctx, avpkt->data, avpkt->size, &s->exif_meta, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_ERROR, "could not parse EXIF data: %s\n", av_err2str(ret));
+
bytestream2_init(&s->gb, avpkt->data, avpkt->size);
// parse image header
@@ -2402,6 +2411,10 @@ again:
}
}
+ ret = ff_exif_attach_ifd(avctx, p, &s->exif_meta);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_ERROR, "error attaching EXIF ifd: %s\n", av_err2str(ret));
+
*got_frame = 1;
return avpkt->size;
@@ -2450,6 +2463,7 @@ static av_cold int tiff_end(AVCodecContext *avctx)
TiffContext *const s = avctx->priv_data;
free_geotags(s);
+ av_exif_free(&s->exif_meta);
ff_lzw_decode_close(&s->lzw);
av_freep(&s->deinvert_buf);
diff --git a/tests/ref/fate/exif-image-tiff b/tests/ref/fate/exif-image-tiff
index 0e604d4271..81737cb983 100644
--- a/tests/ref/fate/exif-image-tiff
+++ b/tests/ref/fate/exif-image-tiff
@@ -32,5 +32,27 @@ color_transfer=unknown
chroma_location=unspecified
TAG:document_name=image_small.tiff
TAG:page_number= 0 / 1
-TAG:software=ImageMagick 6.5.8-0 2010-02-09 Q16 http://www.imagemagick.org
+TAG:0x0129= 0, 1
+TAG:ImageWidth= 200
+TAG:ImageLength= 112
+TAG:BitsPerSample= 8, 8, 8
+TAG:Compression= 1
+TAG:PhotometricInterpretation= 2
+TAG:0x010A= 1
+TAG:0x010D=image_small.tiff
+TAG:StripOffsets= 8, 7808, 15608, 23408, 31208, 39008, 46808, 54608
+ 62408
+TAG:Orientation= 1
+TAG:SamplesPerPixel= 3
+TAG:RowsPerStrip= 13
+TAG:StripByteCounts= 7800, 7800, 7800, 7800, 7800, 7800, 7800, 7800
+ 4800
+TAG:XResolution=1188833536:16777216
+TAG:YResolution=1188833536:16777216
+TAG:PlanarConfiguration= 1
+TAG:ResolutionUnit= 3
+TAG:Software=ImageMagick 6.5.8-0 2010-02-09 Q16 http://www.imagemagick.org
+[SIDE_DATA]
+side_data_type=EXIF metadata
+[/SIDE_DATA]
[/FRAME]
commit 93a8091015978c44462626409af71789080bbef4
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Apr 16 10:33:04 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/exif: add av_exif_get_entry
Add an API function to retrieve am AVExifEntry struct with a given TIFF
tag ID from the AVExifMetadata ifd struct.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 993a931d42..15bbe7749b 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -1080,6 +1080,36 @@ end:
return ret;
}
+static int exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int depth, AVExifEntry **value)
+{
+ int offset = 1;
+
+ if (!ifd || ifd->entries && !ifd->count || ifd->count && !ifd->entries || !value)
+ return AVERROR(EINVAL);
+
+ for (size_t i = 0; i < ifd->count; i++) {
+ if (ifd->entries[i].id == id) {
+ *value = &ifd->entries[i];
+ return i + offset;
+ }
+ if (ifd->entries[i].type == AV_TIFF_IFD) {
+ if (depth < 3) {
+ int ret = exif_get_entry(logctx, &ifd->entries[i].value.ifd, id, depth + 1, value);
+ if (ret)
+ return ret < 0 ? ret : ret + offset;
+ }
+ offset += ifd->entries[i].value.ifd.count;
+ }
+ }
+
+ return 0;
+}
+
+int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int recursive, AVExifEntry **value)
+{
+ return exif_get_entry(logctx, ifd, id, recursive ? 0 : INT_MAX, value);
+}
+
int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value)
{
@@ -1093,12 +1123,9 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif
|| !value || ifd->count == 0xFFFFu)
return AVERROR(EINVAL);
- for (size_t i = 0; i < ifd->count; i++) {
- if (ifd->entries[i].id == id) {
- entry = &ifd->entries[i];
- break;
- }
- }
+ ret = av_exif_get_entry(logctx, ifd, id, 0, &entry);
+ if (ret < 0)
+ return ret;
if (entry) {
exif_free_entry(entry);
@@ -1292,7 +1319,6 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
AVExifEntry *ih = NULL;
AVExifEntry *pw = NULL;
AVExifEntry *ph = NULL;
- AVExifMetadata *exif = NULL;
uint64_t orientation = 1;
uint64_t w = frame->width;
uint64_t h = frame->height;
@@ -1333,7 +1359,7 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
continue;
}
if (entry->id == EXIFIFD_TAG && entry->type == AV_TIFF_IFD) {
- exif = &entry->value.ifd;
+ AVExifMetadata *exif = &entry->value.ifd;
for (size_t j = 0; j < exif->count; j++) {
AVExifEntry *exifentry = &exif->entries[j];
if (exifentry->id == PIXEL_X_TAG && exifentry->count > 0 && exifentry->type == AV_TIFF_SHORT) {
@@ -1387,15 +1413,15 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
goto end;
}
if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) {
+ AVExifMetadata *exif;
+ AVExifEntry *exif_entry;
+ int exif_found = av_exif_get_entry(logctx, &ifd, EXIFIFD_TAG, 0, &exif_entry);
rewrite = 1;
- exif = NULL;
- for (size_t i = 0; i < ifd.count; i++) {
- if (ifd.entries[i].id == EXIFIFD_TAG && ifd.entries[i].type == AV_TIFF_IFD) {
- exif = &ifd.entries[i].value.ifd;
- break;
- }
- }
- if (!exif) {
+ if (exif_found < 0)
+ goto end;
+ if (exif_found > 0) {
+ exif = &exif_entry->value.ifd;
+ } else {
AVExifMetadata exif_new = { 0 };
ret = av_exif_set_entry(logctx, &ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
if (ret < 0) {
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index 9f9f707495..b6d1ffd91d 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -144,6 +144,17 @@ int32_t av_exif_get_tag_id(const char *name);
int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value);
+/**
+ * Get an entry with the tagged ID from the EXIF metadata struct. A pointer to the entry
+ * will be written into *value. If the recursive flag is set to true, this function will check
+ * subdirectories as well.
+ *
+ * If the entry was present and returned successfully, a positive number is returned.
+ * If the entry was not found, *value is left untouched and zero is returned.
+ * If an error occurred, a negative number is returned.
+ */
+int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int recursive, AVExifEntry **value);
+
/**
* Remove an entry from the provided EXIF metadata struct. If the recursive flag is set
* to true, then this function will check subdirectories as well.
commit 303f60684f25ce63951480ef0f759acf41aac938
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 25 17:25:40 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:48 2025 -0400
avcodec/exif: add av_exif_remove_entry
Add an API function that allows popping an exif entry out of the struct
entirely rather than requiring it be replaced with a default value.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index d174d16ddc..993a931d42 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -1132,6 +1132,46 @@ int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTif
return ret;
}
+static int exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int depth)
+{
+ int32_t index = -1;
+ int ret = 0;
+
+ if (!ifd || ifd->entries && !ifd->count || ifd->count && !ifd->entries)
+ return AVERROR(EINVAL);
+
+ for (size_t i = 0; i < ifd->count; i++) {
+ if (ifd->entries[i].id == id) {
+ index = i;
+ break;
+ }
+ if (ifd->entries[i].type == AV_TIFF_IFD && depth < 3) {
+ ret = exif_remove_entry(logctx, &ifd->entries[i].value.ifd, id, depth + 1);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (index < 0)
+ return 0;
+ exif_free_entry(&ifd->entries[index]);
+
+ if (index == --ifd->count) {
+ if (!index)
+ av_freep(&ifd->entries);
+ return 1;
+ }
+
+ memmove(&ifd->entries[index], &ifd->entries[index + 1], (ifd->count - index) * sizeof(*ifd->entries));
+
+ return 1 + (ifd->count - index);
+}
+
+int av_exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int recursive)
+{
+ return exif_remove_entry(logctx, ifd, id, recursive ? 0 : INT_MAX);
+}
+
AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd)
{
AVExifMetadata *ret = av_mallocz(sizeof(*ret));
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index 23c1b37c5d..9f9f707495 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -144,6 +144,16 @@ int32_t av_exif_get_tag_id(const char *name);
int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value);
+/**
+ * Remove an entry from the provided EXIF metadata struct. If the recursive flag is set
+ * to true, then this function will check subdirectories as well.
+ *
+ * If the entry was present and removed successfully, a positive number is returned.
+ * If the entry was not found, zero is returned.
+ * If an error occurred, a negative number is returned.
+ */
+int av_exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int recursive);
+
/**
* Decodes the EXIF data provided in the buffer and writes it into the
* struct *ifd. If this function succeeds, the IFD is owned by the caller
commit 5caaadee7954f2014d9e0a17d1edfb961893bcf1
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 25 17:09:07 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/exif: add orientation ID and matrix conversion routines
Takes existing code that makes display matricies work with EXIF
orientation tags and expose the conversion as a public API.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index fa46202874..d174d16ddc 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -811,7 +811,7 @@ static int attach_displaymatrix(void *logctx, AVFrame *frame, int orientation)
int32_t *matrix;
/* invalid orientation */
if (orientation < 2 || orientation > 8)
- return 0;
+ return AVERROR_INVALIDDATA;
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");
@@ -819,37 +819,7 @@ static int attach_displaymatrix(void *logctx, AVFrame *frame, int orientation)
}
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;
+ return av_exif_orientation_to_matrix(matrix, orientation);
}
#define COLUMN_SEP(i, c) ((i) ? ((i) % (c) ? ", " : "\n") : "")
@@ -1215,31 +1185,59 @@ end:
return ret;
}
-static int exif_get_orientation(const int32_t *display_matrix)
+static const int rotation_lut[2][4] = {
+ {1, 8, 3, 6}, {4, 7, 2, 5},
+};
+
+int av_exif_matrix_to_orientation(const int32_t *matrix)
{
- double rotation = av_display_rotation_get(display_matrix);
+ double rotation = av_display_rotation_get(matrix);
// determinant
- int vflip = ((int64_t)display_matrix[0] * (int64_t)display_matrix[4]
- - (int64_t)display_matrix[1] * (int64_t)display_matrix[3]) < 0;
+ int vflip = ((int64_t)matrix[0] * (int64_t)matrix[4]
+ - (int64_t)matrix[1] * (int64_t)matrix[3]) < 0;
if (!isfinite(rotation))
- return 1;
- if (vflip) {
- if (rotation > 181.0 || rotation > -179.0 && rotation < -89.0)
- return 5;
- if (rotation > 91.0 || rotation < -179.0 && rotation > -269.0)
- return 2;
- if (rotation > 1.0 || rotation < -269.0)
- return 7;
- return 4;
- } else {
- if (rotation > 181.0 || rotation > -179.0 && rotation < -89.0)
- return 6;
- if (rotation > 91.0 || rotation < -179.0 && rotation > -269.0)
- return 3;
- if (rotation > 1.0 || rotation < -269.0)
- return 8;
- return 1;
+ return 0;
+ int rot = (int)(rotation + 0.5);
+ rot = (((rot % 360) + 360) % 360) / 90;
+ return rotation_lut[vflip][rot];
+}
+
+int av_exif_orientation_to_matrix(int32_t *matrix, int orientation)
+{
+ switch (orientation) {
+ case 1:
+ av_display_rotation_set(matrix, 0.0);
+ break;
+ 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:
+ return AVERROR(EINVAL);
}
+
+ return 0;
}
int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ptr, enum AVExifHeaderMode header_mode)
@@ -1270,7 +1268,7 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
return 0;
if (sd_orient)
- orientation = exif_get_orientation((int32_t *) sd_orient->data);
+ orientation = av_exif_matrix_to_orientation((int32_t *) sd_orient->data);
if (orientation != 1)
av_log(logctx, AV_LOG_DEBUG, "matrix contains nontrivial EXIF orientation: %" PRIu64 "\n", orientation);
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index 2661f86582..23c1b37c5d 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -184,6 +184,24 @@ int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **
*/
AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd);
+/**
+ * Convert a display matrix used by AV_FRAME_DATA_DISPLAYMATRIX
+ * into an orientation constant used by EXIF's orientation tag.
+ *
+ * Returns an EXIF orientation between 1 and 8 (inclusive) depending
+ * on the rotation and flip factors. Returns 0 if the matrix is singular.
+ */
+int av_exif_matrix_to_orientation(const int32_t *matrix);
+
+/**
+ * Convert an orientation constant used by EXIF's orientation tag
+ * into a display matrix used by AV_FRAME_DATA_DISPLAYMATRIX.
+ *
+ * Returns 0 on success and negative if the orientation is invalid,
+ * i.e. not between 1 and 8 (inclusive).
+ */
+int av_exif_orientation_to_matrix(int32_t *matrix, int orientation);
+
#if FF_API_OLD_EXIF
/* Used by the AVI demuxer */
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
commit d3190a64c366a79091fe47fddf93c09a7d988802
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 25 12:14:32 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/pngenc: support writing EXIF profiles
Add support to write EXIF profiles using the new EXIF framework, namely
ff_exif_get_buffer, and writing them into eXIf chunks.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index c48eb5daa6..fa46202874 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -1350,6 +1350,13 @@ int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_
}
if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) {
rewrite = 1;
+ exif = NULL;
+ for (size_t i = 0; i < ifd.count; i++) {
+ if (ifd.entries[i].id == EXIFIFD_TAG && ifd.entries[i].type == AV_TIFF_IFD) {
+ exif = &ifd.entries[i].value.ifd;
+ break;
+ }
+ }
if (!exif) {
AVExifMetadata exif_new = { 0 };
ret = av_exif_set_entry(logctx, &ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c
index 9bbb8267cf..635c89e87e 100644
--- a/libavcodec/pngenc.c
+++ b/libavcodec/pngenc.c
@@ -22,6 +22,7 @@
#include "avcodec.h"
#include "codec_internal.h"
#include "encode.h"
+#include "exif_internal.h"
#include "bytestream.h"
#include "lossless_videoencdsp.h"
#include "png.h"
@@ -29,6 +30,7 @@
#include "zlib_wrapper.h"
#include "libavutil/avassert.h"
+#include "libavutil/buffer.h"
#include "libavutil/crc.h"
#include "libavutil/csp.h"
#include "libavutil/libm.h"
@@ -373,6 +375,7 @@ static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
{
AVFrameSideData *side_data;
PNGEncContext *s = avctx->priv_data;
+ AVBufferRef *exif_data = NULL;
int ret;
/* write png header */
@@ -414,6 +417,19 @@ static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
}
}
+ ret = ff_exif_get_buffer(avctx, pict, &exif_data, AV_EXIF_TIFF_HEADER);
+ if (exif_data) {
+ // png_write_chunk accepts an int, not a size_t, so we have to check overflow
+ if (exif_data->size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
+ // that's a very big exif chunk, probably a bug
+ av_log(avctx, AV_LOG_ERROR, "extremely large EXIF buffer detected, not writing\n");
+ else
+ png_write_chunk(&s->bytestream, MKTAG('e','X','I','f'), exif_data->data, exif_data->size);
+ av_buffer_unref(&exif_data);
+ } else if (ret < 0) {
+ av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF metadata: %s\n", av_err2str(ret));
+ }
+
side_data = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE);
if ((ret = png_write_iccp(s, side_data)))
return ret;
diff --git a/tests/ref/fate/cover-art-mp3-id3v2-remux b/tests/ref/fate/cover-art-mp3-id3v2-remux
index 011c123a70..42fe704470 100644
--- a/tests/ref/fate/cover-art-mp3-id3v2-remux
+++ b/tests/ref/fate/cover-art-mp3-id3v2-remux
@@ -1,5 +1,5 @@
-a8dc078d174d5c4ae9769090b8404bae *tests/data/fate/cover-art-mp3-id3v2-remux.mp3
-346394 tests/data/fate/cover-art-mp3-id3v2-remux.mp3
+070ed1d95dc1bd365d68ae795966589f *tests/data/fate/cover-art-mp3-id3v2-remux.mp3
+346509 tests/data/fate/cover-art-mp3-id3v2-remux.mp3
#tb 0: 1/14112000
#media_type 0: audio
#codec_id 0: mp3
@@ -23,7 +23,7 @@ a8dc078d174d5c4ae9769090b8404bae *tests/data/fate/cover-art-mp3-id3v2-remux.mp3
0, -353590, -353590, 368640, 417, 0x15848290, S=1, Skip Samples, 10, 0x034e0055
1, 0, 0, 0, 208350, 0x291b44d1
2, 0, 0, 0, 15760, 0x71d5c418
-3, 0, 0, 0, 112719, 0x117b6853
+3, 0, 0, 0, 112834, 0x7da17554
0, 15050, 15050, 368640, 418, 0x46f684a4
0, 383690, 383690, 368640, 418, 0x46f684a4
0, 752330, 752330, 368640, 418, 0x46f684a4
diff --git a/tests/ref/fate/mov-cover-image b/tests/ref/fate/mov-cover-image
index 0607d56a0c..305a851d98 100644
--- a/tests/ref/fate/mov-cover-image
+++ b/tests/ref/fate/mov-cover-image
@@ -1,5 +1,5 @@
-9a5143f063aedb39fb1f080444cc19a0 *tests/data/fate/mov-cover-image.mp4
-1023905 tests/data/fate/mov-cover-image.mp4
+ea8251f5922d663b174855af325d689f *tests/data/fate/mov-cover-image.mp4
+1024065 tests/data/fate/mov-cover-image.mp4
#extradata 0: 2, 0x00340022
#tb 0: 1/44100
#media_type 0: audio
@@ -20,7 +20,7 @@
0, -1088, -1088, 1024, 6, 0x027e00e8, F=0x5
0, -64, -64, 1024, 6, 0x027e00e8
1, 0, 0, 0, 25441, 0xe82503b0
-2, 0, 0, 0, 44643, 0xdb75ffb5
+2, 0, 0, 0, 44803, 0x1e4a0e8f
0, 960, 960, 1024, 6, 0x027e00e8
0, 1984, 1984, 1024, 6, 0x027e00e8
0, 3008, 3008, 1024, 6, 0x027e00e8
commit 44af3829796427b89c4b76b56652cc5932ada616
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Mon Mar 17 23:04:31 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/pngdec: support reading EXIF profiles
Add support to parse eXIf chunks using the new EXIF framework, as well
as EXIF profiles in zTXt and tEXt with Raw profile type exif.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/pngdec.c b/libavcodec/pngdec.c
index b9c997ab0e..5e138a4f49 100644
--- a/libavcodec/pngdec.c
+++ b/libavcodec/pngdec.c
@@ -39,6 +39,7 @@
#include "bytestream.h"
#include "codec_internal.h"
#include "decode.h"
+#include "exif_internal.h"
#include "apng.h"
#include "png.h"
#include "pngdsp.h"
@@ -125,6 +126,8 @@ typedef struct PNGDecContext {
int pass_row_size; /* decompress row size of the current pass */
int y;
FFZStream zstream;
+
+ AVBufferRef *exif_data;
} PNGDecContext;
/* Mask to determine which pixels are valid in a pass */
@@ -540,6 +543,98 @@ static char *iso88591_to_utf8(const char *in, size_t size_in)
return out;
}
+static int decode_text_to_exif(PNGDecContext *s, const char *txt_utf8)
+{
+ size_t len = strlen(txt_utf8);
+ const char *ptr = txt_utf8;
+ const char *end = txt_utf8 + len;
+ size_t exif_len = 0;
+ uint8_t *exif_ptr;
+ const uint8_t *exif_end;
+
+ // first we find a newline
+ while (*ptr++ != '\n') {
+ if (ptr >= end)
+ return AVERROR_BUFFER_TOO_SMALL;
+ }
+
+ // we check for "exif" and skip over it
+ if (end - ptr < 4 || strncmp("exif", ptr, 4))
+ return AVERROR_INVALIDDATA;
+ ptr += 3;
+
+ // then we find the next printable non-space character
+ while (!av_isgraph(*++ptr)) {
+ if (ptr >= end)
+ return AVERROR_BUFFER_TOO_SMALL;
+ }
+
+ // parse the length
+ while (av_isdigit(*ptr)) {
+ size_t nlen = exif_len * 10 + (*ptr - '0');
+ if (nlen < exif_len) // overflow
+ return AVERROR_INVALIDDATA;
+ exif_len = nlen;
+ if (++ptr >= end)
+ return AVERROR_BUFFER_TOO_SMALL;
+ }
+
+ // then we find the next printable non-space character
+ while (!av_isgraph(*ptr)) {
+ if (++ptr >= end)
+ return AVERROR_BUFFER_TOO_SMALL;
+ }
+
+ // first condition checks for overflow in 2 * exif_len
+ if ((exif_len & ~SIZE_MAX) || end - ptr < 2 * exif_len)
+ return AVERROR_INVALIDDATA;
+ if (exif_len < 10)
+ return AVERROR_INVALIDDATA;
+
+ av_buffer_unref(&s->exif_data);
+ // the buffer starts with "Exif " which we skip over
+ // we don't use AV_EXIF_EXIF00 because that disagrees
+ // with the eXIf chunk format
+ s->exif_data = av_buffer_alloc(exif_len - 6);
+ if (!s->exif_data)
+ return AVERROR(ENOMEM);
+
+ // we subtract one because we call ++ptr later
+ // compiler will optimize out the call
+ ptr += strlen("Exif ") * 2 - 1;
+
+ exif_ptr = s->exif_data->data;
+ exif_end = exif_ptr + s->exif_data->size;
+
+ while (exif_ptr < exif_end) {
+ while (++ptr < end) {
+ if (*ptr >= '0' && *ptr <= '9') {
+ *exif_ptr = (*ptr - '0') << 4;
+ break;
+ }
+ if (*ptr >= 'a' && *ptr <= 'f') {
+ *exif_ptr = (*ptr - 'a' + 10) << 4;
+ break;
+ }
+ }
+ while (++ptr < end) {
+ if (*ptr >= '0' && *ptr <= '9') {
+ *exif_ptr += *ptr - '0';
+ break;
+ }
+ if (*ptr >= 'a' && *ptr <= 'f') {
+ *exif_ptr += *ptr - 'a' + 10;
+ break;
+ }
+ }
+ if (ptr > end)
+ return AVERROR_INVALIDDATA;
+ exif_ptr++;
+ }
+
+ return 0;
+}
+
static int decode_text_chunk(PNGDecContext *s, GetByteContext *gb, int compressed)
{
int ret, method;
@@ -582,6 +677,17 @@ static int decode_text_chunk(PNGDecContext *s, GetByteContext *gb, int compresse
return AVERROR(ENOMEM);
}
+ if (!strcmp(kw_utf8, "Raw profile type exif")) {
+ ret = decode_text_to_exif(s, txt_utf8);
+ if (ret < 0) {;
+ av_buffer_unref(&s->exif_data);
+ } else {
+ av_freep(&kw_utf8);
+ av_freep(&txt_utf8);
+ return ret;
+ }
+ }
+
av_dict_set(&s->frame_metadata, kw_utf8, txt_utf8,
AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
return 0;
@@ -654,6 +760,23 @@ static int decode_phys_chunk(AVCodecContext *avctx, PNGDecContext *s,
return 0;
}
+static int decode_exif_chunk(AVCodecContext *avctx, PNGDecContext *s,
+ GetByteContext *gb)
+{
+ if (!(s->hdr_state & PNG_IHDR)) {
+ av_log(avctx, AV_LOG_ERROR, "eXIf before IHDR\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ av_buffer_unref(&s->exif_data);
+ s->exif_data = av_buffer_alloc(bytestream2_get_bytes_left(gb));
+ if (!s->exif_data)
+ return AVERROR(ENOMEM);
+ bytestream2_get_buffer(gb, s->exif_data->data, s->exif_data->size);
+
+ return 0;
+}
+
/*
* This populates AVCodecContext fields so it must be called before
* ff_thread_finish_setup() to avoid a race condition with respect to the
@@ -1594,6 +1717,11 @@ static int decode_frame_common(AVCodecContext *avctx, PNGDecContext *s,
s->mdcv_max_lum = bytestream2_get_be32u(&gb_chunk);
s->mdcv_min_lum = bytestream2_get_be32u(&gb_chunk);
break;
+ case MKTAG('e', 'X', 'I', 'f'):
+ ret = decode_exif_chunk(avctx, s, &gb_chunk);
+ if (ret < 0)
+ goto fail;
+ break;
case MKTAG('I', 'E', 'N', 'D'):
if (!(s->pic_state & PNG_ALLIMAGE))
av_log(avctx, AV_LOG_ERROR, "IEND without all image\n");
@@ -1622,6 +1750,20 @@ exit_loop:
if (s->bits_per_pixel <= 4)
handle_small_bpp(s, p);
+ if (s->exif_data) {
+ // we swap because ff_exif_attach_buffer adds to p->metadata
+ FFSWAP(AVDictionary *, p->metadata, s->frame_metadata);
+ ret = ff_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_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) {
for (int y = 0; y < s->height; y++) {
uint8_t *row = &p->data[0][p->linesize[0] * y];
@@ -1914,6 +2056,7 @@ static av_cold int png_dec_end(AVCodecContext *avctx)
s->tmp_row_size = 0;
av_freep(&s->iccp_data);
+ av_buffer_unref(&s->exif_data);
av_dict_free(&s->frame_metadata);
ff_inflate_end(&s->zstream);
commit f5ad1c910c168d05cb01315773ab0bd094c9372f
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Mon Mar 17 11:51:33 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/exif: add ff_exif_get_buffer
Add a function to allow encoders to get a self-contained EXIF
buffer to write into an EXIF chunk.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index aa2246ae9a..c48eb5daa6 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -29,6 +29,7 @@
#include <inttypes.h>
+#include "libavutil/avconfig.h"
#include "libavutil/bprint.h"
#include "libavutil/display.h"
#include "libavutil/intreadwrite.h"
@@ -47,6 +48,11 @@
#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];
@@ -616,15 +622,19 @@ static size_t exif_get_ifd_size(const AVExifMetadata *ifd)
return total_size;
}
-static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, const AVExifMetadata *ifd)
+static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, int depth, const AVExifMetadata *ifd)
{
- int offset, ret, tell;
- tell = bytestream2_get_bytes_left_p(pb);
+ int offset, ret, tell, tell2;
+ tell = bytestream2_tell_p(pb);
tput16(pb, le, ifd->count);
offset = tell + IFD_EXTRA_SIZE + BASE_TAG_SIZE * (uint32_t) ifd->count;
av_log(logctx, AV_LOG_DEBUG, "writing IFD with %u entries and initial offset %d\n", ifd->count, offset);
for (size_t i = 0; i < ifd->count; i++) {
const AVExifEntry *entry = &ifd->entries[i];
+ av_log(logctx, AV_LOG_DEBUG, "writing TIFF entry: id: 0x%04" PRIx16 ", type: %d, count: %"
+ PRIu32 ", offset: %d, offset value: %d\n",
+ entry->id, entry->type, entry->count,
+ bytestream2_tell_p(pb), offset);
tput16(pb, le, entry->id);
if (entry->id == MAKERNOTE_TAG && entry->type == AV_TIFF_IFD) {
size_t ifd_size = exif_get_ifd_size(&entry->value.ifd);
@@ -635,29 +645,30 @@ static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, const AVExif
tput32(pb, le, entry->count);
}
if (entry->type == AV_TIFF_IFD) {
- int tell = bytestream2_tell_p(pb);
tput32(pb, le, offset);
+ tell2 = bytestream2_tell_p(pb);
bytestream2_seek_p(pb, offset, SEEK_SET);
if (entry->ifd_offset)
bytestream2_put_buffer(pb, entry->ifd_lead, entry->ifd_offset);
- ret = exif_write_ifd(logctx, pb, le, &entry->value.ifd);
+ ret = exif_write_ifd(logctx, pb, le, depth + 1, &entry->value.ifd);
if (ret < 0)
return ret;
offset += ret + entry->ifd_offset;
- bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+ bytestream2_seek_p(pb, tell2, SEEK_SET);
} else {
size_t payload_size = entry->count * exif_sizes[entry->type];
if (payload_size > 4) {
- int tell = bytestream2_tell_p(pb);
tput32(pb, le, offset);
+ tell2 = bytestream2_tell_p(pb);
bytestream2_seek_p(pb, offset, SEEK_SET);
exif_write_values(pb, le, entry);
offset += payload_size;
- bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+ bytestream2_seek_p(pb, tell2, SEEK_SET);
} else {
/* zero uninitialized excess payload values */
AV_WN32(pb->buffer, 0);
exif_write_values(pb, le, entry);
+ bytestream2_seek_p(pb, 4 - payload_size, SEEK_CUR);
}
}
}
@@ -666,7 +677,7 @@ static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, const AVExif
* we write 0 if this is the top-level exif IFD
* indicating that there are no more IFD pointers
*/
- tput32(pb, le, 0);
+ tput32(pb, le, depth ? offset : 0);
return offset - tell;
}
@@ -722,7 +733,7 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer,
tput32(&pb, le, 8);
}
- ret = exif_write_ifd(logctx, &pb, le, ifd);
+ ret = exif_write_ifd(logctx, &pb, le, 0, ifd);
if (ret < 0) {
av_buffer_unref(&buf);
av_log(logctx, AV_LOG_ERROR, "error writing EXIF data: %s\n", av_err2str(ret));
@@ -1203,3 +1214,178 @@ end:
av_exif_free(&ifd);
return ret;
}
+
+static int exif_get_orientation(const int32_t *display_matrix)
+{
+ double rotation = av_display_rotation_get(display_matrix);
+ // determinant
+ int vflip = ((int64_t)display_matrix[0] * (int64_t)display_matrix[4]
+ - (int64_t)display_matrix[1] * (int64_t)display_matrix[3]) < 0;
+ if (!isfinite(rotation))
+ return 1;
+ if (vflip) {
+ if (rotation > 181.0 || rotation > -179.0 && rotation < -89.0)
+ return 5;
+ if (rotation > 91.0 || rotation < -179.0 && rotation > -269.0)
+ return 2;
+ if (rotation > 1.0 || rotation < -269.0)
+ return 7;
+ return 4;
+ } else {
+ if (rotation > 181.0 || rotation > -179.0 && rotation < -89.0)
+ return 6;
+ if (rotation > 91.0 || rotation < -179.0 && rotation > -269.0)
+ return 3;
+ if (rotation > 1.0 || rotation < -269.0)
+ return 8;
+ return 1;
+ }
+}
+
+int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ptr, enum AVExifHeaderMode header_mode)
+{
+ int ret = 0;
+ AVBufferRef *buffer = NULL;
+ AVFrameSideData *sd_exif = NULL;
+ AVFrameSideData *sd_orient = NULL;
+ AVExifMetadata ifd = { 0 };
+ AVExifEntry *or = NULL;
+ AVExifEntry *iw = NULL;
+ AVExifEntry *ih = NULL;
+ AVExifEntry *pw = NULL;
+ AVExifEntry *ph = NULL;
+ AVExifMetadata *exif = NULL;
+ uint64_t orientation = 1;
+ uint64_t w = frame->width;
+ uint64_t h = frame->height;
+ int rewrite = 0;
+
+ if (!buffer_ptr || *buffer_ptr)
+ return AVERROR(EINVAL);
+
+ sd_exif = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF);
+ sd_orient = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX);
+
+ if (!sd_exif && !sd_orient)
+ return 0;
+
+ if (sd_orient)
+ orientation = exif_get_orientation((int32_t *) sd_orient->data);
+ if (orientation != 1)
+ av_log(logctx, AV_LOG_DEBUG, "matrix contains nontrivial EXIF orientation: %" PRIu64 "\n", orientation);
+
+ if (sd_exif) {
+ ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ goto end;
+ }
+
+ for (size_t i = 0; i < ifd.count; i++) {
+ AVExifEntry *entry = &ifd.entries[i];
+ if (entry->id == ORIENTATION_TAG && entry->count > 0 && entry->type == AV_TIFF_SHORT) {
+ or = entry;
+ continue;
+ }
+ if (entry->id == IMAGE_WIDTH_TAG && entry->count > 0 && entry->type == AV_TIFF_LONG) {
+ iw = entry;
+ continue;
+ }
+ if (entry->id == IMAGE_LENGTH_TAG && entry->count > 0 && entry->type == AV_TIFF_LONG) {
+ ih = entry;
+ continue;
+ }
+ if (entry->id == EXIFIFD_TAG && entry->type == AV_TIFF_IFD) {
+ exif = &entry->value.ifd;
+ for (size_t j = 0; j < exif->count; j++) {
+ AVExifEntry *exifentry = &exif->entries[j];
+ if (exifentry->id == PIXEL_X_TAG && exifentry->count > 0 && exifentry->type == AV_TIFF_SHORT) {
+ pw = exifentry;
+ continue;
+ }
+ if (exifentry->id == PIXEL_Y_TAG && exifentry->count > 0 && exifentry->type == AV_TIFF_SHORT) {
+ ph = exifentry;
+ continue;
+ }
+ }
+ }
+ }
+
+ if (or && or->value.uint[0] != orientation) {
+ rewrite = 1;
+ or->value.uint[0] = orientation;
+ }
+ if (iw && iw->value.uint[0] != w) {
+ rewrite = 1;
+ iw->value.uint[0] = w;
+ }
+ if (ih && ih->value.uint[0] != h) {
+ rewrite = 1;
+ ih->value.uint[0] = h;
+ }
+ if (pw && pw->value.uint[0] != w) {
+ rewrite = 1;
+ pw->value.uint[0] = w;
+ }
+ if (ph && ph->value.uint[0] != h) {
+ rewrite = 1;
+ ph->value.uint[0] = h;
+ }
+ if (!or && orientation != 1) {
+ rewrite = 1;
+ ret = av_exif_set_entry(logctx, &ifd, ORIENTATION_TAG, AV_TIFF_SHORT, 1, NULL, 0, &orientation);
+ if (ret < 0)
+ goto end;
+ }
+ if (!iw && w) {
+ rewrite = 1;
+ ret = av_exif_set_entry(logctx, &ifd, IMAGE_WIDTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &w);
+ if (ret < 0)
+ goto end;
+ }
+ if (!ih && h) {
+ rewrite = 1;
+ ret = av_exif_set_entry(logctx, &ifd, IMAGE_LENGTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &h);
+ if (ret < 0)
+ goto end;
+ }
+ if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) {
+ rewrite = 1;
+ if (!exif) {
+ AVExifMetadata exif_new = { 0 };
+ ret = av_exif_set_entry(logctx, &ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
+ if (ret < 0) {
+ av_exif_free(&exif_new);
+ goto end;
+ }
+ exif = &ifd.entries[ifd.count - 1].value.ifd;
+ }
+ if (!pw && w && w < 0xFFFFu) {
+ ret = av_exif_set_entry(logctx, exif, PIXEL_X_TAG, AV_TIFF_SHORT, 1, NULL, 0, &w);
+ if (ret < 0)
+ goto end;
+ }
+ if (!ph && h && h < 0xFFFFu) {
+ ret = av_exif_set_entry(logctx, exif, PIXEL_Y_TAG, AV_TIFF_SHORT, 1, NULL, 0, &h);
+ if (ret < 0)
+ goto end;
+ }
+ }
+
+ if (rewrite) {
+ ret = av_exif_write(logctx, &ifd, &buffer, header_mode);
+ if (ret < 0)
+ goto end;
+
+ *buffer_ptr = buffer;
+ } else {
+ *buffer_ptr = av_buffer_ref(sd_exif->buf);
+ if (!*buffer_ptr) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ }
+
+end:
+ av_exif_free(&ifd);
+ return ret;
+}
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index 88e5701daa..4fd147fb99 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -43,7 +43,7 @@
* 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 transfered to the frame directly. On failure,
+ * unrefed, or its ownership is transferred to the frame directly. On failure,
* the data buffer is left owned by the caller.
*/
int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum AVExifHeaderMode header_mode);
@@ -59,4 +59,18 @@ int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum
*/
int ff_exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd);
+/**
+ * Gets all relevant side data, collects it into an IFD, and writes it into the
+ * corresponding buffer pointer. This includes both AV_FRAME_DATA_EXIF and other
+ * side data types that are included in the frame data, such as possibly an
+ * instance of AV_FRAME_DATA_DISPLAYMATRIX. It also sets width and height tags
+ * to match those of the AVFrame if they are different.
+ *
+ * Upon error, *buffer will be NULL. The buffer becomes owned by the caller upon
+ * success. The *buffer argument must be NULL before calling. If *buffer is NULL
+ * upon return then a negative return value indicates an error, and a zero return
+ * value indicates that there was no EXIF data to write.
+ */
+int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer, enum AVExifHeaderMode header_mode);
+
#endif /* AVCODEC_EXIF_INTERNAL_H */
commit e3aa1154aab2656c91ce61915f79516d9b563b61
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Mar 12 12:21:21 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/exif: remove ff_exif_decode_ifd
This function is no longer called and its functionality
can be accessed using the new API in exif.h as well as the
various internal attach functions in exif_internal.h.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 66afe11908..aa2246ae9a 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -936,27 +936,21 @@ int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **
return exif_ifd_to_dict(logctx, "", ifd, metadata);
}
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gb, int le, int depth, AVDictionary **metadata)
+#if FF_API_OLD_EXIF
+int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
+ int le, int depth, AVDictionary **metadata)
{
AVExifMetadata ifd = { 0 };
- int ret = exif_parse_ifd_list(logctx, gb, le, depth, &ifd);
+ GetByteContext gb;
+ int ret;
+ bytestream2_init(&gb, buf, size);
+ ret = exif_parse_ifd_list(logctx, &gb, le, depth, &ifd);
if (ret < 0)
return ret;
-
ret = av_exif_ifd_to_dict(logctx, &ifd, metadata);
av_exif_free(&ifd);
-
return ret;
}
-
-#if FF_API_OLD_EXIF
-int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
- int le, int depth, AVDictionary **metadata)
-{
- GetByteContext gb;
- bytestream2_init(&gb, buf, size);
- return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata);
-}
#endif /* FF_API_OLD_EXIF */
static int exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef *og)
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index bd0bd22015..88e5701daa 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -31,10 +31,8 @@
#define AVCODEC_EXIF_INTERNAL_H
#include "libavutil/buffer.h"
-#include "libavutil/dict.h"
#include "libavutil/frame.h"
-#include "bytestream.h"
#include "exif.h"
/**
@@ -61,8 +59,4 @@ int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum
*/
int ff_exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd);
-/* Used by mjpeg decoder */
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gb,
- int le, int depth, AVDictionary **metadata);
-
#endif /* AVCODEC_EXIF_INTERNAL_H */
commit c6cc2115f45ac26ae42442f8796996eb410f4028
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Mar 12 11:27:13 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/webp: use new EXIF parse API
Switch over to the new API to parse EXIF metadata.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index 489cf6f016..56c3ec6d28 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -1456,38 +1456,34 @@ FF_ENABLE_DEPRECATION_WARNINGS
break;
}
case MKTAG('E', 'X', 'I', 'F'): {
- int le, ifd_offset, exif_offset = bytestream2_tell(&gb);
- AVDictionary *exif_metadata = NULL;
- GetByteContext exif_gb;
+ AVBufferRef *exif_buf = NULL;
if (s->has_exif) {
av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra EXIF chunk\n");
goto exif_end;
}
+
if (!(vp8x_flags & VP8X_FLAG_EXIF_METADATA))
av_log(avctx, AV_LOG_WARNING,
"EXIF chunk present, but Exif bit not set in the "
"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");
+ exif_buf = av_buffer_alloc(chunk_size);
+ if (!exif_buf) {
+ av_log(avctx, AV_LOG_WARNING, "unable to allocate EXIF buffer\n");
goto exif_end;
}
+ s->has_exif = 1;
+ memcpy(exif_buf->data, gb.buffer, chunk_size);
- 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;
+ /* if this succeeds then exif_buf is either freed or transferred to the AVFrame */
+ ret = ff_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);
}
- av_dict_copy(&p->metadata, exif_metadata, 0);
-
exif_end:
- av_dict_free(&exif_metadata);
bytestream2_skip(&gb, chunk_size);
break;
}
diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp
index 3a0e6e2c24..7c01ae3d66 100644
--- a/tests/ref/fate/exif-image-webp
+++ b/tests/ref/fate/exif-image-webp
@@ -197,4 +197,7 @@ TAG:ExifIFD/ExposureMode= 0
TAG:ExifIFD/WhiteBalance= 0
TAG:ExifIFD/DigitalZoomRatio= 4000:4000
TAG:ExifIFD/SceneCaptureType= 0
+[SIDE_DATA]
+side_data_type=EXIF metadata
+[/SIDE_DATA]
[/FRAME]
commit 52dba25661305e3c4a6209d46aea43cd327c960e
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Mar 12 09:54:09 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/mjpegdec: use new EXIF parse API
Switch over to the new API to parse EXIF metadata.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index 983a30cb59..46ec3eb938 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -55,7 +55,6 @@
#include "put_bits.h"
#include "exif_internal.h"
#include "bytestream.h"
-#include "tiff_common.h"
static int init_default_huffman_tables(MJpegDecodeContext *s)
@@ -2043,8 +2042,7 @@ 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;
+ int ret;
const uint8_t *aligned;
skip_bits(&s->gb, 16); // skip padding
@@ -2052,26 +2050,15 @@ 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");
- }
+ ret = av_exif_parse_buffer(s->avctx, aligned, len, &s->exif_metadata, AV_EXIF_TIFF_HEADER);
+ if (ret < 0) {
+ av_log(s->avctx, AV_LOG_WARNING, "unable to parse EXIF buffer\n");
+ goto out;
}
- bytes_read = bytestream2_tell(&gbytes);
- skip_bits(&s->gb, bytes_read << 3);
- len -= bytes_read;
+ skip_bits(&s->gb, ret << 3);
+ len -= ret;
goto out;
}
@@ -2384,13 +2371,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_exif_free(&s->exif_metadata);
av_freep(&s->stereo3d);
s->adobe_transform = -1;
@@ -2868,60 +2854,13 @@ 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_metadata.entries) {
+ ret = ff_exif_attach_ifd(avctx, frame, &s->exif_metadata);
+ av_exif_free(&s->exif_metadata);
+ if (ret < 0)
+ av_log(avctx, AV_LOG_WARNING, "couldn't attach EXIF metadata\n");
}
- 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')) &&
@@ -2973,7 +2912,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_exif_free(&s->exif_metadata);
reset_icc_profile(s);
diff --git a/libavcodec/mjpegdec.h b/libavcodec/mjpegdec.h
index 13c524d597..8b9ed67856 100644
--- a/libavcodec/mjpegdec.h
+++ b/libavcodec/mjpegdec.h
@@ -36,6 +36,7 @@
#include "avcodec.h"
#include "blockdsp.h"
+#include "exif.h"
#include "get_bits.h"
#include "hpeldsp.h"
#include "idctdsp.h"
@@ -138,7 +139,7 @@ typedef struct MJpegDecodeContext {
unsigned int ljpeg_buffer_size;
int extern_huff;
- AVDictionary *exif_metadata;
+ AVExifMetadata exif_metadata;
AVStereo3D *stereo3d; ///!< stereoscopic information (cached, since it is read before frame allocation)
diff --git a/tests/ref/fate/exif-image-embedded b/tests/ref/fate/exif-image-embedded
index 574a3af848..a6617bd89f 100644
--- a/tests/ref/fate/exif-image-embedded
+++ b/tests/ref/fate/exif-image-embedded
@@ -32,6 +32,9 @@ color_transfer=unknown
chroma_location=center
TAG:ExifIFD/UserComment=AppleMark
+[SIDE_DATA]
+side_data_type=EXIF metadata
+[/SIDE_DATA]
[/FRAME]
[FRAME]
media_type=audio
diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg
index 5a8cd10063..5135ae32f8 100644
--- a/tests/ref/fate/exif-image-jpg
+++ b/tests/ref/fate/exif-image-jpg
@@ -197,4 +197,7 @@ TAG:ExifIFD/ExposureMode= 0
TAG:ExifIFD/WhiteBalance= 0
TAG:ExifIFD/DigitalZoomRatio= 4000:4000
TAG:ExifIFD/SceneCaptureType= 0
+[SIDE_DATA]
+side_data_type=EXIF metadata
+[/SIDE_DATA]
[/FRAME]
commit bfb17d26306592c85cf0c4e909099c621177b062
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 11 12:22:50 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:47 2025 -0400
avcodec/exif: add deprecation guards for old avpriv_
Add FF_API_OLD_EXIF deprecation guard based on lavc version < 62 in
order to allow removing the avpriv_ next major version bump.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index fd8c1086f4..66afe11908 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -949,6 +949,7 @@ int ff_exif_decode_ifd(void *logctx, GetByteContext *gb, int le, int depth, AVDi
return ret;
}
+#if FF_API_OLD_EXIF
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata)
{
@@ -956,6 +957,7 @@ int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
bytestream2_init(&gb, buf, size);
return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata);
}
+#endif /* FF_API_OLD_EXIF */
static int exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef *og)
{
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index f76ab1e125..2661f86582 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -36,6 +36,7 @@
#include "libavutil/buffer.h"
#include "libavutil/dict.h"
#include "libavutil/rational.h"
+#include "version_major.h"
/** Data type identifiers for TIFF tags */
enum AVTiffDataType {
@@ -183,8 +184,10 @@ int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **
*/
AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd);
+#if FF_API_OLD_EXIF
/* Used by the AVI demuxer */
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata);
+#endif /* FF_API_OLD_EXIF */
#endif /* AVCODEC_EXIF_H */
diff --git a/libavcodec/version_major.h b/libavcodec/version_major.h
index 97e4e12065..6ffe82e2e7 100644
--- a/libavcodec/version_major.h
+++ b/libavcodec/version_major.h
@@ -42,6 +42,7 @@
#define FF_API_V408_CODECID (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_CODEC_PROPS (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_EXR_GAMMA (LIBAVCODEC_VERSION_MAJOR < 63)
+#define FF_API_OLD_EXIF (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_NVDEC_OLD_PIX_FMTS (LIBAVCODEC_VERSION_MAJOR < 63)
commit ba2ea285e0f270a0a885b414cafaace6a89b9a91
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 11 12:15:14 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:46 2025 -0400
avformat/avidec: use new EXIF metadata API instead of avpriv_
This commit switches avidec to use the new metadata API exposed by
the previous commit instead of relying on the existing avipriv_ function
exposed by lavc.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavformat/avidec.c b/libavformat/avidec.c
index 81a0ae31ba..8fb6931e5d 100644
--- a/libavformat/avidec.c
+++ b/libavformat/avidec.c
@@ -427,15 +427,21 @@ static int avi_extract_stream_metadata(AVFormatContext *s, AVStream *st)
tag = bytestream2_get_le32(&gb);
switch (tag) {
- case MKTAG('A', 'V', 'I', 'F'):
+ case MKTAG('A', 'V', 'I', 'F'): {
+ AVExifMetadata ifd = { 0 };
+ int ret;
// skip 4 byte padding
bytestream2_skip(&gb, 4);
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);
- break;
+ ret = av_exif_parse_buffer(s, data + offset, data_size - offset, &ifd, AV_EXIF_ASSUME_LE);
+ if (ret < 0)
+ return ret;
+ ret = av_exif_ifd_to_dict(s, &ifd, &st->metadata);
+ av_exif_free(&ifd);
+ return ret;
+ }
case MKTAG('C', 'A', 'S', 'I'):
avpriv_request_sample(s, "RIFF stream data tag type CASI (%u)", tag);
break;
commit ad77345a5d14862f4701e5ad422b03b14934a5b9
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Thu Mar 6 17:53:41 2025 -0500
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:46 2025 -0400
avcodec/exif: add EXIF parser and struct API
This commit adds a structure to contain parsed EXIF metadata, as well
as code to read and write that struct from/to binary EXIF buffers. Some
internal functions have been moved to exif_internal.h. Code to read
from this new struct and write to an AVDictionary **dict has been added
as well in order to preserve interoperability with existing callers.
The only codec changes so far as of this commit are to call these
interop functions, but in future commits there will be codec changes to
use the new parsing routines instead.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/Changelog b/Changelog
index 98b259f17f..838114934c 100644
--- a/Changelog
+++ b/Changelog
@@ -38,6 +38,7 @@ version 8.0:
- ProRes RAW decoder
- ProRes RAW Vulkan hwaccel
- ffprobe -codec option
+- EXIF Metadata Parsing
version 7.1:
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index fb22541f8d..35408949ac 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -15,6 +15,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 f6f8918586..fd8c1086f4 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-2025 Leo Izen <leo.izen at gmail.com>
*
* This file is part of FFmpeg.
*
@@ -23,16 +24,33 @@
* @file
* EXIF metadata parser
* @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen at gmail.com>
*/
-#include "exif.h"
+#include <inttypes.h>
+
+#include "libavutil/bprint.h"
+#include "libavutil/display.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+
+#include "bytestream.h"
+#include "exif_internal.h"
#include "tiff_common.h"
+#define EXIF_II_LONG 0x49492a00
+#define EXIF_MM_LONG 0x4d4d002a
+
+#define BASE_TAG_SIZE 12
+#define IFD_EXTRA_SIZE 6
+
#define EXIF_TAG_NAME_LENGTH 32
+#define MAKERNOTE_TAG 0x927c
+#define ORIENTATION_TAG 0x112
struct exif_tag {
- char name[EXIF_TAG_NAME_LENGTH];
- uint16_t id;
+ const char name[EXIF_TAG_NAME_LENGTH];
+ uint16_t id;
};
static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification:
@@ -152,15 +170,45 @@ static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification:
{"Saturation", 0xA409},
{"Sharpness", 0xA40A},
{"DeviceSettingDescription", 0xA40B},
- {"SubjectDistanceRange", 0xA40C}
-// {"InteroperabilityIndex", 0x1}, // <- Table 13 Interoperability IFD Attribute Information
+ {"SubjectDistanceRange", 0xA40C},
+
+ /* InteropIFD tags */
+ {"RelatedImageFileFormat", 0x1000},
+ {"RelatedImageWidth", 0x1001},
+ {"RelatedImageLength", 0x1002},
+
+ /* private EXIF tags */
+ {"PrintImageMatching", 0xC4A5}, // <- undocumented meaning
+
+ /* IFD tags */
+ {"ExifIFD", 0x8769}, // <- An IFD pointing to standard Exif metadata
+ {"GPSInfo", 0x8825}, // <- An IFD pointing to GPS Exif Metadata
+ {"InteropIFD", 0xA005}, // <- Table 13 Interoperability IFD Attribute Information
+ {"GlobalParametersIFD", 0x0190},
+ {"ProfileIFD", 0xc6f5},
};
-static const char *exif_get_tag_name(uint16_t id)
-{
- int i;
+/* same as type_sizes but with string == 1 */
+static const size_t exif_sizes[] = {
+ [0] = 0,
+ [AV_TIFF_BYTE] = 1,
+ [AV_TIFF_STRING] = 1,
+ [AV_TIFF_SHORT] = 2,
+ [AV_TIFF_LONG] = 4,
+ [AV_TIFF_RATIONAL] = 8,
+ [AV_TIFF_SBYTE] = 1,
+ [AV_TIFF_UNDEFINED] = 1,
+ [AV_TIFF_SSHORT] = 2,
+ [AV_TIFF_SLONG] = 4,
+ [AV_TIFF_SRATIONAL] = 8,
+ [AV_TIFF_FLOAT] = 4,
+ [AV_TIFF_DOUBLE] = 8,
+ [AV_TIFF_IFD] = 4,
+};
- for (i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
+const char *av_exif_get_tag_name(uint16_t id)
+{
+ for (size_t i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
if (tag_list[i].id == id)
return tag_list[i].name;
}
@@ -168,106 +216,994 @@ static const char *exif_get_tag_name(uint16_t id)
return NULL;
}
+int32_t av_exif_get_tag_id(const char *name)
+{
+ if (!name)
+ return -1;
-static int exif_add_metadata(void *logctx, int count, int type,
- const char *name, const char *sep,
- GetByteContext *gb, int le,
- AVDictionary **metadata)
+ for (size_t i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
+ if (!strcmp(tag_list[i].name, name))
+ return tag_list[i].id;
+ }
+
+ return -1;
+}
+
+static inline void tput16(PutByteContext *pb, const int le, const uint16_t value)
{
- 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 AV_TIFF_DOUBLE : return ff_tadd_doubles_metadata(count, name, sep, gb, le, metadata);
- case AV_TIFF_SSHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 1, metadata);
- case AV_TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 0, metadata);
- case AV_TIFF_SBYTE : return ff_tadd_bytes_metadata(count, name, sep, gb, le, 1, metadata);
- case AV_TIFF_BYTE :
- case AV_TIFF_UNDEFINED: return ff_tadd_bytes_metadata(count, name, sep, gb, le, 0, metadata);
- case AV_TIFF_STRING : return ff_tadd_string_metadata(count, name, gb, le, metadata);
- case AV_TIFF_SRATIONAL:
- case AV_TIFF_RATIONAL : return ff_tadd_rational_metadata(count, name, sep, gb, le, metadata);
- case AV_TIFF_SLONG :
- case AV_TIFF_LONG : return ff_tadd_long_metadata(count, name, sep, gb, le, metadata);
- default:
- avpriv_request_sample(logctx, "TIFF tag type (%u)", type);
- return 0;
- };
+ 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);
+}
-static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
- int depth, AVDictionary **metadata)
+static inline void tput64(PutByteContext *pb, const int le, const uint64_t value)
{
- int ret, cur_pos;
- unsigned id, count;
- enum AVTiffDataType type;
+ le ? bytestream2_put_le64(pb, value) : bytestream2_put_be64(pb, value);
+}
- if (depth > 2) {
- return 0;
+static int exif_read_values(void *logctx, GetByteContext *gb, int le, AVExifEntry *entry)
+{
+ switch (entry->type) {
+ case AV_TIFF_SHORT:
+ case AV_TIFF_LONG:
+ entry->value.uint = av_calloc(entry->count, sizeof(*entry->value.uint));
+ break;
+ case AV_TIFF_SSHORT:
+ case AV_TIFF_SLONG:
+ entry->value.sint = av_calloc(entry->count, sizeof(*entry->value.sint));
+ break;
+ case AV_TIFF_DOUBLE:
+ case AV_TIFF_FLOAT:
+ entry->value.dbl = av_calloc(entry->count, sizeof(*entry->value.dbl));
+ break;
+ case AV_TIFF_RATIONAL:
+ case AV_TIFF_SRATIONAL:
+ entry->value.rat = av_calloc(entry->count, sizeof(*entry->value.rat));
+ break;
+ case AV_TIFF_UNDEFINED:
+ case AV_TIFF_BYTE:
+ entry->value.ubytes = av_mallocz(entry->count);
+ break;
+ case AV_TIFF_SBYTE:
+ entry->value.sbytes = av_mallocz(entry->count);
+ break;
+ case AV_TIFF_STRING:
+ entry->value.str = av_mallocz(entry->count + 1);
+ break;
+ case AV_TIFF_IFD:
+ av_log(logctx, AV_LOG_WARNING, "Bad IFD type for non-IFD tag\n");
+ return AVERROR_INVALIDDATA;
+ }
+ if (!entry->value.ptr)
+ return AVERROR(ENOMEM);
+ switch (entry->type) {
+ case AV_TIFF_SHORT:
+ for (size_t i = 0; i < entry->count; i++)
+ entry->value.uint[i] = ff_tget_short(gb, le);
+ break;
+ case AV_TIFF_LONG:
+ for (size_t i = 0; i < entry->count; i++)
+ entry->value.uint[i] = ff_tget_long(gb, le);
+ break;
+ case AV_TIFF_SSHORT:
+ for (size_t i = 0; i < entry->count; i++)
+ entry->value.sint[i] = (int16_t) ff_tget_short(gb, le);
+ break;
+ case AV_TIFF_SLONG:
+ for (size_t i = 0; i < entry->count; i++)
+ entry->value.sint[i] = (int32_t) ff_tget_long(gb, le);
+ break;
+ case AV_TIFF_DOUBLE:
+ for (size_t i = 0; i < entry->count; i++)
+ entry->value.dbl[i] = ff_tget_double(gb, le);
+ break;
+ case AV_TIFF_FLOAT:
+ for (size_t i = 0; i < entry->count; i++) {
+ av_alias32 alias = { .u32 = ff_tget_long(gb, le) };
+ entry->value.dbl[i] = alias.f32;
+ }
+ break;
+ case AV_TIFF_RATIONAL:
+ case AV_TIFF_SRATIONAL:
+ for (size_t i = 0; i < entry->count; i++) {
+ int32_t num = ff_tget_long(gb, le);
+ int32_t den = ff_tget_long(gb, le);
+ entry->value.rat[i] = av_make_q(num, den);
+ }
+ break;
+ case AV_TIFF_UNDEFINED:
+ case AV_TIFF_BYTE:
+ bytestream2_get_buffer(gb, entry->value.ubytes, entry->count);
+ break;
+ case AV_TIFF_SBYTE:
+ bytestream2_get_buffer(gb, entry->value.sbytes, entry->count);
+ break;
+ case AV_TIFF_STRING:
+ bytestream2_get_buffer(gb, entry->value.str, entry->count);
+ break;
}
- ff_tread_tag(gbytes, le, &id, &type, &count, &cur_pos);
+ return 0;
+}
- if (!bytestream2_tell(gbytes)) {
- bytestream2_seek(gbytes, cur_pos, SEEK_SET);
- return 0;
+static void exif_write_values(PutByteContext *pb, int le, const AVExifEntry *entry)
+{
+ switch (entry->type) {
+ case AV_TIFF_SHORT:
+ for (size_t i = 0; i < entry->count; i++)
+ tput16(pb, le, entry->value.uint[i]);
+ break;
+ case AV_TIFF_LONG:
+ for (size_t i = 0; i < entry->count; i++)
+ tput32(pb, le, entry->value.uint[i]);
+ break;
+ case AV_TIFF_SSHORT:
+ for (size_t i = 0; i < entry->count; i++)
+ tput16(pb, le, entry->value.sint[i]);
+ break;
+ case AV_TIFF_SLONG:
+ for (size_t i = 0; i < entry->count; i++)
+ tput32(pb, le, entry->value.sint[i]);
+ break;
+ case AV_TIFF_DOUBLE:
+ for (size_t i = 0; i < entry->count; i++) {
+ const av_alias64 a = { .f64 = entry->value.dbl[i] };
+ tput64(pb, le, a.u64);
+ }
+ break;
+ case AV_TIFF_FLOAT:
+ for (size_t i = 0; i < entry->count; i++) {
+ const av_alias32 a = { .f32 = entry->value.dbl[i] };
+ tput32(pb, le, a.u32);
+ }
+ break;
+ case AV_TIFF_RATIONAL:
+ case AV_TIFF_SRATIONAL:
+ for (size_t i = 0; i < entry->count; i++) {
+ tput32(pb, le, entry->value.rat[i].num);
+ tput32(pb, le, entry->value.rat[i].den);
+ }
+ break;
+ case AV_TIFF_UNDEFINED:
+ case AV_TIFF_BYTE:
+ bytestream2_put_buffer(pb, entry->value.ubytes, entry->count);
+ break;
+ case AV_TIFF_SBYTE:
+ bytestream2_put_buffer(pb, entry->value.sbytes, entry->count);
+ break;
+ case AV_TIFF_STRING:
+ bytestream2_put_buffer(pb, entry->value.str, entry->count);
+ break;
}
+}
- // read count values and add it metadata
- // 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);
- } else {
- const char *name = exif_get_tag_name(id);
- char buf[7];
+static const uint8_t aoc_header[] = { 'A', 'O', 'C', 0, };
+static const uint8_t casio_header[] = { 'Q', 'V', 'C', 0, 0, 0, };
+static const uint8_t foveon_header[] = { 'F', 'O', 'V', 'E', 'O', 'N', 0, 0, };
+static const uint8_t fuji_header[] = { 'F', 'U', 'J', 'I', };
+static const uint8_t nikon_header[] = { 'N', 'i', 'k', 'o', 'n', 0, };
+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 panasonic_header[] = { 'P', 'a', 'n', 'a', 's', 'o', 'n', 'i', 'c', 0, 0, 0, };
+static const uint8_t sigma_header[] = { 'S', 'I', 'G', 'M', 'A', 0, 0, 0, };
+static const uint8_t sony_header[] = { 'S', 'O', 'N', 'Y', ' ', 'D', 'S', 'C', ' ', 0, 0, 0, };
+
+struct exif_makernote_data {
+ const uint8_t *header;
+ size_t header_size;
+ int result;
+};
+
+#define MAKERNOTE_STRUCT(h, r) { \
+ .header = (h), \
+ .header_size = sizeof((h)), \
+ .result = (r), \
+}
+
+static const struct exif_makernote_data makernote_data[] = {
+ MAKERNOTE_STRUCT(aoc_header, 6),
+ MAKERNOTE_STRUCT(casio_header, -1),
+ MAKERNOTE_STRUCT(foveon_header, 10),
+ MAKERNOTE_STRUCT(fuji_header, -1),
+ MAKERNOTE_STRUCT(olympus1_header, 8),
+ MAKERNOTE_STRUCT(olympus2_header, -1),
+ MAKERNOTE_STRUCT(panasonic_header, 12),
+ MAKERNOTE_STRUCT(sigma_header, 10),
+ MAKERNOTE_STRUCT(sony_header, 12),
+};
+
+/*
+ * derived from Exiv2 MakerNote's article
+ * https://exiv2.org/makernote.html or archived at
+ * https://web.archive.org/web/20250311155857/https://exiv2.org/makernote.html
+ */
+static int exif_get_makernote_offset(GetByteContext *gb)
+{
+ if (bytestream2_get_bytes_left(gb) < BASE_TAG_SIZE)
+ return -1;
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(makernote_data); i++) {
+ if (!memcmp(gb->buffer, makernote_data[i].header, makernote_data[i].header_size))
+ return makernote_data[i].result;
+ }
+
+ 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) == EXIF_MM_LONG || AV_RB32(gb->buffer + 10) == EXIF_II_LONG)
+ return -1;
+ return 8;
+ }
+
+ return 0;
+}
+
+static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
+ int depth, AVExifMetadata *ifd);
+
+static int exif_decode_tag(void *logctx, GetByteContext *gb, int le,
+ int depth, AVExifEntry *entry)
+{
+ int ret = 0, makernote_offset = -1, tell, is_ifd, count;
+ enum AVTiffDataType type;
+ uint32_t payload;
+
+ /* safety check to prevent infinite recursion on malicious IFDs */
+ if (depth > 3)
+ return AVERROR_INVALIDDATA;
+
+ tell = bytestream2_tell(gb);
+
+ entry->id = ff_tget_short(gb, le);
+ type = ff_tget_short(gb, le);
+ count = ff_tget_long(gb, le);
+ payload = ff_tget_long(gb, le);
- if (!name) {
- name = buf;
- snprintf(buf, sizeof(buf), "0x%04X", id);
+ av_log(logctx, AV_LOG_DEBUG, "TIFF Tag: id: 0x%04x, type: %d, count: %u, offset: %d, "
+ "payload: %" PRIu32 "\n", entry->id, type, count, tell, payload);
+
+ is_ifd = type == AV_TIFF_IFD || ff_tis_ifd(entry->id) || entry->id == MAKERNOTE_TAG;
+
+ if (is_ifd) {
+ if (!payload)
+ goto end;
+ bytestream2_seek(gb, payload, SEEK_SET);
+ }
+
+ if (entry->id == MAKERNOTE_TAG) {
+ makernote_offset = exif_get_makernote_offset(gb);
+ if (makernote_offset < 0)
+ is_ifd = 0;
+ }
+
+ if (is_ifd) {
+ entry->type = AV_TIFF_IFD;
+ entry->count = 1;
+ entry->ifd_offset = makernote_offset > 0 ? makernote_offset : 0;
+ if (entry->ifd_offset) {
+ entry->ifd_lead = av_malloc(entry->ifd_offset);
+ if (!entry->ifd_lead)
+ return AVERROR(ENOMEM);
+ bytestream2_get_buffer(gb, entry->ifd_lead, entry->ifd_offset);
+ }
+ ret = exif_parse_ifd_list(logctx, gb, le, depth + 1, &entry->value.ifd);
+ if (ret < 0 && entry->id == MAKERNOTE_TAG) {
+ /*
+ * we guessed that MakerNote was an IFD
+ * but we were probably incorrect at this
+ * point so we try again as a binary blob
+ */
+ av_exif_free(&entry->value.ifd);
+ av_log(logctx, AV_LOG_DEBUG, "unrecognized MakerNote IFD, retrying as blob\n");
+ is_ifd = 0;
}
+ }
- ret = exif_add_metadata(logctx, count, type, name, NULL,
- gbytes, le, metadata);
+ /* inverted condition instead of else so we can fall through from above */
+ if (!is_ifd) {
+ entry->type = type == AV_TIFF_IFD ? AV_TIFF_UNDEFINED : type;
+ entry->count = count;
+ bytestream2_seek(gb, count * exif_sizes[type] > 4 ? payload : tell + 8, SEEK_SET);
+ ret = exif_read_values(logctx, gb, le, entry);
}
- bytestream2_seek(gbytes, cur_pos, SEEK_SET);
+end:
+ bytestream2_seek(gb, tell + BASE_TAG_SIZE, SEEK_SET);
return ret;
}
-
-int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes,
- int le, int depth, AVDictionary **metadata)
+static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
+ int depth, AVExifMetadata *ifd)
{
- int i, ret;
- int entries;
+ uint32_t entries;
+ size_t required_size;
+ void *temp;
- entries = ff_tget_short(gbytes, le);
+ av_log(logctx, AV_LOG_DEBUG, "parsing IFD list at offset: %d\n", bytestream2_tell(gb));
- if (bytestream2_get_bytes_left(gbytes) < entries * 12) {
+ if (bytestream2_get_bytes_left(gb) < 2) {
+ av_log(logctx, AV_LOG_ERROR, "not enough bytes remaining in EXIF buffer: 2 required\n");
return AVERROR_INVALIDDATA;
}
- for (i = 0; i < entries; i++) {
- if ((ret = exif_decode_tag(logctx, gbytes, le, depth, metadata)) < 0) {
+ entries = ff_tget_short(gb, le);
+ if (bytestream2_get_bytes_left(gb) < entries * BASE_TAG_SIZE) {
+ av_log(logctx, AV_LOG_ERROR, "not enough bytes remaining in EXIF buffer. entries: %" PRIu32 "\n", entries);
+ return AVERROR_INVALIDDATA;
+ }
+
+ ifd->count = entries;
+ av_log(logctx, AV_LOG_DEBUG, "entry count for IFD: %u\n", ifd->count);
+
+ if (av_size_mult(ifd->count, sizeof(*ifd->entries), &required_size) < 0)
+ return AVERROR(ENOMEM);
+ temp = av_fast_realloc(ifd->entries, &ifd->size, required_size);
+ if (!temp) {
+ av_freep(&ifd->entries);
+ return AVERROR(ENOMEM);
+ }
+ ifd->entries = temp;
+
+ /* entries have pointers in them which can cause issues if */
+ /* they are freed or realloc'd when garbage */
+ memset(ifd->entries, 0, required_size);
+
+ for (uint32_t i = 0; i < entries; i++) {
+ int ret = exif_decode_tag(logctx, gb, le, depth, &ifd->entries[i]);
+ if (ret < 0)
return ret;
+ }
+
+ /*
+ * at the end of an IFD is an pointer to the next IFD
+ * or zero if there are no more IFDs, which is usually the case
+ */
+ return ff_tget_long(gb, le);
+}
+
+/*
+ * note that this function does not free the entry pointer itself
+ * because it's probably part of a larger array that should be freed
+ * all at once
+ */
+static void exif_free_entry(AVExifEntry *entry)
+{
+ if (!entry)
+ return;
+ if (entry->type == AV_TIFF_IFD)
+ av_exif_free(&entry->value.ifd);
+ else
+ av_freep(&entry->value.ptr);
+ av_freep(&entry->ifd_lead);
+}
+
+void av_exif_free(AVExifMetadata *ifd)
+{
+ if (!ifd)
+ return;
+ if (!ifd->entries) {
+ ifd->count = 0;
+ ifd->size = 0;
+ return;
+ }
+ for (size_t i = 0; i < ifd->count; i++) {
+ AVExifEntry *entry = &ifd->entries[i];
+ exif_free_entry(entry);
+ }
+ av_freep(&ifd->entries);
+ ifd->count = 0;
+ ifd->size = 0;
+}
+
+static size_t exif_get_ifd_size(const AVExifMetadata *ifd)
+{
+ /* 6 == 4 + 2; 2-byte entry-count at the beginning */
+ /* plus 4-byte next-IFD pointer at the end */
+ size_t total_size = IFD_EXTRA_SIZE;
+ for (size_t i = 0; i < ifd->count; i++) {
+ const AVExifEntry *entry = &ifd->entries[i];
+ if (entry->type == AV_TIFF_IFD) {
+ total_size += BASE_TAG_SIZE + exif_get_ifd_size(&entry->value.ifd) + entry->ifd_offset;
+ } else {
+ size_t payload_size = entry->count * exif_sizes[entry->type];
+ total_size += BASE_TAG_SIZE + (payload_size > 4 ? payload_size : 0);
+ }
+ }
+ return total_size;
+}
+
+static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, const AVExifMetadata *ifd)
+{
+ int offset, ret, tell;
+ tell = bytestream2_get_bytes_left_p(pb);
+ tput16(pb, le, ifd->count);
+ offset = tell + IFD_EXTRA_SIZE + BASE_TAG_SIZE * (uint32_t) ifd->count;
+ av_log(logctx, AV_LOG_DEBUG, "writing IFD with %u entries and initial offset %d\n", ifd->count, offset);
+ for (size_t i = 0; i < ifd->count; i++) {
+ const AVExifEntry *entry = &ifd->entries[i];
+ tput16(pb, le, entry->id);
+ if (entry->id == MAKERNOTE_TAG && entry->type == AV_TIFF_IFD) {
+ size_t ifd_size = exif_get_ifd_size(&entry->value.ifd);
+ tput16(pb, le, AV_TIFF_UNDEFINED);
+ tput32(pb, le, ifd_size);
+ } else {
+ tput16(pb, le, entry->type);
+ tput32(pb, le, entry->count);
+ }
+ if (entry->type == AV_TIFF_IFD) {
+ int tell = bytestream2_tell_p(pb);
+ tput32(pb, le, offset);
+ bytestream2_seek_p(pb, offset, SEEK_SET);
+ if (entry->ifd_offset)
+ bytestream2_put_buffer(pb, entry->ifd_lead, entry->ifd_offset);
+ ret = exif_write_ifd(logctx, pb, le, &entry->value.ifd);
+ if (ret < 0)
+ return ret;
+ offset += ret + entry->ifd_offset;
+ bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+ } else {
+ size_t payload_size = entry->count * exif_sizes[entry->type];
+ if (payload_size > 4) {
+ int tell = bytestream2_tell_p(pb);
+ tput32(pb, le, offset);
+ bytestream2_seek_p(pb, offset, SEEK_SET);
+ exif_write_values(pb, le, entry);
+ offset += payload_size;
+ bytestream2_seek_p(pb, tell + 4, SEEK_SET);
+ } else {
+ /* zero uninitialized excess payload values */
+ AV_WN32(pb->buffer, 0);
+ exif_write_values(pb, le, entry);
+ }
}
}
- // return next IDF offset or 0x000000000 or a value < 0 for failure
- return ff_tget_long(gbytes, le);
+ /*
+ * we write 0 if this is the top-level exif IFD
+ * indicating that there are no more IFD pointers
+ */
+ tput32(pb, le, 0);
+ return offset - tell;
+}
+
+int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, enum AVExifHeaderMode header_mode)
+{
+ AVBufferRef *buf = NULL;
+ size_t size, headsize = 8;
+ PutByteContext pb;
+ int ret, off = 0;
+
+#if AV_HAVE_BIGENDIAN
+ int le = 0;
+#else
+ int le = 1;
+#endif
+
+ if (*buffer)
+ return AVERROR(EINVAL);
+
+ size = exif_get_ifd_size(ifd);
+ switch (header_mode) {
+ case AV_EXIF_EXIF00:
+ off = 6;
+ break;
+ case AV_EXIF_T_OFF:
+ off = 4;
+ break;
+ case AV_EXIF_ASSUME_BE:
+ le = 0;
+ headsize = 0;
+ break;
+ case AV_EXIF_ASSUME_LE:
+ le = 1;
+ headsize = 0;
+ break;
+ }
+ buf = av_buffer_alloc(size + off + headsize);
+ if (!buf)
+ return AVERROR(ENOMEM);
+
+ if (header_mode == AV_EXIF_EXIF00) {
+ AV_WL32(buf->data, MKTAG('E','x','i','f'));
+ AV_WN16(buf->data + 4, 0);
+ } else if (header_mode == AV_EXIF_T_OFF) {
+ AV_WN32(buf->data, 0);
+ }
+
+ bytestream2_init_writer(&pb, buf->data + off, buf->size - off);
+
+ if (header_mode != AV_EXIF_ASSUME_BE && header_mode != AV_EXIF_ASSUME_LE) {
+ /* these constants are be32 in both cases */
+ bytestream2_put_be32(&pb, le ? EXIF_II_LONG : EXIF_MM_LONG);
+ tput32(&pb, le, 8);
+ }
+
+ ret = exif_write_ifd(logctx, &pb, le, ifd);
+ if (ret < 0) {
+ av_buffer_unref(&buf);
+ av_log(logctx, AV_LOG_ERROR, "error writing EXIF data: %s\n", av_err2str(ret));
+ return ret;
+ }
+
+ *buffer = buf;
+
+ return 0;
+}
+
+int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
+ AVExifMetadata *ifd, enum AVExifHeaderMode header_mode)
+{
+ int ret, le;
+ GetByteContext gbytes;
+ if (size > INT_MAX)
+ return AVERROR(EINVAL);
+ size_t off = 0;
+ switch (header_mode) {
+ case AV_EXIF_EXIF00:
+ if (size < 6)
+ return AVERROR_INVALIDDATA;
+ off = 6;
+ /* fallthrough */
+ case AV_EXIF_T_OFF:
+ if (size < 4)
+ return AVERROR_INVALIDDATA;
+ if (!off)
+ off = AV_RB32(buf) + 4;
+ /* fallthrough */
+ case AV_EXIF_TIFF_HEADER: {
+ int ifd_offset;
+ if (size <= off)
+ return AVERROR_INVALIDDATA;
+ bytestream2_init(&gbytes, buf + off, size - off);
+ // 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: %s\n", av_err2str(ret));
+ return ret;
+ }
+ bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
+ break;
+ }
+ case AV_EXIF_ASSUME_LE:
+ le = 1;
+ bytestream2_init(&gbytes, buf, size);
+ break;
+ case AV_EXIF_ASSUME_BE:
+ le = 0;
+ bytestream2_init(&gbytes, buf, size);
+ break;
+ default:
+ return AVERROR(EINVAL);
+ }
+
+ /*
+ * parse IFD0 here. If the return value is positive that tells us
+ * there is subimage metadata, but we don't parse that IFD here
+ */
+ ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, ifd);
+ if (ret < 0) {
+ av_exif_free(ifd);
+ av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data: %s\n", av_err2str(ret));
+ return ret;
+ }
+
+ return bytestream2_tell(&gbytes);
+}
+
+static int attach_displaymatrix(void *logctx, AVFrame *frame, int orientation)
+{
+ AVFrameSideData *sd;
+ int32_t *matrix;
+ /* 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;
+}
+
+#define COLUMN_SEP(i, c) ((i) ? ((i) % (c) ? ", " : "\n") : "")
+
+static int exif_ifd_to_dict(void *logctx, const char *prefix, const AVExifMetadata *ifd, AVDictionary **metadata)
+{
+ AVBPrint bp;
+ int ret = 0;
+ char *key = NULL;
+ char *value = NULL;
+
+ if (!prefix)
+ prefix = "";
+
+ for (uint16_t i = 0; i < ifd->count; i++) {
+ const AVExifEntry *entry = &ifd->entries[i];
+ const char *name = av_exif_get_tag_name(entry->id);
+ av_bprint_init(&bp, entry->count * 10, AV_BPRINT_SIZE_UNLIMITED);
+ if (*prefix)
+ av_bprintf(&bp, "%s/", prefix);
+ if (name)
+ av_bprintf(&bp, "%s", name);
+ else
+ av_bprintf(&bp, "0x%04X", entry->id);
+ ret = av_bprint_finalize(&bp, &key);
+ if (ret < 0)
+ goto end;
+ av_bprint_init(&bp, entry->count * 10, AV_BPRINT_SIZE_UNLIMITED);
+ switch (entry->type) {
+ case AV_TIFF_IFD:
+ ret = exif_ifd_to_dict(logctx, key, &entry->value.ifd, metadata);
+ if (ret < 0)
+ goto end;
+ break;
+ case AV_TIFF_SHORT:
+ case AV_TIFF_LONG:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%7" PRIu32, COLUMN_SEP(j, 8), (uint32_t)entry->value.uint[j]);
+ break;
+ case AV_TIFF_SSHORT:
+ case AV_TIFF_SLONG:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%7" PRId32, COLUMN_SEP(j, 8), (int32_t)entry->value.sint[j]);
+ break;
+ case AV_TIFF_RATIONAL:
+ case AV_TIFF_SRATIONAL:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%7i:%-7i", COLUMN_SEP(j, 4), entry->value.rat[j].num, entry->value.rat[j].den);
+ break;
+ case AV_TIFF_DOUBLE:
+ case AV_TIFF_FLOAT:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%.15g", COLUMN_SEP(j, 4), entry->value.dbl[j]);
+ break;
+ case AV_TIFF_STRING:
+ av_bprintf(&bp, "%s", entry->value.str);
+ break;
+ case AV_TIFF_UNDEFINED:
+ case AV_TIFF_BYTE:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%3i", COLUMN_SEP(j, 16), entry->value.ubytes[j]);
+ break;
+ case AV_TIFF_SBYTE:
+ for (uint32_t j = 0; j < entry->count; j++)
+ av_bprintf(&bp, "%s%3i", COLUMN_SEP(j, 16), entry->value.sbytes[j]);
+ break;
+ }
+ if (entry->type != AV_TIFF_IFD) {
+ if (!av_bprint_is_complete(&bp)) {
+ av_bprint_finalize(&bp, NULL);
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ ret = av_bprint_finalize(&bp, &value);
+ if (ret < 0)
+ goto end;
+ ret = av_dict_set(metadata, key, value, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
+ if (ret < 0)
+ goto end;
+ key = NULL;
+ value = NULL;
+ } else {
+ av_freep(&key);
+ }
+ }
+
+end:
+ av_freep(&key);
+ av_freep(&value);
+ return ret;
+}
+
+int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **metadata)
+{
+ return exif_ifd_to_dict(logctx, "", ifd, metadata);
+}
+
+int ff_exif_decode_ifd(void *logctx, GetByteContext *gb, int le, int depth, AVDictionary **metadata)
+{
+ AVExifMetadata ifd = { 0 };
+ int ret = exif_parse_ifd_list(logctx, gb, le, depth, &ifd);
+ if (ret < 0)
+ return ret;
+
+ ret = av_exif_ifd_to_dict(logctx, &ifd, metadata);
+ av_exif_free(&ifd);
+
+ return ret;
}
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata)
{
GetByteContext gb;
-
bytestream2_init(&gb, buf, size);
-
return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata);
}
+
+static int exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef *og)
+{
+ const AVExifEntry *orient = NULL;
+ AVFrameSideData *sd;
+ AVExifMetadata *cloned = NULL;
+ AVBufferRef *written = NULL;
+ int ret;
+
+ 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) {
+ orient = entry;
+ break;
+ }
+ }
+
+ if (orient && orient->value.uint[0] > 1) {
+ av_log(logctx, AV_LOG_DEBUG, "found nontrivial EXIF orientation: %" PRIu64 "\n", orient->value.uint[0]);
+ ret = attach_displaymatrix(logctx, frame, orient->value.uint[0]);
+ if (ret < 0) {
+ av_log(logctx, AV_LOG_WARNING, "unable to attach displaymatrix from EXIF\n");
+ } else {
+ const AVExifEntry *cloned_orient;
+ cloned = av_exif_clone_ifd(ifd);
+ if (!cloned) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ // will have the same offset in the clone as in the original
+ cloned_orient = &cloned->entries[orient - ifd->entries];
+ cloned_orient->value.uint[0] = 1;
+ }
+ }
+
+ ret = av_exif_ifd_to_dict(logctx, cloned ? cloned : ifd, &frame->metadata);
+ if (ret < 0)
+ return ret;
+
+ if (cloned || !og) {
+ ret = av_exif_write(logctx, cloned ? cloned : ifd, &written, 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);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ if (og && written && ret >= 0)
+ av_buffer_unref(&og); // as though we called new_side_data on og;
+ av_exif_free(cloned);
+ av_free(cloned);
+ return ret;
+}
+
+#define EXIF_COPY(fname, srcname) do { \
+ size_t sz; \
+ if (av_size_mult(src->count, sizeof(*(fname)), &sz) < 0) { \
+ ret = AVERROR(ENOMEM); \
+ goto end; \
+ } \
+ (fname) = av_memdup((srcname), sz); \
+ if (!(fname)) { \
+ ret = AVERROR(ENOMEM); \
+ goto end; \
+ } \
+} while (0)
+
+static int exif_clone_entry(AVExifEntry *dst, const AVExifEntry *src)
+{
+ int ret = 0;
+
+ dst->count = src->count;
+ dst->id = src->id;
+ dst->type = src->type;
+
+ dst->ifd_offset = src->ifd_offset;
+ if (src->ifd_lead) {
+ dst->ifd_lead = av_memdup(src->ifd_lead, src->ifd_offset);
+ if (!dst->ifd_lead) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ } else {
+ dst->ifd_lead = NULL;
+ }
+
+ switch(src->type) {
+ case AV_TIFF_IFD: {
+ AVExifMetadata *cloned = av_exif_clone_ifd(&src->value.ifd);
+ if (!cloned) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ dst->value.ifd = *cloned;
+ av_freep(&cloned);
+ break;
+ }
+ case AV_TIFF_SHORT:
+ case AV_TIFF_LONG:
+ EXIF_COPY(dst->value.uint, src->value.uint);
+ break;
+ case AV_TIFF_SLONG:
+ case AV_TIFF_SSHORT:
+ EXIF_COPY(dst->value.sint, src->value.sint);
+ break;
+ case AV_TIFF_RATIONAL:
+ case AV_TIFF_SRATIONAL:
+ EXIF_COPY(dst->value.rat, src->value.rat);
+ break;
+ case AV_TIFF_DOUBLE:
+ case AV_TIFF_FLOAT:
+ EXIF_COPY(dst->value.dbl, src->value.dbl);
+ break;
+ case AV_TIFF_BYTE:
+ case AV_TIFF_UNDEFINED:
+ EXIF_COPY(dst->value.ubytes, src->value.ubytes);
+ break;
+ case AV_TIFF_SBYTE:
+ EXIF_COPY(dst->value.sbytes, src->value.sbytes);
+ break;
+ case AV_TIFF_STRING:
+ EXIF_COPY(dst->value.str, src->value.str);
+ break;
+ }
+
+ return 0;
+
+end:
+ av_freep(&dst->ifd_lead);
+ if (src->type == AV_TIFF_IFD)
+ av_exif_free(&dst->value.ifd);
+ else
+ av_freep(&dst->value.ptr);
+ memset(dst, 0, sizeof(*dst));
+
+ return ret;
+}
+
+int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
+ uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value)
+{
+ void *temp;
+ int ret = 0;
+ AVExifEntry *entry = NULL;
+ AVExifEntry src = { 0 };
+
+ if (!ifd || ifd->entries && !ifd->count || ifd->count && !ifd->entries
+ || ifd_lead && !ifd_offset || !ifd_lead && ifd_offset
+ || !value || ifd->count == 0xFFFFu)
+ return AVERROR(EINVAL);
+
+ for (size_t i = 0; i < ifd->count; i++) {
+ if (ifd->entries[i].id == id) {
+ entry = &ifd->entries[i];
+ break;
+ }
+ }
+
+ if (entry) {
+ exif_free_entry(entry);
+ } else {
+ size_t required_size;
+ ret = av_size_mult(ifd->count + 1, sizeof(*ifd->entries), &required_size);
+ if (ret < 0)
+ return AVERROR(ENOMEM);
+ temp = av_fast_realloc(ifd->entries, &ifd->size, required_size);
+ if (!temp)
+ return AVERROR(ENOMEM);
+ ifd->entries = temp;
+ entry = &ifd->entries[ifd->count++];
+ }
+
+ src.count = count;
+ src.id = id;
+ src.type = type;
+ src.ifd_lead = (uint8_t *) ifd_lead;
+ src.ifd_offset = ifd_offset;
+ if (type == AV_TIFF_IFD)
+ src.value.ifd = * (const AVExifMetadata *) value;
+ else
+ src.value.ptr = (void *) value;
+
+ ret = exif_clone_entry(entry, &src);
+
+ if (ret < 0)
+ ifd->count--;
+
+ return ret;
+}
+
+AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd)
+{
+ AVExifMetadata *ret = av_mallocz(sizeof(*ret));
+ if (!ret)
+ return NULL;
+
+ ret->count = ifd->count;
+ if (ret->count) {
+ size_t required_size;
+ if (av_size_mult(ret->count, sizeof(*ret->entries), &required_size) < 0)
+ goto fail;
+ ret->entries = av_fast_realloc(NULL, &ret->size, required_size);
+ if (!ret->entries)
+ goto fail;
+ }
+
+ for (size_t i = 0; i < ret->count; i++) {
+ const AVExifEntry *entry = &ifd->entries[i];
+ AVExifEntry *ret_entry = &ret->entries[i];
+ int status = exif_clone_entry(ret_entry, entry);
+ if (status < 0)
+ goto fail;
+ }
+
+ return ret;
+
+fail:
+ av_exif_free(ret);
+ av_free(ret);
+ return NULL;
+}
+
+int ff_exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd)
+{
+ return exif_attach_ifd(logctx, frame, ifd, NULL);
+}
+
+int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum AVExifHeaderMode header_mode)
+{
+ int ret;
+ AVExifMetadata ifd = { 0 };
+
+ ret = av_exif_parse_buffer(logctx, data->data, data->size, &ifd, header_mode);
+ if (ret < 0)
+ goto end;
+
+ ret = exif_attach_ifd(logctx, frame, &ifd, data);
+
+end:
+ av_exif_free(&ifd);
+ return ret;
+}
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index 370d4c6eac..f76ab1e125 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-2025 Leo Izen <leo.izen at gmail.com>
*
* This file is part of FFmpeg.
*
@@ -23,14 +24,18 @@
* @file
* EXIF metadata parser
* @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * @author Leo Izen <leo.izen at gmail.com>
*/
#ifndef AVCODEC_EXIF_H
#define AVCODEC_EXIF_H
+#include <stddef.h>
#include <stdint.h>
+
+#include "libavutil/buffer.h"
#include "libavutil/dict.h"
-#include "bytestream.h"
+#include "libavutil/rational.h"
/** Data type identifiers for TIFF tags */
enum AVTiffDataType {
@@ -49,12 +54,137 @@ enum AVTiffDataType {
AV_TIFF_IFD,
};
-/** Recursively decodes all IFD's and
- * adds included TAGS into the metadata dictionary. */
+enum AVExifHeaderMode {
+ /**
+ * The TIFF header starts with 0x49492a00, or 0x4d4d002a.
+ * This one is used internally by FFmpeg.
+ */
+ AV_EXIF_TIFF_HEADER,
+ /** skip the TIFF header, assume little endian */
+ AV_EXIF_ASSUME_LE,
+ /** skip the TIFF header, assume big endian */
+ AV_EXIF_ASSUME_BE,
+ /** The first four bytes point to the actual start, then it's AV_EXIF_TIFF_HEADER */
+ AV_EXIF_T_OFF,
+ /** The first six bytes contain "Exif\0\0", then it's AV_EXIF_TIFF_HEADER */
+ AV_EXIF_EXIF00,
+};
+
+typedef struct AVExifEntry AVExifEntry;
+
+typedef struct AVExifMetadata {
+ /* array of EXIF metadata entries */
+ AVExifEntry *entries;
+ /* number of entries in this array */
+ unsigned int count;
+ /* size of the buffer, used for av_fast_realloc */
+ unsigned int size;
+} AVExifMetadata;
+
+struct AVExifEntry {
+ uint16_t id;
+ enum AVTiffDataType type;
+ uint32_t count;
+
+ /*
+ * These are for IFD-style MakerNote
+ * entries which occur after a fixed
+ * offset rather than at the start of
+ * the entry. The ifd_lead field contains
+ * the leading bytes which typically
+ * identify the type of MakerNote.
+ */
+ uint32_t ifd_offset;
+ uint8_t *ifd_lead;
+
+ /*
+ * An array of entries of size count
+ * Unless it's an IFD, in which case
+ * it's not an array and count = 1
+ */
+ union {
+ void *ptr;
+ int64_t *sint;
+ uint64_t *uint;
+ double *dbl;
+ char *str;
+ uint8_t *ubytes;
+ int8_t *sbytes;
+ AVRational *rat;
+ AVExifMetadata ifd;
+ } value;
+};
+
+/**
+ * Retrieves the tag name associated with the provided tag ID.
+ * If the tag ID is unknown, NULL is returned.
+ *
+ * For example, av_exif_get_tag_name(0x112) returns "Orientation".
+ */
+const char *av_exif_get_tag_name(uint16_t id);
+
+/**
+ * Retrieves the tag ID associated with the provided tag string name.
+ * If the tag name is unknown, a negative number is returned. Otherwise
+ * it always fits inside a uint16_t integer.
+ *
+ * For example, av_exif_get_tag_id("Orientation") returns 274 (0x0112).
+ */
+int32_t av_exif_get_tag_id(const char *name);
+
+/**
+ * Add an entry to the provided EXIF metadata struct. If one already exists with the provided
+ * ID, it will set the existing one to have the other information provided. Otherwise, it
+ * will allocate a new entry.
+ *
+ * This function reallocates ifd->entries using av_realloc and allocates (using av_malloc)
+ * a new value member of the entry, then copies the contents of value into that buffer.
+ */
+int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
+ uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value);
+
+/**
+ * Decodes the EXIF data provided in the buffer and writes it into the
+ * struct *ifd. If this function succeeds, the IFD is owned by the caller
+ * and must be cleared after use by calling av_exif_free(); If this function
+ * fails and returns a negative value, it will call av_exif_free(ifd) before
+ * returning.
+ */
+int av_exif_parse_buffer(void *logctx, const uint8_t *data, size_t size,
+ AVExifMetadata *ifd, enum AVExifHeaderMode header_mode);
+
+/**
+ * Allocates a buffer using av_malloc of an appropriate size and writes the
+ * EXIF data represented by ifd into that buffer.
+ *
+ * Upon error, *buffer will be NULL. The buffer becomes owned by the caller upon
+ * success. The *buffer argument must be NULL before calling.
+ */
+int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, enum AVExifHeaderMode header_mode);
+
+/**
+ * Frees all resources associated with the given EXIF metadata struct.
+ * Does not free the pointer passed itself, in case it is stack-allocated.
+ * The pointer passed to this function must be freed by the caller,
+ * if it is heap-allocated. Passing NULL is permitted.
+ */
+void av_exif_free(AVExifMetadata *ifd);
+
+/**
+ * Recursively reads all tags from the IFD and stores them in the
+ * provided metadata dictionary.
+ */
+int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **metadata);
+
+/**
+ * Allocates a duplicate of the provided EXIF metadata struct. The caller owns
+ * the duplicate and must free it with av_exif_free. Returns NULL if the duplication
+ * process failed.
+ */
+AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd);
+
+/* Used by the AVI demuxer */
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..bd0bd22015
--- /dev/null
+++ b/libavcodec/exif_internal.h
@@ -0,0 +1,68 @@
+/*
+ * EXIF metadata parser - internal functions
+ * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ * Copyright (c) 2024-2025 Leo Izen <leo.izen at 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 at gmail.com>
+ */
+
+#ifndef AVCODEC_EXIF_INTERNAL_H
+#define AVCODEC_EXIF_INTERNAL_H
+
+#include "libavutil/buffer.h"
+#include "libavutil/dict.h"
+#include "libavutil/frame.h"
+
+#include "bytestream.h"
+#include "exif.h"
+
+/**
+ * Attach the data buffer to the frame. This is mostly a wrapper for
+ * av_side_data_new_from_buffer, but it checks if the orientation tag is
+ * present in the provided EXIF buffer. If it is, it zeroes it out and
+ * 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 transfered to the frame directly. On failure,
+ * the data buffer is left owned by the caller.
+ */
+int ff_exif_attach_buffer(void *logctx, AVFrame *frame, AVBufferRef *data, enum AVExifHeaderMode header_mode);
+
+/**
+ * Attach an already-parsed EXIF metadata struct to the frame as a side data
+ * buffer. It writes the EXIF IFD into the buffer and attaches the buffer to
+ * the frame.
+ *
+ * If the metadata struct contains an orientation tag, it will be zeroed before
+ * writing, and instead, an AV_FRAME_DATA_DISPLAYMATRIX will be attached in
+ * addition to the AV_FRAME_DATA_EXIF side data.
+ */
+int ff_exif_attach_ifd(void *logctx, AVFrame *frame, const AVExifMetadata *ifd);
+
+/* Used by mjpeg decoder */
+int ff_exif_decode_ifd(void *logctx, GetByteContext *gb,
+ int le, int depth, AVDictionary **metadata);
+
+#endif /* AVCODEC_EXIF_INTERNAL_H */
diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index 87d1d02077..983a30cb59 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -53,7 +53,7 @@
#include "jpeglsdec.h"
#include "profiles.h"
#include "put_bits.h"
-#include "exif.h"
+#include "exif_internal.h"
#include "bytestream.h"
#include "tiff_common.h"
diff --git a/libavcodec/version.h b/libavcodec/version.h
index da2264a097..755c90bbc1 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
#include "version_major.h"
-#define LIBAVCODEC_VERSION_MINOR 12
+#define LIBAVCODEC_VERSION_MINOR 13
#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 7d77d64524..489cf6f016 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -48,7 +48,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"
diff --git a/tests/ref/fate/exif-image-embedded b/tests/ref/fate/exif-image-embedded
index 2b258b228d..574a3af848 100644
--- a/tests/ref/fate/exif-image-embedded
+++ b/tests/ref/fate/exif-image-embedded
@@ -30,7 +30,7 @@ color_space=bt470bg
color_primaries=unknown
color_transfer=unknown
chroma_location=center
-TAG:UserComment=AppleMark
+TAG:ExifIFD/UserComment=AppleMark
[/FRAME]
[FRAME]
diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg
index 9915f80aed..5a8cd10063 100644
--- a/tests/ref/fate/exif-image-jpg
+++ b/tests/ref/fate/exif-image-jpg
@@ -33,171 +33,136 @@ chroma_location=center
TAG:ImageDescription=
TAG:Make=Canon
TAG:Model=Canon PowerShot SX200 IS
-TAG:Orientation= 1
+TAG:Orientation= 1
TAG:XResolution= 180:1
TAG:YResolution= 180:1
-TAG:ResolutionUnit= 2
+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=
- 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
- 0, 0, 0, 0, 3, 0, 6, 0, 0, 0, 208, 4, 0, 0, 6, 0
- 2, 0, 28, 0, 0, 0, 220, 4, 0, 0, 7, 0, 2, 0, 22, 0
- 0, 0, 252, 4, 0, 0, 8, 0, 4, 0, 1, 0, 0, 0, 17, 166
- 15, 0, 9, 0, 2, 0, 32, 0, 0, 0, 20, 5, 0, 0, 13, 0
- 4, 0, 167, 0, 0, 0, 52, 5, 0, 0, 16, 0, 4, 0, 1, 0
- 0, 0, 0, 0, 96, 2, 38, 0, 3, 0, 48, 0, 0, 0, 208, 7
- 0, 0, 19, 0, 3, 0, 4, 0, 0, 0, 48, 8, 0, 0, 24, 0
- 1, 0, 0, 1, 0, 0, 56, 8, 0, 0, 25, 0, 3, 0, 1, 0
- 0, 0, 1, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 29, 0, 3, 0, 16, 0, 0, 0, 56, 9, 0, 0, 30, 0
- 4, 0, 1, 0, 0, 0, 0, 4, 0, 1, 31, 0, 3, 0, 69, 0
- 0, 0, 88, 9, 0, 0, 34, 0, 3, 0, 208, 0, 0, 0, 226, 9
- 0, 0, 35, 0, 4, 0, 2, 0, 0, 0, 130, 11, 0, 0, 39, 0
- 3, 0, 5, 0, 0, 0, 138, 11, 0, 0, 40, 0, 1, 0, 16, 0
- 0, 0, 148, 11, 0, 0, 208, 0, 4, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 45, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0
- 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 255, 255
- 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0
- 3, 0, 1, 0, 6, 64, 0, 0, 255, 127, 255, 255, 96, 234, 136, 19
-232, 3, 113, 0, 221, 0, 255, 255, 0, 0, 0, 0, 0, 0, 1, 0
- 0, 0, 1, 0, 0, 0, 160, 15, 160, 15, 0, 0, 0, 0, 255, 255
- 0, 0, 255, 127, 255, 127, 0, 0, 0, 0, 255, 255, 90, 0, 2, 0
-136, 19, 250, 0, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0
- 18, 0, 160, 0, 68, 1, 128, 0, 73, 1, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 119, 0, 0, 0, 128, 0, 73, 1, 0, 0, 0, 0
- 23, 0, 250, 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, 73, 77
- 71, 58, 80, 111, 119, 101, 114, 83, 104, 111, 116, 32, 83, 88, 50, 48
- 48, 32, 73, 83, 32, 74, 80, 69, 71, 0, 0, 0, 0, 0, 70, 105
-114, 109, 119, 97, 114, 101, 32, 86, 101, 114, 115, 105, 111, 110, 32, 49
- 46, 48, 48, 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, 5, 0, 0, 0, 211, 1, 0, 0, 155, 1
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1
- 0, 0, 221, 3, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 123, 3
- 0, 0, 165, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0
- 0, 0, 66, 0, 0, 0, 10, 0, 0, 0, 17, 0, 0, 0, 70, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0
- 0, 0, 204, 3, 0, 0, 138, 3, 0, 0, 138, 3, 0, 0, 128, 1
- 0, 0, 66, 4, 0, 0, 165, 255, 255, 255, 0, 0, 0, 0, 0, 0
- 0, 0, 138, 3, 0, 0, 138, 3, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 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, 35, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 102, 255
-255, 255, 208, 0, 0, 0, 114, 255, 255, 255, 208, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 114, 255, 255, 255, 208, 0, 0, 0, 12, 0
- 0, 0, 204, 0, 0, 0, 239, 255, 255, 255, 201, 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, 4, 0, 0, 0, 4, 0, 0, 0, 4
- 0, 0, 0, 5, 0, 0, 8, 0, 0, 0, 239, 255, 255, 255, 201, 0
- 0, 0, 24, 0, 0, 0, 143, 3, 0, 0, 125, 6, 0, 0, 97, 6
- 0, 0, 143, 3, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 0, 0, 91, 1, 0, 0, 113, 4, 0, 0, 204, 3
- 0, 0, 147, 2, 0, 0, 165, 255, 255, 255, 10, 0, 0, 0, 128, 0
- 0, 0, 251, 1, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 74, 2
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 2, 0, 0, 150, 2
- 0, 0, 204, 2, 0, 0, 241, 2, 0, 0, 0, 0, 0, 0, 128, 0
- 0, 0, 0, 0, 0, 0, 8, 162, 255, 255, 70, 2, 0, 0, 69, 2
- 0, 0, 69, 2, 0, 0, 65, 2, 0, 0, 66, 2, 0, 0, 68, 2
- 0, 0, 66, 2, 0, 0, 67, 2, 0, 0, 67, 2, 0, 0, 68, 2
- 0, 0, 18, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 56, 5, 0, 0, 250, 0, 0, 0, 53, 1, 0, 0, 58, 0
- 0, 0, 5, 4, 0, 0, 193, 0, 0, 0, 240, 0, 0, 0, 45, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0
- 0, 0, 8, 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, 107, 0
- 0, 0, 6, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0
- 0, 0, 0, 244, 255, 255, 133, 0, 0, 0, 102, 2, 0, 0, 243, 1
- 0, 0, 0, 0, 0, 0, 99, 2, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 192, 0, 0, 0, 4, 1, 0, 0, 0, 1, 0, 0, 4, 1
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 13, 0, 0, 0, 237, 157
- 54, 41, 96, 0, 4, 0, 9, 0, 9, 0, 160, 15, 200, 8, 100, 0
-100, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0
- 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0
- 18, 0, 18, 0, 18, 0, 238, 255, 0, 0, 18, 0, 238, 255, 0, 0
- 18, 0, 238, 255, 0, 0, 18, 0, 238, 255, 238, 255, 238, 255, 0, 0
- 0, 0, 0, 0, 18, 0, 18, 0, 18, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 159, 0, 15, 0, 104, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 1, 0, 0, 0
- 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 1, 0, 0, 0
- 4, 0, 8, 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, 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, 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, 160, 1, 0, 0, 0, 0, 16, 0, 8, 0, 1, 0
- 1, 0, 128, 2, 224, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 8, 0, 128, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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=
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:YCbCrPositioning= 2
+TAG:ExifIFD/ExposureTime= 1:1250
+TAG:ExifIFD/FNumber= 40:10
+TAG:ExifIFD/ISOSpeedRatings= 160
+TAG:ExifIFD/ExifVersion= 48, 50, 50, 49
+TAG:ExifIFD/DateTimeOriginal=2013:07:18 13:12:03
+TAG:ExifIFD/DateTimeDigitized=2013:07:18 13:12:03
+TAG:ExifIFD/ComponentsConfiguration= 1, 2, 3, 0
+TAG:ExifIFD/CompressedBitsPerPixel= 3:1
+TAG:ExifIFD/ShutterSpeedValue= 329:32
+TAG:ExifIFD/ApertureValue= 128:32
+TAG:ExifIFD/ExposureBiasValue= 0:3
+TAG:ExifIFD/MaxApertureValue= 113:32
+TAG:ExifIFD/MeteringMode= 5
+TAG:ExifIFD/Flash= 16
+TAG:ExifIFD/FocalLength= 5000:1000
+TAG:ExifIFD/MakerNote/GPSLatitudeRef= 96, 2, 0, 3, 0, 0, 0, 4
+ 65535, 1, 9, 0, 0, 0, 0, 0
+ 15, 3, 1, 16390, 0, 32767, 65535, 60000
+ 5000, 1000, 113, 221, 65535, 0, 0, 0
+ 1, 0, 1, 0, 4000, 4000, 0, 0
+ 65535, 0, 32767, 32767, 0, 0, 65535, 90
+TAG:ExifIFD/MakerNote/GPSLatitude= 2, 5000, 250, 187
+TAG:ExifIFD/MakerNote/GPSLongitudeRef= 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSLongitude= 68, 18, 160, 324, 128, 329, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 1, 119, 0, 128, 329, 0
+ 0, 23, 250, 0, 0, 0, 0, 0
+ 0, 0
+TAG:ExifIFD/MakerNote/GPSVersionID= 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSAltitude=IMG:PowerShot SX200 IS JPEG
+TAG:ExifIFD/MakerNote/GPSTimeStamp=Firmware Version 1.00
+TAG:ExifIFD/MakerNote/GPSSatellites=1025553
+TAG:ExifIFD/MakerNote/GPSStatus=
+TAG:ExifIFD/MakerNote/GPSSpeed= 5, 467, 411, 0, 0, 0, 384, 989
+ 56, 0, 0, 0, 0, 576, 891, 4294967205
+ 0, 0, 7, 66, 10, 17, 70, 0
+ 0, 81, 0, 972, 906, 906, 384, 1090
+4294967205, 0, 0, 906, 906, 0, 0, 1
+ 0, 5, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 35, 120, 120, 4294967142, 208
+4294967154, 208, 0, 0, 4294967154, 208, 12, 204
+4294967279, 201, 0, 0, 0, 0, 0, 0
+ 1024, 1024, 1024, 1280, 8, 4294967279, 201, 24
+ 911, 1661, 1633, 911, 100, 0, 0, 1
+ 347, 1137, 972, 659, 4294967205, 10, 128, 507
+ 4, 0, 586, 0, 0, 609, 662, 716
+ 753, 0, 128, 0, 4294943240, 582, 581, 581
+ 577, 578, 580, 578, 579, 579, 580, 1554
+ 0, 0, 0, 1336, 250, 309, 58, 1029
+ 193, 240, 45, 0, 0, 3, 3, 8
+ 0, 0, 0, 0, 0, 0, 107, 4294967046
+ 0, 65535, 0, 4294964224, 133, 614, 499, 0
+ 611, 0, 0, 192, 260, 256, 260, 0
+ 0, 96, 0, 0, 33, 13, 691445229
+TAG:ExifIFD/MakerNote/GPSImgDirectionRef=39845888
+TAG:ExifIFD/MakerNote/0x0026= 96, 4, 9, 9, 4000, 2248, 100, 100
+ 18, 18, 18, 18, 18, 18, 18, 18
+ 18, 18, 18, 18, 18, 18, 18, 18
+ 18, 18, 65518, 0, 18, 65518, 0, 18
+ 65518, 0, 18, 65518, 65518, 65518, 0, 0
+ 0, 18, 18, 18, 1, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDestLatitudeRef= 0, 159, 15, 104
+TAG:ExifIFD/MakerNote/GPSDestBearing= 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
+ 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
+ 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
+ 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
+ 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
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDestDistanceRef= 1
+TAG:ExifIFD/MakerNote/GPSAreaInformation= 0
+TAG:ExifIFD/MakerNote/GPSDateStamp= 32, 1, 0, 2, 2, 2, 2, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDifferential=16778240
+TAG:ExifIFD/MakerNote/0x001F= 138, 1, 0, 4, 8, 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, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0022= 416, 0, 0, 16, 8, 1, 1, 640
+ 480, 0, 0, 0, 0, 0, 8, 384
+ 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
+ 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
+ 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
+ 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
+TAG:ExifIFD/MakerNote/0x0023= 8, 0
+TAG:ExifIFD/MakerNote/0x0027= 10, 0, 65535, 0, 0
+TAG:ExifIFD/MakerNote/0x0028=239, 154, 237, 228, 191, 235, 20, 171, 30, 6, 2, 129, 88, 251, 56, 49
+TAG:ExifIFD/MakerNote/0x00D0= 0
+TAG:ExifIFD/MakerNote/0x002D= 0
+TAG:ExifIFD/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
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
@@ -214,22 +179,22 @@ 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
+TAG:ExifIFD/FlashpixVersion= 48, 49, 48, 48
+TAG:ExifIFD/ColorSpace= 1
+TAG:ExifIFD/PixelXDimension= 4000
+TAG:ExifIFD/PixelYDimension= 2248
+TAG:ExifIFD/InteropIFD/GPSLatitudeRef=R98
+TAG:ExifIFD/InteropIFD/GPSLatitude= 48, 49, 48, 48
+TAG:ExifIFD/InteropIFD/RelatedImageWidth= 4000
+TAG:ExifIFD/InteropIFD/RelatedImageLength= 2248
+TAG:ExifIFD/FocalPlaneXResolution=4000000:244
+TAG:ExifIFD/FocalPlaneYResolution=2248000:183
+TAG:ExifIFD/FocalPlaneResolutionUnit= 2
+TAG:ExifIFD/SensingMethod= 2
+TAG:ExifIFD/FileSource= 3
+TAG:ExifIFD/CustomRendered= 0
+TAG:ExifIFD/ExposureMode= 0
+TAG:ExifIFD/WhiteBalance= 0
+TAG:ExifIFD/DigitalZoomRatio= 4000:4000
+TAG:ExifIFD/SceneCaptureType= 0
[/FRAME]
diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp
index eaae281a86..3a0e6e2c24 100644
--- a/tests/ref/fate/exif-image-webp
+++ b/tests/ref/fate/exif-image-webp
@@ -33,171 +33,136 @@ chroma_location=unspecified
TAG:ImageDescription=
TAG:Make=Canon
TAG:Model=Canon PowerShot SX200 IS
-TAG:Orientation= 1
+TAG:Orientation= 1
TAG:XResolution= 180:1
TAG:YResolution= 180:1
-TAG:ResolutionUnit= 2
+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=
- 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
- 0, 0, 0, 0, 3, 0, 6, 0, 0, 0, 208, 4, 0, 0, 6, 0
- 2, 0, 28, 0, 0, 0, 220, 4, 0, 0, 7, 0, 2, 0, 22, 0
- 0, 0, 252, 4, 0, 0, 8, 0, 4, 0, 1, 0, 0, 0, 17, 166
- 15, 0, 9, 0, 2, 0, 32, 0, 0, 0, 20, 5, 0, 0, 13, 0
- 4, 0, 167, 0, 0, 0, 52, 5, 0, 0, 16, 0, 4, 0, 1, 0
- 0, 0, 0, 0, 96, 2, 38, 0, 3, 0, 48, 0, 0, 0, 208, 7
- 0, 0, 19, 0, 3, 0, 4, 0, 0, 0, 48, 8, 0, 0, 24, 0
- 1, 0, 0, 1, 0, 0, 56, 8, 0, 0, 25, 0, 3, 0, 1, 0
- 0, 0, 1, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 29, 0, 3, 0, 16, 0, 0, 0, 56, 9, 0, 0, 30, 0
- 4, 0, 1, 0, 0, 0, 0, 4, 0, 1, 31, 0, 3, 0, 69, 0
- 0, 0, 88, 9, 0, 0, 34, 0, 3, 0, 208, 0, 0, 0, 226, 9
- 0, 0, 35, 0, 4, 0, 2, 0, 0, 0, 130, 11, 0, 0, 39, 0
- 3, 0, 5, 0, 0, 0, 138, 11, 0, 0, 40, 0, 1, 0, 16, 0
- 0, 0, 148, 11, 0, 0, 208, 0, 4, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 45, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0
- 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 255, 255
- 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0
- 3, 0, 1, 0, 6, 64, 0, 0, 255, 127, 255, 255, 96, 234, 136, 19
-232, 3, 113, 0, 221, 0, 255, 255, 0, 0, 0, 0, 0, 0, 1, 0
- 0, 0, 1, 0, 0, 0, 160, 15, 160, 15, 0, 0, 0, 0, 255, 255
- 0, 0, 255, 127, 255, 127, 0, 0, 0, 0, 255, 255, 90, 0, 2, 0
-136, 19, 250, 0, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0
- 18, 0, 160, 0, 68, 1, 128, 0, 73, 1, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 119, 0, 0, 0, 128, 0, 73, 1, 0, 0, 0, 0
- 23, 0, 250, 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, 73, 77
- 71, 58, 80, 111, 119, 101, 114, 83, 104, 111, 116, 32, 83, 88, 50, 48
- 48, 32, 73, 83, 32, 74, 80, 69, 71, 0, 0, 0, 0, 0, 70, 105
-114, 109, 119, 97, 114, 101, 32, 86, 101, 114, 115, 105, 111, 110, 32, 49
- 46, 48, 48, 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, 5, 0, 0, 0, 211, 1, 0, 0, 155, 1
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1
- 0, 0, 221, 3, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 123, 3
- 0, 0, 165, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0
- 0, 0, 66, 0, 0, 0, 10, 0, 0, 0, 17, 0, 0, 0, 70, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0
- 0, 0, 204, 3, 0, 0, 138, 3, 0, 0, 138, 3, 0, 0, 128, 1
- 0, 0, 66, 4, 0, 0, 165, 255, 255, 255, 0, 0, 0, 0, 0, 0
- 0, 0, 138, 3, 0, 0, 138, 3, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 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, 35, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 102, 255
-255, 255, 208, 0, 0, 0, 114, 255, 255, 255, 208, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 114, 255, 255, 255, 208, 0, 0, 0, 12, 0
- 0, 0, 204, 0, 0, 0, 239, 255, 255, 255, 201, 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, 4, 0, 0, 0, 4, 0, 0, 0, 4
- 0, 0, 0, 5, 0, 0, 8, 0, 0, 0, 239, 255, 255, 255, 201, 0
- 0, 0, 24, 0, 0, 0, 143, 3, 0, 0, 125, 6, 0, 0, 97, 6
- 0, 0, 143, 3, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 1, 0, 0, 0, 91, 1, 0, 0, 113, 4, 0, 0, 204, 3
- 0, 0, 147, 2, 0, 0, 165, 255, 255, 255, 10, 0, 0, 0, 128, 0
- 0, 0, 251, 1, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 74, 2
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 2, 0, 0, 150, 2
- 0, 0, 204, 2, 0, 0, 241, 2, 0, 0, 0, 0, 0, 0, 128, 0
- 0, 0, 0, 0, 0, 0, 8, 162, 255, 255, 70, 2, 0, 0, 69, 2
- 0, 0, 69, 2, 0, 0, 65, 2, 0, 0, 66, 2, 0, 0, 68, 2
- 0, 0, 66, 2, 0, 0, 67, 2, 0, 0, 67, 2, 0, 0, 68, 2
- 0, 0, 18, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 56, 5, 0, 0, 250, 0, 0, 0, 53, 1, 0, 0, 58, 0
- 0, 0, 5, 4, 0, 0, 193, 0, 0, 0, 240, 0, 0, 0, 45, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0
- 0, 0, 8, 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, 107, 0
- 0, 0, 6, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0
- 0, 0, 0, 244, 255, 255, 133, 0, 0, 0, 102, 2, 0, 0, 243, 1
- 0, 0, 0, 0, 0, 0, 99, 2, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 192, 0, 0, 0, 4, 1, 0, 0, 0, 1, 0, 0, 4, 1
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 13, 0, 0, 0, 237, 157
- 54, 41, 96, 0, 4, 0, 9, 0, 9, 0, 160, 15, 200, 8, 100, 0
-100, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0
- 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0, 18, 0
- 18, 0, 18, 0, 18, 0, 238, 255, 0, 0, 18, 0, 238, 255, 0, 0
- 18, 0, 238, 255, 0, 0, 18, 0, 238, 255, 238, 255, 238, 255, 0, 0
- 0, 0, 0, 0, 18, 0, 18, 0, 18, 0, 1, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 159, 0, 15, 0, 104, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 1, 0, 0, 0
- 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 1, 0, 0, 0
- 4, 0, 8, 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, 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, 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, 160, 1, 0, 0, 0, 0, 16, 0, 8, 0, 1, 0
- 1, 0, 128, 2, 224, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 8, 0, 128, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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=
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:YCbCrPositioning= 2
+TAG:ExifIFD/ExposureTime= 1:1250
+TAG:ExifIFD/FNumber= 40:10
+TAG:ExifIFD/ISOSpeedRatings= 160
+TAG:ExifIFD/ExifVersion= 48, 50, 50, 49
+TAG:ExifIFD/DateTimeOriginal=2013:07:18 13:12:03
+TAG:ExifIFD/DateTimeDigitized=2013:07:18 13:12:03
+TAG:ExifIFD/ComponentsConfiguration= 1, 2, 3, 0
+TAG:ExifIFD/CompressedBitsPerPixel= 3:1
+TAG:ExifIFD/ShutterSpeedValue= 329:32
+TAG:ExifIFD/ApertureValue= 128:32
+TAG:ExifIFD/ExposureBiasValue= 0:3
+TAG:ExifIFD/MaxApertureValue= 113:32
+TAG:ExifIFD/MeteringMode= 5
+TAG:ExifIFD/Flash= 16
+TAG:ExifIFD/FocalLength= 5000:1000
+TAG:ExifIFD/MakerNote/GPSLatitudeRef= 96, 2, 0, 3, 0, 0, 0, 4
+ 65535, 1, 9, 0, 0, 0, 0, 0
+ 15, 3, 1, 16390, 0, 32767, 65535, 60000
+ 5000, 1000, 113, 221, 65535, 0, 0, 0
+ 1, 0, 1, 0, 4000, 4000, 0, 0
+ 65535, 0, 32767, 32767, 0, 0, 65535, 90
+TAG:ExifIFD/MakerNote/GPSLatitude= 2, 5000, 250, 187
+TAG:ExifIFD/MakerNote/GPSLongitudeRef= 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSLongitude= 68, 18, 160, 324, 128, 329, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 1, 119, 0, 128, 329, 0
+ 0, 23, 250, 0, 0, 0, 0, 0
+ 0, 0
+TAG:ExifIFD/MakerNote/GPSVersionID= 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSAltitude=IMG:PowerShot SX200 IS JPEG
+TAG:ExifIFD/MakerNote/GPSTimeStamp=Firmware Version 1.00
+TAG:ExifIFD/MakerNote/GPSSatellites=1025553
+TAG:ExifIFD/MakerNote/GPSStatus=
+TAG:ExifIFD/MakerNote/GPSSpeed= 5, 467, 411, 0, 0, 0, 384, 989
+ 56, 0, 0, 0, 0, 576, 891, 4294967205
+ 0, 0, 7, 66, 10, 17, 70, 0
+ 0, 81, 0, 972, 906, 906, 384, 1090
+4294967205, 0, 0, 906, 906, 0, 0, 1
+ 0, 5, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 35, 120, 120, 4294967142, 208
+4294967154, 208, 0, 0, 4294967154, 208, 12, 204
+4294967279, 201, 0, 0, 0, 0, 0, 0
+ 1024, 1024, 1024, 1280, 8, 4294967279, 201, 24
+ 911, 1661, 1633, 911, 100, 0, 0, 1
+ 347, 1137, 972, 659, 4294967205, 10, 128, 507
+ 4, 0, 586, 0, 0, 609, 662, 716
+ 753, 0, 128, 0, 4294943240, 582, 581, 581
+ 577, 578, 580, 578, 579, 579, 580, 1554
+ 0, 0, 0, 1336, 250, 309, 58, 1029
+ 193, 240, 45, 0, 0, 3, 3, 8
+ 0, 0, 0, 0, 0, 0, 107, 4294967046
+ 0, 65535, 0, 4294964224, 133, 614, 499, 0
+ 611, 0, 0, 192, 260, 256, 260, 0
+ 0, 96, 0, 0, 33, 13, 691445229
+TAG:ExifIFD/MakerNote/GPSImgDirectionRef=39845888
+TAG:ExifIFD/MakerNote/0x0026= 96, 4, 9, 9, 4000, 2248, 100, 100
+ 18, 18, 18, 18, 18, 18, 18, 18
+ 18, 18, 18, 18, 18, 18, 18, 18
+ 18, 18, 65518, 0, 18, 65518, 0, 18
+ 65518, 0, 18, 65518, 65518, 65518, 0, 0
+ 0, 18, 18, 18, 1, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDestLatitudeRef= 0, 159, 15, 104
+TAG:ExifIFD/MakerNote/GPSDestBearing= 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
+ 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
+ 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
+ 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
+ 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
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDestDistanceRef= 1
+TAG:ExifIFD/MakerNote/GPSAreaInformation= 0
+TAG:ExifIFD/MakerNote/GPSDateStamp= 32, 1, 0, 2, 2, 2, 2, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/GPSDifferential=16778240
+TAG:ExifIFD/MakerNote/0x001F= 138, 1, 0, 4, 8, 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, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0022= 416, 0, 0, 16, 8, 1, 1, 640
+ 480, 0, 0, 0, 0, 0, 8, 384
+ 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
+ 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
+ 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
+ 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
+TAG:ExifIFD/MakerNote/0x0023= 8, 0
+TAG:ExifIFD/MakerNote/0x0027= 10, 0, 65535, 0, 0
+TAG:ExifIFD/MakerNote/0x0028=239, 154, 237, 228, 191, 235, 20, 171, 30, 6, 2, 129, 88, 251, 56, 49
+TAG:ExifIFD/MakerNote/0x00D0= 0
+TAG:ExifIFD/MakerNote/0x002D= 0
+TAG:ExifIFD/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
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
@@ -214,22 +179,22 @@ 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
+TAG:ExifIFD/FlashpixVersion= 48, 49, 48, 48
+TAG:ExifIFD/ColorSpace= 1
+TAG:ExifIFD/PixelXDimension= 4000
+TAG:ExifIFD/PixelYDimension= 2248
+TAG:ExifIFD/InteropIFD/GPSLatitudeRef=R98
+TAG:ExifIFD/InteropIFD/GPSLatitude= 48, 49, 48, 48
+TAG:ExifIFD/InteropIFD/RelatedImageWidth= 4000
+TAG:ExifIFD/InteropIFD/RelatedImageLength= 2248
+TAG:ExifIFD/FocalPlaneXResolution=4000000:244
+TAG:ExifIFD/FocalPlaneYResolution=2248000:183
+TAG:ExifIFD/FocalPlaneResolutionUnit= 2
+TAG:ExifIFD/SensingMethod= 2
+TAG:ExifIFD/FileSource= 3
+TAG:ExifIFD/CustomRendered= 0
+TAG:ExifIFD/ExposureMode= 0
+TAG:ExifIFD/WhiteBalance= 0
+TAG:ExifIFD/DigitalZoomRatio= 4000:4000
+TAG:ExifIFD/SceneCaptureType= 0
[/FRAME]
commit bb90b262d6d23f1bca3587a48abc15b951cbbf05
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Wed Mar 5 09:20:20 2025 -0500
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:46 2025 -0400
avutil/side_data: add EXIF side data type
This commit adds support for the additional side data type
AV_FRAME_DATA_EXIF, which contains a buffer of an EXIF metadata
payload.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavutil/frame.h b/libavutil/frame.h
index d440cfba2e..d7d98e67e4 100644
--- a/libavutil/frame.h
+++ b/libavutil/frame.h
@@ -254,6 +254,12 @@ enum AVFrameSideDataType {
* libavutil/tdrdi.h.
*/
AV_FRAME_DATA_3D_REFERENCE_DISPLAYS,
+
+ /**
+ * Extensible image file format metadata. The payload is a buffer containing
+ * EXIF metadata, starting with either 49 49 2a 00, or 4d 4d 00 2a.
+ */
+ AV_FRAME_DATA_EXIF,
};
enum AVActiveFormatDescription {
diff --git a/libavutil/side_data.c b/libavutil/side_data.c
index fa2a2c2a13..8df117478a 100644
--- a/libavutil/side_data.c
+++ b/libavutil/side_data.c
@@ -53,6 +53,7 @@ static const AVSideDataDescriptor sd_props[] = {
[AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT] = { "Ambient viewing environment", AV_SIDE_DATA_PROP_GLOBAL },
[AV_FRAME_DATA_SPHERICAL] = { "Spherical Mapping", AV_SIDE_DATA_PROP_GLOBAL | AV_SIDE_DATA_PROP_SIZE_DEPENDENT },
[AV_FRAME_DATA_ICC_PROFILE] = { "ICC profile", AV_SIDE_DATA_PROP_GLOBAL | AV_SIDE_DATA_PROP_COLOR_DEPENDENT },
+ [AV_FRAME_DATA_EXIF] = { "EXIF metadata", 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_VIDEO_HINT] = { "Encoding video hint", AV_SIDE_DATA_PROP_SIZE_DEPENDENT },
[AV_FRAME_DATA_3D_REFERENCE_DISPLAYS] = { "3D Reference Displays Information", AV_SIDE_DATA_PROP_GLOBAL },
diff --git a/libavutil/version.h b/libavutil/version.h
index 5cde712c9f..27b9fe73d8 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@
*/
#define LIBAVUTIL_VERSION_MAJOR 60
-#define LIBAVUTIL_VERSION_MINOR 9
+#define LIBAVUTIL_VERSION_MINOR 10
#define LIBAVUTIL_VERSION_MICRO 100
#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
commit a99fff4e2d4058c57599ba0b968862af82bdad5b
Author: Leo Izen <leo.izen at gmail.com>
AuthorDate: Tue Mar 11 13:41:19 2025 -0400
Commit: Leo Izen <leo.izen at gmail.com>
CommitDate: Tue Aug 19 11:26:40 2025 -0400
avcodec/tiff_common: rename TIFF enum constants
This makes the enum TiffTypes public by moving it to the public header
exif.h and renaming it to AVTiffDataType, as well as adding an AV_
prefix in front of each of the entry names. This allows callers to use
enum AVTiffDataType without pulling in all of tiff_common.h, as that
header is not public.
Signed-off-by: Leo Izen <leo.izen at gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 959d114d09..f6f8918586 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -180,17 +180,17 @@ static int exif_add_metadata(void *logctx, int count, int type,
"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);
- case TIFF_SBYTE : return ff_tadd_bytes_metadata(count, name, sep, gb, le, 1, metadata);
- case TIFF_BYTE :
- case TIFF_UNDEFINED: return ff_tadd_bytes_metadata(count, name, sep, gb, le, 0, metadata);
- case TIFF_STRING : return ff_tadd_string_metadata(count, name, gb, le, metadata);
- case TIFF_SRATIONAL:
- case TIFF_RATIONAL : return ff_tadd_rational_metadata(count, name, sep, gb, le, metadata);
- case TIFF_SLONG :
- case TIFF_LONG : return ff_tadd_long_metadata(count, name, sep, gb, le, metadata);
+ case AV_TIFF_DOUBLE : return ff_tadd_doubles_metadata(count, name, sep, gb, le, metadata);
+ case AV_TIFF_SSHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 1, metadata);
+ case AV_TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 0, metadata);
+ case AV_TIFF_SBYTE : return ff_tadd_bytes_metadata(count, name, sep, gb, le, 1, metadata);
+ case AV_TIFF_BYTE :
+ case AV_TIFF_UNDEFINED: return ff_tadd_bytes_metadata(count, name, sep, gb, le, 0, metadata);
+ case AV_TIFF_STRING : return ff_tadd_string_metadata(count, name, gb, le, metadata);
+ case AV_TIFF_SRATIONAL:
+ case AV_TIFF_RATIONAL : return ff_tadd_rational_metadata(count, name, sep, gb, le, metadata);
+ case AV_TIFF_SLONG :
+ case AV_TIFF_LONG : return ff_tadd_long_metadata(count, name, sep, gb, le, metadata);
default:
avpriv_request_sample(logctx, "TIFF tag type (%u)", type);
return 0;
@@ -203,7 +203,7 @@ static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le,
{
int ret, cur_pos;
unsigned id, count;
- enum TiffTypes type;
+ enum AVTiffDataType type;
if (depth > 2) {
return 0;
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
index f70d21391a..370d4c6eac 100644
--- a/libavcodec/exif.h
+++ b/libavcodec/exif.h
@@ -32,6 +32,23 @@
#include "libavutil/dict.h"
#include "bytestream.h"
+/** Data type identifiers for TIFF tags */
+enum AVTiffDataType {
+ AV_TIFF_BYTE = 1,
+ AV_TIFF_STRING,
+ AV_TIFF_SHORT,
+ AV_TIFF_LONG,
+ AV_TIFF_RATIONAL,
+ AV_TIFF_SBYTE,
+ AV_TIFF_UNDEFINED,
+ AV_TIFF_SSHORT,
+ AV_TIFF_SLONG,
+ AV_TIFF_SRATIONAL,
+ AV_TIFF_FLOAT,
+ AV_TIFF_DOUBLE,
+ AV_TIFF_IFD,
+};
+
/** 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,
diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index e515845a83..b468598fbb 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -273,9 +273,9 @@ static int add_metadata(int count, int type,
const char *name, const char *sep, TiffContext *s, AVFrame *frame)
{
switch(type) {
- case TIFF_DOUBLE: return ff_tadd_doubles_metadata(count, name, sep, &s->gb, s->le, &frame->metadata);
- case TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, &s->gb, s->le, 0, &frame->metadata);
- case TIFF_STRING: return ff_tadd_string_metadata(count, name, &s->gb, s->le, &frame->metadata);
+ case AV_TIFF_DOUBLE: return ff_tadd_doubles_metadata(count, name, sep, &s->gb, s->le, &frame->metadata);
+ case AV_TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, &s->gb, s->le, 0, &frame->metadata);
+ case AV_TIFF_STRING: return ff_tadd_string_metadata(count, name, &s->gb, s->le, &frame->metadata);
default : return AVERROR_INVALIDDATA;
};
}
@@ -1271,12 +1271,12 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
off = bytestream2_tell(&s->gb);
if (count == 1) {
switch (type) {
- case TIFF_BYTE:
- case TIFF_SHORT:
- case TIFF_LONG:
+ case AV_TIFF_BYTE:
+ case AV_TIFF_SHORT:
+ case AV_TIFF_LONG:
value = ff_tget(&s->gb, type, s->le);
break;
- case TIFF_RATIONAL:
+ case AV_TIFF_RATIONAL:
value = ff_tget_long(&s->gb, s->le);
value2 = ff_tget_long(&s->gb, s->le);
if (!value2) {
@@ -1285,7 +1285,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
break;
- case TIFF_STRING:
+ case AV_TIFF_STRING:
if (count <= 4) {
break;
}
@@ -1320,9 +1320,9 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
s->bpp = value;
else {
switch (type) {
- case TIFF_BYTE:
- case TIFF_SHORT:
- case TIFF_LONG:
+ case AV_TIFF_BYTE:
+ case AV_TIFF_SHORT:
+ case AV_TIFF_LONG:
s->bpp = 0;
if (bytestream2_get_bytes_left(&s->gb) < type_sizes[type] * count)
return AVERROR_INVALIDDATA;
@@ -1389,7 +1389,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
break;
case TIFF_ROWSPERSTRIP:
- if (!value || (type == TIFF_LONG && value == UINT_MAX))
+ if (!value || (type == AV_TIFF_LONG && value == UINT_MAX))
value = s->height;
s->rps = FFMIN(value, s->height);
break;
@@ -1470,7 +1470,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
return AVERROR_INVALIDDATA;
s->black_level[0] = value / (float)value2;
for (int i = 0; i < count && count > 1; i++) {
- if (type == TIFF_RATIONAL) {
+ if (type == AV_TIFF_RATIONAL) {
value = ff_tget_long(&s->gb, s->le);
value2 = ff_tget_long(&s->gb, s->le);
if (!value2) {
@@ -1479,7 +1479,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
s->black_level[i] = value / (float)value2;
- } else if (type == TIFF_SRATIONAL) {
+ } else if (type == AV_TIFF_SRATIONAL) {
int value = ff_tget_long(&s->gb, s->le);
int value2 = ff_tget_long(&s->gb, s->le);
if (!value2) {
@@ -1786,7 +1786,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
break;
case DNG_ANALOG_BALANCE:
- if (type != TIFF_RATIONAL)
+ if (type != AV_TIFF_RATIONAL)
break;
for (int i = 0; i < 3; i++) {
@@ -1801,7 +1801,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
break;
case DNG_AS_SHOT_NEUTRAL:
- if (type != TIFF_RATIONAL)
+ if (type != AV_TIFF_RATIONAL)
break;
for (int i = 0; i < 3; i++) {
@@ -1816,7 +1816,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame)
}
break;
case DNG_AS_SHOT_WHITE_XY:
- if (type != TIFF_RATIONAL)
+ if (type != AV_TIFF_RATIONAL)
break;
for (int i = 0; i < 2; i++) {
diff --git a/libavcodec/tiff_common.c b/libavcodec/tiff_common.c
index 22ebca814c..ee8061944b 100644
--- a/libavcodec/tiff_common.c
+++ b/libavcodec/tiff_common.c
@@ -64,9 +64,9 @@ double ff_tget_double(GetByteContext *gb, int le)
unsigned ff_tget(GetByteContext *gb, int type, int le)
{
switch (type) {
- case TIFF_BYTE: return bytestream2_get_byte(gb);
- case TIFF_SHORT: return ff_tget_short(gb, le);
- case TIFF_LONG: return ff_tget_long(gb, le);
+ case AV_TIFF_BYTE: return bytestream2_get_byte(gb);
+ case AV_TIFF_SHORT: return ff_tget_short(gb, le);
+ case AV_TIFF_LONG: return ff_tget_long(gb, le);
default: return UINT_MAX;
}
}
@@ -273,7 +273,7 @@ int ff_tread_tag(GetByteContext *gb, int le, unsigned *tag, unsigned *type,
// seek to offset if this is an IFD-tag or
// if count values do not fit into the offset value
- if (ifd_tag || (*count > 4 || !(type_sizes[*type] * (*count) <= 4 || *type == TIFF_STRING))) {
+ if (ifd_tag || (*count > 4 || !(type_sizes[*type] * (*count) <= 4 || *type == AV_TIFF_STRING))) {
bytestream2_seek(gb, ff_tget_long (gb, le), SEEK_SET);
}
diff --git a/libavcodec/tiff_common.h b/libavcodec/tiff_common.h
index e429b8eaeb..11f2e739a4 100644
--- a/libavcodec/tiff_common.h
+++ b/libavcodec/tiff_common.h
@@ -31,23 +31,7 @@
#include <stdint.h>
#include "libavutil/dict.h"
#include "bytestream.h"
-
-/** data type identifiers for TIFF tags */
-enum TiffTypes {
- TIFF_BYTE = 1,
- TIFF_STRING,
- TIFF_SHORT,
- TIFF_LONG,
- TIFF_RATIONAL,
- TIFF_SBYTE,
- TIFF_UNDEFINED,
- TIFF_SSHORT,
- TIFF_SLONG,
- TIFF_SRATIONAL,
- TIFF_FLOAT,
- TIFF_DOUBLE,
- TIFF_IFD
-};
+#include "exif.h"
/** sizes of various TIFF field types (string size = 100)*/
static const uint8_t type_sizes[14] = {
diff --git a/libavcodec/tiffenc.c b/libavcodec/tiffenc.c
index 66facb4174..9a5291c257 100644
--- a/libavcodec/tiffenc.c
+++ b/libavcodec/tiffenc.c
@@ -105,7 +105,7 @@ static inline int check_size(TiffEncoderContext *s, uint64_t need)
* @param type type of values
* @param flip = 0 - normal copy, >0 - flip
*/
-static void tnput(uint8_t **p, int n, const uint8_t *val, enum TiffTypes type,
+static void tnput(uint8_t **p, int n, const uint8_t *val, enum AVTiffDataType type,
int flip)
{
int i;
@@ -126,7 +126,7 @@ static void tnput(uint8_t **p, int n, const uint8_t *val, enum TiffTypes type,
* @param ptr_val pointer to values
*/
static int add_entry(TiffEncoderContext *s, enum TiffTags tag,
- enum TiffTypes type, int count, const void *ptr_val)
+ enum AVTiffDataType type, int count, const void *ptr_val)
{
uint8_t *entries_ptr = s->entries + 12 * s->num_entries;
@@ -150,12 +150,12 @@ static int add_entry(TiffEncoderContext *s, enum TiffTags tag,
}
static int add_entry1(TiffEncoderContext *s,
- enum TiffTags tag, enum TiffTypes type, int val)
+ enum TiffTags tag, enum AVTiffDataType type, int val)
{
uint16_t w = val;
uint32_t dw = val;
return add_entry(s, tag, type, 1,
- type == TIFF_SHORT ? (void *)&w : (void *)&dw);
+ type == AV_TIFF_SHORT ? (void *)&w : (void *)&dw);
}
/**
@@ -453,23 +453,23 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
s->num_entries = 0;
- ADD_ENTRY1(s, TIFF_SUBFILE, TIFF_LONG, 0);
- ADD_ENTRY1(s, TIFF_WIDTH, TIFF_LONG, s->width);
- ADD_ENTRY1(s, TIFF_HEIGHT, TIFF_LONG, s->height);
+ ADD_ENTRY1(s, TIFF_SUBFILE, AV_TIFF_LONG, 0);
+ ADD_ENTRY1(s, TIFF_WIDTH, AV_TIFF_LONG, s->width);
+ ADD_ENTRY1(s, TIFF_HEIGHT, AV_TIFF_LONG, s->height);
if (s->bpp_tab_size)
- ADD_ENTRY(s, TIFF_BPP, TIFF_SHORT, s->bpp_tab_size, bpp_tab);
+ ADD_ENTRY(s, TIFF_BPP, AV_TIFF_SHORT, s->bpp_tab_size, bpp_tab);
- ADD_ENTRY1(s, TIFF_COMPR, TIFF_SHORT, s->compr);
- ADD_ENTRY1(s, TIFF_PHOTOMETRIC, TIFF_SHORT, s->photometric_interpretation);
- ADD_ENTRY(s, TIFF_STRIP_OFFS, TIFF_LONG, strips, s->strip_offsets);
+ ADD_ENTRY1(s, TIFF_COMPR, AV_TIFF_SHORT, s->compr);
+ ADD_ENTRY1(s, TIFF_PHOTOMETRIC, AV_TIFF_SHORT, s->photometric_interpretation);
+ ADD_ENTRY(s, TIFF_STRIP_OFFS, AV_TIFF_LONG, strips, s->strip_offsets);
if (s->bpp_tab_size)
- ADD_ENTRY1(s, TIFF_SAMPLES_PER_PIXEL, TIFF_SHORT, s->bpp_tab_size);
+ ADD_ENTRY1(s, TIFF_SAMPLES_PER_PIXEL, AV_TIFF_SHORT, s->bpp_tab_size);
- ADD_ENTRY1(s, TIFF_ROWSPERSTRIP, TIFF_LONG, s->rps);
- ADD_ENTRY(s, TIFF_STRIP_SIZE, TIFF_LONG, strips, s->strip_sizes);
- ADD_ENTRY(s, TIFF_XRES, TIFF_RATIONAL, 1, res);
+ ADD_ENTRY1(s, TIFF_ROWSPERSTRIP, AV_TIFF_LONG, s->rps);
+ ADD_ENTRY(s, TIFF_STRIP_SIZE, AV_TIFF_LONG, strips, s->strip_sizes);
+ ADD_ENTRY(s, TIFF_XRES, AV_TIFF_RATIONAL, 1, res);
if (avctx->sample_aspect_ratio.num > 0 &&
avctx->sample_aspect_ratio.den > 0) {
AVRational y = av_mul_q(av_make_q(s->dpi, 1),
@@ -477,11 +477,11 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
res[0] = y.num;
res[1] = y.den;
}
- ADD_ENTRY(s, TIFF_YRES, TIFF_RATIONAL, 1, res);
- ADD_ENTRY1(s, TIFF_RES_UNIT, TIFF_SHORT, 2);
+ ADD_ENTRY(s, TIFF_YRES, AV_TIFF_RATIONAL, 1, res);
+ ADD_ENTRY1(s, TIFF_RES_UNIT, AV_TIFF_SHORT, 2);
if (!(avctx->flags & AV_CODEC_FLAG_BITEXACT))
- ADD_ENTRY(s, TIFF_SOFTWARE_NAME, TIFF_STRING,
+ ADD_ENTRY(s, TIFF_SOFTWARE_NAME, AV_TIFF_STRING,
strlen(LIBAVCODEC_IDENT) + 1, LIBAVCODEC_IDENT);
if (avctx->pix_fmt == AV_PIX_FMT_PAL8) {
@@ -492,17 +492,17 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
pal[i + 256] = ((rgb >> 8) & 0xff) * 257;
pal[i + 512] = (rgb & 0xff) * 257;
}
- ADD_ENTRY(s, TIFF_PAL, TIFF_SHORT, 256 * 3, pal);
+ ADD_ENTRY(s, TIFF_PAL, AV_TIFF_SHORT, 256 * 3, pal);
}
if (alpha)
- ADD_ENTRY1(s,TIFF_EXTRASAMPLES, TIFF_SHORT, 2);
+ ADD_ENTRY1(s,TIFF_EXTRASAMPLES, AV_TIFF_SHORT, 2);
if (is_yuv) {
/** according to CCIR Recommendation 601.1 */
uint32_t refbw[12] = { 15, 1, 235, 1, 128, 1, 240, 1, 128, 1, 240, 1 };
- ADD_ENTRY(s, TIFF_YCBCR_SUBSAMPLING, TIFF_SHORT, 2, s->subsampling);
+ ADD_ENTRY(s, TIFF_YCBCR_SUBSAMPLING, AV_TIFF_SHORT, 2, s->subsampling);
if (avctx->chroma_sample_location == AVCHROMA_LOC_TOPLEFT)
- ADD_ENTRY1(s, TIFF_YCBCR_POSITIONING, TIFF_SHORT, 2);
- ADD_ENTRY(s, TIFF_REFERENCE_BW, TIFF_RATIONAL, 6, refbw);
+ ADD_ENTRY1(s, TIFF_YCBCR_POSITIONING, AV_TIFF_SHORT, 2);
+ ADD_ENTRY(s, TIFF_REFERENCE_BW, AV_TIFF_RATIONAL, 6, refbw);
}
// write offset to dir
bytestream_put_le32(&offset, ptr - pkt->data);
diff --git a/libavformat/mlvdec.c b/libavformat/mlvdec.c
index 44f5c20755..a0d5e7fb55 100644
--- a/libavformat/mlvdec.c
+++ b/libavformat/mlvdec.c
@@ -429,7 +429,7 @@ static int read_header(AVFormatContext *avctx)
static void write_tiff_short(PutByteContext *pb, int tag, int value)
{
bytestream2_put_le16(pb, tag);
- bytestream2_put_le16(pb, TIFF_SHORT);
+ bytestream2_put_le16(pb, AV_TIFF_SHORT);
bytestream2_put_le32(pb, 1);
bytestream2_put_le16(pb, value);
bytestream2_put_le16(pb, 0);
@@ -438,7 +438,7 @@ static void write_tiff_short(PutByteContext *pb, int tag, int value)
static void write_tiff_short2(PutByteContext *pb, int tag, int v1, int v2)
{
bytestream2_put_le16(pb, tag);
- bytestream2_put_le16(pb, TIFF_SHORT);
+ bytestream2_put_le16(pb, AV_TIFF_SHORT);
bytestream2_put_le32(pb, 2);
bytestream2_put_le16(pb, v1);
bytestream2_put_le16(pb, v2);
@@ -447,7 +447,7 @@ static void write_tiff_short2(PutByteContext *pb, int tag, int v1, int v2)
static void write_tiff_long(PutByteContext *pb, int tag, int value)
{
bytestream2_put_le16(pb, tag);
- bytestream2_put_le16(pb, TIFF_LONG);
+ bytestream2_put_le16(pb, AV_TIFF_LONG);
bytestream2_put_le32(pb, 1);
bytestream2_put_le32(pb, value);
}
@@ -455,7 +455,7 @@ static void write_tiff_long(PutByteContext *pb, int tag, int value)
static void write_tiff_byte4(PutByteContext *pb, int tag, int v1, int v2, int v3, int v4)
{
bytestream2_put_le16(pb, tag);
- bytestream2_put_le16(pb, TIFF_BYTE);
+ bytestream2_put_le16(pb, AV_TIFF_BYTE);
bytestream2_put_le32(pb, 4);
bytestream2_put_byte(pb, v1);
bytestream2_put_byte(pb, v2);
@@ -506,7 +506,7 @@ static int get_packet_lj92(AVFormatContext *avctx, AVStream *st, AVIOContext *pb
write_tiff_long(pb, DNG_WHITE_LEVEL, mlv->white_level);
bytestream2_put_le16(pb, DNG_COLOR_MATRIX1);
- bytestream2_put_le16(pb, TIFF_SRATIONAL);
+ bytestream2_put_le16(pb, AV_TIFF_SRATIONAL);
bytestream2_put_le32(pb, 9);
bytestream2_put_le32(pb, 0); /* matrixofs */
matrixofs = pb->buffer - 4;
-----------------------------------------------------------------------
Summary of changes:
Changelog | 1 +
doc/APIchanges | 17 +
fftools/ffprobe.c | 2 +
libavcodec/Makefile | 1 +
libavcodec/exif.c | 1351 ++++++++++++++++++++++++++++--
libavcodec/exif.h | 201 ++++-
libavcodec/exif_internal.h | 84 ++
libavcodec/libjxldec.c | 78 +-
libavcodec/libjxlenc.c | 65 +-
libavcodec/mjpegdec.c | 91 +-
libavcodec/mjpegdec.h | 3 +-
libavcodec/pngdec.c | 143 ++++
libavcodec/pngenc.c | 16 +
libavcodec/tiff.c | 48 +-
libavcodec/tiff_common.c | 8 +-
libavcodec/tiff_common.h | 18 +-
libavcodec/tiffenc.c | 46 +-
libavcodec/version.h | 2 +-
libavcodec/version_major.h | 1 +
libavcodec/webp.c | 30 +-
libavformat/avidec.c | 14 +-
libavformat/mlvdec.c | 10 +-
libavutil/frame.h | 6 +
libavutil/side_data.c | 1 +
libavutil/version.h | 2 +-
tests/ref/fate/cover-art-mp3-id3v2-remux | 6 +-
tests/ref/fate/exif-image-embedded | 6 +-
tests/ref/fate/exif-image-jpg | 329 ++++----
tests/ref/fate/exif-image-tiff | 25 +-
tests/ref/fate/exif-image-webp | 329 ++++----
tests/ref/fate/mov-cover-image | 6 +-
31 files changed, 2323 insertions(+), 617 deletions(-)
create mode 100644 libavcodec/exif_internal.h
hooks/post-receive
--
More information about the ffmpeg-cvslog
mailing list