FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/img2enc.c
Date: 2026-10-29 23:19:09
Exec Total Coverage
Lines: 90 202 44.6%
Functions: 6 7 85.7%
Branches: 50 150 33.3%

Line Branch Exec Source
1 /*
2 * Image format
3 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
4 * Copyright (c) 2004 Michael Niedermayer
5 *
6 * This file is part of FFmpeg.
7 *
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 #include <time.h>
24 #ifdef _WIN32
25 #include <sys/utime.h>
26 #else
27 #include <sys/time.h>
28 #endif
29
30 #include "config_components.h"
31
32 #include "libavutil/avutil.h"
33 #include "libavutil/intreadwrite.h"
34 #include "libavutil/avstring.h"
35 #include "libavutil/bprint.h"
36 #include "libavutil/dict.h"
37 #include "libavutil/log.h"
38 #include "libavutil/mathematics.h"
39 #include "libavutil/mem.h"
40 #include "libavutil/opt.h"
41 #include "libavutil/parseutils.h"
42 #include "libavutil/pixdesc.h"
43 #include "libavutil/time_internal.h"
44 #include "avformat.h"
45 #include "avio_internal.h"
46 #include "internal.h"
47 #include "img2.h"
48 #include "mux.h"
49
50 typedef struct VideoMuxData {
51 const AVClass *class; /**< Class for private options. */
52 int start_img_number;
53 int img_number;
54 int split_planes; /**< use independent file for each Y, U, V plane */
55 int update;
56 int use_strftime;
57 int frame_pts;
58 const char *muxer;
59 int use_rename;
60 AVDictionary *protocol_opts;
61 int update_filemtime;
62 int64_t creation_ts; /**< creation_time in microseconds since epoch */
63 } VideoMuxData;
64
65 3 static void set_file_mtime(AVFormatContext *s, const char *path, int64_t ts_us)
66 {
67 3 int64_t sec = ts_us / 1000000;
68 3 int64_t usec = ts_us % 1000000;
69
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (usec < 0) {
71 ✗ sec--;
72 ✗ usec += 1000000;
73 }
74
75 #ifdef _WIN32
76 struct _utimbuf ut;
77 ut.actime = sec;
78 ut.modtime = sec;
79 if (_utime(path, &ut) < 0)
80 #else
81 3 struct timeval times[2] = {
82 { .tv_sec = sec, .tv_usec = usec },
83 { .tv_sec = sec, .tv_usec = usec },
84 };
85
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (utimes(path, times) < 0)
86 #endif
87 ✗ av_log(s, AV_LOG_WARNING,
88 "Failed to set file modification time for %s\n", path);
89 3 }
90
91 70 static int write_header(AVFormatContext *s)
92 {
93 70 VideoMuxData *img = s->priv_data;
94 70 AVStream *st = s->streams[0];
95 70 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(st->codecpar->format);
96
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (st->codecpar->codec_id == AV_CODEC_ID_GIF) {
98 ✗ img->muxer = "gif";
99
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 } else if (st->codecpar->codec_id == AV_CODEC_ID_FITS) {
100 ✗ img->muxer = "fits";
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 } else if (st->codecpar->codec_id == AV_CODEC_ID_AV1) {
102 ✗ img->muxer = "avif";
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 } else if (st->codecpar->codec_id == AV_CODEC_ID_RAWVIDEO) {
104 ✗ const char *str = strrchr(s->url, '.');
105 ✗ img->split_planes = str
106 ✗ && !av_strcasecmp(str + 1, "y")
107 ✗ && s->nb_streams == 1
108 ✗ && desc
109 /* Semi-planar formats such as NV12 keep several
110 * components in one plane, so splitting per
111 * component needs one plane per component.
112 */
113 ✗ && av_pix_fmt_count_planes(st->codecpar->format)
114 ✗ == desc->nb_components
115 ✗ && desc->nb_components >= 3;
116 }
117 70 img->img_number = img->start_img_number;
118
119
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 69 times.
70 if (img->update_filemtime) {
120 1 const char *proto = avio_find_protocol_name(s->url);
121 AVDictionaryEntry *entry;
122 int64_t parsed_ts;
123
124
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!proto || strcmp(proto, "file")) {
125 ✗ av_log(s, AV_LOG_WARNING,
126 "update_filemtime is only supported for local files, "
127 "it will be ignored\n");
128 ✗ img->update_filemtime = 0;
129 } else {
130 1 entry = av_dict_get(s->metadata, "creation_time", NULL, 0);
131
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (!entry || av_parse_time(&parsed_ts, entry->value, 0) < 0) {
132 ✗ av_log(s, AV_LOG_WARNING,
133 "No valid creation_time metadata found, "
134 "update_filemtime will be ignored\n");
135 ✗ img->update_filemtime = 0;
136 } else {
137 1 img->creation_ts = parsed_ts;
138 }
139 }
140 }
141
142 70 return 0;
143 }
144
145 ✗ static int write_muxed_file(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt)
146 {
147 ✗ VideoMuxData *img = s->priv_data;
148 ✗ AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
149 AVStream *st;
150 ✗ AVPacket *const pkt2 = ffformatcontext(s)->pkt;
151 ✗ AVFormatContext *fmt = NULL;
152 int ret;
153
154 /* URL is not used directly as we are overriding the IO context later. */
155 ✗ ret = avformat_alloc_output_context2(&fmt, NULL, img->muxer, s->url);
156 ✗ if (ret < 0)
157 ✗ return ret;
158 ✗ st = avformat_new_stream(fmt, NULL);
159 ✗ if (!st) {
160 ✗ ret = AVERROR(ENOMEM);
161 ✗ goto out;
162 }
163 ✗ st->id = pkt->stream_index;
164
165 ✗ fmt->pb = pb;
166
167 ✗ ret = av_packet_ref(pkt2, pkt);
168 ✗ if (ret < 0)
169 ✗ goto out;
170 ✗ pkt2->stream_index = 0;
171
172 ✗ if ((ret = avcodec_parameters_copy(st->codecpar, par)) < 0 ||
173 ✗ (ret = avformat_write_header(fmt, NULL)) < 0 ||
174 ✗ (ret = av_interleaved_write_frame(fmt, pkt2)) < 0 ||
175 ✗ (ret = av_write_trailer(fmt))) {}
176
177 ✗ av_packet_unref(pkt2);
178 ✗ out:
179 ✗ avformat_free_context(fmt);
180 ✗ return ret;
181 }
182
183 151 static int write_packet_pipe(AVFormatContext *s, AVPacket *pkt)
184 {
185 151 VideoMuxData *img = s->priv_data;
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 151 times.
151 if (img->muxer) {
187 ✗ int ret = write_muxed_file(s, s->pb, pkt);
188 ✗ if (ret < 0)
189 ✗ return ret;
190 } else {
191 151 avio_write(s->pb, pkt->data, pkt->size);
192 }
193 151 img->img_number++;
194 151 return 0;
195 }
196
197 677 static int write_and_close(AVFormatContext *s, AVIOContext **pb, const unsigned char *buf, int size)
198 {
199 677 avio_write(*pb, buf, size);
200 677 avio_flush(*pb);
201 677 return ff_format_io_close(s, pb);
202 }
203
204 677 static int write_packet(AVFormatContext *s, AVPacket *pkt)
205 {
206 677 VideoMuxData *img = s->priv_data;
207 677 AVIOContext *pb[4] = {0};
208 677 char* target[4] = {0};
209 677 char* tmp[4] = {0};
210 677 char* filepaths[4] = {0};
211 677 AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
212 677 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format);
213 int ret, i;
214 677 AVDictionary *options = NULL;
215 AVBPrint filename;
216 677 av_bprint_init(&filename, 0, AV_BPRINT_SIZE_UNLIMITED);
217
218
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 if (img->update) {
219 ✗ av_bprintf(&filename, "%s", s->url);
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 } else if (img->use_strftime) {
221 time_t now0;
222 struct tm *tm, tmpbuf;
223 ✗ time(&now0);
224 ✗ tm = localtime_r(&now0, &tmpbuf);
225 ✗ av_bprint_strftime(&filename, s->url, tm);
226
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 } else if (img->frame_pts) {
227 ✗ if (ff_bprint_get_frame_filename(&filename, s->url, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
228 ✗ av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
229 ✗ ret = AVERROR(EINVAL);
230 ✗ goto fail;
231 }
232
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 666 times.
677 } else if (ff_bprint_get_frame_filename(&filename, s->url,
233 677 img->img_number,
234 AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
235
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (img->img_number == img->start_img_number) {
236 11 av_log(s, AV_LOG_WARNING, "The specified filename '%s' does not contain an image sequence pattern or a pattern is invalid.\n", s->url);
237 11 av_log(s, AV_LOG_WARNING,
238 "Use a pattern such as %%03d for an image sequence or "
239 "use the -update option (with -frames:v 1 if needed) to write a single image.\n");
240 11 av_bprint_clear(&filename);
241 11 av_bprintf(&filename, "%s", s->url);
242 } else {
243 ✗ av_log(s, AV_LOG_ERROR, "Cannot write more than one file with the same name. Are you missing the -update option or a sequence pattern?\n");
244 ✗ ret = AVERROR(EINVAL);
245 ✗ goto fail;
246 }
247 }
248
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 677 times.
677 if (!av_bprint_is_complete(&filename)) {
249 ✗ ret = AVERROR(ENOMEM);
250 ✗ goto fail;
251 }
252
1/2
✓ Branch 0 taken 677 times.
✗ Branch 1 not taken.
677 for (i = 0; i < 4; i++) {
253 677 av_dict_copy(&options, img->protocol_opts, 0);
254
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 if (img->use_rename) {
255 ✗ tmp[i] = av_asprintf("%s.tmp", filename.str);
256 ✗ target[i] = av_strdup(filename.str);
257 ✗ if (!tmp[i] || !target[i]) {
258 ✗ ret = AVERROR(ENOMEM);
259 ✗ goto fail;
260 }
261 }
262
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 677 times.
677 if ((ret = s->io_open(s, &pb[i], tmp[i] ? tmp[i] : filename.str, AVIO_FLAG_WRITE, &options)) < 0) {
263 ✗ av_log(s, AV_LOG_ERROR, "Could not open file : %s\n", tmp[i] ? tmp[i] : filename.str);
264 ✗ goto fail;
265 }
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 if (options) {
267 ✗ av_log(s, AV_LOG_ERROR, "Could not recognize some protocol options\n");
268 ✗ ret = AVERROR(EINVAL);
269 ✗ goto fail;
270 }
271
272
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 674 times.
677 if (img->update_filemtime) {
273 3 filepaths[i] = av_strdup(filename.str);
274
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!filepaths[i]) {
275 ✗ ret = AVERROR(ENOMEM);
276 ✗ goto fail;
277 }
278 }
279
280
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
677 if (!img->split_planes || i+1 >= desc->nb_components)
281 break;
282 ✗ filename.str[filename.len - 1] = "UVAx"[i];
283 }
284 677 av_bprint_finalize(&filename, NULL);
285
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 if (img->split_planes) {
287 ✗ int64_t ysize = (int64_t)par->width * par->height;
288 ✗ int64_t usize = (int64_t)AV_CEIL_RSHIFT(par->width, desc->log2_chroma_w) * AV_CEIL_RSHIFT(par->height, desc->log2_chroma_h);
289 ✗ if (desc->comp[0].depth >= 9) {
290 ✗ ysize *= 2;
291 ✗ usize *= 2;
292 }
293 ✗ if (par->width <= 0 || par->height <= 0 ||
294 ✗ ysize > pkt->size ||
295 ✗ ysize + 2*usize + (desc->nb_components > 3) * ysize > pkt->size) {
296 ✗ ret = AVERROR(EINVAL);
297 ✗ goto fail;
298 }
299
300 ✗ if ((ret = write_and_close(s, &pb[0], pkt->data , ysize)) < 0 ||
301 ✗ (ret = write_and_close(s, &pb[1], pkt->data + ysize , usize)) < 0 ||
302 ✗ (ret = write_and_close(s, &pb[2], pkt->data + ysize + usize, usize)) < 0)
303 ✗ goto fail;
304 ✗ if (desc->nb_components > 3)
305 ✗ ret = write_and_close(s, &pb[3], pkt->data + ysize + 2*usize, ysize);
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 } else if (img->muxer) {
307 ✗ if ((ret = write_muxed_file(s, pb[0], pkt)) < 0)
308 ✗ goto fail;
309 ✗ ret = ff_format_io_close(s, &pb[0]);
310 } else {
311 677 ret = write_and_close(s, &pb[0], pkt->data, pkt->size);
312 }
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 677 times.
677 if (ret < 0)
314 ✗ goto fail;
315
316
2/4
✓ Branch 0 taken 677 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 677 times.
677 for (i = 0; i < 4 && tmp[i]; i++) {
317 ✗ int ret = ff_rename(tmp[i], target[i], s);
318 ✗ if (ret < 0)
319 ✗ goto fail;
320 ✗ av_freep(&tmp[i]);
321 ✗ av_freep(&target[i]);
322 }
323
324
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 674 times.
677 if (img->update_filemtime) {
325 3 AVStream *st = s->streams[pkt->stream_index];
326 3 int64_t frame_ts = img->creation_ts;
327 3 int skip = 0;
328
329
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (pkt->pts != AV_NOPTS_VALUE) {
330 3 int64_t offset = av_rescale_q(pkt->pts, st->time_base,
331 3 AV_TIME_BASE_Q);
332
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
3 if (offset == INT64_MIN ||
333
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 (offset > 0 && img->creation_ts > INT64_MAX - offset) ||
334 ✗ (offset < 0 && img->creation_ts < INT64_MIN - offset)) {
335 ✗ av_log(s, AV_LOG_WARNING,
336 "Integer overflow computing file mtime, skipping\n");
337 ✗ skip = 1;
338 } else {
339 3 frame_ts += offset;
340 }
341 }
342
343
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!skip) {
344
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
6 for (i = 0; i < 4 && filepaths[i]; i++)
345 3 set_file_mtime(s, filepaths[i], frame_ts);
346 }
347 }
348
349
2/2
✓ Branch 0 taken 2708 times.
✓ Branch 1 taken 677 times.
3385 for (i = 0; i < FF_ARRAY_ELEMS(filepaths); i++)
350 2708 av_freep(&filepaths[i]);
351
352 677 img->img_number++;
353 677 return 0;
354
355 ✗ fail:
356 ✗ av_bprint_finalize(&filename, NULL);
357 ✗ av_dict_free(&options);
358 ✗ for (i = 0; i < FF_ARRAY_ELEMS(pb); i++) {
359 ✗ av_freep(&tmp[i]);
360 ✗ av_freep(&target[i]);
361 ✗ av_freep(&filepaths[i]);
362 ✗ if (pb[i])
363 ✗ ff_format_io_close(s, &pb[i]);
364 }
365 ✗ return ret;
366 }
367
368 70 static int query_codec(enum AVCodecID id, int std_compliance)
369 {
370 int i;
371
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 for (i = 0; ff_img_tags[i].id != AV_CODEC_ID_NONE; i++)
372
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 if (ff_img_tags[i].id == id)
373 70 return 1;
374
375 // Anything really can be stored in img2
376 ✗ return std_compliance < FF_COMPLIANCE_NORMAL;
377 }
378
379 #define OFFSET(x) offsetof(VideoMuxData, x)
380 #define ENC AV_OPT_FLAG_ENCODING_PARAM
381 static const AVOption muxoptions[] = {
382 { "update", "continuously overwrite one file", OFFSET(update), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
383 { "start_number", "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, ENC },
384 { "strftime", "use strftime for filename", OFFSET(use_strftime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
385 { "frame_pts", "use current frame pts for filename", OFFSET(frame_pts), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
386 { "atomic_writing", "write files atomically (using temporary files and renames)", OFFSET(use_rename), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
387 { "protocol_opts", "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
388 { "update_filemtime", "set output file mtime from creation_time metadata plus frame offset", OFFSET(update_filemtime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
389 { NULL },
390 };
391
392 #if CONFIG_IMAGE2_MUXER
393 static const AVClass img2mux_class = {
394 .class_name = "image2 muxer",
395 .item_name = av_default_item_name,
396 .option = muxoptions,
397 .version = LIBAVUTIL_VERSION_INT,
398 };
399
400 const FFOutputFormat ff_image2_muxer = {
401 .p.name = "image2",
402 .p.long_name = NULL_IF_CONFIG_SMALL("image2 sequence"),
403 .p.extensions = "bmp,dpx,exr,jls,jpeg,jpg,jxs,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,phm,"
404 "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
405 "im24,sunras,vbn,xbm,xface,pix,y,avif,qoi,hdr,wbmp",
406 .priv_data_size = sizeof(VideoMuxData),
407 .p.video_codec = AV_CODEC_ID_MJPEG,
408 .write_header = write_header,
409 .write_packet = write_packet,
410 .query_codec = query_codec,
411 .p.flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS | AVFMT_NOFILE,
412 .p.priv_class = &img2mux_class,
413 };
414 #endif
415 #if CONFIG_IMAGE2PIPE_MUXER
416 const FFOutputFormat ff_image2pipe_muxer = {
417 .p.name = "image2pipe",
418 .p.long_name = NULL_IF_CONFIG_SMALL("piped image2 sequence"),
419 .priv_data_size = sizeof(VideoMuxData),
420 .p.video_codec = AV_CODEC_ID_MJPEG,
421 .write_header = write_header,
422 .write_packet = write_packet_pipe,
423 .query_codec = query_codec,
424 .p.flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS
425 };
426 #endif
427