From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 414534BCDE for ; Mon, 25 Aug 2025 14:58:55 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 9F9DB68C904; Mon, 25 Aug 2025 17:58:52 +0300 (EEST) Received: from 0f4167fb2350 (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 51B7F68016E for ; Mon, 25 Aug 2025 17:58:51 +0300 (EEST) MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] =?utf-8?q?=5BPATCH=5D_avcodec/libjxldec=3A_submit?= =?utf-8?q?_frame_after_file_is_complete_=28PR_=2320337=29?= X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Leo Izen via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: Leo Izen Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Message-Id: <20250825145852.9F9DB68C904@ffbox0-bg.ffmpeg.org> Date: Mon, 25 Aug 2025 17:58:52 +0300 (EEST) Archived-At: List-Archive: List-Post: 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 >From cbb8fd2c12d56ad4a937ed1c637b5829b29b0aa3 Mon Sep 17 00:00:00 2001 From: Leo Izen 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 --- 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".