Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avcodec/libjxldec: submit frame after file is complete (PR #20337)
@ 2025-08-25 14:58 Leo Izen via ffmpeg-devel
  0 siblings, 0 replies; only message in thread
From: Leo Izen via ffmpeg-devel @ 2025-08-25 14:58 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Leo Izen

PR #20337 opened by Leo Izen (Traneptora)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20337
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20337.patch

This commit causes the libjxl decoder wrapper to hold onto the decoded
frame until the trailing metadata after the frame is possibly complete
before it submits the frame. This allows EXIF and other metadata boxes
that occur after the frame is fully rendered to be attached to it as
side data.

Signed-off-by: Leo Izen <leo.izen@gmail.com>


From cbb8fd2c12d56ad4a937ed1c637b5829b29b0aa3 Mon Sep 17 00:00:00 2001
From: Leo Izen <leo.izen@gmail.com>
Date: Mon, 25 Aug 2025 10:56:25 -0400
Subject: [PATCH] avcodec/libjxldec: submit frame after file is complete

This commit causes the libjxl decoder wrapper to hold onto the decoded
frame until the trailing metadata after the frame is possibly complete
before it submits the frame. This allows EXIF and other metadata boxes
that occur after the frame is fully rendered to be attached to it as
side data.

Signed-off-by: Leo Izen <leo.izen@gmail.com>
---
 libavcodec/libjxldec.c | 108 ++++++++++++++++++++++++++---------------
 1 file changed, 69 insertions(+), 39 deletions(-)

diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c
index 31f4592d1c..13308ed01d 100644
--- a/libavcodec/libjxldec.c
+++ b/libavcodec/libjxldec.c
@@ -61,6 +61,8 @@ typedef struct LibJxlDecodeContext {
     int prev_is_last;
     AVRational anim_timebase;
     AVFrame *frame;
+    int frame_complete;
+    JxlDecoderStatus jret;
     AVBufferRef *exif;
     size_t exif_pos;
 } LibJxlDecodeContext;
@@ -371,10 +373,56 @@ static int libjxl_color_encoding_event(AVCodecContext *avctx, AVFrame *frame)
     return 0;
 }
 
+static int libjxl_attach_sidedata(AVCodecContext *avctx)
+{
+    LibJxlDecodeContext *ctx = avctx->priv_data;
+    int ret = 0;
+
+    if (ctx->iccp) {
+        ret = ff_frame_new_side_data_from_buf(avctx, ctx->frame, AV_FRAME_DATA_ICC_PROFILE, &ctx->iccp);
+        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: %s\n", av_err2str(ret));
+            return ret;
+        }
+        /*
+         * 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: %s\n", av_err2str(ret));
+        ret = ff_decode_exif_attach_ifd(avctx, ctx->frame, &ifd);
+        if (ret < 0)
+            av_log(avctx, AV_LOG_ERROR, "Unable to attach EXIF ifd: %s\n", av_err2str(ret));
+        av_exif_free(&ifd);
+    }
+
+    return ret;
+}
+
+static int libjxl_finalize_frame(AVCodecContext *avctx, AVFrame *dst, AVFrame *src)
+{
+    LibJxlDecodeContext *ctx = avctx->priv_data;
+    int ret = libjxl_attach_sidedata(avctx);
+    av_frame_move_ref(dst, src);
+    ctx->frame_complete = 0;
+    return ret;
+}
+
 static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
 {
     LibJxlDecodeContext *ctx = avctx->priv_data;
-    JxlDecoderStatus jret = JXL_DEC_SUCCESS;
     int ret;
     AVPacket *pkt = ctx->avpkt;
 
@@ -391,23 +439,26 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
             ctx->frame_duration = 0;
             if (!pkt->size) {
                 /* jret set by the last iteration of the loop */
-                if (jret == JXL_DEC_NEED_MORE_INPUT) {
+                if (ctx->jret == JXL_DEC_NEED_MORE_INPUT && !ctx->frame_complete) {
                     av_log(avctx, AV_LOG_ERROR, "Unexpected end of JXL codestream\n");
                     return AVERROR_INVALIDDATA;
-                } else {
-                    return AVERROR_EOF;
+                } else if (ctx->frame_complete) {
+                    libjxl_finalize_frame(avctx, frame, ctx->frame);
+                    ctx->jret = JXL_DEC_SUCCESS;
+                    return 0;
                 }
+                return AVERROR_EOF;
             }
         }
 
-        jret = JxlDecoderSetInput(ctx->decoder, pkt->data, pkt->size);
-        if (jret == JXL_DEC_ERROR) {
+        ctx->jret = JxlDecoderSetInput(ctx->decoder, pkt->data, pkt->size);
+        if (ctx->jret == JXL_DEC_ERROR) {
             /* this should never happen here unless there's a bug in libjxl */
             av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n");
             return AVERROR_EXTERNAL;
         }
 
-        jret = JxlDecoderProcessInput(ctx->decoder);
+        ctx->jret = JxlDecoderProcessInput(ctx->decoder);
         /*
          * JxlDecoderReleaseInput returns the number
          * of bytes remaining to be read, rather than
@@ -417,7 +468,7 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
         pkt->data += pkt->size - remaining;
         pkt->size = remaining;
 
-        switch(jret) {
+        switch(ctx->jret) {
         case JXL_DEC_ERROR:
             av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n");
             return AVERROR_INVALIDDATA;
@@ -493,33 +544,6 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
         case JXL_DEC_FULL_IMAGE:
             /* full image is one frame, even if animated */
             av_log(avctx, AV_LOG_DEBUG, "FULL_IMAGE event emitted\n");
-            if (ctx->iccp) {
-                ret = ff_frame_new_side_data_from_buf(avctx, ctx->frame, AV_FRAME_DATA_ICC_PROFILE, &ctx->iccp);
-                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_decode_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);
@@ -531,8 +555,13 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
                 ctx->frame->pts += pkt->pts;
             ctx->accumulated_pts += ctx->frame_duration;
             ctx->frame->pkt_dts = pkt->dts;
-            av_frame_move_ref(frame, ctx->frame);
-            return 0;
+            if (ctx->basic_info.have_animation && !ctx->prev_is_last) {
+                libjxl_finalize_frame(avctx, frame, ctx->frame);
+                return 0;
+            } else {
+                ctx->frame_complete = 1;
+                continue;
+            }
         case JXL_DEC_SUCCESS:
             av_log(avctx, AV_LOG_DEBUG, "SUCCESS event emitted\n");
             /*
@@ -541,9 +570,10 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
              * but it will also be fired when the next image of
              * an image2pipe sequence is loaded up
              */
+            libjxl_finalize_frame(avctx, frame, ctx->frame);
             JxlDecoderReset(ctx->decoder);
             libjxl_init_jxl_decoder(avctx);
-            continue;
+            return 0;
         case JXL_DEC_BOX: {
             char type[4];
             av_log(avctx, AV_LOG_DEBUG, "BOX event emitted\n");
@@ -587,7 +617,7 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame)
             continue;
         }
         default:
-             av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret);
+             av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", ctx->jret);
              return AVERROR_EXTERNAL;
         }
     }
-- 
2.49.1

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

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

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-08-25 14:58 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-08-25 14:58 [FFmpeg-devel] [PATCH] avcodec/libjxldec: submit frame after file is complete (PR #20337) Leo Izen via ffmpeg-devel

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

This inbox may be cloned and mirrored by anyone:

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

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

Example config snippet for mirrors.


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