FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/jpegmpfdec.c
Date: 2026-10-29 07:45:57
Exec Total Coverage
Lines: 180 275 65.5%
Functions: 9 10 90.0%
Branches: 115 233 49.4%

Line Branch Exec Source
1 /*
2 * JPEG Multi-Picture Format demuxer
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * @file
23 * Demuxer for the JPEG Multi-Picture Format file format.
24 */
25
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "libavutil/intreadwrite.h"
30 #include "libavutil/gain_map.h"
31 #include "libavutil/mem.h"
32 #include "libavutil/opt.h"
33
34 #include "libavcodec/bytestream.h"
35 #include "libavcodec/exif.h"
36 #include "libavcodec/mjpeg.h"
37
38 #include "avformat.h"
39 #include "demux.h"
40 #include "jpegmpf.h"
41 #include "internal.h"
42
43 typedef struct MPFDemuxImage {
44 AVStream *st;
45 int64_t start;
46 int64_t size;
47
48 uint32_t type;
49 int index; /* of the corresponding MP Entry */
50 int child; /* is a dependent image */
51 } MPFDemuxImage;
52
53 typedef struct MPFDemuxContext {
54 const AVClass *class;
55 AVRational framerate;
56 int loop;
57
58 MPFDemuxImage *images; /* sorted by position in the file */
59 int nb_images;
60
61 int cur_image; /* index of next image to return */
62 int64_t pts; /* number of times all images were returned */
63 int has_gain_map; /* the primary image carries ISO 21496-1 metadata */
64 int passthrough; /* not an MPF file */
65 int first_img; /* index of the first individual image */
66
67 /* image data parsed during mpf_read_header() */
68 AVPacket *initial;
69 int64_t file_start;
70 } MPFDemuxContext;
71
72 #define CHECK_IDENT(strc) (len >= sizeof(strc) && !memcmp(gb->buffer, strc, sizeof(strc)))
73
74 8353 static int mpf_probe(const AVProbeData *p)
75 {
76 8353 GetByteContext gbc, *gb = &gbc;
77 8353 bytestream2_init(gb, p->buf, p->buf_size);
78
2/2
✓ Branch 1 taken 8321 times.
✓ Branch 2 taken 32 times.
8353 if (bytestream2_get_be16(gb) != (0xFF00 | SOI))
79 8321 return 0;
80
81
1/2
✓ Branch 1 taken 147 times.
✗ Branch 2 not taken.
147 while (bytestream2_get_bytes_left(gb) >= 4) {
82
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
147 if (bytestream2_get_byte(gb) != 0xFF)
83 ✗ return 0; /* not a JPEG marker */
84
85 147 int marker = bytestream2_get_byte(gb);
86
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
147 while (marker == 0xFF && bytestream2_get_bytes_left(gb) > 0) /* fill bytes */
87 ✗ marker = bytestream2_get_byte(gb);
88
89
6/8
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 127 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 90 times.
✓ Branch 5 taken 37 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 90 times.
147 if (marker == SOS || marker == TEM || (marker >= RST0 && marker <= EOI))
90 20 return 0; /* start of entropy coded data */
91
2/4
✓ Branch 0 taken 127 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 127 times.
✗ Branch 4 not taken.
127 else if (marker == 0xFF || bytestream2_get_bytes_left(gb) < 2)
92 break; /* need more data */
93
94 127 const int len = bytestream2_get_be16(gb) - 2;
95
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 127 times.
127 if (len < 0)
96 ✗ return 0;
97
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 117 times.
127 if (len > bytestream2_get_bytes_left(gb))
98 10 break;
99
100
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 110 times.
117 switch (marker) {
101 7 case APP2:
102 /* Marker segments unique to MPF files; the base image of a gain
103 * map file only carries the version fields of the ISO 21496-1
104 * metadata (two 16-bit integers); we don't want to false positive
105 * on standalone JPEGs that happen to contain gain map metadata */
106
3/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 2 times.
7 if (CHECK_IDENT("MPF") ||
107
2/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
5 (CHECK_IDENT(AV_ISO21496_IDENTIFIER) &&
108 len == sizeof(AV_ISO21496_IDENTIFIER) + sizeof(uint16_t[2])))
109 2 return AVPROBE_SCORE_EXTENSION + 10;
110 5 break;
111 }
112
113 115 bytestream2_skip(gb, len);
114 }
115
116 /* If we ran out of probe bytes, we don't know yet whether this is an
117 * MPF file or not, unless the extension is unique to MPF files */
118
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (av_match_ext(p->filename, "mpo"))
119 ✗ return AVPROBE_SCORE_EXTENSION + 5;
120
121 10 return AVERROR(EAGAIN);
122 }
123
124 /* returns the offset of the first byte read */
125 62 static int grow_header(AVFormatContext *ctx, int size)
126 {
127 62 MPFDemuxContext *s = ctx->priv_data;
128 62 const int pos = s->initial->size;
129 62 int ret = av_append_packet(ctx->pb, s->initial, size);
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (ret < 0)
131 ✗ return ret;
132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (ret < size)
133 ✗ return AVERROR_INVALIDDATA;
134 62 return pos;
135 }
136
137 2 static int parse_mp_index(AVFormatContext *ctx, const uint8_t *data, int len,
138 int64_t tiff_base)
139 {
140 2 MPFDemuxContext *s = ctx->priv_data;
141 2 AVExifMetadata ifd = { 0 };
142 2 AVExifEntry *num = NULL, *entries = NULL;
143 int ret;
144
145
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (len < 2)
146 ✗ return AVERROR_INVALIDDATA;
147 2 const int le = AV_RB16(data) == AV_RB16("II");
148
149 2 ret = av_exif_parse_buffer(ctx, data, len, &ifd, AV_EXIF_TIFF_HEADER);
150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
151 ✗ return ret;
152
153
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 if ((ret = av_exif_get_entry(ctx, &ifd, MPF_TAG_NumberOfImages, 0, &num)) < 0 ||
154 2 (ret = av_exif_get_entry(ctx, &ifd, MPF_TAG_MPEntry, 0, &entries)) < 0)
155 ✗ goto fail;
156
157
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 if (!num || num->type != AV_TIFF_LONG || num->count != 1 ||
158
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 !entries || entries->type != AV_TIFF_UNDEFINED) {
159 ✗ av_log(ctx, AV_LOG_ERROR, "Missing or malformed MP Index IFD\n");
160 ✗ ret = AVERROR_INVALIDDATA;
161 ✗ goto fail;
162 }
163
164 /* The spec asserts MPEntry count = 16 * NumberOfImages, but tolerate
165 * excess entries, e.g. left behind after deleting an image */
166 2 const int entry_size = sizeof(uint32_t[3]) + sizeof(uint16_t[2]);
167 2 const uint64_t nb_images = num->value.uint[0];
168
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!nb_images || nb_images * entry_size > entries->count) {
169 ✗ av_log(ctx, AV_LOG_ERROR, "Invalid number of images: %"PRIu64"\n",
170 nb_images);
171 ✗ ret = AVERROR_INVALIDDATA;
172 ✗ goto fail;
173 }
174
175 2 s->images = av_calloc(nb_images, sizeof(*s->images));
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!s->images) {
177 ✗ ret = AVERROR(ENOMEM);
178 ✗ goto fail;
179 }
180
181 GetByteContext gb;
182 2 bytestream2_init(&gb, entries->value.ubytes, entries->count);
183
184 2 const int explode = ctx->error_recognition & AV_EF_EXPLODE;
185
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < nb_images; i++) {
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 const uint32_t attr = le ? bytestream2_get_le32u(&gb) : bytestream2_get_be32u(&gb);
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 const uint32_t size = le ? bytestream2_get_le32u(&gb) : bytestream2_get_be32u(&gb);
188
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 const uint32_t offset = le ? bytestream2_get_le32u(&gb) : bytestream2_get_be32u(&gb);
189 4 bytestream2_skipu(&gb, sizeof(uint16_t[2])); /* dependent images */
190
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if ((attr & MPF_ATTR_FORMAT_MASK) != MPF_FORMAT_JPEG) {
192 ✗ const int fatal = !offset || explode;
193 ✗ av_log(ctx, fatal ? AV_LOG_ERROR : AV_LOG_WARNING, "Image %d: "
194 "unsupported format 0x%"PRIx32"\n", i,
195 attr & MPF_ATTR_FORMAT_MASK);
196 ✗ if (fatal) {
197 ✗ ret = AVERROR_INVALIDDATA;
198 ✗ goto fail;
199 }
200 ✗ continue;
201 }
202
203 /* the First Individual Image is identified by an offset of 0 */
204
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (!offset) {
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (s->first_img >= 0) {
206 ✗ av_log(ctx, AV_LOG_ERROR, "Duplicate First Individual Image\n");
207 ✗ ret = AVERROR_INVALIDDATA;
208 ✗ goto fail;
209 }
210 2 s->first_img = s->nb_images;
211 }
212
213 4 MPFDemuxImage *img = &s->images[s->nb_images++];
214 4 img->index = i;
215
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 img->start = offset ? tiff_base + offset : s->file_start;
216 4 img->size = size;
217 4 img->type = attr & MPF_ATTR_TYPE_MASK;
218 4 img->child = !!(attr & MPF_DEPENDENT_CHILD);
219 }
220
221 2 ret = 0;
222 2 fail:
223 2 av_exif_free(&ifd);
224 2 return ret;
225 }
226
227 4 static int parse_app2(AVFormatContext *ctx, int offset, int len)
228 {
229 4 MPFDemuxContext *s = ctx->priv_data;
230 4 GetByteContext gbc, *gb = &gbc;
231 4 bytestream2_init(gb, s->initial->data + offset, len);
232
233
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (CHECK_IDENT(AV_ISO21496_IDENTIFIER)) {
234 2 s->has_gain_map = 1;
235
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 } else if (CHECK_IDENT("MPF")) {
236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (s->images) {
237 ✗ const int explode = ctx->error_recognition & AV_EF_EXPLODE;
238 ✗ av_log(ctx, explode ? AV_LOG_ERROR : AV_LOG_WARNING,
239 "Duplicate MP Index IFD\n");
240 ✗ return explode ? AVERROR_INVALIDDATA : 0;
241 }
242
243 /* MP offsets are relative to the start of the TIFF header */
244 2 const int skip = sizeof("MPF");
245 2 return parse_mp_index(ctx, gb->buffer + skip, len - skip,
246 2 s->file_start + offset + skip);
247 }
248
249 2 return 0;
250 }
251
252 2 static int cmp_image_start(const void *a, const void *b)
253 {
254 2 const MPFDemuxImage *ia = a, *ib = b;
255
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (ia->start != ib->start)
256 2 return FFDIFFSIGN(ia->start, ib->start);
257 ✗ return FFDIFFSIGN(ia->index, ib->index);
258 }
259
260 2 static int add_gain_map_group(AVFormatContext *ctx, const MPFDemuxImage *base,
261 const MPFDemuxImage *gain)
262 {
263
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (gain == base) {
264 ✗ av_log(ctx, AV_LOG_ERROR, "The first image cannot be a gain map\n");
265 ✗ return AVERROR_INVALIDDATA;
266 }
267
268 2 AVStreamGroup *stg = avformat_stream_group_create(ctx, AV_STREAM_GROUP_PARAMS_GAIN_MAP, NULL);
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!stg)
270 ✗ return AVERROR(ENOMEM);
271
272 int ret;
273
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 if ((ret = avformat_stream_group_add_stream(stg, base->st)) < 0 ||
274 2 (ret = avformat_stream_group_add_stream(stg, gain->st)) < 0)
275 ✗ return ret;
276
277 2 stg->params.layered_video->el_index = stg->nb_entries - 1;
278 2 return 0;
279 }
280
281 2 static int mpf_read_header(AVFormatContext *ctx)
282 {
283 2 MPFDemuxContext *s = ctx->priv_data;
284 int ret;
285
286 2 s->first_img = -1;
287 2 s->initial = av_packet_alloc();
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!s->initial)
289 ✗ return AVERROR(ENOMEM);
290
291 2 s->file_start = avio_tell(ctx->pb);
292
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = grow_header(ctx, 2)) < 0)
293 ✗ return ret;
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (AV_RB16(s->initial->data) != (0xFF00 | SOI))
295 ✗ return AVERROR_INVALIDDATA;
296
297 /* Walk the JPEG marker sections up until the start of entropy coded data */
298 14 for (;;) {
299 16 const int pos = s->initial->size;
300
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((ret = grow_header(ctx, 1)) < 0)
301 ✗ return ret;
302 16 uint8_t marker = s->initial->data[ret];
303
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (marker != 0xFF) {
304 ✗ av_log(ctx, AV_LOG_ERROR, "Malformed JPEG marker at 0x%x\n", pos);
305 ✗ return AVERROR_INVALIDDATA;
306 }
307
308
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 while (marker == 0xFF) {
309
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((ret = grow_header(ctx, 1)) < 0)
310 ✗ return ret;
311 16 marker = s->initial->data[ret];
312 }
313
314
6/8
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
16 if (marker == SOS || marker == TEM || (marker >= RST0 && marker <= EOI))
315 break; /* start of entropy coded data */
316
317
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if ((ret = grow_header(ctx, 2)) < 0)
318 ✗ return ret;
319 14 const int len = AV_RB16(s->initial->data + ret) - 2;
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (len < 0) {
321 ✗ av_log(ctx, AV_LOG_ERROR, "Invalid marker segment at 0x%x\n", pos);
322 ✗ return AVERROR_INVALIDDATA;
323 }
324
325
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if ((ret = grow_header(ctx, len)) < 0)
326 ✗ return ret;
327
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
14 if (marker == APP2 && (ret = parse_app2(ctx, ret, len)) < 0)
328 ✗ return ret;
329 }
330
331
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!s->images) {
332 /* Not an MPF file, just return as-is to the underlying decoder */
333 ✗ const int64_t file_size = avio_size(ctx->pb);
334 ✗ s->passthrough = file_size <= s->file_start;
335 ✗ s->first_img = 0;
336 ✗ s->nb_images = 1;
337 ✗ s->images = av_malloc(sizeof(*s->images));
338 ✗ if (!s->images)
339 ✗ return AVERROR(ENOMEM);
340 ✗ s->images[0] = (MPFDemuxImage){
341 .type = MPF_TYPE_PRIMARY,
342 ✗ .start = s->file_start,
343 ✗ .size = s->passthrough ? s->initial->size
344 ✗ : file_size - s->file_start,
345 };
346 }
347
348
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (s->first_img < 0) {
349 ✗ av_log(ctx, AV_LOG_ERROR, "No MP Entry for the first image\n");
350 ✗ return AVERROR_INVALIDDATA;
351 }
352
353 /* Compatibility hack for libultrahdr, which produces invalid MPF files
354 * whose gain maps have no MP type. Needed to support a large number
355 * of JPEG Ultra HDR images in the wild */
356 2 const MPFDemuxImage *first = &s->images[s->first_img];
357
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (ctx->strict_std_compliance < FF_COMPLIANCE_STRICT &&
358
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 s->has_gain_map && s->nb_images == 2)
359 {
360 2 MPFDemuxImage *other = &s->images[1 - s->first_img];
361
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (other->type == MPF_TYPE_UNDEFINED)
362 ✗ other->type = MPF_TYPE_GAIN_MAP;
363 }
364
365
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < s->nb_images; i++) {
366 4 s->images[i].st = avformat_new_stream(ctx, NULL);
367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!s->images[i].st)
368 ✗ return AVERROR(ENOMEM);
369 }
370
371
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < s->nb_images; i++) {
372 4 MPFDemuxImage *img = &s->images[i];
373 4 AVStream *st = img->st;
374
375 4 st->id = img->index;
376 4 st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
377 4 st->codecpar->codec_id = AV_CODEC_ID_MJPEG;
378
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (img == first)
379 2 st->disposition |= AV_DISPOSITION_DEFAULT;
380
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 if (img->child || mpf_type_is_child(img->type))
381 2 st->disposition |= AV_DISPOSITION_DEPENDENT;
382 4 avpriv_set_pts_info(st, 64, s->framerate.den, s->framerate.num);
383 4 st->avg_frame_rate = st->r_frame_rate = s->framerate;
384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (s->passthrough) /* we don't know the image size */
385 ✗ ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL;
386
387
2/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 switch (img->type & MPF_TYPE_INFO_MASK) {
388 2 case MPF_TYPE_GAIN_MAP:
389 2 ret = add_gain_map_group(ctx, first, img);
390
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
391 ✗ return ret;
392 2 break;
393 2 case MPF_TYPE_LARGE_THUMBNAIL:
394 case MPF_TYPE_MULTI_FRAME:
395 case MPF_TYPE_PRIMARY:
396 case MPF_TYPE_ORIGINAL:
397 /* no special handling */
398 2 break;
399 ✗ case MPF_TYPE_UNDEFINED:
400 default:
401 ✗ av_log(ctx, img->child ? AV_LOG_VERBOSE : AV_LOG_WARNING,
402 "Image %d: unhandled MP type 0x%06"PRIx32", treating as an "
403 "%s image\n", img->index, img->type,
404 ✗ img->child ? "unspecified dependent" : "unrelated");
405 ✗ break;
406 }
407 }
408
409 /* Sort the images array by file offset for mpf_read_packet() */
410 2 qsort(s->images, s->nb_images, sizeof(*s->images), cmp_image_start);
411 2 s->first_img = 0; /* by construction */
412
413
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (int i = 0; i < s->nb_images - 1; i++) {
414 2 MPFDemuxImage *img = &s->images[i], *next = &s->images[i + 1];
415
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (next->start < img->start + img->size) {
416 ✗ const int explode = ctx->error_recognition & AV_EF_EXPLODE;
417 ✗ av_log(ctx, explode ? AV_LOG_ERROR : AV_LOG_WARNING,
418 "Image %d overlaps image %d%s\n", img->index, next->index,
419 explode ? "" : ", truncating");
420 ✗ if (explode)
421 ✗ return AVERROR_INVALIDDATA;
422 ✗ img->size = next->start - img->start;
423 }
424 }
425
426 2 return 0;
427 }
428
429 6 static int mpf_read_packet(AVFormatContext *ctx, AVPacket *pkt)
430 {
431 6 MPFDemuxContext *s = ctx->priv_data;
432 6 AVIOContext *pb = ctx->pb;
433 int ret;
434
435
3/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (s->cur_image >= s->nb_images && s->loop && !s->passthrough) {
436 ✗ s->cur_image = 0;
437 ✗ s->pts++;
438 }
439
440
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (s->cur_image >= s->nb_images) {
441
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!s->passthrough)
442 2 return AVERROR_EOF;
443
444 ✗ if ((ret = av_get_packet(pb, pkt, 4096)) < 0)
445 ✗ return ret;
446 ✗ pkt->flags &= ~AV_PKT_FLAG_CORRUPT; /* short read at EOF */
447 ✗ pkt->stream_index = 0;
448 ✗ return 0;
449 }
450
451 4 const MPFDemuxImage *img = &s->images[s->cur_image++];
452
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (img->size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
453 ✗ return AVERROR(ERANGE);
454
455
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (s->initial) {
456 2 av_packet_move_ref(pkt, s->initial);
457 2 av_packet_free(&s->initial);
458 2 av_shrink_packet(pkt, FFMIN(pkt->size, img->size));
459 } else {
460 2 int64_t pos = avio_seek(pb, img->start, SEEK_SET);
461
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (pos < 0) {
462 ✗ av_log(ctx, AV_LOG_ERROR, "Failed seeking to image %d at offset "
463 ✗ "%"PRId64"\n", img->st->id, img->start);
464 ✗ return pos;
465 }
466 }
467
468
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (pkt->size < img->size) {
469 4 ret = av_append_packet(pb, pkt, img->size - pkt->size);
470
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (ret < 0 && !pkt->size)
471 ✗ return ret;
472 }
473
474 4 pkt->stream_index = img->st->index;
475 4 pkt->pts = pkt->dts = s->pts;
476 4 pkt->duration = 1;
477 4 pkt->flags |= AV_PKT_FLAG_KEY;
478 4 return 0;
479 }
480
481 ✗ static int mpf_read_seek(AVFormatContext *ctx, int stream_index,
482 int64_t timestamp, int flags)
483 {
484 ✗ MPFDemuxContext *s = ctx->priv_data;
485 ✗ if (s->passthrough || timestamp < 0 || (!s->loop && timestamp > 0))
486 ✗ return -1;
487
488 /* Don't seek if the initial packet is still pending; since this would
489 * desynchronize the input stream otherwise */
490 ✗ if (!s->initial) {
491 ✗ int64_t pos = avio_seek(ctx->pb, s->images[0].start, SEEK_SET);
492 ✗ if (pos < 0)
493 ✗ return pos;
494 }
495
496 ✗ s->cur_image = 0;
497 ✗ s->pts = timestamp;
498 ✗ return 0;
499 }
500
501 2 static int mpf_read_close(AVFormatContext *ctx)
502 {
503 2 MPFDemuxContext *s = ctx->priv_data;
504 2 av_freep(&s->images);
505 2 av_packet_free(&s->initial);
506 2 return 0;
507 }
508
509 #define OFFSET(x) offsetof(MPFDemuxContext, x)
510 #define DEC AV_OPT_FLAG_DECODING_PARAM
511 static const AVOption mpf_options[] = {
512 { "framerate", "set the video framerate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, DEC },
513 { "loop", "loop over the images in the file", OFFSET(loop), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
514 { NULL },
515 };
516
517 static const AVClass mpf_class = {
518 .class_name = "jpeg_mpf demuxer",
519 .item_name = av_default_item_name,
520 .option = mpf_options,
521 .version = LIBAVUTIL_VERSION_INT,
522 };
523
524 const FFInputFormat ff_jpeg_mpf_demuxer = {
525 .p.name = "jpeg_mpf",
526 .p.long_name = NULL_IF_CONFIG_SMALL("JPEG Multi-Picture Format"),
527 .p.priv_class = &mpf_class,
528 .p.extensions = "mpo",
529 .priv_data_size = sizeof(MPFDemuxContext),
530 .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP | FF_INFMT_FLAG_ID3V2_AUTO,
531 .read_probe = mpf_probe,
532 .read_header = mpf_read_header,
533 .read_packet = mpf_read_packet,
534 .read_close = mpf_read_close,
535 .read_seek = mpf_read_seek,
536 };
537