From a5fac7bf5ac95c9b24b37b2b6c7a3bf42de749ec Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Sun, 4 Oct 2026 19:21:09 +0200 Subject: [PATCH] avformat/jpegmpfdec: add -loop and -framerate options Otherwise this demuxer would regress relative to image2 for basic JPEG inputs that happen to contain an HDR gain map, which are often produced by e.g. smartphones. Signed-off-by: Niklas Haas --- doc/demuxers.texi | 10 ++++++++++ libavformat/jpegmpfdec.c | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/doc/demuxers.texi b/doc/demuxers.texi index 5b56e8b476..bc8116d9d2 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -760,6 +760,16 @@ frame side data by the decoder. Images of any other type (e.g. large thumbnails or multi-view images) are exposed as additional streams. +This demuxer accepts the following options, which behave like the options of +the same name of the @samp{image2} demuxer: +@table @option +@item framerate +Set the frame rate of the video streams. The default is 25. +@item loop +If set to 1, loop over the images in the file. The default is 0. This requires +seekable input. +@end table + @section ktx Khronos Texture 1.0 demuxer for ASTC textures. diff --git a/libavformat/jpegmpfdec.c b/libavformat/jpegmpfdec.c index 8dad0de780..b5b72b58cb 100644 --- a/libavformat/jpegmpfdec.c +++ b/libavformat/jpegmpfdec.c @@ -29,6 +29,7 @@ #include "libavutil/intreadwrite.h" #include "libavutil/gain_map.h" #include "libavutil/mem.h" +#include "libavutil/opt.h" #include "libavcodec/bytestream.h" #include "libavcodec/exif.h" @@ -50,10 +51,15 @@ typedef struct MPFDemuxImage { } MPFDemuxImage; typedef struct MPFDemuxContext { + const AVClass *class; + AVRational framerate; + int loop; + MPFDemuxImage *images; /* sorted by position in the file */ int nb_images; int cur_image; /* index of next image to return */ + int64_t pts; /* number of times all images were returned */ int has_gain_map; /* the primary image carries ISO 21496-1 metadata */ int passthrough; /* not an MPF file */ int first_img; /* index of the first individual image */ @@ -373,7 +379,8 @@ static int mpf_read_header(AVFormatContext *ctx) st->disposition |= AV_DISPOSITION_DEFAULT; if (img->child || mpf_type_is_child(img->type)) st->disposition |= AV_DISPOSITION_DEPENDENT; - avpriv_set_pts_info(st, 64, 1, 25); + avpriv_set_pts_info(st, 64, s->framerate.den, s->framerate.num); + st->avg_frame_rate = st->r_frame_rate = s->framerate; if (s->passthrough) /* we don't know the image size */ ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL; @@ -425,6 +432,11 @@ static int mpf_read_packet(AVFormatContext *ctx, AVPacket *pkt) AVIOContext *pb = ctx->pb; int ret; + if (s->cur_image >= s->nb_images && s->loop && !s->passthrough) { + s->cur_image = 0; + s->pts++; + } + if (s->cur_image >= s->nb_images) { if (!s->passthrough) return AVERROR_EOF; @@ -460,7 +472,7 @@ static int mpf_read_packet(AVFormatContext *ctx, AVPacket *pkt) } pkt->stream_index = img->st->index; - pkt->pts = pkt->dts = 0; + pkt->pts = pkt->dts = s->pts; pkt->duration = 1; pkt->flags |= AV_PKT_FLAG_KEY; return 0; @@ -470,7 +482,7 @@ static int mpf_read_seek(AVFormatContext *ctx, int stream_index, int64_t timestamp, int flags) { MPFDemuxContext *s = ctx->priv_data; - if (s->passthrough || timestamp != 0) + if (s->passthrough || timestamp < 0 || (!s->loop && timestamp > 0)) return -1; /* Don't seek if the initial packet is still pending; since this would @@ -482,6 +494,7 @@ static int mpf_read_seek(AVFormatContext *ctx, int stream_index, } s->cur_image = 0; + s->pts = timestamp; return 0; } @@ -493,9 +506,25 @@ static int mpf_read_close(AVFormatContext *ctx) return 0; } +#define OFFSET(x) offsetof(MPFDemuxContext, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM +static const AVOption mpf_options[] = { + { "framerate", "set the video framerate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, DEC }, + { "loop", "loop over the images in the file", OFFSET(loop), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC }, + { NULL }, +}; + +static const AVClass mpf_class = { + .class_name = "jpeg_mpf demuxer", + .item_name = av_default_item_name, + .option = mpf_options, + .version = LIBAVUTIL_VERSION_INT, +}; + const FFInputFormat ff_jpeg_mpf_demuxer = { .p.name = "jpeg_mpf", .p.long_name = NULL_IF_CONFIG_SMALL("JPEG Multi-Picture Format"), + .p.priv_class = &mpf_class, .p.extensions = "mpo", .priv_data_size = sizeof(MPFDemuxContext), .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP | FF_INFMT_FLAG_ID3V2_AUTO,