From: Leo Izen via ffmpeg-devel <ffmpeg-devel@ffmpeg.org> To: ffmpeg-devel@ffmpeg.org Cc: Leo Izen <code@ffmpeg.org> Subject: [FFmpeg-devel] [PATCH] avcodec/libjxldec: submit frame after file is complete (PR #20337) Date: Mon, 25 Aug 2025 17:58:52 +0300 (EEST) Message-ID: <20250825145852.9F9DB68C904@ffbox0-bg.ffmpeg.org> (raw) 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".
reply other threads:[~2025-08-25 14:58 UTC|newest] Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20250825145852.9F9DB68C904@ffbox0-bg.ffmpeg.org \ --to=ffmpeg-devel@ffmpeg.org \ --cc=code@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
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